diff --git a/.gitattributes b/.gitattributes index f7bd4d061..a0643702b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -84,19 +84,16 @@ # treat as binary ############################################################################### *.basis binary +*.a binary *.dll binary -*.eot binary +*.dylib binary *.exe binary -*.otf binary *.pdf binary *.ppt binary *.pptx binary *.pvr binary +*.so binary *.snk binary -*.ttc binary -*.ttf binary -*.woff binary -*.woff2 binary *.xls binary *.xlsx binary ############################################################################### @@ -113,6 +110,7 @@ ############################################################################### # Handle image files by git lfs ############################################################################### +*.pdf filter=lfs diff=lfs merge=lfs -text *.jpg filter=lfs diff=lfs merge=lfs -text *.jpeg filter=lfs diff=lfs merge=lfs -text *.bmp filter=lfs diff=lfs merge=lfs -text @@ -126,6 +124,7 @@ *.dds filter=lfs diff=lfs merge=lfs -text *.ktx filter=lfs diff=lfs merge=lfs -text *.ktx2 filter=lfs diff=lfs merge=lfs -text +*.astc filter=lfs diff=lfs merge=lfs -text *.pam filter=lfs diff=lfs merge=lfs -text *.pbm filter=lfs diff=lfs merge=lfs -text *.pgm filter=lfs diff=lfs merge=lfs -text @@ -143,3 +142,12 @@ # Handle ICC files by git lfs ############################################################################### *.icc filter=lfs diff=lfs merge=lfs -text +############################################################################### +# Handle font files by git lfs +############################################################################### +*.eot filter=lfs diff=lfs merge=lfs -text +*.otf filter=lfs diff=lfs merge=lfs -text +*.ttc filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.woff filter=lfs diff=lfs merge=lfs -text +*.woff2 filter=lfs diff=lfs merge=lfs -text diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..5f9f69435 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,3 @@ +# GitHub Copilot Instructions + +Read and follow [AGENTS.md](../AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Prefer existing local patterns and repository configuration whenever generated code or suggestions are accepted. diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index e00757cb7..bb0962784 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -11,7 +11,7 @@ on: branches: - main - release/* - types: [ labeled, opened, synchronize, reopened ] + types: [ opened, synchronize, reopened ] jobs: # Prime a single LFS cache and expose the exact key for the matrix @@ -27,7 +27,7 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v4 + 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@v4 + uses: actions/cache@v6 with: path: .git/lfs key: ${{ steps.expose-key.outputs.lfs_key }} @@ -62,58 +62,52 @@ jobs: needs: WarmLFS strategy: matrix: - isARM: - - ${{ contains(github.event.pull_request.labels.*.name, 'arch:arm32') || contains(github.event.pull_request.labels.*.name, 'arch:arm64') }} options: - os: ubuntu-latest - framework: net9.0 - sdk: 9.0.x + framework: net11.0 + sdk: 11.0.x sdk-preview: true runtime: -x64 codecov: false - - os: macos-13 # macos-latest runs on arm64 runners where libgdiplus is unavailable - framework: net9.0 - sdk: 9.0.x + - os: macos-26 + framework: net11.0 + sdk: 11.0.x sdk-preview: true runtime: -x64 codecov: false - os: windows-latest - framework: net9.0 - sdk: 9.0.x + framework: net11.0 + sdk: 11.0.x sdk-preview: true runtime: -x64 codecov: false - - os: buildjet-4vcpu-ubuntu-2204-arm - framework: net9.0 - sdk: 9.0.x + - os: ubuntu-22.04-arm + framework: net11.0 + sdk: 11.0.x sdk-preview: true runtime: -x64 codecov: false - os: ubuntu-latest - framework: net8.0 - sdk: 8.0.x + framework: net10.0 + sdk: 10.0.x runtime: -x64 codecov: false - - os: macos-13 # macos-latest runs on arm64 runners where libgdiplus is unavailable - framework: net8.0 - sdk: 8.0.x + - os: macos-26 + framework: net10.0 + sdk: 10.0.x runtime: -x64 codecov: false - os: windows-latest - framework: net8.0 - sdk: 8.0.x + framework: net10.0 + sdk: 10.0.x runtime: -x64 codecov: false - - os: buildjet-4vcpu-ubuntu-2204-arm - framework: net8.0 - sdk: 8.0.x + - os: ubuntu-22.04-arm + framework: net10.0 + sdk: 10.0.x runtime: -x64 codecov: false - exclude: - - isARM: false - options: - os: buildjet-4vcpu-ubuntu-2204-arm runs-on: ${{ matrix.options.os }} @@ -124,6 +118,18 @@ jobs: sudo apt-get update sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev + - name: Install libgdi+, which is required for tests running on macos + if: ${{ contains(matrix.options.os, 'macos-26') }} + run: | + brew update + brew install mono-libgdiplus + # Create symlinks to make libgdiplus discoverable + sudo mkdir -p /usr/local/lib + sudo ln -sf $(brew --prefix)/lib/libgdiplus.dylib /usr/local/lib/libgdiplus.dylib + # Verify installation + ls -la $(brew --prefix)/lib/libgdiplus* || echo "libgdiplus not found in brew prefix" + ls -la /usr/local/lib/libgdiplus* || echo "libgdiplus not found in /usr/local/lib" + - name: Git Config shell: bash run: | @@ -131,14 +137,14 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v4 + 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@v4 + uses: actions/cache@v6 with: path: .git/lfs key: ${{ needs.WarmLFS.outputs.lfs_key }} @@ -148,10 +154,10 @@ jobs: run: git lfs pull - name: NuGet Install - uses: NuGet/setup-nuget@v2 + uses: NuGet/setup-nuget@v4 - name: NuGet Setup Cache - uses: actions/cache@v4 + uses: actions/cache@v6 id: nuget-cache with: path: ~/.nuget @@ -160,17 +166,18 @@ jobs: - name: DotNet Setup if: ${{ matrix.options.sdk-preview != true }} - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v6 with: dotnet-version: | - 8.0.x + 10.0.x - name: DotNet Setup Preview if: ${{ matrix.options.sdk-preview == true }} - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v6 with: + dotnet-quality: preview dotnet-version: | - 9.0.x + 11.0.x - name: DotNet Build if: ${{ matrix.options.sdk-preview != true }} @@ -203,7 +210,7 @@ jobs: XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit - name: Export Failed Output - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: failure() with: name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip @@ -221,16 +228,16 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 submodules: recursive - name: NuGet Install - uses: NuGet/setup-nuget@v2 + uses: NuGet/setup-nuget@v4 - name: NuGet Setup Cache - uses: actions/cache@v4 + 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 a7278a817..eca2f190f 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -11,7 +11,7 @@ jobs: matrix: options: - os: ubuntu-latest - framework: net8.0 + framework: net10.0 runtime: -x64 codecov: true @@ -31,7 +31,7 @@ jobs: git config --global core.longpaths true - name: Git Checkout - uses: actions/checkout@v4 + 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@v4 + uses: actions/cache@v6 id: lfs-cache with: path: .git/lfs @@ -56,10 +56,10 @@ jobs: run: git lfs pull - name: NuGet Install - uses: NuGet/setup-nuget@v2 + uses: NuGet/setup-nuget@v4 - name: NuGet Setup Cache - uses: actions/cache@v4 + uses: actions/cache@v6 id: nuget-cache with: path: ~/.nuget @@ -67,10 +67,10 @@ jobs: restore-keys: ${{ runner.os }}-nuget- - name: DotNet Setup - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v6 with: dotnet-version: | - 8.0.x + 10.0.x - name: DotNet Build shell: pwsh @@ -86,14 +86,15 @@ jobs: XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit - name: Export Failed Output - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 if: failure() with: name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip path: tests/Images/ActualOutput/ - name: Codecov Update - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v7 if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') with: flags: unittests + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index fadf36964..a8d2917be 100644 --- a/.gitignore +++ b/.gitignore @@ -227,3 +227,5 @@ artifacts/ #lfs hooks/** lfs/** + +.dotnet diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..814e3ce26 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,41 @@ +# Six Labors AI Coding Guidelines + +These instructions apply to the entire repository. More-specific `AGENTS.md` files may add to or override them for their directory tree. + +## Working Practices + +- Inspect the relevant implementation, tests, benchmarks, project files, and nearby code before proposing or making changes. Do not infer current behavior when the source is available. +- Make the smallest complete change that solves the requested problem. Avoid unrelated cleanup, speculative abstractions, and formatting churn. +- Match established architecture, naming, formatting, documentation, and test patterns. Treat `.editorconfig`, analyzers, and repository build settings as authoritative. +- Preserve public API and observable behavior unless the task explicitly requires a change. Public API documentation must describe observable behavior, not implementation details. +- Do not use reflection against built assemblies, ad hoc assembly loading, or temporary probe projects unless explicitly requested. +- Build .NET projects in Release configuration unless explicitly instructed otherwise. + +## Performance + +- Treat throughput, latency, memory use, and binary size as design constraints, especially in pixel-processing, drawing, parsing, encoding, and other hot paths. +- Avoid unnecessary allocations, copies, boxing, closures, interface dispatch, repeated enumeration, and extra passes over data. +- Reuse the repository's existing memory ownership, pooling, span, vectorization, and parallelization patterns. Do not introduce a new mechanism when an established one fits. +- Keep hot loops simple and bounds-check-friendly. Hoist invariant work, preserve locality, and use the narrowest suitable data types without sacrificing correctness. +- Do not trade correctness or maintainability for assumed speed. Support non-obvious optimizations with measurements or clear evidence, and add or update benchmarks when performance is the purpose of the change. +- Consider all supported target frameworks and runtime capabilities. Do not regress fallback paths while optimizing newer runtimes. + +## C# Conventions + +- Follow the existing code around the change; local patterns take precedence over generic preferences. +- Do not use `record` or `record struct` types. +- Prefer established invariants over redundant guards. Validate at real external boundaries and do not add defensive checks for internally controlled states. +- Do not extract single-use helpers merely to name a block. Extract only for genuine reuse, an established local pattern, or meaningful complexity reduction. +- Add vertical whitespace after multi-line statements and declarations and between distinct logical stages. Never add trailing whitespace. +- Document every method, constructor, and property, regardless of whether it is public, internal, protected, or private. Keep public API documentation limited to observable behavior; use private and internal documentation to capture the contract and intent needed to maintain the code. +- Add inline comments throughout complex code. Explain algorithms, formulas, invariants, ownership, compatibility behavior, and performance tradeoffs at the operations and decisions they govern. Explain why the code is shaped that way rather than narrating the syntax. +- Document SIMD code especially thoroughly. Explain the vector layout, lane meaning, widening or narrowing, masks, shuffles, constants, alignment or remainder handling, supported instruction paths, scalar equivalence, and the reason each non-obvious operation is correct. +- Write algorithm and SIMD comments for a maintainer who is unfamiliar with the implementation. The reader should not need to reconstruct intent from external documentation, issue history, or benchmark results. + +## Verification + +- Add or update focused tests when behavior changes, following the test framework and conventions already used by the project. +- Never hack, weaken, skip, conditionally bypass, or otherwise manipulate a test to make it pass. Fix the production defect or the genuine test defect while preserving the test's intended coverage and sensitivity. +- Do not update golden files, reference images, snapshots, baselines, or expected-output artifacts to resolve a test failure. Treat a mismatch as evidence to investigate and correct the implementation. +- Run the narrowest relevant formatting, test, and Release build commands, then expand verification in proportion to the risk and scope of the change. +- Report what changed, the verification performed, and any remaining risks or unverified assumptions. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..5f08a449a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +# Claude Code Instructions + +Read and follow [AGENTS.md](AGENTS.md) as the repository-wide source of coding, performance, and verification requirements. Apply any more-specific `AGENTS.md` or `CLAUDE.md` found below the files being changed. diff --git a/Directory.Build.props b/Directory.Build.props index 755cbe3b3..ab898020c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,10 +21,6 @@ - - 12.0 - - - 4.0 + + 5.0 - net8.0;net9.0 + net10.0;net11.0 - net8.0 + net10.0 @@ -47,7 +47,8 @@ - + + @@ -61,6 +62,11 @@ True InlineArray.tt + + True + True + ImageExtensions.Save.tt + True True @@ -141,15 +147,20 @@ True DefaultPixelBlenders.Generated.tt - + True True - PorterDuffFunctions.Generated.tt + AssociatedAlphaPixelBlenders.Generated.tt - + + True True + AssociatedAlphaPorterDuffFunctions.Generated.tt + + True - ImageExtensions.Save.tt + True + PorterDuffFunctions.Generated.tt @@ -230,6 +241,14 @@ DefaultPixelBlenders.Generated.cs TextTemplatingFileGenerator + + AssociatedAlphaPixelBlenders.Generated.cs + TextTemplatingFileGenerator + + + AssociatedAlphaPorterDuffFunctions.Generated.cs + TextTemplatingFileGenerator + TextTemplatingFileGenerator ImageExtensions.Save.cs diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index dff8f577f..4881518c9 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -91,7 +90,7 @@ public sealed class Image : Image Configuration configuration, Buffer2D pixelBuffer, ImageMetadata metadata) - : this(configuration, pixelBuffer.FastMemoryGroup, pixelBuffer.Width, pixelBuffer.Height, metadata) + : this(configuration, pixelBuffer.FastMemoryGroup, pixelBuffer.Width, pixelBuffer.Height, pixelBuffer.RowStride, metadata) { } @@ -110,8 +109,29 @@ public sealed class Image : Image int width, int height, ImageMetadata metadata) + : this(configuration, memoryGroup, width, height, width, metadata) + { + } + + /// + /// Initializes a new instance of the class + /// wrapping an external . + /// + /// The configuration providing initialization code which allows extending the library. + /// The memory source. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The number of elements between row starts. + /// The images metadata. + internal Image( + Configuration configuration, + MemoryGroup memoryGroup, + int width, + int height, + int rowStride, + ImageMetadata metadata) : base(configuration, TPixel.GetPixelTypeInfo(), metadata, width, height) - => this.frames = new ImageFrameCollection(this, width, height, memoryGroup); + => this.frames = new ImageFrameCollection(this, width, height, rowStride, memoryGroup); /// /// Initializes a new instance of the class @@ -287,16 +307,24 @@ public sealed class Image : Image } /// - /// Copy image pixels to . + /// Copy image pixels to using the root frame backing row layout. /// + /// + /// Destination length must be at least + /// ((Height - 1) * Frames.RootFrame.PixelBuffer.RowStride) + Width. + /// /// The to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); + public void CopyPixelDataTo(Span destination) => this.Frames.RootFrame.CopyPixelDataTo(destination); /// - /// Copy image pixels to . + /// Copy image pixels to using the root frame backing row layout. /// + /// + /// Destination length must be at least + /// (((Height - 1) * Frames.RootFrame.PixelBuffer.RowStride) + Width) * sizeof(TPixel) bytes. + /// /// The of to copy image pixels to. - public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); + public void CopyPixelDataTo(Span destination) => this.Frames.RootFrame.CopyPixelDataTo(destination); /// /// Gets the representation of the pixels as a in the source image's pixel format @@ -311,17 +339,7 @@ public sealed class Image : Image /// The referencing the image buffer. /// The indicating the success. public bool DangerousTryGetSinglePixelMemory(out Memory memory) - { - IMemoryGroup mg = this.GetPixelMemoryGroup(); - if (mg.Count > 1) - { - memory = default; - return false; - } - - memory = mg.Single(); - return true; - } + => this.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out memory); /// /// Clones the current image. diff --git a/src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs b/src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs new file mode 100644 index 000000000..764bb37e8 --- /dev/null +++ b/src/ImageSharp/Memory/AllocationTrackedMemoryManager{T}.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; + +namespace SixLabors.ImageSharp.Memory; + +/// +/// Provides the tracked memory-owner contract required by . +/// +/// The element type. +/// +/// Custom allocators implement +/// and return a derived type. The base allocator attaches allocation tracking after the owner has been +/// created so custom implementations cannot forget, duplicate, or mismatch the reservation lifecycle. +/// +public abstract class AllocationTrackedMemoryManager : MemoryManager + where T : struct +{ + private AllocationTrackingState allocationTracking; + + /// + /// Releases resources held by the concrete tracked owner. + /// + /// + /// when the owner is being disposed deterministically; + /// otherwise, . + /// + /// + /// Implementations release their own resources here. Allocation tracking is released by the sealed base + /// dispose path after this method returns. + /// + protected abstract void DisposeCore(bool disposing); + + /// + protected sealed override void Dispose(bool disposing) + { + try + { + this.DisposeCore(disposing); + } + finally + { + this.ReleaseAllocationTracking(); + } + } + + /// + /// Attaches allocation tracking to this owner after allocation has succeeded. + /// + /// The allocator that owns the reservation for this instance. + /// The reserved allocation size, in bytes. + /// + /// calls this exactly once after AllocateCore returns. + /// Derived allocators should not call it themselves; they only construct the concrete owner. + /// + protected internal virtual void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + => this.allocationTracking.Attach(allocator, lengthInBytes); + + /// + /// Releases any tracked allocation bytes associated with this instance. + /// + /// + /// Calling this more than once is safe; only the first call after tracking has been attached releases bytes. + /// + private void ReleaseAllocationTracking() => this.allocationTracking.Release(); +} diff --git a/src/ImageSharp/Memory/AllocationTrackingState.cs b/src/ImageSharp/Memory/AllocationTrackingState.cs new file mode 100644 index 000000000..1e9a632ed --- /dev/null +++ b/src/ImageSharp/Memory/AllocationTrackingState.cs @@ -0,0 +1,47 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Memory; + +/// +/// Tracks a single allocator reservation and releases it exactly once. +/// +/// +/// This type is intended to live as a mutable field on the owning object. It should not be copied +/// after tracking has been attached, because the owner relies on a single shared release state. +/// +internal struct AllocationTrackingState +{ + private MemoryAllocator? allocator; + private long lengthInBytes; + private int released; + + /// + /// Attaches allocator reservation tracking to the current owner. + /// + /// The allocator that owns the reservation. + /// The reserved allocation size, in bytes. + /// + /// Must complete-before the owning object's reference is observable to any other thread. + /// guarantees this by attaching synchronously on the allocating + /// thread before returning the owner; reference publication then provides the release fence + /// that makes these field writes visible to a subsequent on another thread. + /// + internal void Attach(MemoryAllocator allocator, long lengthInBytes) + { + this.allocator = allocator; + this.lengthInBytes = lengthInBytes; + } + + /// + /// Releases the attached allocator reservation once. + /// + internal void Release() + { + if (Interlocked.Exchange(ref this.released, 1) == 0 && this.allocator != null) + { + this.allocator.ReleaseAccumulatedBytes(this.lengthInBytes); + this.allocator = null; + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs index 3ead1c5df..986ed7f7c 100644 --- a/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs +++ b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs @@ -1,9 +1,19 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Memory; +/// +/// Provides helper methods for working with . +/// internal static class AllocationOptionsExtensions { - public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag; + /// + /// Returns a value indicating whether the specified flag is set on the allocation options. + /// + /// The allocation options to inspect. + /// The flag to test for. + /// if is set; otherwise, . + public static bool Has(this AllocationOptions options, AllocationOptions flag) + => (options & flag) == flag; } diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs index 9f34602fb..c22e827a8 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs @@ -47,7 +47,7 @@ internal class BasicArrayBuffer : ManagedBufferBase public override Span GetSpan() => this.Array.AsSpan(0, this.Length); /// - protected override void Dispose(bool disposing) + protected override void DisposeCore(bool disposing) { } diff --git a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs index a6ed797d6..84dd065f5 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Memory.Internals; /// Provides a base class for implementations by implementing pinning logic for adaption. /// /// The element type. -internal abstract class ManagedBufferBase : MemoryManager +internal abstract class ManagedBufferBase : AllocationTrackedMemoryManager where T : struct { private GCHandle pinHandle; diff --git a/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs index 4a202a96c..b0fe0b649 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/RefCountedMemoryLifetimeGuard.cs @@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Memory.Internals; /// internal abstract class RefCountedMemoryLifetimeGuard : IDisposable { + private AllocationTrackingState allocationTracking; private int refCount = 1; private int disposed; private int released; @@ -38,6 +39,14 @@ internal abstract class RefCountedMemoryLifetimeGuard : IDisposable public void ReleaseRef() => this.ReleaseRef(false); + /// + /// Attaches allocator reservation tracking to this lifetime guard. + /// + /// The allocator that owns the reservation. + /// The reserved allocation size, in bytes. + public void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + => this.allocationTracking.Attach(allocator, lengthInBytes); + public void Dispose() { int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); @@ -69,6 +78,10 @@ internal abstract class RefCountedMemoryLifetimeGuard : IDisposable } this.Release(); + + // Guard-backed resources can be recovered by finalization, so their allocator + // reservation must follow the guard's actual release point instead of the owner object. + this.allocationTracking.Release(); } } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs index 02bdf0f48..97fab1b68 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -13,7 +13,7 @@ internal class SharedArrayPoolBuffer : ManagedBufferBase, IRefCounted where T : struct { private readonly int lengthInBytes; - private LifetimeGuard lifetimeGuard; + private readonly LifetimeGuard lifetimeGuard; public SharedArrayPoolBuffer(int lengthInElements) { @@ -24,7 +24,10 @@ internal class SharedArrayPoolBuffer : ManagedBufferBase, IRefCounted public byte[]? Array { get; private set; } - protected override void Dispose(bool disposing) + protected internal override void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + => this.lifetimeGuard.AttachAllocationTracking(allocator, lengthInBytes); + + protected override void DisposeCore(bool disposing) { if (this.Array == null) { diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs index 854b40e0c..3a729cdf2 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Memory.Internals; /// access to unmanaged buffers allocated by . /// /// The element type. -internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted +internal sealed unsafe class UnmanagedBuffer : AllocationTrackedMemoryManager, IRefCounted where T : struct { private readonly int lengthInElements; @@ -31,6 +31,9 @@ internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted public void* Pointer => this.lifetimeGuard.Handle.Pointer; + protected internal override void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + => this.lifetimeGuard.AttachAllocationTracking(allocator, lengthInBytes); + public override Span GetSpan() { DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); @@ -52,7 +55,7 @@ internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted } /// - protected override void Dispose(bool disposing) + protected override void DisposeCore(bool disposing) { DebugGuard.IsTrue(disposing, nameof(disposing), "Unmanaged buffers should not have finalizer!"); diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs index 6b31cadf4..632e1bec0 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs @@ -39,13 +39,13 @@ internal struct UnmanagedMemoryHandle : IEquatable Interlocked.Increment(ref totalOutstandingHandles); } - public IntPtr Handle => this.handle; + public readonly IntPtr Handle => this.handle; - public bool IsInvalid => this.Handle == IntPtr.Zero; + public readonly bool IsInvalid => this.Handle == IntPtr.Zero; - public bool IsValid => this.Handle != IntPtr.Zero; + public readonly bool IsValid => this.Handle != IntPtr.Zero; - public unsafe void* Pointer => (void*)this.Handle; + public readonly unsafe void* Pointer => (void*)this.Handle; /// /// Gets the total outstanding handle allocations for testing purposes. @@ -121,9 +121,9 @@ internal struct UnmanagedMemoryHandle : IEquatable this.lengthInBytes = 0; } - public bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle); + public readonly bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle); - public override bool Equals(object? obj) => obj is UnmanagedMemoryHandle other && this.Equals(other); + public override readonly bool Equals(object? obj) => obj is UnmanagedMemoryHandle other && this.Equals(other); - public override int GetHashCode() => this.handle.GetHashCode(); + public override readonly int GetHashCode() => this.handle.GetHashCode(); } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 8eaf0b6d6..591c3b9dd 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -12,6 +12,10 @@ namespace SixLabors.ImageSharp.Memory; public abstract class MemoryAllocator { private const int OneGigabyte = 1 << 30; + private long accumulativeAllocatedBytes; + private long memoryGroupAllocationLimitBytes = Environment.Is64BitProcess ? 4L * OneGigabyte : OneGigabyte; + private long accumulativeAllocationLimitBytes = long.MaxValue; + private int singleBufferAllocationLimitBytes = OneGigabyte; /// /// Gets the default platform-specific global instance that @@ -23,9 +27,68 @@ public abstract class MemoryAllocator /// public static MemoryAllocator Default { get; } = Create(); - internal long MemoryGroupAllocationLimitBytes { get; private set; } = Environment.Is64BitProcess ? 4L * OneGigabyte : OneGigabyte; + /// + /// Gets or sets the maximum number of bytes that can be allocated by a memory group. + /// A memory group backs the pixel buffer of a single image, so this limits the total image size. + /// + /// + /// The default limit is determined by the process architecture: 4 GB for 64-bit processes and + /// 1 GB for 32-bit processes. The setter is available to derived allocators and requires a positive value. + /// + /// The value is not greater than zero. + public long MemoryGroupAllocationLimitBytes + { + get => this.memoryGroupAllocationLimitBytes; + protected set + { + Guard.MustBeGreaterThan(value, 0, nameof(this.MemoryGroupAllocationLimitBytes)); + this.memoryGroupAllocationLimitBytes = value; + } + } - internal int SingleBufferAllocationLimitBytes { get; private set; } = OneGigabyte; + /// + /// Gets or sets the maximum accumulative size, in bytes, of all active allocations made through this allocator instance. + /// + /// + /// Defaults to , effectively imposing no limit on the accumulative total. + /// When set, this provides a safeguard against excessive memory consumption by capping the combined size of + /// outstanding allocations issued by this instance.
+ /// When the accumulative size of active allocations exceeds this limit, an will be thrown to + /// prevent further allocations and signal that the limit has been breached. + /// The setter is available to derived allocators and requires a positive value. + ///
+ /// The value is not greater than zero. + public long AccumulativeAllocationLimitBytes + { + get => this.accumulativeAllocationLimitBytes; + protected set + { + Guard.MustBeGreaterThan(value, 0, nameof(this.AccumulativeAllocationLimitBytes)); + this.accumulativeAllocationLimitBytes = value; + } + } + + /// + /// Gets or sets the maximum size, in bytes, that can be allocated for a single contiguous buffer. + /// This limit applies to and to contiguous image buffers + /// requested through . + /// + /// + /// The single buffer allocation limit is set to 1 GB by default. + /// A single contiguous buffer can never exceed bytes; larger images are + /// backed by discontiguous memory groups limited by . + /// The setter is available to derived allocators and requires a positive value. + /// + /// The value is not greater than zero. + public int SingleBufferAllocationLimitBytes + { + get => this.singleBufferAllocationLimitBytes; + protected set + { + Guard.MustBeGreaterThan(value, 0, nameof(this.SingleBufferAllocationLimitBytes)); + this.singleBufferAllocationLimitBytes = value; + } + } /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. @@ -47,13 +110,38 @@ public abstract class MemoryAllocator public static MemoryAllocator Create(MemoryAllocatorOptions options) { UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(options.MaximumPoolSizeMegabytes); + allocator.ApplyOptions(options); + return allocator; + } + + /// + /// Applies the supplied to this instance. + /// Derived allocators can call this from their constructors to accept user configuration. + /// + /// The options to apply. Properties left as are ignored. + /// + /// The applied single buffer limit is capped to , + /// because a single contiguous buffer can never be larger than the total allocation limit. + /// + protected void ApplyOptions(MemoryAllocatorOptions options) + { if (options.AllocationLimitMegabytes.HasValue) { - allocator.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024L * 1024L; - allocator.SingleBufferAllocationLimitBytes = (int)Math.Min(allocator.SingleBufferAllocationLimitBytes, allocator.MemoryGroupAllocationLimitBytes); + this.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024L * 1024L; } - return allocator; + if (options.SingleBufferAllocationLimitMegabytes.HasValue) + { + // The option setter caps the value at 2047 MB, so converting to bytes cannot overflow. + this.SingleBufferAllocationLimitBytes = (int)(options.SingleBufferAllocationLimitMegabytes.Value * 1024L * 1024L); + } + + this.SingleBufferAllocationLimitBytes = (int)Math.Min(this.SingleBufferAllocationLimitBytes, this.MemoryGroupAllocationLimitBytes); + + if (options.AccumulativeAllocationLimitMegabytes.HasValue) + { + this.AccumulativeAllocationLimitBytes = options.AccumulativeAllocationLimitMegabytes.Value * 1024L * 1024L; + } } /// @@ -63,15 +151,60 @@ public abstract class MemoryAllocator /// Size of the buffer to allocate. /// The allocation options. /// A buffer of values of type . - /// When length is zero or negative. - /// When length is over the capacity of the allocator. - public abstract IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + /// When length is negative or over the capacity of the allocator. + public IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + where T : struct + { + long lengthInBytes = this.GetValidatedAllocationLengthInBytes(length); + bool shouldTrack = this.AccumulativeAllocationLimitBytes != long.MaxValue && lengthInBytes != 0; + if (shouldTrack) + { + this.ReserveAllocation(lengthInBytes); + } + + try + { + AllocationTrackedMemoryManager owner = this.AllocateCore(length, options); + if (shouldTrack) + { + owner.AttachAllocationTracking(this, lengthInBytes); + } + + return owner; + } + catch + { + if (shouldTrack) + { + this.ReleaseAccumulatedBytes(lengthInBytes); + } + + throw; + } + } + + /// + /// Allocates a tracked memory owner for . + /// + /// Type of the data stored in the buffer. + /// Size of the buffer to allocate. + /// The allocation options. + /// A tracked memory owner of values of type . + /// + /// Implementations should only allocate and initialize the concrete owner. The base allocator + /// reserves bytes, attaches tracking to the returned owner, and releases the reservation if allocation fails. + /// + protected abstract AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) where T : struct; /// /// Releases all retained resources not being in use. /// Eg: by resetting array pools and letting GC to free the arrays. /// + /// + /// This does not dispose active allocations; callers are responsible for disposing all + /// instances to release memory. + /// public virtual void ReleaseRetainedResources() { } @@ -102,11 +235,109 @@ public abstract class MemoryAllocator InvalidMemoryOperationException.ThrowAllocationOverLimitException(totalLengthInBytes, this.MemoryGroupAllocationLimitBytes); } - // Cast to long is safe because we already checked that the total length is within the limit. - return this.AllocateGroupCore(totalLength, (long)totalLengthInBytes, bufferAlignment, options); + long totalLengthInBytesLong = (long)totalLengthInBytes; + bool shouldTrack = this.AccumulativeAllocationLimitBytes != long.MaxValue && totalLengthInBytesLong != 0; + if (shouldTrack) + { + this.ReserveAllocation(totalLengthInBytesLong); + } + + try + { + MemoryGroup group = this.AllocateGroupCore(totalLength, totalLengthInBytesLong, bufferAlignment, options); + if (shouldTrack) + { + group.AttachAllocationTracking(this, totalLengthInBytesLong); + } + + return group; + } + catch + { + if (shouldTrack) + { + this.ReleaseAccumulatedBytes(totalLengthInBytesLong); + } + + throw; + } } internal virtual MemoryGroup AllocateGroupCore(long totalLengthInElements, long totalLengthInBytes, int bufferAlignment, AllocationOptions options) where T : struct => MemoryGroup.Allocate(this, totalLengthInElements, bufferAlignment, options); + + /// + /// Allocates a single segment for construction. + /// + /// Type of the data stored in the buffer. + /// Size of the segment to allocate. + /// The allocation options. + /// A segment owner for the requested buffer length. + /// + /// The default implementation validates the segment size then calls + /// directly so group construction can reserve and release the total allocation once. + /// + internal virtual IMemoryOwner AllocateGroupBuffer(int length, AllocationOptions options = AllocationOptions.None) + where T : struct + { + _ = this.GetValidatedAllocationLengthInBytes(length); + return this.AllocateCore(length, options); + } + + /// + /// Returns the validated allocation length in bytes. + /// + /// Type of the data stored in the buffer. + /// Size of the buffer to allocate. + /// The allocation length in bytes. + private long GetValidatedAllocationLengthInBytes(int length) + where T : struct + { + if (length < 0) + { + InvalidMemoryOperationException.ThrowNegativeAllocationException(length); + } + + ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf(); + if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes) + { + InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); + } + + return (long)lengthInBytes; + } + + /// + /// Reserves accumulative allocation bytes before creating the underlying buffer. + /// + /// The number of bytes to reserve. + private void ReserveAllocation(long lengthInBytes) + { + if (lengthInBytes <= 0) + { + return; + } + + long total = Interlocked.Add(ref this.accumulativeAllocatedBytes, lengthInBytes); + if (total > this.AccumulativeAllocationLimitBytes) + { + _ = Interlocked.Add(ref this.accumulativeAllocatedBytes, -lengthInBytes); + InvalidMemoryOperationException.ThrowAccumulativeAllocationOverLimitException(lengthInBytes, total, this.AccumulativeAllocationLimitBytes); + } + } + + /// + /// Releases accumulative allocation bytes previously tracked by this allocator. + /// + /// The number of bytes to release. + internal void ReleaseAccumulatedBytes(long lengthInBytes) + { + if (lengthInBytes <= 0) + { + return; + } + + _ = Interlocked.Add(ref this.accumulativeAllocatedBytes, -lengthInBytes); + } } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs index d9ba62c1e..0578fd64d 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs @@ -8,8 +8,15 @@ namespace SixLabors.ImageSharp.Memory; /// public struct MemoryAllocatorOptions { + /// + /// The largest single-buffer limit, in Megabytes, that still fits bytes. + /// + private const int MaxSingleBufferAllocationLimitMegabytes = 2047; + private int? maximumPoolSizeMegabytes; private int? allocationLimitMegabytes; + private int? accumulativeAllocationLimitMegabytes; + private int? singleBufferAllocationLimitMegabytes; /// /// Gets or sets a value defining the maximum size of the 's internal memory pool @@ -17,7 +24,7 @@ public struct MemoryAllocatorOptions /// public int? MaximumPoolSizeMegabytes { - get => this.maximumPoolSizeMegabytes; + readonly get => this.maximumPoolSizeMegabytes; set { if (value.HasValue) @@ -35,15 +42,78 @@ public struct MemoryAllocatorOptions /// public int? AllocationLimitMegabytes { - get => this.allocationLimitMegabytes; + readonly get => this.allocationLimitMegabytes; set { if (value.HasValue) { Guard.MustBeGreaterThan(value.Value, 0, nameof(this.AllocationLimitMegabytes)); + if (this.AccumulativeAllocationLimitMegabytes.HasValue) + { + Guard.MustBeLessThanOrEqualTo( + value.Value, + this.AccumulativeAllocationLimitMegabytes.Value, + nameof(this.AllocationLimitMegabytes)); + } } this.allocationLimitMegabytes = value; } } + + /// + /// Gets or sets a value defining the maximum size, in Megabytes, of a single contiguous buffer + /// that the created can allocate. + /// means the default of 1 GB. + /// + /// + /// This limit applies to contiguous buffers, including image buffers requested through + /// . A single contiguous buffer can never exceed + /// bytes, so the largest accepted value is 2047. The applied limit is also + /// capped to because a single buffer can never be larger + /// than the total allocation limit. + /// + public int? SingleBufferAllocationLimitMegabytes + { + readonly get => this.singleBufferAllocationLimitMegabytes; + set + { + if (value.HasValue) + { + Guard.MustBeGreaterThan(value.Value, 0, nameof(this.SingleBufferAllocationLimitMegabytes)); + Guard.MustBeLessThanOrEqualTo( + value.Value, + MaxSingleBufferAllocationLimitMegabytes, + nameof(this.SingleBufferAllocationLimitMegabytes)); + } + + this.singleBufferAllocationLimitMegabytes = value; + } + } + + /// + /// Gets or sets a value defining the maximum accumulative size, in Megabytes, of all active allocations made + /// through the created instance. + /// (the default) imposes no limit on the accumulative total. + /// + public int? AccumulativeAllocationLimitMegabytes + { + readonly get => this.accumulativeAllocationLimitMegabytes; + set + { + if (value.HasValue) + { + Guard.MustBeGreaterThan(value.Value, 0, nameof(this.AccumulativeAllocationLimitMegabytes)); + if (this.AllocationLimitMegabytes.HasValue) + { + Guard.MustBeGreaterThanOrEqualTo( + value.Value, + this.AllocationLimitMegabytes.Value, + nameof(this.AccumulativeAllocationLimitMegabytes)); + } + } + + this.accumulativeAllocationLimitMegabytes = value; + } + } } diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 675afe8b9..5d183fa44 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory; @@ -12,23 +10,24 @@ namespace SixLabors.ImageSharp.Memory; ///
public sealed class SimpleGcMemoryAllocator : MemoryAllocator { + /// + /// Initializes a new instance of the class with default limits. + /// + public SimpleGcMemoryAllocator() + : this(default) + { + } + + /// + /// Initializes a new instance of the class with custom limits. + /// + /// The to apply. + public SimpleGcMemoryAllocator(MemoryAllocatorOptions options) => this.ApplyOptions(options); + /// protected internal override int GetBufferCapacityInBytes() => int.MaxValue; /// - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - { - if (length < 0) - { - InvalidMemoryOperationException.ThrowNegativeAllocationException(length); - } - - ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf(); - if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes) - { - InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); - } - - return new BasicArrayBuffer(new T[length]); - } + protected override AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) + => new BasicArrayBuffer(new T[length]); } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index 10defe6cd..cfffc679a 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Memory.Internals; @@ -71,30 +70,25 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes); } - // This delegate allows overriding the method returning the available system memory, - // so we can test our workaround for https://github.com/dotnet/runtime/issues/65466 - internal static Func GetTotalAvailableMemoryBytes { get; set; } = () => GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; + internal UniformUnmanagedMemoryPoolMemoryAllocator( + int sharedArrayPoolThresholdInBytes, + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes, + MemoryAllocatorOptions options) + : this(sharedArrayPoolThresholdInBytes, poolBufferSizeInBytes, maxPoolSizeInBytes, unmanagedBufferSizeInBytes) + => this.ApplyOptions(options); /// protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes; /// - public override IMemoryOwner Allocate( + protected override AllocationTrackedMemoryManager AllocateCore( int length, AllocationOptions options = AllocationOptions.None) { - if (length < 0) - { - InvalidMemoryOperationException.ThrowNegativeAllocationException(length); - } - - ulong lengthInBytes = (ulong)length * (ulong)Unsafe.SizeOf(); - if (lengthInBytes > (ulong)this.SingleBufferAllocationLimitBytes) - { - InvalidMemoryOperationException.ThrowAllocationOverLimitException(lengthInBytes, this.SingleBufferAllocationLimitBytes); - } - - if (lengthInBytes <= (ulong)this.sharedArrayPoolThresholdInBytes) + int lengthInBytes = length * Unsafe.SizeOf(); + if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes) { SharedArrayPoolBuffer buffer = new(length); if (options.Has(AllocationOptions.Clean)) @@ -105,17 +99,16 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato return buffer; } - if (lengthInBytes <= (ulong)this.poolBufferSizeInBytes) + if (lengthInBytes <= this.poolBufferSizeInBytes) { UnmanagedMemoryHandle mem = this.pool.Rent(); if (mem.IsValid) { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); - return buffer; + return this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); } } - return this.nonPoolAllocator.Allocate(length, options); + return UnmanagedMemoryAllocator.AllocateBuffer(length, options); } /// @@ -155,20 +148,14 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato private static long GetDefaultMaxPoolSizeBytes() { - // On 64 bit set the pool size to a portion of the total available memory. - // https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327 if (Environment.Is64BitProcess) { - long total = GetTotalAvailableMemoryBytes(); - - // Workaround for https://github.com/dotnet/runtime/issues/65466 - if (total > 0) - { - return (long)((ulong)total / 8); - } + // On 64 bit set the pool size to a portion of the total available memory. + GCMemoryInfo info = GC.GetGCMemoryInfo(); + return info.TotalAvailableMemoryBytes / 8; } - // Stick to a conservative value of 128 Megabytes on other platforms and 32 bit .NET 5.0: + // Stick to a conservative value of 128 Megabytes on 32 bit. return 128 * OneMegabyte; } } diff --git a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs index daf1a7992..eb52da7c0 100644 --- a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs @@ -18,7 +18,14 @@ internal class UnmanagedMemoryAllocator : MemoryAllocator protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes; - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + protected override AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) + where T : struct + => AllocateBuffer(length, options); + + // The pooled allocator uses this internal entry point when it needs a raw unmanaged owner without + // nesting another allocator-level reservation cycle around the fallback allocation. + internal static UnmanagedBuffer AllocateBuffer(int length, AllocationOptions options = AllocationOptions.None) + where T : struct { UnmanagedBuffer buffer = UnmanagedBuffer.Allocate(length); if (options.Has(AllocationOptions.Clean)) diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index f0fa1438d..290d978d0 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -45,7 +45,7 @@ public static class Buffer2DExtensions Buffer2DRegion sourceRegion = source.GetRegion(rectangle); if (sourceRegion.IsFullBufferArea) { - sourceRegion.Buffer.FastMemoryGroup.CopyTo(buffer.FastMemoryGroup); + sourceRegion.Buffer.CopyTo(buffer); } else { @@ -81,7 +81,7 @@ public static class Buffer2DExtensions CheckColumnRegionsDoNotOverlap(buffer, sourceIndex, destinationIndex, columnCount); int elementSize = Unsafe.SizeOf(); - int width = buffer.Width * elementSize; + int rowByteStride = buffer.RowStride * elementSize; int sOffset = sourceIndex * elementSize; int dOffset = destinationIndex * elementSize; long count = columnCount * elementSize; @@ -98,66 +98,46 @@ public static class Buffer2DExtensions Buffer.MemoryCopy(sPtr, dPtr, count, count); - basePtr += width; + basePtr += rowByteStride; } } } /// - /// Returns a representing the full area of the buffer. - /// - /// The element type - /// The - /// The - internal static Rectangle FullRectangle(this Buffer2D buffer) - where T : struct - => new(0, 0, buffer.Width, buffer.Height); - - /// - /// Return a to the subregion represented by 'rectangle' + /// Return a to the subregion represented by . /// /// The element type /// The /// The rectangle subregion /// The - internal static Buffer2DRegion GetRegion(this Buffer2D buffer, Rectangle rectangle) + public static Buffer2DRegion GetRegion(this Buffer2D buffer, Rectangle rectangle) where T : unmanaged => new(buffer, rectangle); - internal static Buffer2DRegion GetRegion(this Buffer2D buffer, int x, int y, int width, int height) + /// + /// Return a to the specified area of . + /// + /// The element type. + /// The . + /// The X coordinate of the region. + /// The Y coordinate of the region. + /// The region width. + /// The region height. + /// The . + public static Buffer2DRegion GetRegion(this Buffer2D buffer, int x, int y, int width, int height) where T : unmanaged => new(buffer, new Rectangle(x, y, width, height)); /// - /// Return a to the whole area of 'buffer' + /// Return a to the whole area of . /// /// The element type /// The /// The - internal static Buffer2DRegion GetRegion(this Buffer2D buffer) + public static Buffer2DRegion GetRegion(this Buffer2D buffer) where T : unmanaged => new(buffer); - /// - /// Returns the size of the buffer. - /// - /// The element type - /// The - /// The of the buffer - internal static Size Size(this Buffer2D buffer) - where T : struct => - new(buffer.Width, buffer.Height); - - /// - /// Gets the bounds of the buffer. - /// - /// The element type - /// The - /// The - internal static Rectangle Bounds(this Buffer2D buffer) - where T : struct => - new(0, 0, buffer.Width, buffer.Height); - [Conditional("DEBUG")] private static void CheckColumnRegionsDoNotOverlap( Buffer2D buffer, diff --git a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs index f4b257b58..c78c4ce9e 100644 --- a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs +++ b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs @@ -15,17 +15,17 @@ public readonly struct Buffer2DRegion /// Initializes a new instance of the struct. /// /// The . - /// The defining a rectangular area within the buffer. + /// The defining a rectangular area within the buffer. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Buffer2DRegion(Buffer2D buffer, Rectangle rectangle) + public Buffer2DRegion(Buffer2D buffer, Rectangle bounds) { - DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.X, 0, nameof(rectangle)); - DebugGuard.MustBeGreaterThanOrEqualTo(rectangle.Y, 0, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, buffer.Width, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, buffer.Height, nameof(rectangle)); + DebugGuard.MustBeGreaterThanOrEqualTo(bounds.X, 0, nameof(bounds)); + DebugGuard.MustBeGreaterThanOrEqualTo(bounds.Y, 0, nameof(bounds)); + DebugGuard.MustBeLessThanOrEqualTo(bounds.Width, buffer.Width, nameof(bounds)); + DebugGuard.MustBeLessThanOrEqualTo(bounds.Height, buffer.Height, nameof(bounds)); this.Buffer = buffer; - this.Rectangle = rectangle; + this.Bounds = bounds; } /// @@ -34,15 +34,10 @@ public readonly struct Buffer2DRegion /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public Buffer2DRegion(Buffer2D buffer) - : this(buffer, buffer.FullRectangle()) + : this(buffer, buffer.Bounds) { } - /// - /// Gets the rectangle specifying the boundaries of the area in . - /// - public Rectangle Rectangle { get; } - /// /// Gets the being pointed by this instance. /// @@ -51,27 +46,32 @@ public readonly struct Buffer2DRegion /// /// Gets the width /// - public int Width => this.Rectangle.Width; + public int Width => this.Bounds.Width; /// /// Gets the height /// - public int Height => this.Rectangle.Height; + public int Height => this.Bounds.Height; /// - /// Gets the pixel stride which is equal to the width of . + /// Gets the number of elements between row starts in . /// - public int Stride => this.Buffer.Width; + public int Stride => this.Buffer.RowStride; /// /// Gets the size of the area. /// - internal Size Size => this.Rectangle.Size; + public Size Size => this.Bounds.Size; + + /// + /// Gets the rectangle specifying the boundaries of the area in . + /// + public Rectangle Bounds { get; } /// /// Gets a value indicating whether the area refers to the entire /// - internal bool IsFullBufferArea => this.Size == this.Buffer.Size(); + internal bool IsFullBufferArea => this.Size == this.Buffer.Size; /// /// Gets or sets a value at the given index. @@ -79,7 +79,7 @@ public readonly struct Buffer2DRegion /// The position inside a row /// The row index /// The reference to the value - internal ref T this[int x, int y] => ref this.Buffer[x + this.Rectangle.X, y + this.Rectangle.Y]; + internal ref T this[int x, int y] => ref this.Buffer[x + this.Bounds.X, y + this.Bounds.Y]; /// /// Gets a span to row 'y' inside this area. @@ -89,9 +89,9 @@ public readonly struct Buffer2DRegion [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span DangerousGetRowSpan(int y) { - int yy = this.Rectangle.Y + y; - int xx = this.Rectangle.X; - int width = this.Rectangle.Width; + int yy = this.Bounds.Y + y; + int xx = this.Bounds.X; + int width = this.Bounds.Width; return this.Buffer.DangerousGetRowSpan(yy).Slice(xx, width); } @@ -114,16 +114,16 @@ public readonly struct Buffer2DRegion /// /// Returns a subregion as . (Similar to .) /// - /// The specifying the boundaries of the subregion + /// The specifying the boundaries of the subregion /// The subregion [MethodImpl(MethodImplOptions.AggressiveInlining)] public Buffer2DRegion GetSubRegion(Rectangle rectangle) { - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Rectangle.Width, nameof(rectangle)); - DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Rectangle.Height, nameof(rectangle)); + DebugGuard.MustBeLessThanOrEqualTo(rectangle.Width, this.Bounds.Width, nameof(rectangle)); + DebugGuard.MustBeLessThanOrEqualTo(rectangle.Height, this.Bounds.Height, nameof(rectangle)); - int x = this.Rectangle.X + rectangle.X; - int y = this.Rectangle.Y + rectangle.Y; + int x = this.Bounds.X + rectangle.X; + int y = this.Bounds.Y + rectangle.Y; rectangle = new Rectangle(x, y, rectangle.Width, rectangle.Height); return new Buffer2DRegion(this.Buffer, rectangle); } @@ -135,8 +135,8 @@ public readonly struct Buffer2DRegion [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref T GetReferenceToOrigin() { - int y = this.Rectangle.Y; - int x = this.Rectangle.X; + int y = this.Bounds.Y; + int x = this.Bounds.X; return ref this.Buffer.DangerousGetRowSpan(y)[x]; } @@ -146,13 +146,13 @@ public readonly struct Buffer2DRegion internal void Clear() { // Optimization for when the size of the area is the same as the buffer size. - if (this.IsFullBufferArea) + if (this.IsFullBufferArea && this.Buffer.RowStride == this.Buffer.Width) { - this.Buffer.FastMemoryGroup.Clear(); + this.Buffer.Clear(default); return; } - for (int y = 0; y < this.Rectangle.Height; y++) + for (int y = 0; y < this.Bounds.Height; y++) { Span row = this.DangerousGetRowSpan(y); row.Clear(); @@ -166,13 +166,13 @@ public readonly struct Buffer2DRegion internal void Fill(T value) { // Optimization for when the size of the area is the same as the buffer size. - if (this.IsFullBufferArea) + if (this.IsFullBufferArea && this.Buffer.RowStride == this.Buffer.Width) { - this.Buffer.FastMemoryGroup.Fill(value); + this.Buffer.Clear(value); return; } - for (int y = 0; y < this.Rectangle.Height; y++) + for (int y = 0; y < this.Bounds.Height; y++) { Span row = this.DangerousGetRowSpan(y); row.Fill(value); diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 39c6e62e1..cfd7664f0 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Memory; @@ -20,21 +21,53 @@ public sealed class Buffer2D : IDisposable /// The number of elements in a row. /// The number of rows. internal Buffer2D(MemoryGroup memoryGroup, int width, int height) + : this(memoryGroup, width, height, width) { + } + + /// + /// Initializes a new instance of the class. + /// + /// The to wrap. + /// The number of elements in a row. + /// The number of rows. + /// The number of elements between row starts. + internal Buffer2D(MemoryGroup memoryGroup, int width, int height, int rowStride) + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(rowStride, width, nameof(rowStride)); + this.FastMemoryGroup = memoryGroup; - this.Width = width; - this.Height = height; + this.Size = new Size(width, height); + this.RowStride = rowStride; } /// /// Gets the width. /// - public int Width { get; private set; } + public int Width => this.Size.Width; /// /// Gets the height. /// - public int Height { get; private set; } + public int Height => this.Size.Height; + + /// + /// Gets the size of the buffer. + /// + public Size Size { get; private set; } + + /// + /// Gets the bounds of the buffer. + /// + /// The + public Rectangle Bounds => new(0, 0, this.Width, this.Height); + + /// + /// Gets the number of elements between row starts in the backing memory. + /// + public int RowStride { get; private set; } /// /// Gets the backing . @@ -75,6 +108,168 @@ public sealed class Buffer2D : IDisposable } } + /// + /// Wraps an existing memory area as a with tightly packed rows. + /// + /// + /// This method does not transfer ownership of to the returned . + /// The caller is responsible for ensuring that the memory remains valid for the entire lifetime of the returned buffer. + /// If originates from an (for example from ), + /// do not dispose that owner while the returned buffer is still in use. + /// + /// The source memory. + /// The number of elements in each row. + /// The number of rows. + /// The wrapped instance. + /// Thrown when or is not positive. + /// Thrown when is shorter than width * height. +#pragma warning disable CA1000 // Do not declare static members on generic types + public static Buffer2D WrapMemory(Memory memory, int width, int height) +#pragma warning restore CA1000 // Do not declare static members on generic types + => WrapMemory(memory, width, height, width); + + /// + /// Wraps an existing memory area as a using the specified row stride. + /// + /// + /// This method does not transfer ownership of to the returned . + /// The caller is responsible for ensuring that the memory remains valid for the entire lifetime of the returned buffer. + /// If originates from an (for example from ), + /// do not dispose that owner while the returned buffer is still in use. + /// The minimum required length is ((height - 1) * stride) + width elements. + /// + /// The source memory. + /// The number of elements in each row. + /// The number of rows. + /// The number of elements between row starts in the source memory. + /// The wrapped instance. + /// + /// Thrown when or is not positive, + /// or when is less than . + /// + /// Thrown when is shorter than the required buffer size. +#pragma warning disable CA1000 // Do not declare static members on generic types + public static Buffer2D WrapMemory(Memory memory, int width, int height, int stride) +#pragma warning restore CA1000 // Do not declare static members on generic types + { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(stride, width, nameof(stride)); + + long requiredLength = checked(((long)(height - 1) * stride) + width); + Guard.IsTrue(memory.Length >= requiredLength, nameof(memory), "The length of the input memory is less than the specified buffer size"); + + MemoryGroup memorySource = MemoryGroup.Wrap(memory); + return new Buffer2D(memorySource, width, height, stride); + } + + /// + /// Gets the representation of the values as a single contiguous + /// when the backing group is a single tightly packed segment. + /// + /// The referencing the buffer. + /// + /// when the buffer can be copied as one contiguous block + /// without per-row handling; otherwise . + /// + public bool DangerousTryGetSingleMemory(out Memory memory) + { + if (this.MemoryGroup.Count > 1 || this.RowStride != this.Width) + { + memory = default; + return false; + } + + int logicalLength = checked((int)((long)this.Width * this.Height)); + memory = this.MemoryGroup[0][..logicalLength]; + return true; + } + + /// + /// Copies this buffer into using the source logical row layout. + /// + /// + /// When dimensions are equal, destination stride is respected. + /// When dimensions differ, source stride is used to copy the source logical layout into destination memory. + /// + /// The destination buffer. + internal void CopyTo(Buffer2D destination) + { + Guard.NotNull(destination, nameof(destination)); + + bool sameDimensions = this.Width == destination.Width && this.Height == destination.Height; + int destinationStride = sameDimensions ? destination.RowStride : this.RowStride; + + // Different dimensions use source logical layout. This supports SwapOrCopyContent, + // where metadata is swapped after data copy. + this.FastMemoryGroup.CopyTo( + this.RowStride, + destination.FastMemoryGroup, + destinationStride, + this.Width, + this.Height); + } + + /// + /// Copies this buffer into using the source row stride as destination layout. + /// + /// The destination span. + internal void CopyTo(Span destination) + { + long requiredLength = checked(((long)(this.Height - 1) * this.RowStride) + this.Width); + Guard.MustBeGreaterThanOrEqualTo(destination.Length, requiredLength, nameof(destination)); + + this.FastMemoryGroup.CopyTo( + this.RowStride, + destination, + this.RowStride, + this.Width, + this.Height); + } + + /// + /// Copies tightly packed row-major data from into this buffer. + /// + /// The source data. + internal void CopyFrom(ReadOnlySpan source) => this.CopyFrom(source, this.Width); + + /// + /// Copies row-major data from into this buffer using + /// elements between source row starts. + /// + /// The source data. + /// The number of elements between source row starts. + internal void CopyFrom(ReadOnlySpan source, int sourceStride) + { + Guard.MustBeGreaterThanOrEqualTo(sourceStride, this.Width, nameof(sourceStride)); + + long requiredLength = checked(((long)(this.Height - 1) * sourceStride) + this.Width); + Guard.MustBeGreaterThanOrEqualTo(source.Length, requiredLength, nameof(source)); + + // Copy row by row so padded source rows map correctly into the destination logical rows. + int sourceOffset = 0; + for (int y = 0; y < this.Height; y++) + { + source.Slice(sourceOffset, this.Width).CopyTo(this.DangerousGetRowSpan(y)); + sourceOffset += sourceStride; + } + } + + /// + /// Clears this buffer when is default; otherwise fills it with . + /// + /// The fill value. + internal void Clear(T value) + { + if (value.Equals(default)) + { + this.FastMemoryGroup.Clear(); + return; + } + + this.FastMemoryGroup.Fill(value); + } + /// /// Disposes the instance /// @@ -102,7 +297,13 @@ public sealed class Buffer2D : IDisposable this.ThrowYOutOfRangeException(y); } - return this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); + if (this.RowStride == this.Width) + { + return this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); + } + + int rowStart = checked(y * this.RowStride); + return this.FastMemoryGroup[0].Span.Slice(rowStart, this.Width); } internal bool DangerousTryGetPaddedRowSpan(int y, int padding, out Span paddedSpan) @@ -111,8 +312,10 @@ public sealed class Buffer2D : IDisposable DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); int stride = this.Width + padding; - - Span slice = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width); + long rowStart = y * (long)this.RowStride; + Span slice = this.RowStride == this.Width + ? this.FastMemoryGroup.GetRemainingSliceOfBuffer(rowStart) + : this.FastMemoryGroup[0].Span[checked((int)rowStart)..]; if (slice.Length < stride) { @@ -127,7 +330,10 @@ public sealed class Buffer2D : IDisposable [MethodImpl(InliningOptions.ShortMethod)] internal ref T GetElementUnsafe(int x, int y) { - Span span = this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); + Span span = this.RowStride == this.Width + ? this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width) + : this.FastMemoryGroup[0].Span.Slice(checked(y * this.RowStride), this.Width); + return ref span[x]; } @@ -141,6 +347,13 @@ public sealed class Buffer2D : IDisposable { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + + if (this.RowStride != this.Width) + { + int rowStart = checked(y * this.RowStride); + return this.FastMemoryGroup[0].Slice(rowStart, this.Width); + } + return this.FastMemoryGroup.View.GetBoundedMemorySlice(y * (long)this.Width, this.Width); } @@ -185,21 +398,30 @@ public sealed class Buffer2D : IDisposable } else { - if (destination.FastMemoryGroup.TotalLength != source.FastMemoryGroup.TotalLength) + long sourceLayoutLength = GetRequiredLength(source.Width, source.Height, source.RowStride); + long destinationLayoutLength = GetRequiredLength(destination.Width, destination.Height, destination.RowStride); + + bool destinationCanRepresentSource = destination.FastMemoryGroup.TotalLength >= sourceLayoutLength; + bool sourceCanRepresentDestination = source.FastMemoryGroup.TotalLength >= destinationLayoutLength; + if (!destinationCanRepresentSource || !sourceCanRepresentDestination) { throw new InvalidMemoryOperationException( "Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images."); } - source.FastMemoryGroup.CopyTo(destination.MemoryGroup); + source.CopyTo(destination); } - (destination.Width, source.Width) = (source.Width, destination.Width); - (destination.Height, source.Height) = (source.Height, destination.Height); + (destination.Size, source.Size) = (source.Size, destination.Size); + (destination.RowStride, source.RowStride) = (source.RowStride, destination.RowStride); return swapped; } [MethodImpl(InliningOptions.ColdPath)] private void ThrowYOutOfRangeException(int y) => throw new ArgumentOutOfRangeException($"DangerousGetRowSpan({y}). Y was out of range. Height={this.Height}"); + + [MethodImpl(InliningOptions.ShortMethod)] + private static long GetRequiredLength(int width, int height, int stride) + => checked(((long)(height - 1) * stride) + width); } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs index 7e9719ea7..03f26aab0 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/IMemoryGroup{T}.cs @@ -15,12 +15,12 @@ public interface IMemoryGroup : IReadOnlyList> /// Gets the number of elements per contiguous sub-buffer preceding the last buffer. /// The last buffer is allowed to be smaller. /// - int BufferLength { get; } + public int BufferLength { get; } /// /// Gets the aggregate number of elements in the group. /// - long TotalLength { get; } + public long TotalLength { get; } /// /// Gets a value indicating whether the group has been invalidated. @@ -29,7 +29,7 @@ public interface IMemoryGroup : IReadOnlyList> /// Invalidation usually occurs when an image processor capable to alter the image dimensions replaces /// the image buffers internally. /// - bool IsValid { get; } + public bool IsValid { get; } /// /// Returns a value-type implementing an allocation-free enumerator of the memory groups in the current @@ -39,5 +39,5 @@ public interface IMemoryGroup : IReadOnlyList> /// implementation, which is still available when casting to one of the underlying interfaces. /// /// A new instance mapping the current values in use. - new MemoryGroupEnumerator GetEnumerator(); + public new MemoryGroupEnumerator GetEnumerator(); } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 148b5f6bf..b399d3d70 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -71,128 +71,159 @@ internal static class MemoryGroupExtensions return memory.Slice(bufferStart, length); } - internal static void CopyTo(this IMemoryGroup source, Span target) + /// + /// Copies a 2D logical region from into + /// using the provided source and target strides. + /// + /// The element type. + /// The source memory group. + /// Elements between source row starts. + /// The destination span. + /// Elements between destination row starts. + /// The logical row width to copy. + /// The number of rows to copy. + internal static void CopyTo( + this IMemoryGroup source, + int sourceStride, + Span target, + int targetStride, + int width, + int height) where T : struct { Guard.NotNull(source, nameof(source)); - Guard.MustBeGreaterThanOrEqualTo(target.Length, source.TotalLength, nameof(target)); + Guard.MustBeGreaterThanOrEqualTo(width, 0, nameof(width)); + Guard.MustBeGreaterThanOrEqualTo(height, 0, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(sourceStride, width, nameof(sourceStride)); + Guard.MustBeGreaterThanOrEqualTo(targetStride, width, nameof(targetStride)); - MemoryGroupCursor cur = new(source); - long position = 0; - while (position < source.TotalLength) - { - int fwd = Math.Min(cur.LookAhead(), target.Length); - cur.GetSpan(fwd).CopyTo(target); + long sourceRequired = height == 0 ? 0 : checked(((long)(height - 1) * sourceStride) + width); + long targetRequired = height == 0 ? 0 : checked(((long)(height - 1) * targetStride) + width); + Guard.MustBeGreaterThanOrEqualTo(source.TotalLength, sourceRequired, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(target.Length, targetRequired, nameof(target)); - cur.Forward(fwd); - target = target[fwd..]; - position += fwd; + if (width == 0 || height == 0) + { + return; } - } - internal static void CopyTo(this Span source, IMemoryGroup target) - where T : struct - => CopyTo((ReadOnlySpan)source, target); + MemoryGroupCursor sourceCursor = new(source); + int sourceSkip = sourceStride - width; - internal static void CopyTo(this ReadOnlySpan source, IMemoryGroup target) - where T : struct - { - Guard.NotNull(target, nameof(target)); - Guard.MustBeGreaterThanOrEqualTo(target.TotalLength, source.Length, nameof(target)); - - MemoryGroupCursor cur = new(target); - - while (!source.IsEmpty) + for (int y = 0; y < height; y++) { - int fwd = Math.Min(cur.LookAhead(), source.Length); - source[..fwd].CopyTo(cur.GetSpan(fwd)); - cur.Forward(fwd); - source = source[fwd..]; + int rowStart = checked(y * targetStride); + Span destinationRow = target.Slice(rowStart, width); + CopyFromCursorToSpan(ref sourceCursor, destinationRow); + + // Trailing padding after the last row is optional, so only skip between rows. + if (y < height - 1) + { + ForwardCursor(ref sourceCursor, sourceSkip); + } } } - internal static void CopyTo(this IMemoryGroup? source, IMemoryGroup? target) + /// + /// Copies a 2D logical region from into + /// using the provided source and target strides. + /// + /// The element type. + /// The source memory group. + /// Elements between source row starts. + /// The destination memory group. + /// Elements between destination row starts. + /// The logical row width to copy. + /// The number of rows to copy. + internal static void CopyTo( + this IMemoryGroup source, + int sourceStride, + IMemoryGroup target, + int targetStride, + int width, + int height) where T : struct { Guard.NotNull(source, nameof(source)); Guard.NotNull(target, nameof(target)); Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); - Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); + Guard.MustBeGreaterThanOrEqualTo(width, 0, nameof(width)); + Guard.MustBeGreaterThanOrEqualTo(height, 0, nameof(height)); + Guard.MustBeGreaterThanOrEqualTo(sourceStride, width, nameof(sourceStride)); + Guard.MustBeGreaterThanOrEqualTo(targetStride, width, nameof(targetStride)); - if (source.IsEmpty()) + long sourceRequired = height == 0 ? 0 : checked(((long)(height - 1) * sourceStride) + width); + long targetRequired = height == 0 ? 0 : checked(((long)(height - 1) * targetStride) + width); + Guard.MustBeGreaterThanOrEqualTo(source.TotalLength, sourceRequired, nameof(source)); + Guard.MustBeGreaterThanOrEqualTo(target.TotalLength, targetRequired, nameof(target)); + + if (width == 0 || height == 0) { return; } - long position = 0; - MemoryGroupCursor srcCur = new(source); - MemoryGroupCursor trgCur = new(target); + MemoryGroupCursor sourceCursor = new(source); + MemoryGroupCursor targetCursor = new(target); + int sourceSkip = sourceStride - width; + int targetSkip = targetStride - width; - while (position < source.TotalLength) + for (int y = 0; y < height; y++) { - int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); - Span srcSpan = srcCur.GetSpan(fwd); - Span trgSpan = trgCur.GetSpan(fwd); - srcSpan.CopyTo(trgSpan); - - srcCur.Forward(fwd); - trgCur.Forward(fwd); - position += fwd; + CopyFromCursorToCursor(ref sourceCursor, ref targetCursor, width); + + // Trailing padding after the last row is optional, so only skip between rows. + if (y < height - 1) + { + ForwardCursor(ref sourceCursor, sourceSkip); + ForwardCursor(ref targetCursor, targetSkip); + } } } - internal static void TransformTo( - this IMemoryGroup source, - IMemoryGroup target, - TransformItemsDelegate transform) - where TSource : struct - where TTarget : struct + private static void CopyFromCursorToCursor( + ref MemoryGroupCursor source, + ref MemoryGroupCursor target, + int count) + where T : struct { - Guard.NotNull(source, nameof(source)); - Guard.NotNull(target, nameof(target)); - Guard.NotNull(transform, nameof(transform)); - Guard.IsTrue(source.IsValid, nameof(source), "Source group must be valid."); - Guard.IsTrue(target.IsValid, nameof(target), "Target group must be valid."); - Guard.MustBeLessThanOrEqualTo(source.TotalLength, target.TotalLength, "Destination buffer too short!"); - - if (source.IsEmpty()) + int remaining = count; + while (remaining > 0) { - return; + int fwd = Math.Min(remaining, Math.Min(source.LookAhead(), target.LookAhead())); + source.GetSpan(fwd).CopyTo(target.GetSpan(fwd)); + source.Forward(fwd); + target.Forward(fwd); + remaining -= fwd; } + } - long position = 0; - MemoryGroupCursor srcCur = new(source); - MemoryGroupCursor trgCur = new(target); - - while (position < source.TotalLength) + private static void CopyFromCursorToSpan(ref MemoryGroupCursor source, Span target) + where T : struct + { + int remaining = target.Length; + while (remaining > 0) { - int fwd = Math.Min(srcCur.LookAhead(), trgCur.LookAhead()); - Span srcSpan = srcCur.GetSpan(fwd); - Span trgSpan = trgCur.GetSpan(fwd); - transform(srcSpan, trgSpan); - - srcCur.Forward(fwd); - trgCur.Forward(fwd); - position += fwd; + int copied = target.Length - remaining; + int fwd = Math.Min(remaining, source.LookAhead()); + source.GetSpan(fwd).CopyTo(target[copied..]); + source.Forward(fwd); + remaining -= fwd; } } - internal static void TransformInplace( - this IMemoryGroup memoryGroup, - TransformItemsInplaceDelegate transform) + private static void ForwardCursor(ref MemoryGroupCursor cursor, int steps) where T : struct { - foreach (Memory memory in memoryGroup) + int remaining = steps; + while (remaining > 0) { - transform(memory.Span); + int fwd = Math.Min(remaining, cursor.LookAhead()); + cursor.Forward(fwd); + remaining -= fwd; } } - internal static bool IsEmpty(this IMemoryGroup group) - where T : struct - => group.Count == 0; - private struct MemoryGroupCursor where T : struct { diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 950e2a019..75e93ce7f 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -31,23 +31,23 @@ internal abstract partial class MemoryGroup /// [MethodImpl(InliningOptions.ShortMethod)] - public override MemoryGroupEnumerator GetEnumerator() - { - return new MemoryGroupEnumerator(this); - } + public override MemoryGroupEnumerator GetEnumerator() => new(this); /// IEnumerator> IEnumerable>.GetEnumerator() - { + /* The runtime sees the Array class as if it implemented the * type-generic collection interfaces explicitly, so here we * can just cast the source array to IList> (or to * an equivalent type), and invoke the generic GetEnumerator * method directly from that interface reference. This saves * having to create our own iterator block here. */ - return ((IList>)this.source).GetEnumerator(); - } + => ((IList>)this.source).GetEnumerator(); - public override void Dispose() => this.View.Invalidate(); + public override void Dispose() + { + this.View.Invalidate(); + this.ReleaseAllocationTracking(); + } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index af896ee0e..c4b22f156 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -60,6 +60,58 @@ internal abstract partial class MemoryGroup } } + internal override void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) + { + if (this.groupLifetimeGuard != null) + { + // Pool-owned multi-buffer groups recover leaked handles through the group guard finalizer. + this.groupLifetimeGuard.AttachAllocationTracking(allocator, lengthInBytes); + return; + } + + IMemoryOwner[]? memoryOwners = this.memoryOwners; + if (memoryOwners?.Length == 1 && memoryOwners[0] is AllocationTrackedMemoryManager trackedOwner) + { + // Single-buffer groups should release tracking with the buffer owner when that owner has + // a more precise lifetime, such as an existing pooled-resource finalizer. + trackedOwner.AttachAllocationTracking(allocator, lengthInBytes); + return; + } + + if (memoryOwners?.Length > 1) + { + foreach (IMemoryOwner memoryOwner in memoryOwners) + { + if (memoryOwner is not AllocationTrackedMemoryManager) + { + // Splitting is only valid when every segment can own its reservation. A single + // untracked segment makes the whole group ineligible, and this preflight has + // not attached anything yet, so the entire group can fall back immediately. + base.AttachAllocationTracking(allocator, lengthInBytes); + return; + } + } + + // Non-pool multi-buffer groups have no group-level finalizer, so each segment carries + // its own share of the reservation through the segment owner or its lifetime guard. + long remainingLengthInBytes = lengthInBytes; + int lastOwnerIndex = memoryOwners.Length - 1; + for (int i = 0; i < lastOwnerIndex; i++) + { + trackedOwner = (AllocationTrackedMemoryManager)memoryOwners[i]; + long ownerLengthInBytes = (long)trackedOwner.Memory.Length * Unsafe.SizeOf(); + trackedOwner.AttachAllocationTracking(allocator, ownerLengthInBytes); + remainingLengthInBytes -= ownerLengthInBytes; + } + + trackedOwner = (AllocationTrackedMemoryManager)memoryOwners[lastOwnerIndex]; + trackedOwner.AttachAllocationTracking(allocator, remainingLengthInBytes); + return; + } + + base.AttachAllocationTracking(allocator, lengthInBytes); + } + private static IMemoryOwner[] CreateBuffers( UnmanagedMemoryHandle[] pooledBuffers, int bufferLength, @@ -73,8 +125,8 @@ internal abstract partial class MemoryGroup result[i] = currentBuffer; } - ObservedBuffer lastBuffer = ObservedBuffer.Create(pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer, options); - result[result.Length - 1] = lastBuffer; + ObservedBuffer lastBuffer = ObservedBuffer.Create(pooledBuffers[^1], sizeOfLastBuffer, options); + result[^1] = lastBuffer; return result; } @@ -155,6 +207,7 @@ internal abstract partial class MemoryGroup } } + this.ReleaseAllocationTracking(); this.memoryOwners = null; this.IsValid = false; this.groupLifetimeGuard = null; diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 6dd99fcb0..e0b9bca5e 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -21,6 +21,7 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable { private static readonly int ElementSize = Unsafe.SizeOf(); + private AllocationTrackingState allocationTracking; private MemoryGroupSpanCache memoryGroupSpanCache; private MemoryGroup(int bufferLength, long totalLength) @@ -52,16 +53,36 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable /// public abstract MemoryGroupEnumerator GetEnumerator(); + /// + /// Attaches allocation tracking by specifying the allocator and the length, in bytes, to be tracked. + /// + /// The memory allocator to use for tracking allocations. + /// The length, in bytes, of the memory region to track. Must be greater than or equal to zero. + /// + /// Intended for one-time initialization after the group has been created; callers should avoid changing + /// tracking state concurrently with disposal. + /// + internal virtual void AttachAllocationTracking(MemoryAllocator allocator, long lengthInBytes) => + this.allocationTracking.Attach(allocator, lengthInBytes); + + /// + /// Releases any resources or tracking information associated with allocation tracking for this instance. + /// + /// + /// This method is intended to be called when allocation tracking is no longer needed. It is safe + /// to call multiple times; subsequent calls after the first have no effect, even when called concurrently. + /// + internal void ReleaseAllocationTracking() => this.allocationTracking.Release(); + /// IEnumerator> IEnumerable>.GetEnumerator() - { + /* This method is implemented in each derived class. * Implementing the method here as non-abstract and throwing, * then reimplementing it explicitly in each derived class, is * a workaround for the lack of support for abstract explicit * interface method implementations in C#. */ - throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); - } + => throw new NotImplementedException($"The type {this.GetType()} needs to override IEnumerable>.GetEnumerator()"); /// IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable>)this).GetEnumerator(); @@ -81,8 +102,8 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable int bufferAlignmentInElements, AllocationOptions options = AllocationOptions.None) { - int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes(); Guard.NotNull(allocator, nameof(allocator)); + int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes(); if (totalLengthInElements < 0) { @@ -97,8 +118,8 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable if (totalLengthInElements == 0) { - IMemoryOwner[] buffers0 = [allocator.Allocate(0, options)]; - return new Owned(buffers0, 0, 0, true); + IMemoryOwner[] emptyBuffer = [allocator.AllocateGroupBuffer(0, options)]; + return new Owned(emptyBuffer, 0, 0, true); } int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; @@ -123,12 +144,12 @@ internal abstract partial class MemoryGroup : IMemoryGroup, IDisposable IMemoryOwner[] buffers = new IMemoryOwner[bufferCount]; for (int i = 0; i < buffers.Length - 1; i++) { - buffers[i] = allocator.Allocate(bufferLength, options); + buffers[i] = allocator.AllocateGroupBuffer(bufferLength, options); } if (bufferCount > 0) { - buffers[^1] = allocator.Allocate(sizeOfLastBuffer, options); + buffers[^1] = allocator.AllocateGroupBuffer(sizeOfLastBuffer, options); } return new Owned(buffers, bufferLength, totalLengthInElements, true); diff --git a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs index 81210f13d..724af35e1 100644 --- a/src/ImageSharp/Memory/InvalidMemoryOperationException.cs +++ b/src/ImageSharp/Memory/InvalidMemoryOperationException.cs @@ -39,4 +39,9 @@ public class InvalidMemoryOperationException : InvalidOperationException [DoesNotReturn] internal static void ThrowAllocationOverLimitException(ulong length, long limit) => throw new InvalidMemoryOperationException($"Attempted to allocate a buffer of length={length} that exceeded the limit {limit}."); + + [DoesNotReturn] + internal static void ThrowAccumulativeAllocationOverLimitException(long requestedLength, long totalLength, long limit) => + throw new InvalidMemoryOperationException( + $"Attempted to allocate a buffer of length={requestedLength} that would increase the accumulative allocation size to {totalLength}, exceeding the limit {limit}."); } diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index ff306e1e4..57ebcab76 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -29,6 +29,9 @@ public static class MemoryAllocatorExtensions AllocationOptions options = AllocationOptions.None) where T : struct { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + long groupLength = (long)width * height; MemoryGroup memoryGroup; if (preferContiguosImageBuffers && groupLength < int.MaxValue) @@ -104,6 +107,9 @@ public static class MemoryAllocatorExtensions AllocationOptions options = AllocationOptions.None) where T : struct { + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + long groupLength = (long)width * height; MemoryGroup memoryGroup = memoryAllocator.AllocateGroup( groupLength, diff --git a/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs b/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs deleted file mode 100644 index bc3d17f8f..000000000 --- a/src/ImageSharp/Memory/TransformItemsDelegate{TSource, TTarget}.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -#pragma warning disable SA1649 // File name should match first type name -internal delegate void TransformItemsDelegate(ReadOnlySpan source, Span target); -#pragma warning restore SA1649 // File name should match first type name diff --git a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs b/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs deleted file mode 100644 index d1ef51fb8..000000000 --- a/src/ImageSharp/Memory/TransformItemsInplaceDelegate.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Memory; - -internal delegate void TransformItemsInplaceDelegate(Span data); diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs index d7932f90b..aa2eb29e7 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs @@ -318,7 +318,7 @@ public sealed class ExifProfile : IDeepCloneable { if (location.Value?.Length == 2) { - Vector2 point = TransformUtils.ProjectiveTransform2D(location.Value[0], location.Value[1], matrix); + Vector2 point = TransformUtilities.ProjectiveTransform2D(location.Value[0], location.Value[1], matrix); // Ensure the point is within the image dimensions. point = Vector2.Clamp(point, Vector2.Zero, new Vector2(width - 1, height - 1)); @@ -340,18 +340,18 @@ public sealed class ExifProfile : IDeepCloneable if (area.Value?.Length == 4) { RectangleF rectangle = new(area.Value[0], area.Value[1], area.Value[2], area.Value[3]); - if (!TransformUtils.TryGetTransformedRectangle(rectangle, matrix, out Rectangle bounds)) + if (!TransformUtilities.TryGetTransformedRectangle(rectangle, matrix, out RectangleF bounds)) { return; } // Ensure the bounds are within the image dimensions. - bounds = Rectangle.Intersect(bounds, new Rectangle(0, 0, width, height)); + bounds = RectangleF.Intersect(bounds, new Rectangle(0, 0, width, height)); - area.Value[0] = (ushort)bounds.X; - area.Value[1] = (ushort)bounds.Y; - area.Value[2] = (ushort)bounds.Width; - area.Value[3] = (ushort)bounds.Height; + area.Value[0] = (ushort)MathF.Floor(bounds.X); + area.Value[1] = (ushort)MathF.Floor(bounds.Y); + area.Value[2] = (ushort)MathF.Ceiling(bounds.Width); + area.Value[3] = (ushort)MathF.Ceiling(bounds.Height); this.SetValue(ExifTag.SubjectArea, area.Value); } else diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index 732e3eab2..659df01a6 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -403,6 +403,13 @@ internal sealed class ExifWriter return WriteUInt32((uint)longNumber, destination, offset); } + // ExifLong8Array retains ulong storage but reports Long when every value fits + // in 32 bits, allowing BigTIFF offsets to be serialized by classic EXIF writers. + if (value is ulong long8Value) + { + return WriteUInt32((uint)long8Value, destination, offset); + } + return WriteUInt32((uint)value, destination, offset); case ExifDataType.Long8: return WriteUInt64((ulong)value, destination, offset); diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs index 9e89d24ff..2f1b15b6b 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -20,82 +20,46 @@ internal sealed partial class IccDataReader public IccTagDataEntry ReadTagDataEntry(IccTagTableEntry info) { this.currentIndex = (int)info.Offset; - switch (this.ReadTagDataEntryHeader()) - { - case IccTypeSignature.Chromaticity: - return this.ReadChromaticityTagDataEntry(); - case IccTypeSignature.ColorantOrder: - return this.ReadColorantOrderTagDataEntry(); - case IccTypeSignature.ColorantTable: - return this.ReadColorantTableTagDataEntry(); - case IccTypeSignature.Curve: - return this.ReadCurveTagDataEntry(); - case IccTypeSignature.Data: - return this.ReadDataTagDataEntry(info.DataSize); - case IccTypeSignature.DateTime: - return this.ReadDateTimeTagDataEntry(); - case IccTypeSignature.Lut16: - return this.ReadLut16TagDataEntry(); - case IccTypeSignature.Lut8: - return this.ReadLut8TagDataEntry(); - case IccTypeSignature.LutAToB: - return this.ReadLutAtoBTagDataEntry(); - case IccTypeSignature.LutBToA: - return this.ReadLutBtoATagDataEntry(); - case IccTypeSignature.Measurement: - return this.ReadMeasurementTagDataEntry(); - case IccTypeSignature.MultiLocalizedUnicode: - return this.ReadMultiLocalizedUnicodeTagDataEntry(); - case IccTypeSignature.MultiProcessElements: - return this.ReadMultiProcessElementsTagDataEntry(); - case IccTypeSignature.NamedColor2: - return this.ReadNamedColor2TagDataEntry(); - case IccTypeSignature.ParametricCurve: - return this.ReadParametricCurveTagDataEntry(); - case IccTypeSignature.ProfileSequenceDesc: - return this.ReadProfileSequenceDescTagDataEntry(); - case IccTypeSignature.ProfileSequenceIdentifier: - return this.ReadProfileSequenceIdentifierTagDataEntry(); - case IccTypeSignature.ResponseCurveSet16: - return this.ReadResponseCurveSet16TagDataEntry(); - case IccTypeSignature.S15Fixed16Array: - return this.ReadFix16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.Signature: - return this.ReadSignatureTagDataEntry(); - case IccTypeSignature.Text: - return this.ReadTextTagDataEntry(info.DataSize); - case IccTypeSignature.U16Fixed16Array: - return this.ReadUFix16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt16Array: - return this.ReadUInt16ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt32Array: - return this.ReadUInt32ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt64Array: - return this.ReadUInt64ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.UInt8Array: - return this.ReadUInt8ArrayTagDataEntry(info.DataSize); - case IccTypeSignature.ViewingConditions: - return this.ReadViewingConditionsTagDataEntry(); - case IccTypeSignature.Xyz: - return this.ReadXyzTagDataEntry(info.DataSize); + return this.ReadTagDataEntryHeader() switch + { + IccTypeSignature.Chromaticity => this.ReadChromaticityTagDataEntry(), + IccTypeSignature.ColorantOrder => this.ReadColorantOrderTagDataEntry(), + IccTypeSignature.ColorantTable => this.ReadColorantTableTagDataEntry(), + IccTypeSignature.Curve => this.ReadCurveTagDataEntry(), + IccTypeSignature.Data => this.ReadDataTagDataEntry(info.DataSize), + IccTypeSignature.DateTime => this.ReadDateTimeTagDataEntry(), + IccTypeSignature.Lut16 => this.ReadLut16TagDataEntry(), + IccTypeSignature.Lut8 => this.ReadLut8TagDataEntry(), + IccTypeSignature.LutAToB => this.ReadLutAtoBTagDataEntry(), + IccTypeSignature.LutBToA => this.ReadLutBtoATagDataEntry(), + IccTypeSignature.Measurement => this.ReadMeasurementTagDataEntry(), + IccTypeSignature.MultiLocalizedUnicode => this.ReadMultiLocalizedUnicodeTagDataEntry(), + IccTypeSignature.MultiProcessElements => this.ReadMultiProcessElementsTagDataEntry(), + IccTypeSignature.NamedColor2 => this.ReadNamedColor2TagDataEntry(), + IccTypeSignature.ParametricCurve => this.ReadParametricCurveTagDataEntry(), + IccTypeSignature.ProfileSequenceDesc => this.ReadProfileSequenceDescTagDataEntry(), + IccTypeSignature.ProfileSequenceIdentifier => this.ReadProfileSequenceIdentifierTagDataEntry(), + IccTypeSignature.ResponseCurveSet16 => this.ReadResponseCurveSet16TagDataEntry(), + IccTypeSignature.S15Fixed16Array => this.ReadFix16ArrayTagDataEntry(info.DataSize), + IccTypeSignature.Signature => this.ReadSignatureTagDataEntry(), + IccTypeSignature.Text => this.ReadTextTagDataEntry(info.DataSize), + IccTypeSignature.U16Fixed16Array => this.ReadUFix16ArrayTagDataEntry(info.DataSize), + IccTypeSignature.UInt16Array => this.ReadUInt16ArrayTagDataEntry(info.DataSize), + IccTypeSignature.UInt32Array => this.ReadUInt32ArrayTagDataEntry(info.DataSize), + IccTypeSignature.UInt64Array => this.ReadUInt64ArrayTagDataEntry(info.DataSize), + IccTypeSignature.UInt8Array => this.ReadUInt8ArrayTagDataEntry(info.DataSize), + IccTypeSignature.ViewingConditions => this.ReadViewingConditionsTagDataEntry(), + IccTypeSignature.Xyz => this.ReadXyzTagDataEntry(info.DataSize), // V2 Types: - case IccTypeSignature.TextDescription: - return this.ReadTextDescriptionTagDataEntry(); - case IccTypeSignature.CrdInfo: - return this.ReadCrdInfoTagDataEntry(); - case IccTypeSignature.Screening: - return this.ReadScreeningTagDataEntry(); - case IccTypeSignature.UcrBg: - return this.ReadUcrBgTagDataEntry(info.DataSize); + IccTypeSignature.TextDescription => this.ReadTextDescriptionTagDataEntry(), + IccTypeSignature.CrdInfo => this.ReadCrdInfoTagDataEntry(), + IccTypeSignature.Screening => this.ReadScreeningTagDataEntry(), + IccTypeSignature.UcrBg => this.ReadUcrBgTagDataEntry(info.DataSize), // Unsupported or unknown - case IccTypeSignature.DeviceSettings: - case IccTypeSignature.NamedColor: - case IccTypeSignature.Unknown: - default: - return this.ReadUnknownTagDataEntry(info.DataSize); - } + _ => this.ReadUnknownTagDataEntry(info.DataSize), + }; } /// @@ -477,7 +441,7 @@ internal sealed partial class IccDataReader return new IccMultiLocalizedUnicodeTagDataEntry(text); - CultureInfo ReadCulture(string language, string country) + static CultureInfo ReadCulture(string language, string country) { if (string.IsNullOrWhiteSpace(language)) { diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs index 6019a0bff..2cf637654 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataWriter/IccDataWriter.TagDataEntry.cs @@ -478,7 +478,7 @@ internal sealed partial class IccDataWriter this.dataStream.Position += cultureCount * 12; // TODO: Investigate cost of Linq GroupBy - IGrouping[] texts = value.Texts.GroupBy(t => t.Text).ToArray(); + IGrouping[] texts = [.. value.Texts.GroupBy(t => t.Text)]; uint[] offset = new uint[texts.Length]; int[] lengths = new int[texts.Length]; diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs new file mode 100644 index 000000000..bfa4ab9bd --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.SRGB.cs @@ -0,0 +1,346 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.ColorProfiles; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Provides logic for identifying canonical IEC 61966-2-1 (sRGB) matrix-TRC ICC profiles, +/// distinguishing them from appearance or device-specific variants. +/// +public sealed partial class IccProfile +{ + // sRGB v2 Preference + private static readonly IccProfileId StandardRgbV2 = new(0x3D0EB2DE, 0xAE9397BE, 0x9B6726CE, 0x8C0A43CE); + + // sRGB v4 Preference + private static readonly IccProfileId StandardRgbV4 = new(0x34562ABF, 0x994CCD06, 0x6D2C5721, 0xD0D68C5D); + + /// + /// Detects canonical sRGB matrix+TRC profiles quickly and safely. + /// Rules: + /// 1) Accept known IEC sRGB v2 and v4 by profile ID. + /// 2) Require RGB, PCS=XYZ, ICC v2 or v4, and no A2B*/B2A* LUTs. + /// 3) Require rTRC, gTRC, bTRC to exist and be identical by parameters or sampled shape. + /// 4) Accept if rXYZ/gXYZ/bXYZ already match the D50-adapted sRGB colorants within tolerance. + /// 5) If white point ≈ D65, adapt only the colorant columns to D50 using Bradford + /// via and then compare. + /// This rejects channel-swapped and appearance profiles while allowing real sRGB. + /// + /// + /// Reference D50-adapted sRGB colorants from Bruce Lindbloom: + /// + /// R=(0.4360747, 0.2225045, 0.0139322) + /// G=(0.3850649, 0.7168786, 0.0971045) + /// B=(0.1430804, 0.0606169, 0.7141733) + /// + internal bool IsCanonicalSrgbMatrixTrc() + { + IccProfileHeader h = this.Header; + + // Fast path for known IEC sRGB profile IDs + if (h.Id == StandardRgbV2 || h.Id == StandardRgbV4) + { + return true; + } + + // Header gating to avoid parsing work for obvious non-matches + if (h.FileSignature != "acsp") + { + return false; + } + + if (h.DataColorSpace != IccColorSpaceType.Rgb) + { + return false; + } + + if (h.ProfileConnectionSpace != IccColorSpaceType.CieXyz) + { + return false; + } + + if (h.Version.Major is not 2 and not 4) + { + return false; + } + + this.InitializeEntries(); + IccTagDataEntry[] entries = this.entries; + + // Reject device/display LUT profiles. We only accept matrix+TRC encodings. + if (Has(entries, IccProfileTag.AToB0) || Has(entries, IccProfileTag.AToB1) || Has(entries, IccProfileTag.AToB2) || + Has(entries, IccProfileTag.BToA0) || Has(entries, IccProfileTag.BToA1) || Has(entries, IccProfileTag.BToA2)) + { + return false; + } + + // Required matrix+TRC tags + if (!TryGetXyz(entries, IccProfileTag.MediaWhitePoint, out Vector3 wtpt)) + { + return false; + } + + if (!TryGetXyz(entries, IccProfileTag.RedMatrixColumn, out Vector3 rXYZ)) + { + return false; + } + + if (!TryGetXyz(entries, IccProfileTag.GreenMatrixColumn, out Vector3 gXYZ)) + { + return false; + } + + if (!TryGetXyz(entries, IccProfileTag.BlueMatrixColumn, out Vector3 bXYZ)) + { + return false; + } + + // TRCs must exist and be identical across channels. This filters many trick profiles. + if (!TryGetTrc(entries, IccProfileTag.RedTrc, out Trc tR)) + { + return false; + } + + if (!TryGetTrc(entries, IccProfileTag.GreenTrc, out Trc tG)) + { + return false; + } + + if (!TryGetTrc(entries, IccProfileTag.BlueTrc, out Trc tB)) + { + return false; + } + + if (!tR.Equals(tG) || !tR.Equals(tB)) + { + return false; + } + + // D50-adapted sRGB colorants (compare as columns: r,g,b), tight epsilon + const float eps = 2e-3F; + Vector3 rRef = new(0.4360747F, 0.2225045F, 0.0139322F); + Vector3 gRef = new(0.3850649F, 0.7168786F, 0.0971045F); + Vector3 bRef = new(0.1430804F, 0.0606169F, 0.7141733F); + + // First, accept if the stored colorants are already the D50 sRGB primaries. + // Many v2 sRGB profiles store D50-adapted colorants while declaring wtpt≈D65. + if (Near(rXYZ, rRef, eps) && Near(gXYZ, gRef, eps) && Near(bXYZ, bRef, eps)) + { + return true; + } + + // If the profile declares a D65 white, adapt the colorant columns to D50 and compare again. + // We never adapt when they already match, to avoid compounding rounding. + if (Near(wtpt, KnownIlluminants.D65.AsVector3Unsafe(), 2e-3F)) + { + CieXyz fromWp = new(wtpt); // Declared white + CieXyz toWp = KnownIlluminants.D50; // PCS white + Matrix4x4 matrix = KnownChromaticAdaptationMatrices.Bradford; + + rXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(rXYZ), (fromWp, toWp), matrix).AsVector3Unsafe(); + gXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(gXYZ), (fromWp, toWp), matrix).AsVector3Unsafe(); + bXYZ = VonKriesChromaticAdaptation.Transform(new CieXyz(bXYZ), (fromWp, toWp), matrix).AsVector3Unsafe(); + } + + // Require identity mapping of primaries, no permutation + if (!Near(rXYZ, rRef, eps) || !Near(gXYZ, gRef, eps) || !Near(bXYZ, bRef, eps)) + { + return false; + } + + return true; + + static bool Has(ReadOnlySpan span, IccProfileTag tag) + { + for (int i = 0; i < span.Length; i++) + { + if (span[i].TagSignature == tag) + { + return true; + } + } + + return false; + } + + static bool TryGetXyz(ReadOnlySpan span, IccProfileTag tag, out Vector3 xyz) + { + for (int i = 0; i < span.Length; i++) + { + IccTagDataEntry e = span[i]; + if (e.TagSignature != tag) + { + continue; + } + + if (e is IccXyzTagDataEntry x && x.Data is { Length: >= 1 }) + { + xyz = x.Data[0]; + return true; + } + + break; + } + + xyz = default; + return false; + } + + static bool TryGetTrc(ReadOnlySpan span, IccProfileTag tag, out Trc trc) + { + for (int i = 0; i < span.Length; i++) + { + IccTagDataEntry e = span[i]; + if (e.TagSignature != tag) + { + continue; + } + + if (e is IccParametricCurveTagDataEntry p) + { + trc = Trc.FromParametric(p.Curve); + return true; + } + + if (e is IccCurveTagDataEntry c) + { + trc = Trc.FromCurveLut(c.CurveData); + return true; + } + + break; + } + + trc = default; + return false; + } + + static bool Near(in Vector3 a, in Vector3 b, float tol) + => MathF.Abs(a.X - b.X) <= tol && + MathF.Abs(a.Y - b.Y) <= tol && + MathF.Abs(a.Z - b.Z) <= tol; + } + + /// + /// Compact, allocation-free descriptor of a TRC for equality and optional sRGB check. + /// + private readonly struct Trc : IEquatable + { + private readonly byte kind; // 0 = none, 1 = parametric, 2 = sampled + private readonly float g; // parametric payload or downsampled hash + private readonly float a; + private readonly float b; + private readonly float c; + private readonly float d; + private readonly float e; + private readonly float f; + private readonly int n; // for sampled, length or a small signature + + private Trc(byte kind, float g, float a, float b, float c, float d, float e, float f, int n) + { + this.kind = kind; + this.g = g; + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + this.n = n; + } + + public static Trc FromParametric(IccParametricCurve c) + + // Normalize by curve type to a stable tuple + // The types map to piecewise forms, but equality across channels is the key requirement here + => new(1, c.G, c.A, c.B, c.C, c.D, c.E, c.F, (int)c.Type); + + public static Trc FromCurveLut(float[] data) + { + // Exact sequence equality is enforced by the calling code using the same Trc construction + // Record a short signature to compare cheaply, avoid copying + if (data == null) + { + return default; + } + + int n = data.Length; + if (n == 0) + { + return default; + } + + // Downsample a few points to a robust fingerprint + // Use fixed indices to avoid allocations + float s0 = data[0]; + float s1 = data[n >> 2]; + float s2 = data[n >> 1]; + float s3 = data[(n * 3) >> 2]; + float s4 = data[n - 1]; + + return new Trc( + 2, + s0, + s1, + s2, + s3, + s4, + 0F, + 0F, + n); + } + + public override bool Equals(object? obj) => obj is Trc trc && this.Equals(trc); + + public bool Equals(Trc other) + { + if (this.kind != other.kind) + { + return false; + } + + if (this.kind == 0) + { + return false; + } + + if (this.kind == 1) + { + // parametric: exact parameter match and type match + return this.n == other.n && + this.g == other.g && this.a == other.a && + this.b == other.b && this.c == other.c && + this.d == other.d && this.e == other.e && this.f == other.f; + } + + // sampled: same length and same 5-point fingerprint + return this.n == other.n && + this.g == other.g && this.a == other.a && + this.b == other.b && this.c == other.c && this.d == other.d; + } + + // Optional stricter sRGB check if you need it later + public bool IsSrgbLike() + { + if (this.kind == 1) + { + // Accept common sRGB parametric encodings where type and parameters match + // IEC 61966-2-1 maps to Type4 or Type5 forms in practice + // Tighten only if you must exclude gamma~2.2 profiles that share primaries + return true; + } + + return true; + } + + public override int GetHashCode() + { + int a = HashCode.Combine(this.kind, this.g, this.a, this.b, this.c, this.d, this.e); + int b = HashCode.Combine(this.f, this.n); + return HashCode.Combine(a, b); + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs index 392ccb306..eaba0a045 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; /// /// Represents an ICC profile /// -public sealed class IccProfile : IDeepCloneable +public sealed partial class IccProfile : IDeepCloneable { /// /// The byte array to read the ICC profile from @@ -110,8 +110,8 @@ public sealed class IccProfile : IDeepCloneable // need to copy some values because they need to be zero for the hashing Span temp = stackalloc byte[24]; data.AsSpan(profileFlagPos, 4).CopyTo(temp); - data.AsSpan(renderingIntentPos, 4).CopyTo(temp.Slice(4)); - data.AsSpan(profileIdPos, 16).CopyTo(temp.Slice(8)); + data.AsSpan(renderingIntentPos, 4).CopyTo(temp[4..]); + data.AsSpan(profileIdPos, 16).CopyTo(temp[8..]); try { @@ -131,7 +131,7 @@ public sealed class IccProfile : IDeepCloneable } finally { - temp.Slice(0, 4).CopyTo(data.AsSpan(profileFlagPos)); + temp[..4].CopyTo(data.AsSpan(profileFlagPos)); temp.Slice(4, 4).CopyTo(data.AsSpan(renderingIntentPos)); temp.Slice(8, 16).CopyTo(data.AsSpan(profileIdPos)); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs index b50885d02..959668aaf 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs @@ -11,17 +11,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; /// public sealed class IccProfileHeader { - private static readonly Vector3 TruncatedD50 = new(0.9642029F, 1F, 0.8249054F); - - // sRGB v2 Preference - private static readonly IccProfileId StandardRgbV2 = new(0x3D0EB2DE, 0xAE9397BE, 0x9B6726CE, 0x8C0A43CE); - - // sRGB v4 Preference - private static readonly IccProfileId StandardRgbV4 = new(0x34562ABF, 0x994CCD06, 0x6D2C5721, 0xD0D68C5D); - - // sRGB v4 Appearance - private static readonly IccProfileId StandardRgbV4A = new(0xDF1132A1, 0x746E97B0, 0xAD85719, 0xBE711E08); - /// /// Gets or sets the profile size in bytes (will be ignored when writing a profile). /// @@ -108,31 +97,4 @@ public sealed class IccProfileHeader /// Gets or sets the profile ID (hash). /// public IccProfileId Id { get; set; } - - internal static bool IsLikelySrgb(IccProfileHeader header) - { - // Reject known perceptual-appearance profile - // This profile employs perceptual rendering intents to maintain color appearance across different - // devices and media, which can lead to variations from standard sRGB representations. - if (header.Id == StandardRgbV4A) - { - return false; - } - - // Accept known sRGB profile IDs - if (header.Id == StandardRgbV2 || header.Id == StandardRgbV4) - { - return true; - } - - // Fallback: best-guess heuristic - return - header.FileSignature == "acsp" && - header.DataColorSpace == IccColorSpaceType.Rgb && - (header.ProfileConnectionSpace == IccColorSpaceType.CieXyz || header.ProfileConnectionSpace == IccColorSpaceType.CieLab) && - (header.Class == IccProfileClass.DisplayDevice || header.Class == IccProfileClass.ColorSpace) && - header.PcsIlluminant == TruncatedD50 && - (header.Version.Major == 2 || header.Version.Major == 4) && - !string.Equals(header.CmmType, "ADBE", StringComparison.Ordinal); - } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs index 084ec388d..5e1c1942a 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccReader.cs @@ -102,7 +102,7 @@ internal sealed class IccReader entries.Add(entry); } - return entries.ToArray(); + return [.. entries]; } private static IccTagTableEntry[] ReadTagTable(IccDataReader reader) @@ -132,6 +132,6 @@ internal sealed class IccReader } } - return table.ToArray(); + return [.. table]; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs index 6bc0560f0..4a88a76f6 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccWriter.cs @@ -81,6 +81,6 @@ internal sealed class IccWriter } } - return table.ToArray(); + return [.. table]; } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs index 1938ad630..0f61857ba 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccChromaticityTagDataEntry.cs @@ -140,7 +140,7 @@ internal sealed class IccChromaticityTagDataEntry : IccTagDataEntry, IEquatable< [0.155, 0.070] ]; default: - throw new ArgumentException("Unrecognized colorant encoding"); + throw new InvalidIccProfileException("Unrecognized colorant encoding"); } } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs index 9bf323263..77bc45bd4 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutAToBTagDataEntry.cs @@ -64,44 +64,7 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable @@ -165,7 +128,7 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + /// Compares two curve arrays, treating consistently. + /// private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) { bool thisNull = thisCurves is null; @@ -202,7 +168,7 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null - && this.ClutValues != null - && this.CurveA != null; + /// + /// Validates the configured processing stages and derives the external channel counts. + /// + /// + /// Stages are evaluated in ICC mAB order: A, CLUT, M, Matrix, B. + /// Sparse pipelines are valid as long as adjacent stages agree on channel counts. + /// + private (int InputChannelCount, int OutputChannelCount) GetChannelCounts() + { + // There are at most five possible mAB stages: A, CLUT, M, Matrix, and B. + List<(int Input, int Output, string Name)> stages = new(5); - private bool IsMMatrixB() - => this.CurveB != null - && this.Matrix3x3 != null - && this.Matrix3x1 != null - && this.CurveM != null; + if (this.CurveA != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + stages.Add((this.CurveA.Length, this.CurveA.Length, nameof(this.CurveA))); + } - private bool IsAClutB() - => this.CurveB != null - && this.ClutValues != null - && this.CurveA != null; + if (this.ClutValues != null) + { + stages.Add((this.ClutValues.InputChannelCount, this.ClutValues.OutputChannelCount, nameof(this.ClutValues))); + } - private bool IsB() => this.CurveB != null; + if (this.CurveM != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveM.Length, 1, 15, nameof(this.CurveM)); + stages.Add((this.CurveM.Length, this.CurveM.Length, nameof(this.CurveM))); + } + + if (this.Matrix3x3 != null || this.Matrix3x1 != null) + { + Guard.IsTrue(this.Matrix3x3 != null && this.Matrix3x1 != null, nameof(this.Matrix3x3), "Matrix must include both the 3x3 and 3x1 components"); + stages.Add((3, 3, nameof(this.Matrix3x3))); + } + if (this.CurveB != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); + stages.Add((this.CurveB.Length, this.CurveB.Length, nameof(this.CurveB))); + } + + Guard.IsTrue(stages.Count > 0, nameof(this.CurveB), "AToB tag must contain at least one processing element"); + + for (int i = 1; i < stages.Count; i++) + { + Guard.IsTrue( + stages[i - 1].Output == stages[i].Input, + stages[i].Name, + $"Output channel count of {stages[i - 1].Name} does not match input channel count of {stages[i].Name}"); + } + + return (stages[0].Input, stages[^1].Output); + } + + /// + /// Verifies that every supplied curve entry is a supported one-dimensional curve type. + /// private void VerifyCurve(IccTagDataEntry[] curves, string name) { if (curves != null) @@ -240,6 +242,9 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + /// Verifies the dimensions of the optional matrix components. + /// private static void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) { if (matrix3x1 != null) @@ -254,6 +259,9 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + /// Creates the one-dimensional matrix vector when present. + /// private static Vector3? CreateMatrix3x1(float[] matrix) { if (matrix is null) @@ -264,6 +272,9 @@ internal sealed class IccLutAToBTagDataEntry : IccTagDataEntry, IEquatable + /// Creates the three-by-three matrix when present. + /// private static Matrix4x4? CreateMatrix3x3(float[,] matrix) { if (matrix is null) diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs index 033b80989..37e7b408d 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccLutBToATagDataEntry.cs @@ -64,44 +64,7 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable @@ -165,7 +128,7 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + /// Compares two curve arrays, treating consistently. + /// private static bool EqualsCurve(IccTagDataEntry[] thisCurves, IccTagDataEntry[] entryCurves) { bool thisNull = thisCurves is null; @@ -201,7 +167,7 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable this.CurveB != null && this.Matrix3x3 != null && this.Matrix3x1 != null && this.CurveM != null && this.ClutValues != null && this.CurveA != null; + /// + /// Validates the configured processing stages and derives the external channel counts. + /// + /// + /// Stages are evaluated in ICC mBA order: B, Matrix, M, CLUT, A. + /// Sparse pipelines are valid as long as adjacent stages agree on channel counts. + /// + private (int InputChannelCount, int OutputChannelCount) GetChannelCounts() + { + // There are at most five possible mBA stages: B, Matrix, M, CLUT, and A. + List<(int Input, int Output, string Name)> stages = new(5); - private bool IsBMatrixM() - => this.CurveB != null && this.Matrix3x3 != null && this.Matrix3x1 != null && this.CurveM != null; + if (this.CurveB != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveB.Length, 1, 15, nameof(this.CurveB)); + stages.Add((this.CurveB.Length, this.CurveB.Length, nameof(this.CurveB))); + } - private bool IsBClutA() - => this.CurveB != null && this.ClutValues != null && this.CurveA != null; + if (this.Matrix3x3 != null || this.Matrix3x1 != null) + { + Guard.IsTrue(this.Matrix3x3 != null && this.Matrix3x1 != null, nameof(this.Matrix3x3), "Matrix must include both the 3x3 and 3x1 components"); + stages.Add((3, 3, nameof(this.Matrix3x3))); + } - private bool IsB() => this.CurveB != null; + if (this.CurveM != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveM.Length, 1, 15, nameof(this.CurveM)); + stages.Add((this.CurveM.Length, this.CurveM.Length, nameof(this.CurveM))); + } + + if (this.ClutValues != null) + { + stages.Add((this.ClutValues.InputChannelCount, this.ClutValues.OutputChannelCount, nameof(this.ClutValues))); + } + if (this.CurveA != null) + { + Guard.MustBeBetweenOrEqualTo(this.CurveA.Length, 1, 15, nameof(this.CurveA)); + stages.Add((this.CurveA.Length, this.CurveA.Length, nameof(this.CurveA))); + } + + Guard.IsTrue(stages.Count > 0, nameof(this.CurveB), "BToA tag must contain at least one processing element"); + + for (int i = 1; i < stages.Count; i++) + { + Guard.IsTrue( + stages[i - 1].Output == stages[i].Input, + stages[i].Name, + $"Output channel count of {stages[i - 1].Name} does not match input channel count of {stages[i].Name}"); + } + + return (stages[0].Input, stages[^1].Output); + } + + /// + /// Verifies that every supplied curve entry is a supported one-dimensional curve type. + /// private void VerifyCurve(IccTagDataEntry[] curves, string name) { if (curves != null) @@ -229,6 +241,9 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + /// Verifies the dimensions of the optional matrix components. + /// private static void VerifyMatrix(float[,] matrix3x3, float[] matrix3x1) { if (matrix3x1 != null) @@ -243,6 +258,9 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + /// Creates the one-dimensional matrix vector when present. + /// private static Vector3? CreateMatrix3x1(float[] matrix) { if (matrix is null) @@ -253,6 +271,9 @@ internal sealed class IccLutBToATagDataEntry : IccTagDataEntry, IEquatable + /// Creates the three-by-three matrix when present. + /// private static Matrix4x4? CreateMatrix3x3(float[,] matrix) { if (matrix is null) diff --git a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs index 7db26e5c5..5f9496140 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/TagDataEntries/IccTextDescriptionTagDataEntry.cs @@ -102,7 +102,7 @@ internal sealed class IccTextDescriptionTagDataEntry : IccTagDataEntry, IEquatab localString = new IccLocalizedString(string.Empty); } - return new IccMultiLocalizedUnicodeTagDataEntry(new[] { localString }, textEntry.TagSignature); + return new IccMultiLocalizedUnicodeTagDataEntry([localString], textEntry.TagSignature); static CultureInfo GetCulture(uint value) { diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs index 5f07e5589..fb47409be 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLut.cs @@ -23,13 +23,8 @@ internal readonly struct IccLut : IEquatable { Guard.NotNull(values, nameof(values)); - const float max = ushort.MaxValue; - this.Values = new float[values.Length]; - for (int i = 0; i < values.Length; i++) - { - this.Values[i] = values[i] / max; - } + IccLutNormalizer.Normalize(values, this.Values); } /// @@ -40,13 +35,8 @@ internal readonly struct IccLut : IEquatable { Guard.NotNull(values, nameof(values)); - const float max = byte.MaxValue; - this.Values = new float[values.Length]; - for (int i = 0; i < values.Length; i++) - { - this.Values[i] = values[i] / max; - } + IccLutNormalizer.Normalize(values, this.Values); } /// diff --git a/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLutNormalizer.cs b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLutNormalizer.cs new file mode 100644 index 000000000..67796cad3 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/ICC/Various/IccLutNormalizer.cs @@ -0,0 +1,253 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; + +/// +/// Converts integer ICC lookup-table entries to their normalized single-precision representation. +/// +internal static class IccLutNormalizer +{ + /// + /// Defines the scalar and SIMD conversion for an integer lookup-table element type. + /// + /// The integer element type. + private interface INormalizeOperator + where T : unmanaged + { + /// + /// Gets the divisor that maps the integer range to [0, 1]. + /// + public static abstract float Divisor { get; } + + /// + /// Converts one scalar value. + /// + /// The integer value. + /// The normalized value. + public static abstract float Invoke(T source); + + /// + /// Converts one 128-bit input vector and stores the expanded single-precision results. + /// + /// The packed integer values. + /// The normalization divisor. + /// The first destination element. + public static abstract void Invoke(Vector128 source, Vector128 divisor, ref float destination); + + /// + /// Converts one 256-bit input vector and stores the expanded single-precision results. + /// + /// The packed integer values. + /// The normalization divisor. + /// The first destination element. + public static abstract void Invoke(Vector256 source, Vector256 divisor, ref float destination); + + /// + /// Converts one 512-bit input vector and stores the expanded single-precision results. + /// + /// The packed integer values. + /// The normalization divisor. + /// The first destination element. + public static abstract void Invoke(Vector512 source, Vector512 divisor, ref float destination); + } + + /// + /// Converts byte lookup-table entries to normalized single-precision values. + /// + /// The integer lookup-table entries. + /// The normalized destination values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Normalize(ReadOnlySpan source, Span destination) + => Normalize(source, destination); + + /// + /// Converts unsigned-short lookup-table entries to normalized single-precision values. + /// + /// The integer lookup-table entries. + /// The normalized destination values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Normalize(ReadOnlySpan source, Span destination) + => Normalize(source, destination); + + /// + /// Converts an integer lookup table using the widest available portable SIMD width, followed by narrower + /// widths and a scalar remainder. + /// + /// The integer element type. + /// The conversion implementation. + /// The integer lookup-table entries. + /// The normalized destination values. + private static void Normalize(ReadOnlySpan source, Span destination) + where T : unmanaged + where TOperator : struct, INormalizeOperator + { + ref T sourceRef = ref MemoryMarshal.GetReference(source); + ref float destinationRef = ref MemoryMarshal.GetReference(destination); + nuint length = (uint)source.Length; + nuint index = 0; + + if (Vector512.IsHardwareAccelerated) + { + Vector512 divisor = Vector512.Create(TOperator.Divisor); + nuint count = (uint)Vector512.Count; + + while (length - index >= count) + { + ref float destinationStart = ref Unsafe.Add(ref destinationRef, index); + TOperator.Invoke(Vector512.LoadUnsafe(ref sourceRef, index), divisor, ref destinationStart); + index += count; + } + } + + if (Vector256.IsHardwareAccelerated) + { + Vector256 divisor = Vector256.Create(TOperator.Divisor); + nuint count = (uint)Vector256.Count; + + while (length - index >= count) + { + ref float destinationStart = ref Unsafe.Add(ref destinationRef, index); + TOperator.Invoke(Vector256.LoadUnsafe(ref sourceRef, index), divisor, ref destinationStart); + index += count; + } + } + + if (Vector128.IsHardwareAccelerated) + { + Vector128 divisor = Vector128.Create(TOperator.Divisor); + nuint count = (uint)Vector128.Count; + + while (length - index >= count) + { + ref float destinationStart = ref Unsafe.Add(ref destinationRef, index); + TOperator.Invoke(Vector128.LoadUnsafe(ref sourceRef, index), divisor, ref destinationStart); + index += count; + } + } + + // Preserve the scalar division expression for the final partial vector. Multiplication by a reciprocal + // is not bit-equivalent for every input and would change the values stored in the ICC profile model. + while (index < length) + { + Unsafe.Add(ref destinationRef, index) = TOperator.Invoke(Unsafe.Add(ref sourceRef, index)); + index++; + } + } + + /// + /// Converts packed byte entries to normalized single-precision values. + /// + private readonly struct ByteNormalizeOperator : INormalizeOperator + { + /// + public static float Divisor => byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Invoke(byte source) + => source / (float)byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector128 source, Vector128 divisor, ref float destination) + { + // [b0..b15] becomes four ordered groups of four UInt32 values. Every widened value is at most + // 255, so reinterpreting UInt32 as Int32 before conversion preserves its numeric value. + (Vector128 lower16, Vector128 upper16) = Vector128.Widen(source); + (Vector128 values0, Vector128 values1) = Vector128.Widen(lower16); + (Vector128 values2, Vector128 values3) = Vector128.Widen(upper16); + + (Vector128.ConvertToSingle(values0.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector128.ConvertToSingle(values1.AsInt32()) / divisor).StoreUnsafe(ref destination, 4); + (Vector128.ConvertToSingle(values2.AsInt32()) / divisor).StoreUnsafe(ref destination, 8); + (Vector128.ConvertToSingle(values3.AsInt32()) / divisor).StoreUnsafe(ref destination, 12); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector256 source, Vector256 divisor, ref float destination) + { + // [b0..b31] becomes four ordered groups of eight UInt32 values, matching four contiguous + // Vector256 stores without shuffling the converted results. + (Vector256 lower16, Vector256 upper16) = Vector256.Widen(source); + (Vector256 values0, Vector256 values1) = Vector256.Widen(lower16); + (Vector256 values2, Vector256 values3) = Vector256.Widen(upper16); + + (Vector256.ConvertToSingle(values0.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector256.ConvertToSingle(values1.AsInt32()) / divisor).StoreUnsafe(ref destination, 8); + (Vector256.ConvertToSingle(values2.AsInt32()) / divisor).StoreUnsafe(ref destination, 16); + (Vector256.ConvertToSingle(values3.AsInt32()) / divisor).StoreUnsafe(ref destination, 24); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector512 source, Vector512 divisor, ref float destination) + { + // [b0..b63] becomes four ordered groups of sixteen UInt32 values, matching four contiguous + // Vector512 stores. The portable widening APIs map to zero-extension instructions. + (Vector512 lower16, Vector512 upper16) = Vector512.Widen(source); + (Vector512 values0, Vector512 values1) = Vector512.Widen(lower16); + (Vector512 values2, Vector512 values3) = Vector512.Widen(upper16); + + (Vector512.ConvertToSingle(values0.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector512.ConvertToSingle(values1.AsInt32()) / divisor).StoreUnsafe(ref destination, 16); + (Vector512.ConvertToSingle(values2.AsInt32()) / divisor).StoreUnsafe(ref destination, 32); + (Vector512.ConvertToSingle(values3.AsInt32()) / divisor).StoreUnsafe(ref destination, 48); + } + } + + /// + /// Converts packed unsigned-short entries to normalized single-precision values. + /// + private readonly struct UInt16NormalizeOperator : INormalizeOperator + { + /// + public static float Divisor => ushort.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Invoke(ushort source) + => source / (float)ushort.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector128 source, Vector128 divisor, ref float destination) + { + // [u0..u7] becomes two ordered groups of four UInt32 values. Every value is at most 65535, + // so signed conversion after reinterpretation is numerically identical to unsigned conversion. + (Vector128 lower, Vector128 upper) = Vector128.Widen(source); + + (Vector128.ConvertToSingle(lower.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector128.ConvertToSingle(upper.AsInt32()) / divisor).StoreUnsafe(ref destination, 4); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector256 source, Vector256 divisor, ref float destination) + { + // [u0..u15] becomes two ordered groups of eight UInt32 values, matching two contiguous + // Vector256 stores without a result shuffle. + (Vector256 lower, Vector256 upper) = Vector256.Widen(source); + + (Vector256.ConvertToSingle(lower.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector256.ConvertToSingle(upper.AsInt32()) / divisor).StoreUnsafe(ref destination, 8); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Invoke(Vector512 source, Vector512 divisor, ref float destination) + { + // [u0..u31] becomes two ordered groups of sixteen UInt32 values, matching two contiguous + // Vector512 stores. The portable widening APIs map to zero-extension instructions. + (Vector512 lower, Vector512 upper) = Vector512.Widen(source); + + (Vector512.ConvertToSingle(lower.AsInt32()) / divisor).StoreUnsafe(ref destination); + (Vector512.ConvertToSingle(upper.AsInt32()) / divisor).StoreUnsafe(ref destination, 16); + } + } +} diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs index 2d5fe6a09..bbbeb83e0 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcRecordNumber.cs @@ -9,12 +9,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.IPTC; internal enum IptcRecordNumber : byte { /// - /// A Envelope Record. + /// An Envelope Record. /// Envelope = 0x01, /// - /// A Application Record. + /// An Application Record. /// Application = 0x02 } diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs index 7735810b3..65daf5936 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Diagnostics; +using System.Globalization; using System.Text; namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc; @@ -9,7 +10,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc; /// /// Represents a single value of the IPTC profile. /// -[DebuggerDisplay("{Tag} = {ToString(),nq} ({GetType().Name,nq})")] +[DebuggerDisplay("{Tag} = {DebuggerDisplayValue(),nq} ({GetType().Name,nq})")] public sealed class IptcValue : IDeepCloneable { private byte[] data = []; @@ -213,4 +214,37 @@ public sealed class IptcValue : IDeepCloneable return encoding.GetString(this.data); } + + private string DebuggerDisplayValue() + { + // IPTC RecordVersion (2:00) is a 2-byte binary value, commonly 0x0004. + // Showing it as UTF-8 produces control characters like "\0\u0004". + if (this.Tag == IptcTag.RecordVersion && this.data.Length == 2) + { + int version = (this.data[0] << 8) | this.data[1]; + return version.ToString(CultureInfo.InvariantCulture); + } + + // Prefer readable text if it looks like it, otherwise show hex. + // (Avoid surprising debugger output for binary payloads.) + bool printable = true; + for (int i = 0; i < this.data.Length; i++) + { + byte b = this.data[i]; + + // If any byte is an ASCII control character, treat this value as binary. + if (b is < 0x20 or 0x7F) + { + printable = false; + break; + } + } + + if (printable) + { + return this.Value; + } + + return Convert.ToHexString(this.data); + } } diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs index 77ff35df0..639f09722 100644 --- a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics; using System.Text; +using System.Xml; using System.Xml.Linq; namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp; @@ -25,18 +25,17 @@ public sealed class XmpProfile : IDeepCloneable /// Initializes a new instance of the class. /// /// The UTF8 encoded byte array to read the XMP profile from. - public XmpProfile(byte[]? data) => this.Data = data; + public XmpProfile(byte[]? data) => this.Data = NormalizeDataIfNeeded(data); /// - /// Initializes a new instance of the class - /// by making a copy from another XMP profile. + /// Initializes a new instance of the class from an XML document. + /// The document is serialized as UTF-8 without BOM. /// - /// The other XMP profile, from which the clone should be made from. - private XmpProfile(XmpProfile other) + /// The XMP XML document. + public XmpProfile(XDocument document) { - Guard.NotNull(other, nameof(other)); - - this.Data = other.Data; + Guard.NotNull(document, nameof(document)); + this.Data = SerializeDocument(document); } /// @@ -45,30 +44,28 @@ public sealed class XmpProfile : IDeepCloneable internal byte[]? Data { get; private set; } /// - /// Gets the raw XML document containing the XMP profile. + /// Convert the content of this into an . /// - /// The - public XDocument? GetDocument() + /// The instance, or if no XMP data is present. + public XDocument? ToXDocument() { - byte[]? byteArray = this.Data; - if (byteArray is null) + byte[]? data = this.Data; + if (data is null || data.Length == 0) { return null; } - // Strip leading whitespace, as the XmlReader doesn't like them. - int count = byteArray.Length; - for (int i = count - 1; i > 0; i--) + using MemoryStream stream = new(data, writable: false); + + XmlReaderSettings settings = new() { - if (byteArray[i] is 0 or 0x0f) - { - count--; - } - } + DtdProcessing = DtdProcessing.Ignore, + XmlResolver = null, + CloseInput = false + }; - using MemoryStream stream = new(byteArray, 0, count); - using StreamReader reader = new(stream, Encoding.UTF8); - return XDocument.Load(reader); + using XmlReader reader = XmlReader.Create(stream, settings); + return XDocument.Load(reader, LoadOptions.PreserveWhitespace); } /// @@ -77,12 +74,101 @@ public sealed class XmpProfile : IDeepCloneable /// The public byte[] ToByteArray() { - Guard.NotNull(this.Data); - byte[] result = new byte[this.Data.Length]; + byte[]? data = this.Data; + + if (data is null) + { + return []; + } + + byte[] result = new byte[data.Length]; this.Data.AsSpan().CopyTo(result); return result; } /// - public XmpProfile DeepClone() => new(this); + public XmpProfile DeepClone() + { + byte[]? data = this.Data; + if (data is null) + { + // Preserve the semantics of an "empty" profile when cloning. + return new XmpProfile(); + } + + byte[] clone = new byte[data.Length]; + data.AsSpan().CopyTo(clone); + return new XmpProfile(clone); + } + + private static byte[] SerializeDocument(XDocument document) + { + using MemoryStream ms = new(); + + XmlWriterSettings writerSettings = new() + { + Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), // no BOM + OmitXmlDeclaration = true, // generally safer for XMP consumers + Indent = false, + NewLineHandling = NewLineHandling.None + }; + + using (XmlWriter xw = XmlWriter.Create(ms, writerSettings)) + { + document.Save(xw); + } + + return ms.ToArray(); + } + + private static byte[]? NormalizeDataIfNeeded(byte[]? data) + { + if (data is null || data.Length == 0) + { + return data; + } + + // Allocation-free fast path for the normal case. + + // Check for UTF-8 BOM (0xEF,0xBB,0xBF) + bool hasBom = data.Length >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF; + + // XMP metadata is commonly stored in fixed-size container blocks (e.g. TIFF tag 700). + // Producers often pad unused space so the packet can be updated in-place without + // rewriting the file. In practice this padding is either NUL (0x00) from the container + // or 0x0F used by Adobe XMP writers. Both are invalid XML and must be trimmed. + bool hasTrailingPad = data[^1] is 0 or 0x0F; + + if (!hasBom && !hasTrailingPad) + { + return data; + } + + int start = hasBom ? 3 : 0; + int end = data.Length; + + if (hasTrailingPad) + { + while (end > start) + { + byte b = data[end - 1]; + if (b is not 0 and not 0x0F) + { + break; + } + + end--; + } + } + + int length = end - start; + if (length <= 0) + { + return null; + } + + byte[] normalized = new byte[length]; + Buffer.BlockCopy(data, start, normalized, 0, length); + return normalized; + } } diff --git a/src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs new file mode 100644 index 000000000..ad26eb4e6 --- /dev/null +++ b/src/ImageSharp/PixelFormats/AssociatedAlphaPixelOperations{TPixel}.cs @@ -0,0 +1,387 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Numerics; +using SixLabors.ImageSharp.ColorProfiles.Companding; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides bulk operations for pixel formats that store associated alpha. +/// +/// The associated-alpha pixel format. +public abstract class AssociatedAlphaPixelOperations : PixelOperations + where TPixel : unmanaged, IPixel +{ + /// + public override PixelBlender GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) + => AssociatedAlphaPixelBlenders.GetPixelBlender(colorMode, alphaMode); + + /// + protected abstract override void ToUnassociatedVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination); + + /// + protected abstract override void ToAssociatedVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination); + + /// + protected abstract override void FromUnassociatedVector4Destructive( + Configuration configuration, + Span source, + Span destination); + + /// + protected abstract override void FromAssociatedVector4Destructive( + Configuration configuration, + Span source, + Span destination); + + /// + protected abstract override void ToUnassociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination); + + /// + protected abstract override void ToAssociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination); + + /// + protected abstract override void FromUnassociatedScaledVector4Destructive( + Configuration configuration, + Span source, + Span destination); + + /// + protected abstract override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination); + + /// + public override void From( + Configuration configuration, + ReadOnlySpan source, + Span destination) + { + if (source.IsEmpty) + { + return; + } + + // Cap large conversions at 1,024 vectors while avoiding a 16 KiB rental for short spans. + int sliceLength = Math.Min(source.Length, 1024); + int numberOfSlices = source.Length / sliceLength; + + using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); + Span vectorSpan = tempVectors.GetSpan()[..sliceLength]; + + // Convert through unassociated vectors so the destination operation can quantize alpha to its own storage before associating RGB. + for (int i = 0; i < numberOfSlices; i++) + { + int start = i * sliceLength; + ReadOnlySpan sourceSlice = source.Slice(start, sliceLength); + Span destinationSlice = destination.Slice(start, sliceLength); + PixelOperations.Instance.ToVector4( + configuration, + sourceSlice, + vectorSpan, + PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + this.FromUnassociatedScaledVector4Destructive(configuration, vectorSpan, destinationSlice); + } + + int endOfCompleteSlices = numberOfSlices * sliceLength; + int remainder = source.Length - endOfCompleteSlices; + + if (remainder > 0) + { + ReadOnlySpan sourceSlice = source[endOfCompleteSlices..]; + Span destinationSlice = destination.Slice(endOfCompleteSlices, remainder); + vectorSpan = vectorSpan[..remainder]; + PixelOperations.Instance.ToVector4( + configuration, + sourceSlice, + vectorSpan, + PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + this.FromUnassociatedScaledVector4Destructive(configuration, vectorSpan, destinationSlice); + } + } + + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destination, + PixelConversionModifiers modifiers) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); + + bool associated = modifiers.IsDefined(PixelConversionModifiers.Premultiply) || !modifiers.IsDefined(PixelConversionModifiers.UnPremultiply); + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); + + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + // Transfer functions operate on straight color components. Associated input must therefore be unassociated before companding. + if (associated) + { + Numerics.UnPremultiply(sourceVectors); + } + + SRgbCompanding.Compress(sourceVectors); + + if (scaled) + { + this.FromUnassociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedVector4Destructive(configuration, sourceVectors, destination); + } + + return; + } + + if (scaled) + { + if (associated) + { + this.FromAssociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + } + else if (associated) + { + this.FromAssociatedVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedVector4Destructive(configuration, sourceVectors, destination); + } + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan source, + Span destinationVectors, + PixelConversionModifiers modifiers) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationVectors, nameof(destinationVectors)); + + bool associated = modifiers.IsDefined(PixelConversionModifiers.Premultiply) || !modifiers.IsDefined(PixelConversionModifiers.UnPremultiply); + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); + + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + // Extract straight color before applying the transfer function; companding associated components would make RGB depend on alpha. + if (scaled) + { + this.ToUnassociatedScaledVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedVector4(configuration, source, destinationVectors); + } + + Span converted = destinationVectors[..source.Length]; + SRgbCompanding.Expand(converted); + + if (associated) + { + Numerics.Premultiply(converted); + } + + return; + } + + if (scaled) + { + if (associated) + { + this.ToAssociatedScaledVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedScaledVector4(configuration, source, destinationVectors); + } + } + else if (associated) + { + this.ToAssociatedVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedVector4(configuration, source, destinationVectors); + } + } + + /// + public override void FromArgb32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromBgr24(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromBgra32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromL8(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromL16(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromLa16(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromLa32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromRgb24(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromRgba32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromRgb48(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromRgba64(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void FromBgra5551(Configuration configuration, ReadOnlySpan source, Span destination) + => this.From(configuration, source, destination); + + /// + public override void ToArgb32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToAbgr32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToBgr24(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToBgra32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToL8(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToL16(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToLa16(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToLa32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToRgb24(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToRgba32(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToRgb48(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToRgba64(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + public override void ToBgra5551(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ConvertToUnassociated(configuration, source, destination); + + /// + /// Converts associated source pixels to an unassociated destination format. + /// + /// The destination pixel format. + /// The configuration. + /// The source pixels. + /// The destination pixels. + private void ConvertToUnassociated( + Configuration configuration, + ReadOnlySpan source, + Span destination) + where TDestinationPixel : unmanaged, IPixel + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + if (source.IsEmpty) + { + return; + } + + int sliceLength = Math.Min(source.Length, 1024); + int numberOfSlices = source.Length / sliceLength; + + using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); + Span vectorSpan = tempVectors.GetSpan()[..sliceLength]; + PixelOperations destinationOperations = PixelOperations.Instance; + + // Generated destination dispatch routes conversion back through this source operation's ToX override. Extract through this + // operation's protected bulk hook, then use the destination's public modifier contract to avoid recursive dispatch. + for (int i = 0; i < numberOfSlices; i++) + { + int start = i * sliceLength; + ReadOnlySpan sourceSlice = source.Slice(start, sliceLength); + Span destinationSlice = destination.Slice(start, sliceLength); + this.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan); + destinationOperations.FromVector4Destructive(configuration, vectorSpan, destinationSlice, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + } + + int endOfCompleteSlices = numberOfSlices * sliceLength; + int remainder = source.Length - endOfCompleteSlices; + + if (remainder > 0) + { + ReadOnlySpan sourceSlice = source[endOfCompleteSlices..]; + Span destinationSlice = destination.Slice(endOfCompleteSlices, remainder); + vectorSpan = vectorSpan[..remainder]; + this.ToUnassociatedScaledVector4(configuration, sourceSlice, vectorSpan); + destinationOperations.FromVector4Destructive(configuration, vectorSpan, destinationSlice, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + } + } +} diff --git a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs index 02936f160..53f22eb09 100644 --- a/src/ImageSharp/PixelFormats/HalfTypeHelper.cs +++ b/src/ImageSharp/PixelFormats/HalfTypeHelper.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.PixelFormats; @@ -10,6 +12,27 @@ namespace SixLabors.ImageSharp.PixelFormats; /// internal static class HalfTypeHelper { + // IEEE 754 binary16 has a largest finite magnitude of 65504. Scaled pixel vectors map that complete finite + // interval to [0, 1], while native vectors continue to expose the stored floating-point value directly. + internal const float FiniteMinimum = -65504F; + internal const float FiniteMaximum = 65504F; + internal const float FiniteRange = FiniteMaximum - FiniteMinimum; + internal const float InverseFiniteRange = (float)(1D / FiniteRange); + internal const float ScaledMidpoint = .5F; + + // These constants mirror the binary16 conversion used by System.Half. Keeping the vector conversion + // bit-for-bit equivalent to the scalar runtime conversion makes SIMD a pure throughput optimization. + private const uint HalfExponentMask = 0x7C00; + private const uint HalfSignMask = 0x8000; + private const uint HalfToSingleBitsMask = 0x0FFF_E000; + private const uint SingleExponentLowerBound = 0x3880_0000; + private const uint SingleExponentOffset = 0x3800_0000; + private const uint SingleExponent126 = 0x3F00_0000; + private const uint SingleBiasedExponentMask = 0x7F80_0000; + private const uint SingleExponent13 = 0x0680_0000; + private const uint SingleSignMask = 0x8000_0000; + private const float MaxHalfValueBelowInfinity = 65520F; + /// /// Packs a into an /// @@ -25,4 +48,304 @@ internal static class HalfTypeHelper /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static float Unpack(ushort value) => (float)BitConverter.UInt16BitsToHalf(value); + + /// + /// Normalizes a finite binary16 value to the scaled pixel range. + /// + /// The native binary16 value represented as a . + /// The normalized value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static float ToScaled(float value) => (value * InverseFiniteRange) + ScaledMidpoint; + + /// + /// Normalizes finite binary16 values to the scaled pixel range. + /// + /// The native binary16 values. + /// The normalized values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector2 ToScaled(Vector2 value) => (value * InverseFiniteRange) + new Vector2(ScaledMidpoint); + + /// + /// Normalizes finite binary16 values to the scaled pixel range. + /// + /// The native binary16 values. + /// The normalized values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector4 ToScaled(Vector4 value) => (value * InverseFiniteRange) + new Vector4(ScaledMidpoint); + + /// + /// Expands a normalized value to the finite binary16 range. + /// + /// The normalized value. + /// The native binary16 value represented as a . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static float FromScaled(float value) => (value * FiniteRange) + FiniteMinimum; + + /// + /// Expands normalized values to the finite binary16 range. + /// + /// The normalized values. + /// The native binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector2 FromScaled(Vector2 value) => (value * FiniteRange) + new Vector2(FiniteMinimum); + + /// + /// Expands normalized values to the finite binary16 range. + /// + /// The normalized values. + /// The native binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector4 FromScaled(Vector4 value) => (value * FiniteRange) + new Vector4(FiniteMinimum); + + /// + /// Unpacks eight binary16 values into two vectors of single-precision values. + /// + /// The packed binary16 values. + /// The unpacked lower and upper values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (Vector128 Lower, Vector128 Upper) Unpack(Vector128 value) + { + (Vector128 lower, Vector128 upper) = Vector128.Widen(value); + return (ConvertHalfBitsToSingle(lower), ConvertHalfBitsToSingle(upper)); + } + + /// + /// Unpacks sixteen binary16 values into two vectors of single-precision values. + /// + /// The packed binary16 values. + /// The unpacked lower and upper values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (Vector256 Lower, Vector256 Upper) Unpack(Vector256 value) + { + (Vector256 lower, Vector256 upper) = Vector256.Widen(value); + return (ConvertHalfBitsToSingle(lower), ConvertHalfBitsToSingle(upper)); + } + + /// + /// Unpacks thirty-two binary16 values into two vectors of single-precision values. + /// + /// The packed binary16 values. + /// The unpacked lower and upper values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static (Vector512 Lower, Vector512 Upper) Unpack(Vector512 value) + { + (Vector512 lower, Vector512 upper) = Vector512.Widen(value); + return (ConvertHalfBitsToSingle(lower), ConvertHalfBitsToSingle(upper)); + } + + /// + /// Packs eight single-precision values into binary16 storage. + /// + /// The lower single-precision values. + /// The upper single-precision values. + /// The packed binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector128 Pack(Vector128 lower, Vector128 upper) + => Vector128.Narrow(ConvertSingleToHalfBits(lower), ConvertSingleToHalfBits(upper)); + + /// + /// Packs sixteen single-precision values into binary16 storage. + /// + /// The lower single-precision values. + /// The upper single-precision values. + /// The packed binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector256 Pack(Vector256 lower, Vector256 upper) + => Vector256.Narrow(ConvertSingleToHalfBits(lower), ConvertSingleToHalfBits(upper)); + + /// + /// Packs thirty-two single-precision values into binary16 storage. + /// + /// The lower single-precision values. + /// The upper single-precision values. + /// The packed binary16 values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector512 Pack(Vector512 lower, Vector512 upper) + => Vector512.Narrow(ConvertSingleToHalfBits(lower), ConvertSingleToHalfBits(upper)); + + /// + /// Rounds single-precision values through binary16 without changing the vector width. + /// + /// The single-precision values. + /// The values after binary16 quantization. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector128 RoundToHalf(Vector128 value) + => ConvertHalfBitsToSingle(ConvertSingleToHalfBits(value)); + + /// + /// Rounds single-precision values through binary16 without changing the vector width. + /// + /// The single-precision values. + /// The values after binary16 quantization. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector256 RoundToHalf(Vector256 value) + => ConvertHalfBitsToSingle(ConvertSingleToHalfBits(value)); + + /// + /// Rounds single-precision values through binary16 without changing the vector width. + /// + /// The single-precision values. + /// The values after binary16 quantization. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector512 RoundToHalf(Vector512 value) + => ConvertHalfBitsToSingle(ConvertSingleToHalfBits(value)); + + /// + /// Converts zero-extended binary16 bit patterns to single-precision values. + /// + /// The binary16 bit patterns. + /// The converted single-precision values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ConvertHalfBitsToSingle(Vector128 value) + { + Vector128 sign = Vector128.ShiftLeft(value & Vector128.Create(HalfSignMask), 16); + Vector128 exponent = value & Vector128.Create(HalfExponentMask); + Vector128 subnormalMask = Vector128.Equals(exponent, Vector128.Zero); + Vector128 infinityOrNaNMask = Vector128.Equals(exponent, Vector128.Create(HalfExponentMask)); + Vector128 maskedExponentLowerBound = subnormalMask & Vector128.Create(SingleExponentLowerBound); + Vector128 exponentOffset = Vector128.Create(SingleExponentOffset) | maskedExponentLowerBound; + + // Binary16 and binary32 fraction fields differ by thirteen bits. Subnormals and special values + // need different exponent offsets before that shared field layout can be reinterpreted as float. + Vector128 bits = Vector128.ShiftLeft(value, 13) & Vector128.Create(HalfToSingleBitsMask); + exponentOffset = Vector128.ConditionalSelect(infinityOrNaNMask, Vector128.ShiftLeft(exponentOffset, 1), exponentOffset); + bits += exponentOffset; + Vector128 absoluteValue = (bits.AsSingle() - maskedExponentLowerBound.AsSingle()).AsUInt32(); + return (absoluteValue | sign).AsSingle(); + } + + /// + /// Converts zero-extended binary16 bit patterns to single-precision values. + /// + /// The binary16 bit patterns. + /// The converted single-precision values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertHalfBitsToSingle(Vector256 value) + { + Vector256 sign = Vector256.ShiftLeft(value & Vector256.Create(HalfSignMask), 16); + Vector256 exponent = value & Vector256.Create(HalfExponentMask); + Vector256 subnormalMask = Vector256.Equals(exponent, Vector256.Zero); + Vector256 infinityOrNaNMask = Vector256.Equals(exponent, Vector256.Create(HalfExponentMask)); + Vector256 maskedExponentLowerBound = subnormalMask & Vector256.Create(SingleExponentLowerBound); + Vector256 exponentOffset = Vector256.Create(SingleExponentOffset) | maskedExponentLowerBound; + + // Binary16 and binary32 fraction fields differ by thirteen bits. Subnormals and special values + // need different exponent offsets before that shared field layout can be reinterpreted as float. + Vector256 bits = Vector256.ShiftLeft(value, 13) & Vector256.Create(HalfToSingleBitsMask); + exponentOffset = Vector256.ConditionalSelect(infinityOrNaNMask, Vector256.ShiftLeft(exponentOffset, 1), exponentOffset); + bits += exponentOffset; + Vector256 absoluteValue = (bits.AsSingle() - maskedExponentLowerBound.AsSingle()).AsUInt32(); + return (absoluteValue | sign).AsSingle(); + } + + /// + /// Converts zero-extended binary16 bit patterns to single-precision values. + /// + /// The binary16 bit patterns. + /// The converted single-precision values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ConvertHalfBitsToSingle(Vector512 value) + { + Vector512 sign = Vector512.ShiftLeft(value & Vector512.Create(HalfSignMask), 16); + Vector512 exponent = value & Vector512.Create(HalfExponentMask); + Vector512 subnormalMask = Vector512.Equals(exponent, Vector512.Zero); + Vector512 infinityOrNaNMask = Vector512.Equals(exponent, Vector512.Create(HalfExponentMask)); + Vector512 maskedExponentLowerBound = subnormalMask & Vector512.Create(SingleExponentLowerBound); + Vector512 exponentOffset = Vector512.Create(SingleExponentOffset) | maskedExponentLowerBound; + + // Binary16 and binary32 fraction fields differ by thirteen bits. Subnormals and special values + // need different exponent offsets before that shared field layout can be reinterpreted as float. + Vector512 bits = Vector512.ShiftLeft(value, 13) & Vector512.Create(HalfToSingleBitsMask); + exponentOffset = Vector512.ConditionalSelect(infinityOrNaNMask, Vector512.ShiftLeft(exponentOffset, 1), exponentOffset); + bits += exponentOffset; + Vector512 absoluteValue = (bits.AsSingle() - maskedExponentLowerBound.AsSingle()).AsUInt32(); + return (absoluteValue | sign).AsSingle(); + } + + /// + /// Converts single-precision values to zero-extended binary16 bit patterns. + /// + /// The single-precision values. + /// The binary16 bit patterns in 32-bit lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ConvertSingleToHalfBits(Vector128 value) + { + Vector128 bits = value.AsUInt32(); + Vector128 sign = Vector128.ShiftRightLogical(bits & Vector128.Create(SingleSignMask), 16); + Vector128 realMask = Vector128.Equals(value, value).AsUInt32(); + value = Vector128.Abs(value); + value = Vector128.Min(Vector128.Create(MaxHalfValueBelowInfinity), value); + Vector128 exponentOffset = Vector128.Max(value, Vector128.Create(SingleExponentLowerBound).AsSingle()).AsUInt32(); + exponentOffset &= Vector128.Create(SingleBiasedExponentMask); + exponentOffset += Vector128.Create(SingleExponent13); + + // Adding an exponent-sized float rounds the significand to binary16 precision using IEEE + // round-to-nearest-even. The remaining integer operations realign the exponent and sign fields. + value += exponentOffset.AsSingle(); + bits = value.AsUInt32() - Vector128.Create(SingleExponent126); + Vector128 newExponent = Vector128.ShiftRightLogical(bits, 13); + Vector128 maskedHalfExponentForNaN = ~realMask & Vector128.Create(HalfExponentMask); + bits &= realMask; + bits += newExponent; + bits &= ~maskedHalfExponentForNaN; + return bits | maskedHalfExponentForNaN | sign; + } + + /// + /// Converts single-precision values to zero-extended binary16 bit patterns. + /// + /// The single-precision values. + /// The binary16 bit patterns in 32-bit lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertSingleToHalfBits(Vector256 value) + { + Vector256 bits = value.AsUInt32(); + Vector256 sign = Vector256.ShiftRightLogical(bits & Vector256.Create(SingleSignMask), 16); + Vector256 realMask = Vector256.Equals(value, value).AsUInt32(); + value = Vector256.Abs(value); + value = Vector256.Min(Vector256.Create(MaxHalfValueBelowInfinity), value); + Vector256 exponentOffset = Vector256.Max(value, Vector256.Create(SingleExponentLowerBound).AsSingle()).AsUInt32(); + exponentOffset &= Vector256.Create(SingleBiasedExponentMask); + exponentOffset += Vector256.Create(SingleExponent13); + + // Adding an exponent-sized float rounds the significand to binary16 precision using IEEE + // round-to-nearest-even. The remaining integer operations realign the exponent and sign fields. + value += exponentOffset.AsSingle(); + bits = value.AsUInt32() - Vector256.Create(SingleExponent126); + Vector256 newExponent = Vector256.ShiftRightLogical(bits, 13); + Vector256 maskedHalfExponentForNaN = ~realMask & Vector256.Create(HalfExponentMask); + bits &= realMask; + bits += newExponent; + bits &= ~maskedHalfExponentForNaN; + return bits | maskedHalfExponentForNaN | sign; + } + + /// + /// Converts single-precision values to zero-extended binary16 bit patterns. + /// + /// The single-precision values. + /// The binary16 bit patterns in 32-bit lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ConvertSingleToHalfBits(Vector512 value) + { + Vector512 bits = value.AsUInt32(); + Vector512 sign = Vector512.ShiftRightLogical(bits & Vector512.Create(SingleSignMask), 16); + Vector512 realMask = Vector512.Equals(value, value).AsUInt32(); + value = Vector512.Abs(value); + value = Vector512.Min(Vector512.Create(MaxHalfValueBelowInfinity), value); + Vector512 exponentOffset = Vector512.Max(value, Vector512.Create(SingleExponentLowerBound).AsSingle()).AsUInt32(); + exponentOffset &= Vector512.Create(SingleBiasedExponentMask); + exponentOffset += Vector512.Create(SingleExponent13); + + // Adding an exponent-sized float rounds the significand to binary16 precision using IEEE + // round-to-nearest-even. The remaining integer operations realign the exponent and sign fields. + value += exponentOffset.AsSingle(); + bits = value.AsUInt32() - Vector512.Create(SingleExponent126); + Vector512 newExponent = Vector512.ShiftRightLogical(bits, 13); + Vector512 maskedHalfExponentForNaN = ~realMask & Vector512.Create(HalfExponentMask); + bits &= realMask; + bits += newExponent; + bits &= ~maskedHalfExponentForNaN; + return bits | maskedHalfExponentForNaN | sign; + } } diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 528b3e76d..a4f6312dd 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -14,120 +14,150 @@ namespace SixLabors.ImageSharp.PixelFormats; public interface IPixel : IPixel, IEquatable where TSelf : unmanaged, IPixel { -#pragma warning disable CA1000 // Do not declare static members on generic types /// /// Creates a instance for this pixel type. /// This method is not intended to be consumed directly. Use instead. /// /// The instance. - static abstract PixelOperations CreatePixelOperations(); + public static abstract PixelOperations CreatePixelOperations(); /// - /// Initializes the pixel instance from a generic a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1 + /// Initializes the pixel instance from a generic ("scaled") representation using the pixel type's native alpha representation. + /// The scaled representation uses 0 and 1 as its nominal component bounds. /// /// The vector to load the pixel from. /// The . - static abstract TSelf FromScaledVector4(Vector4 source); + public static abstract TSelf FromScaledVector4(Vector4 source); /// - /// Initializes the pixel instance from a which is specific to the current pixel type. + /// Initializes the pixel instance from a generic ("scaled") whose color components use unassociated alpha. + /// The scaled representation uses 0 and 1 as its nominal component bounds. /// /// The vector to load the pixel from. /// The . - static abstract TSelf FromVector4(Vector4 source); + public static abstract TSelf FromUnassociatedScaledVector4(Vector4 source); + + /// + /// Initializes the pixel instance from a generic ("scaled") whose color components use associated alpha, + /// representing color multiplied by the logical opacity represented by alpha. + /// The scaled representation uses 0 and 1 as its nominal component bounds. + /// + /// The vector to load the pixel from. + /// The . + public static abstract TSelf FromAssociatedScaledVector4(Vector4 source); + + /// + /// Initializes the pixel instance from a which is specific to the current pixel type and uses its native alpha representation. + /// + /// The vector to load the pixel from. + /// The . + public static abstract TSelf FromVector4(Vector4 source); + + /// + /// Initializes the pixel instance from a which is specific to the current pixel type and whose color components use unassociated alpha. + /// + /// The vector to load the pixel from. + /// The . + public static abstract TSelf FromUnassociatedVector4(Vector4 source); + + /// + /// Initializes the pixel instance from a which is specific to the current pixel type and whose color components use associated alpha, + /// representing color multiplied by the logical opacity represented by alpha. + /// + /// The vector to load the pixel from. + /// The . + public static abstract TSelf FromAssociatedVector4(Vector4 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromAbgr32(Abgr32 source); + public static abstract TSelf FromAbgr32(Abgr32 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromArgb32(Argb32 source); + public static abstract TSelf FromArgb32(Argb32 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromBgra5551(Bgra5551 source); + public static abstract TSelf FromBgra5551(Bgra5551 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromBgr24(Bgr24 source); + public static abstract TSelf FromBgr24(Bgr24 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromBgra32(Bgra32 source); + public static abstract TSelf FromBgra32(Bgra32 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromL8(L8 source); + public static abstract TSelf FromL8(L8 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromL16(L16 source); + public static abstract TSelf FromL16(L16 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromLa16(La16 source); + public static abstract TSelf FromLa16(La16 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromLa32(La32 source); + public static abstract TSelf FromLa32(La32 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromRgb24(Rgb24 source); + public static abstract TSelf FromRgb24(Rgb24 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromRgba32(Rgba32 source); + public static abstract TSelf FromRgba32(Rgba32 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromRgb48(Rgb48 source); + public static abstract TSelf FromRgb48(Rgb48 source); /// /// Initializes the pixel instance from an value. /// /// The value. /// The . - static abstract TSelf FromRgba64(Rgba64 source); -#pragma warning restore CA1000 // Do not declare static members on generic types + public static abstract TSelf FromRgba64(Rgba64 source); } /// @@ -139,26 +169,58 @@ public interface IPixel /// Gets the pixel type information. /// /// The . - static abstract PixelTypeInfo GetPixelTypeInfo(); + public static abstract PixelTypeInfo GetPixelTypeInfo(); /// /// Convert the pixel instance into representation. /// /// The - Rgba32 ToRgba32(); + public Rgba32 ToRgba32(); /// - /// Expands the pixel into a generic ("scaled") representation - /// with values scaled and clamped between 0 and 1. + /// Expands the pixel into a generic ("scaled") representation using the pixel type's native alpha representation. + /// The scaled representation uses 0 and 1 as its nominal component bounds. /// The vector components are typically expanded in least to greatest significance order. /// /// The . - Vector4 ToScaledVector4(); + public Vector4 ToScaledVector4(); + + /// + /// Expands the pixel into a generic ("scaled") whose color components use unassociated alpha. + /// The scaled representation uses 0 and 1 as its nominal component bounds. + /// When alpha is zero and the pixel's native representation is associated, the color components remain unchanged + /// because no unassociated value can be recovered. + /// + /// The . + public Vector4 ToUnassociatedScaledVector4(); + + /// + /// Expands the pixel into a generic ("scaled") whose color components use associated alpha, + /// representing color multiplied by the logical opacity represented by alpha. + /// The scaled representation uses 0 and 1 as its nominal component bounds. + /// + /// The . + public Vector4 ToAssociatedScaledVector4(); /// - /// Expands the pixel into a which is specific to the current pixel type. + /// Expands the pixel into a which is specific to the current pixel type and uses its native alpha representation. /// The vector components are typically expanded in least to greatest significance order. /// /// The . - Vector4 ToVector4(); + public Vector4 ToVector4(); + + /// + /// Expands the pixel into a which is specific to the current pixel type and whose color components use unassociated alpha. + /// When alpha is zero and the pixel's native representation is associated, the color components remain unchanged + /// because no unassociated value can be recovered. + /// + /// The . + public Vector4 ToUnassociatedVector4(); + + /// + /// Expands the pixel into a which is specific to the current pixel type and whose color components use associated alpha, + /// representing color multiplied by the logical opacity represented by alpha. + /// + /// The . + public Vector4 ToAssociatedVector4(); } diff --git a/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs b/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs index f42a264db..b003e62a4 100644 --- a/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs +++ b/src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs @@ -66,5 +66,10 @@ public enum PixelAlphaCompositionMode /// /// Clear where they overlap. /// - Xor + Xor, + + /// + /// Adds the source and destination, clamping the result to the supported color and alpha range. + /// + Plus, } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs new file mode 100644 index 000000000..b553e3412 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.cs @@ -0,0 +1,6108 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System.Numerics; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Provides the associated-alpha equations consumed by the shared pixel-blending traversal. +/// +internal static class AssociatedAlphaPixelBlenderOperators +{ + /// + /// Applies the "NormalSrc" associated-alpha composition equation. + /// + public readonly struct NormalSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrc(background, source, amount); + } + + /// + /// Applies the "MultiplySrc" associated-alpha composition equation. + /// + public readonly struct MultiplySrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrc(background, source, amount); + } + + /// + /// Applies the "AddSrc" associated-alpha composition equation. + /// + public readonly struct AddSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrc(background, source, amount); + } + + /// + /// Applies the "SubtractSrc" associated-alpha composition equation. + /// + public readonly struct SubtractSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrc(background, source, amount); + } + + /// + /// Applies the "ScreenSrc" associated-alpha composition equation. + /// + public readonly struct ScreenSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrc(background, source, amount); + } + + /// + /// Applies the "DarkenSrc" associated-alpha composition equation. + /// + public readonly struct DarkenSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrc(background, source, amount); + } + + /// + /// Applies the "LightenSrc" associated-alpha composition equation. + /// + public readonly struct LightenSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrc(background, source, amount); + } + + /// + /// Applies the "OverlaySrc" associated-alpha composition equation. + /// + public readonly struct OverlaySrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrc(background, source, amount); + } + + /// + /// Applies the "HardLightSrc" associated-alpha composition equation. + /// + public readonly struct HardLightSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrc(background, source, amount); + } + + /// + /// Applies the "ColorDodgeSrc" associated-alpha composition equation. + /// + public readonly struct ColorDodgeSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrc(background, source, amount); + } + + /// + /// Applies the "ColorBurnSrc" associated-alpha composition equation. + /// + public readonly struct ColorBurnSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrc(background, source, amount); + } + + /// + /// Applies the "SoftLightSrc" associated-alpha composition equation. + /// + public readonly struct SoftLightSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrc(background, source, amount); + } + + /// + /// Applies the "DifferenceSrc" associated-alpha composition equation. + /// + public readonly struct DifferenceSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrc(background, source, amount); + } + + /// + /// Applies the "ExclusionSrc" associated-alpha composition equation. + /// + public readonly struct ExclusionSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrc(background, source, amount); + } + + /// + /// Applies the "HueSrc" associated-alpha composition equation. + /// + public readonly struct HueSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueSrc(background, source, amount); + } + + /// + /// Applies the "SaturationSrc" associated-alpha composition equation. + /// + public readonly struct SaturationSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrc(background, source, amount); + } + + /// + /// Applies the "ColorSrc" associated-alpha composition equation. + /// + public readonly struct ColorSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrc(background, source, amount); + } + + /// + /// Applies the "LuminositySrc" associated-alpha composition equation. + /// + public readonly struct LuminositySrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrc(background, source, amount); + } + + /// + /// Applies the "NormalSrcAtop" associated-alpha composition equation. + /// + public readonly struct NormalSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcAtop(background, source, amount); + } + + /// + /// Applies the "MultiplySrcAtop" associated-alpha composition equation. + /// + public readonly struct MultiplySrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcAtop(background, source, amount); + } + + /// + /// Applies the "AddSrcAtop" associated-alpha composition equation. + /// + public readonly struct AddSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcAtop(background, source, amount); + } + + /// + /// Applies the "SubtractSrcAtop" associated-alpha composition equation. + /// + public readonly struct SubtractSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcAtop(background, source, amount); + } + + /// + /// Applies the "ScreenSrcAtop" associated-alpha composition equation. + /// + public readonly struct ScreenSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcAtop(background, source, amount); + } + + /// + /// Applies the "DarkenSrcAtop" associated-alpha composition equation. + /// + public readonly struct DarkenSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcAtop(background, source, amount); + } + + /// + /// Applies the "LightenSrcAtop" associated-alpha composition equation. + /// + public readonly struct LightenSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcAtop(background, source, amount); + } + + /// + /// Applies the "OverlaySrcAtop" associated-alpha composition equation. + /// + public readonly struct OverlaySrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcAtop(background, source, amount); + } + + /// + /// Applies the "HardLightSrcAtop" associated-alpha composition equation. + /// + public readonly struct HardLightSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcAtop(background, source, amount); + } + + /// + /// Applies the "ColorDodgeSrcAtop" associated-alpha composition equation. + /// + public readonly struct ColorDodgeSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcAtop(background, source, amount); + } + + /// + /// Applies the "ColorBurnSrcAtop" associated-alpha composition equation. + /// + public readonly struct ColorBurnSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcAtop(background, source, amount); + } + + /// + /// Applies the "SoftLightSrcAtop" associated-alpha composition equation. + /// + public readonly struct SoftLightSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcAtop(background, source, amount); + } + + /// + /// Applies the "DifferenceSrcAtop" associated-alpha composition equation. + /// + public readonly struct DifferenceSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcAtop(background, source, amount); + } + + /// + /// Applies the "ExclusionSrcAtop" associated-alpha composition equation. + /// + public readonly struct ExclusionSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcAtop(background, source, amount); + } + + /// + /// Applies the "HueSrcAtop" associated-alpha composition equation. + /// + public readonly struct HueSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcAtop(background, source, amount); + } + + /// + /// Applies the "SaturationSrcAtop" associated-alpha composition equation. + /// + public readonly struct SaturationSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcAtop(background, source, amount); + } + + /// + /// Applies the "ColorSrcAtop" associated-alpha composition equation. + /// + public readonly struct ColorSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcAtop(background, source, amount); + } + + /// + /// Applies the "LuminositySrcAtop" associated-alpha composition equation. + /// + public readonly struct LuminositySrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcAtop(background, source, amount); + } + + /// + /// Applies the "NormalSrcOver" associated-alpha composition equation. + /// + public readonly struct NormalSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOver(background, source, amount); + } + + /// + /// Applies the "MultiplySrcOver" associated-alpha composition equation. + /// + public readonly struct MultiplySrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOver(background, source, amount); + } + + /// + /// Applies the "AddSrcOver" associated-alpha composition equation. + /// + public readonly struct AddSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOver(background, source, amount); + } + + /// + /// Applies the "SubtractSrcOver" associated-alpha composition equation. + /// + public readonly struct SubtractSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOver(background, source, amount); + } + + /// + /// Applies the "ScreenSrcOver" associated-alpha composition equation. + /// + public readonly struct ScreenSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOver(background, source, amount); + } + + /// + /// Applies the "DarkenSrcOver" associated-alpha composition equation. + /// + public readonly struct DarkenSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOver(background, source, amount); + } + + /// + /// Applies the "LightenSrcOver" associated-alpha composition equation. + /// + public readonly struct LightenSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOver(background, source, amount); + } + + /// + /// Applies the "OverlaySrcOver" associated-alpha composition equation. + /// + public readonly struct OverlaySrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOver(background, source, amount); + } + + /// + /// Applies the "HardLightSrcOver" associated-alpha composition equation. + /// + public readonly struct HardLightSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOver(background, source, amount); + } + + /// + /// Applies the "ColorDodgeSrcOver" associated-alpha composition equation. + /// + public readonly struct ColorDodgeSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcOver(background, source, amount); + } + + /// + /// Applies the "ColorBurnSrcOver" associated-alpha composition equation. + /// + public readonly struct ColorBurnSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcOver(background, source, amount); + } + + /// + /// Applies the "SoftLightSrcOver" associated-alpha composition equation. + /// + public readonly struct SoftLightSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcOver(background, source, amount); + } + + /// + /// Applies the "DifferenceSrcOver" associated-alpha composition equation. + /// + public readonly struct DifferenceSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcOver(background, source, amount); + } + + /// + /// Applies the "ExclusionSrcOver" associated-alpha composition equation. + /// + public readonly struct ExclusionSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcOver(background, source, amount); + } + + /// + /// Applies the "HueSrcOver" associated-alpha composition equation. + /// + public readonly struct HueSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcOver(background, source, amount); + } + + /// + /// Applies the "SaturationSrcOver" associated-alpha composition equation. + /// + public readonly struct SaturationSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcOver(background, source, amount); + } + + /// + /// Applies the "ColorSrcOver" associated-alpha composition equation. + /// + public readonly struct ColorSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcOver(background, source, amount); + } + + /// + /// Applies the "LuminositySrcOver" associated-alpha composition equation. + /// + public readonly struct LuminositySrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcOver(background, source, amount); + } + + /// + /// Applies the "NormalSrcIn" associated-alpha composition equation. + /// + public readonly struct NormalSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcIn(background, source, amount); + } + + /// + /// Applies the "MultiplySrcIn" associated-alpha composition equation. + /// + public readonly struct MultiplySrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcIn(background, source, amount); + } + + /// + /// Applies the "AddSrcIn" associated-alpha composition equation. + /// + public readonly struct AddSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcIn(background, source, amount); + } + + /// + /// Applies the "SubtractSrcIn" associated-alpha composition equation. + /// + public readonly struct SubtractSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcIn(background, source, amount); + } + + /// + /// Applies the "ScreenSrcIn" associated-alpha composition equation. + /// + public readonly struct ScreenSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcIn(background, source, amount); + } + + /// + /// Applies the "DarkenSrcIn" associated-alpha composition equation. + /// + public readonly struct DarkenSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcIn(background, source, amount); + } + + /// + /// Applies the "LightenSrcIn" associated-alpha composition equation. + /// + public readonly struct LightenSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcIn(background, source, amount); + } + + /// + /// Applies the "OverlaySrcIn" associated-alpha composition equation. + /// + public readonly struct OverlaySrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcIn(background, source, amount); + } + + /// + /// Applies the "HardLightSrcIn" associated-alpha composition equation. + /// + public readonly struct HardLightSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcIn(background, source, amount); + } + + /// + /// Applies the "ColorDodgeSrcIn" associated-alpha composition equation. + /// + public readonly struct ColorDodgeSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcIn(background, source, amount); + } + + /// + /// Applies the "ColorBurnSrcIn" associated-alpha composition equation. + /// + public readonly struct ColorBurnSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcIn(background, source, amount); + } + + /// + /// Applies the "SoftLightSrcIn" associated-alpha composition equation. + /// + public readonly struct SoftLightSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcIn(background, source, amount); + } + + /// + /// Applies the "DifferenceSrcIn" associated-alpha composition equation. + /// + public readonly struct DifferenceSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcIn(background, source, amount); + } + + /// + /// Applies the "ExclusionSrcIn" associated-alpha composition equation. + /// + public readonly struct ExclusionSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcIn(background, source, amount); + } + + /// + /// Applies the "HueSrcIn" associated-alpha composition equation. + /// + public readonly struct HueSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcIn(background, source, amount); + } + + /// + /// Applies the "SaturationSrcIn" associated-alpha composition equation. + /// + public readonly struct SaturationSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcIn(background, source, amount); + } + + /// + /// Applies the "ColorSrcIn" associated-alpha composition equation. + /// + public readonly struct ColorSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcIn(background, source, amount); + } + + /// + /// Applies the "LuminositySrcIn" associated-alpha composition equation. + /// + public readonly struct LuminositySrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcIn(background, source, amount); + } + + /// + /// Applies the "NormalSrcOut" associated-alpha composition equation. + /// + public readonly struct NormalSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalSrcOut(background, source, amount); + } + + /// + /// Applies the "MultiplySrcOut" associated-alpha composition equation. + /// + public readonly struct MultiplySrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplySrcOut(background, source, amount); + } + + /// + /// Applies the "AddSrcOut" associated-alpha composition equation. + /// + public readonly struct AddSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddSrcOut(background, source, amount); + } + + /// + /// Applies the "SubtractSrcOut" associated-alpha composition equation. + /// + public readonly struct SubtractSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractSrcOut(background, source, amount); + } + + /// + /// Applies the "ScreenSrcOut" associated-alpha composition equation. + /// + public readonly struct ScreenSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenSrcOut(background, source, amount); + } + + /// + /// Applies the "DarkenSrcOut" associated-alpha composition equation. + /// + public readonly struct DarkenSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenSrcOut(background, source, amount); + } + + /// + /// Applies the "LightenSrcOut" associated-alpha composition equation. + /// + public readonly struct LightenSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenSrcOut(background, source, amount); + } + + /// + /// Applies the "OverlaySrcOut" associated-alpha composition equation. + /// + public readonly struct OverlaySrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlaySrcOut(background, source, amount); + } + + /// + /// Applies the "HardLightSrcOut" associated-alpha composition equation. + /// + public readonly struct HardLightSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightSrcOut(background, source, amount); + } + + /// + /// Applies the "ColorDodgeSrcOut" associated-alpha composition equation. + /// + public readonly struct ColorDodgeSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeSrcOut(background, source, amount); + } + + /// + /// Applies the "ColorBurnSrcOut" associated-alpha composition equation. + /// + public readonly struct ColorBurnSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnSrcOut(background, source, amount); + } + + /// + /// Applies the "SoftLightSrcOut" associated-alpha composition equation. + /// + public readonly struct SoftLightSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightSrcOut(background, source, amount); + } + + /// + /// Applies the "DifferenceSrcOut" associated-alpha composition equation. + /// + public readonly struct DifferenceSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceSrcOut(background, source, amount); + } + + /// + /// Applies the "ExclusionSrcOut" associated-alpha composition equation. + /// + public readonly struct ExclusionSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionSrcOut(background, source, amount); + } + + /// + /// Applies the "HueSrcOut" associated-alpha composition equation. + /// + public readonly struct HueSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueSrcOut(background, source, amount); + } + + /// + /// Applies the "SaturationSrcOut" associated-alpha composition equation. + /// + public readonly struct SaturationSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationSrcOut(background, source, amount); + } + + /// + /// Applies the "ColorSrcOut" associated-alpha composition equation. + /// + public readonly struct ColorSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorSrcOut(background, source, amount); + } + + /// + /// Applies the "LuminositySrcOut" associated-alpha composition equation. + /// + public readonly struct LuminositySrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminositySrcOut(background, source, amount); + } + + /// + /// Applies the "NormalDest" associated-alpha composition equation. + /// + public readonly struct NormalDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDest(background, source, amount); + } + + /// + /// Applies the "MultiplyDest" associated-alpha composition equation. + /// + public readonly struct MultiplyDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDest(background, source, amount); + } + + /// + /// Applies the "AddDest" associated-alpha composition equation. + /// + public readonly struct AddDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddDest(background, source, amount); + } + + /// + /// Applies the "SubtractDest" associated-alpha composition equation. + /// + public readonly struct SubtractDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDest(background, source, amount); + } + + /// + /// Applies the "ScreenDest" associated-alpha composition equation. + /// + public readonly struct ScreenDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDest(background, source, amount); + } + + /// + /// Applies the "DarkenDest" associated-alpha composition equation. + /// + public readonly struct DarkenDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDest(background, source, amount); + } + + /// + /// Applies the "LightenDest" associated-alpha composition equation. + /// + public readonly struct LightenDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDest(background, source, amount); + } + + /// + /// Applies the "OverlayDest" associated-alpha composition equation. + /// + public readonly struct OverlayDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDest(background, source, amount); + } + + /// + /// Applies the "HardLightDest" associated-alpha composition equation. + /// + public readonly struct HardLightDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDest(background, source, amount); + } + + /// + /// Applies the "ColorDodgeDest" associated-alpha composition equation. + /// + public readonly struct ColorDodgeDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDest(background, source, amount); + } + + /// + /// Applies the "ColorBurnDest" associated-alpha composition equation. + /// + public readonly struct ColorBurnDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDest(background, source, amount); + } + + /// + /// Applies the "SoftLightDest" associated-alpha composition equation. + /// + public readonly struct SoftLightDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDest(background, source, amount); + } + + /// + /// Applies the "DifferenceDest" associated-alpha composition equation. + /// + public readonly struct DifferenceDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDest(background, source, amount); + } + + /// + /// Applies the "ExclusionDest" associated-alpha composition equation. + /// + public readonly struct ExclusionDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDest(background, source, amount); + } + + /// + /// Applies the "HueDest" associated-alpha composition equation. + /// + public readonly struct HueDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueDest(background, source, amount); + } + + /// + /// Applies the "SaturationDest" associated-alpha composition equation. + /// + public readonly struct SaturationDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDest(background, source, amount); + } + + /// + /// Applies the "ColorDest" associated-alpha composition equation. + /// + public readonly struct ColorDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDest(background, source, amount); + } + + /// + /// Applies the "LuminosityDest" associated-alpha composition equation. + /// + public readonly struct LuminosityDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDest(background, source, amount); + } + + /// + /// Applies the "NormalDestAtop" associated-alpha composition equation. + /// + public readonly struct NormalDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestAtop(background, source, amount); + } + + /// + /// Applies the "MultiplyDestAtop" associated-alpha composition equation. + /// + public readonly struct MultiplyDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestAtop(background, source, amount); + } + + /// + /// Applies the "AddDestAtop" associated-alpha composition equation. + /// + public readonly struct AddDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestAtop(background, source, amount); + } + + /// + /// Applies the "SubtractDestAtop" associated-alpha composition equation. + /// + public readonly struct SubtractDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestAtop(background, source, amount); + } + + /// + /// Applies the "ScreenDestAtop" associated-alpha composition equation. + /// + public readonly struct ScreenDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestAtop(background, source, amount); + } + + /// + /// Applies the "DarkenDestAtop" associated-alpha composition equation. + /// + public readonly struct DarkenDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestAtop(background, source, amount); + } + + /// + /// Applies the "LightenDestAtop" associated-alpha composition equation. + /// + public readonly struct LightenDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestAtop(background, source, amount); + } + + /// + /// Applies the "OverlayDestAtop" associated-alpha composition equation. + /// + public readonly struct OverlayDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestAtop(background, source, amount); + } + + /// + /// Applies the "HardLightDestAtop" associated-alpha composition equation. + /// + public readonly struct HardLightDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestAtop(background, source, amount); + } + + /// + /// Applies the "ColorDodgeDestAtop" associated-alpha composition equation. + /// + public readonly struct ColorDodgeDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestAtop(background, source, amount); + } + + /// + /// Applies the "ColorBurnDestAtop" associated-alpha composition equation. + /// + public readonly struct ColorBurnDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestAtop(background, source, amount); + } + + /// + /// Applies the "SoftLightDestAtop" associated-alpha composition equation. + /// + public readonly struct SoftLightDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestAtop(background, source, amount); + } + + /// + /// Applies the "DifferenceDestAtop" associated-alpha composition equation. + /// + public readonly struct DifferenceDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestAtop(background, source, amount); + } + + /// + /// Applies the "ExclusionDestAtop" associated-alpha composition equation. + /// + public readonly struct ExclusionDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestAtop(background, source, amount); + } + + /// + /// Applies the "HueDestAtop" associated-alpha composition equation. + /// + public readonly struct HueDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueDestAtop(background, source, amount); + } + + /// + /// Applies the "SaturationDestAtop" associated-alpha composition equation. + /// + public readonly struct SaturationDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestAtop(background, source, amount); + } + + /// + /// Applies the "ColorDestAtop" associated-alpha composition equation. + /// + public readonly struct ColorDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestAtop(background, source, amount); + } + + /// + /// Applies the "LuminosityDestAtop" associated-alpha composition equation. + /// + public readonly struct LuminosityDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestAtop(background, source, amount); + } + + /// + /// Applies the "NormalDestOver" associated-alpha composition equation. + /// + public readonly struct NormalDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOver(background, source, amount); + } + + /// + /// Applies the "MultiplyDestOver" associated-alpha composition equation. + /// + public readonly struct MultiplyDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOver(background, source, amount); + } + + /// + /// Applies the "AddDestOver" associated-alpha composition equation. + /// + public readonly struct AddDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOver(background, source, amount); + } + + /// + /// Applies the "SubtractDestOver" associated-alpha composition equation. + /// + public readonly struct SubtractDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOver(background, source, amount); + } + + /// + /// Applies the "ScreenDestOver" associated-alpha composition equation. + /// + public readonly struct ScreenDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOver(background, source, amount); + } + + /// + /// Applies the "DarkenDestOver" associated-alpha composition equation. + /// + public readonly struct DarkenDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOver(background, source, amount); + } + + /// + /// Applies the "LightenDestOver" associated-alpha composition equation. + /// + public readonly struct LightenDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOver(background, source, amount); + } + + /// + /// Applies the "OverlayDestOver" associated-alpha composition equation. + /// + public readonly struct OverlayDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOver(background, source, amount); + } + + /// + /// Applies the "HardLightDestOver" associated-alpha composition equation. + /// + public readonly struct HardLightDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOver(background, source, amount); + } + + /// + /// Applies the "ColorDodgeDestOver" associated-alpha composition equation. + /// + public readonly struct ColorDodgeDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestOver(background, source, amount); + } + + /// + /// Applies the "ColorBurnDestOver" associated-alpha composition equation. + /// + public readonly struct ColorBurnDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestOver(background, source, amount); + } + + /// + /// Applies the "SoftLightDestOver" associated-alpha composition equation. + /// + public readonly struct SoftLightDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestOver(background, source, amount); + } + + /// + /// Applies the "DifferenceDestOver" associated-alpha composition equation. + /// + public readonly struct DifferenceDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestOver(background, source, amount); + } + + /// + /// Applies the "ExclusionDestOver" associated-alpha composition equation. + /// + public readonly struct ExclusionDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestOver(background, source, amount); + } + + /// + /// Applies the "HueDestOver" associated-alpha composition equation. + /// + public readonly struct HueDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueDestOver(background, source, amount); + } + + /// + /// Applies the "SaturationDestOver" associated-alpha composition equation. + /// + public readonly struct SaturationDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestOver(background, source, amount); + } + + /// + /// Applies the "ColorDestOver" associated-alpha composition equation. + /// + public readonly struct ColorDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestOver(background, source, amount); + } + + /// + /// Applies the "LuminosityDestOver" associated-alpha composition equation. + /// + public readonly struct LuminosityDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestOver(background, source, amount); + } + + /// + /// Applies the "NormalDestIn" associated-alpha composition equation. + /// + public readonly struct NormalDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestIn(background, source, amount); + } + + /// + /// Applies the "MultiplyDestIn" associated-alpha composition equation. + /// + public readonly struct MultiplyDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestIn(background, source, amount); + } + + /// + /// Applies the "AddDestIn" associated-alpha composition equation. + /// + public readonly struct AddDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestIn(background, source, amount); + } + + /// + /// Applies the "SubtractDestIn" associated-alpha composition equation. + /// + public readonly struct SubtractDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestIn(background, source, amount); + } + + /// + /// Applies the "ScreenDestIn" associated-alpha composition equation. + /// + public readonly struct ScreenDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestIn(background, source, amount); + } + + /// + /// Applies the "DarkenDestIn" associated-alpha composition equation. + /// + public readonly struct DarkenDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestIn(background, source, amount); + } + + /// + /// Applies the "LightenDestIn" associated-alpha composition equation. + /// + public readonly struct LightenDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestIn(background, source, amount); + } + + /// + /// Applies the "OverlayDestIn" associated-alpha composition equation. + /// + public readonly struct OverlayDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestIn(background, source, amount); + } + + /// + /// Applies the "HardLightDestIn" associated-alpha composition equation. + /// + public readonly struct HardLightDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestIn(background, source, amount); + } + + /// + /// Applies the "ColorDodgeDestIn" associated-alpha composition equation. + /// + public readonly struct ColorDodgeDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestIn(background, source, amount); + } + + /// + /// Applies the "ColorBurnDestIn" associated-alpha composition equation. + /// + public readonly struct ColorBurnDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestIn(background, source, amount); + } + + /// + /// Applies the "SoftLightDestIn" associated-alpha composition equation. + /// + public readonly struct SoftLightDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestIn(background, source, amount); + } + + /// + /// Applies the "DifferenceDestIn" associated-alpha composition equation. + /// + public readonly struct DifferenceDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestIn(background, source, amount); + } + + /// + /// Applies the "ExclusionDestIn" associated-alpha composition equation. + /// + public readonly struct ExclusionDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestIn(background, source, amount); + } + + /// + /// Applies the "HueDestIn" associated-alpha composition equation. + /// + public readonly struct HueDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueDestIn(background, source, amount); + } + + /// + /// Applies the "SaturationDestIn" associated-alpha composition equation. + /// + public readonly struct SaturationDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestIn(background, source, amount); + } + + /// + /// Applies the "ColorDestIn" associated-alpha composition equation. + /// + public readonly struct ColorDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestIn(background, source, amount); + } + + /// + /// Applies the "LuminosityDestIn" associated-alpha composition equation. + /// + public readonly struct LuminosityDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestIn(background, source, amount); + } + + /// + /// Applies the "NormalDestOut" associated-alpha composition equation. + /// + public readonly struct NormalDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalDestOut(background, source, amount); + } + + /// + /// Applies the "MultiplyDestOut" associated-alpha composition equation. + /// + public readonly struct MultiplyDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyDestOut(background, source, amount); + } + + /// + /// Applies the "AddDestOut" associated-alpha composition equation. + /// + public readonly struct AddDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddDestOut(background, source, amount); + } + + /// + /// Applies the "SubtractDestOut" associated-alpha composition equation. + /// + public readonly struct SubtractDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractDestOut(background, source, amount); + } + + /// + /// Applies the "ScreenDestOut" associated-alpha composition equation. + /// + public readonly struct ScreenDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenDestOut(background, source, amount); + } + + /// + /// Applies the "DarkenDestOut" associated-alpha composition equation. + /// + public readonly struct DarkenDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenDestOut(background, source, amount); + } + + /// + /// Applies the "LightenDestOut" associated-alpha composition equation. + /// + public readonly struct LightenDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenDestOut(background, source, amount); + } + + /// + /// Applies the "OverlayDestOut" associated-alpha composition equation. + /// + public readonly struct OverlayDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayDestOut(background, source, amount); + } + + /// + /// Applies the "HardLightDestOut" associated-alpha composition equation. + /// + public readonly struct HardLightDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightDestOut(background, source, amount); + } + + /// + /// Applies the "ColorDodgeDestOut" associated-alpha composition equation. + /// + public readonly struct ColorDodgeDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeDestOut(background, source, amount); + } + + /// + /// Applies the "ColorBurnDestOut" associated-alpha composition equation. + /// + public readonly struct ColorBurnDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnDestOut(background, source, amount); + } + + /// + /// Applies the "SoftLightDestOut" associated-alpha composition equation. + /// + public readonly struct SoftLightDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightDestOut(background, source, amount); + } + + /// + /// Applies the "DifferenceDestOut" associated-alpha composition equation. + /// + public readonly struct DifferenceDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceDestOut(background, source, amount); + } + + /// + /// Applies the "ExclusionDestOut" associated-alpha composition equation. + /// + public readonly struct ExclusionDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionDestOut(background, source, amount); + } + + /// + /// Applies the "HueDestOut" associated-alpha composition equation. + /// + public readonly struct HueDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueDestOut(background, source, amount); + } + + /// + /// Applies the "SaturationDestOut" associated-alpha composition equation. + /// + public readonly struct SaturationDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationDestOut(background, source, amount); + } + + /// + /// Applies the "ColorDestOut" associated-alpha composition equation. + /// + public readonly struct ColorDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDestOut(background, source, amount); + } + + /// + /// Applies the "LuminosityDestOut" associated-alpha composition equation. + /// + public readonly struct LuminosityDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityDestOut(background, source, amount); + } + + /// + /// Applies the "NormalClear" associated-alpha composition equation. + /// + public readonly struct NormalClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalClear(background, source, amount); + } + + /// + /// Applies the "MultiplyClear" associated-alpha composition equation. + /// + public readonly struct MultiplyClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyClear(background, source, amount); + } + + /// + /// Applies the "AddClear" associated-alpha composition equation. + /// + public readonly struct AddClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddClear(background, source, amount); + } + + /// + /// Applies the "SubtractClear" associated-alpha composition equation. + /// + public readonly struct SubtractClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractClear(background, source, amount); + } + + /// + /// Applies the "ScreenClear" associated-alpha composition equation. + /// + public readonly struct ScreenClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenClear(background, source, amount); + } + + /// + /// Applies the "DarkenClear" associated-alpha composition equation. + /// + public readonly struct DarkenClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenClear(background, source, amount); + } + + /// + /// Applies the "LightenClear" associated-alpha composition equation. + /// + public readonly struct LightenClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenClear(background, source, amount); + } + + /// + /// Applies the "OverlayClear" associated-alpha composition equation. + /// + public readonly struct OverlayClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayClear(background, source, amount); + } + + /// + /// Applies the "HardLightClear" associated-alpha composition equation. + /// + public readonly struct HardLightClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightClear(background, source, amount); + } + + /// + /// Applies the "ColorDodgeClear" associated-alpha composition equation. + /// + public readonly struct ColorDodgeClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeClear(background, source, amount); + } + + /// + /// Applies the "ColorBurnClear" associated-alpha composition equation. + /// + public readonly struct ColorBurnClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnClear(background, source, amount); + } + + /// + /// Applies the "SoftLightClear" associated-alpha composition equation. + /// + public readonly struct SoftLightClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightClear(background, source, amount); + } + + /// + /// Applies the "DifferenceClear" associated-alpha composition equation. + /// + public readonly struct DifferenceClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceClear(background, source, amount); + } + + /// + /// Applies the "ExclusionClear" associated-alpha composition equation. + /// + public readonly struct ExclusionClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionClear(background, source, amount); + } + + /// + /// Applies the "HueClear" associated-alpha composition equation. + /// + public readonly struct HueClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueClear(background, source, amount); + } + + /// + /// Applies the "SaturationClear" associated-alpha composition equation. + /// + public readonly struct SaturationClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationClear(background, source, amount); + } + + /// + /// Applies the "ColorClear" associated-alpha composition equation. + /// + public readonly struct ColorClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorClear(background, source, amount); + } + + /// + /// Applies the "LuminosityClear" associated-alpha composition equation. + /// + public readonly struct LuminosityClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityClear(background, source, amount); + } + + /// + /// Applies the "NormalXor" associated-alpha composition equation. + /// + public readonly struct NormalXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalXor(background, source, amount); + } + + /// + /// Applies the "MultiplyXor" associated-alpha composition equation. + /// + public readonly struct MultiplyXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyXor(background, source, amount); + } + + /// + /// Applies the "AddXor" associated-alpha composition equation. + /// + public readonly struct AddXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddXor(background, source, amount); + } + + /// + /// Applies the "SubtractXor" associated-alpha composition equation. + /// + public readonly struct SubtractXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractXor(background, source, amount); + } + + /// + /// Applies the "ScreenXor" associated-alpha composition equation. + /// + public readonly struct ScreenXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenXor(background, source, amount); + } + + /// + /// Applies the "DarkenXor" associated-alpha composition equation. + /// + public readonly struct DarkenXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenXor(background, source, amount); + } + + /// + /// Applies the "LightenXor" associated-alpha composition equation. + /// + public readonly struct LightenXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenXor(background, source, amount); + } + + /// + /// Applies the "OverlayXor" associated-alpha composition equation. + /// + public readonly struct OverlayXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayXor(background, source, amount); + } + + /// + /// Applies the "HardLightXor" associated-alpha composition equation. + /// + public readonly struct HardLightXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightXor(background, source, amount); + } + + /// + /// Applies the "ColorDodgeXor" associated-alpha composition equation. + /// + public readonly struct ColorDodgeXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgeXor(background, source, amount); + } + + /// + /// Applies the "ColorBurnXor" associated-alpha composition equation. + /// + public readonly struct ColorBurnXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnXor(background, source, amount); + } + + /// + /// Applies the "SoftLightXor" associated-alpha composition equation. + /// + public readonly struct SoftLightXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightXor(background, source, amount); + } + + /// + /// Applies the "DifferenceXor" associated-alpha composition equation. + /// + public readonly struct DifferenceXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferenceXor(background, source, amount); + } + + /// + /// Applies the "ExclusionXor" associated-alpha composition equation. + /// + public readonly struct ExclusionXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionXor(background, source, amount); + } + + /// + /// Applies the "HueXor" associated-alpha composition equation. + /// + public readonly struct HueXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HueXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HueXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HueXor(background, source, amount); + } + + /// + /// Applies the "SaturationXor" associated-alpha composition equation. + /// + public readonly struct SaturationXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationXor(background, source, amount); + } + + /// + /// Applies the "ColorXor" associated-alpha composition equation. + /// + public readonly struct ColorXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorXor(background, source, amount); + } + + /// + /// Applies the "LuminosityXor" associated-alpha composition equation. + /// + public readonly struct LuminosityXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityXor(background, source, amount); + } + + /// + /// Applies the "NormalPlus" associated-alpha composition equation. + /// + public readonly struct NormalPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.NormalPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.NormalPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.NormalPlus(background, source, amount); + } + + /// + /// Applies the "MultiplyPlus" associated-alpha composition equation. + /// + public readonly struct MultiplyPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.MultiplyPlus(background, source, amount); + } + + /// + /// Applies the "AddPlus" associated-alpha composition equation. + /// + public readonly struct AddPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.AddPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.AddPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.AddPlus(background, source, amount); + } + + /// + /// Applies the "SubtractPlus" associated-alpha composition equation. + /// + public readonly struct SubtractPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SubtractPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SubtractPlus(background, source, amount); + } + + /// + /// Applies the "ScreenPlus" associated-alpha composition equation. + /// + public readonly struct ScreenPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ScreenPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ScreenPlus(background, source, amount); + } + + /// + /// Applies the "DarkenPlus" associated-alpha composition equation. + /// + public readonly struct DarkenPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DarkenPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DarkenPlus(background, source, amount); + } + + /// + /// Applies the "LightenPlus" associated-alpha composition equation. + /// + public readonly struct LightenPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LightenPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LightenPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LightenPlus(background, source, amount); + } + + /// + /// Applies the "OverlayPlus" associated-alpha composition equation. + /// + public readonly struct OverlayPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.OverlayPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.OverlayPlus(background, source, amount); + } + + /// + /// Applies the "HardLightPlus" associated-alpha composition equation. + /// + public readonly struct HardLightPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HardLightPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HardLightPlus(background, source, amount); + } + + /// + /// Applies the "ColorDodgePlus" associated-alpha composition equation. + /// + public readonly struct ColorDodgePlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgePlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgePlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorDodgePlus(background, source, amount); + } + + /// + /// Applies the "ColorBurnPlus" associated-alpha composition equation. + /// + public readonly struct ColorBurnPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorBurnPlus(background, source, amount); + } + + /// + /// Applies the "SoftLightPlus" associated-alpha composition equation. + /// + public readonly struct SoftLightPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SoftLightPlus(background, source, amount); + } + + /// + /// Applies the "DifferencePlus" associated-alpha composition equation. + /// + public readonly struct DifferencePlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.DifferencePlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.DifferencePlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.DifferencePlus(background, source, amount); + } + + /// + /// Applies the "ExclusionPlus" associated-alpha composition equation. + /// + public readonly struct ExclusionPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ExclusionPlus(background, source, amount); + } + + /// + /// Applies the "HuePlus" associated-alpha composition equation. + /// + public readonly struct HuePlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.HuePlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.HuePlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.HuePlus(background, source, amount); + } + + /// + /// Applies the "SaturationPlus" associated-alpha composition equation. + /// + public readonly struct SaturationPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.SaturationPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.SaturationPlus(background, source, amount); + } + + /// + /// Applies the "ColorPlus" associated-alpha composition equation. + /// + public readonly struct ColorPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.ColorPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.ColorPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.ColorPlus(background, source, amount); + } + + /// + /// Applies the "LuminosityPlus" associated-alpha composition equation. + /// + public readonly struct LuminosityPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.LuminosityPlus(background, source, amount); + } + +} + +/// +/// Provides associated-alpha pixel blenders for each color and alpha composition pair. +/// +/// The associated-alpha destination pixel format. +internal static partial class AssociatedAlphaPixelBlenders + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the shared blender for the "NormalSrc" composition equation. + /// + public static PixelBlender NormalSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplySrc" composition equation. + /// + public static PixelBlender MultiplySrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddSrc" composition equation. + /// + public static PixelBlender AddSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractSrc" composition equation. + /// + public static PixelBlender SubtractSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenSrc" composition equation. + /// + public static PixelBlender ScreenSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenSrc" composition equation. + /// + public static PixelBlender DarkenSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenSrc" composition equation. + /// + public static PixelBlender LightenSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlaySrc" composition equation. + /// + public static PixelBlender OverlaySrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightSrc" composition equation. + /// + public static PixelBlender HardLightSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeSrc" composition equation. + /// + public static PixelBlender ColorDodgeSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnSrc" composition equation. + /// + public static PixelBlender ColorBurnSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightSrc" composition equation. + /// + public static PixelBlender SoftLightSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceSrc" composition equation. + /// + public static PixelBlender DifferenceSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionSrc" composition equation. + /// + public static PixelBlender ExclusionSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueSrc" composition equation. + /// + public static PixelBlender HueSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationSrc" composition equation. + /// + public static PixelBlender SaturationSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorSrc" composition equation. + /// + public static PixelBlender ColorSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminositySrc" composition equation. + /// + public static PixelBlender LuminositySrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalSrcAtop" composition equation. + /// + public static PixelBlender NormalSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplySrcAtop" composition equation. + /// + public static PixelBlender MultiplySrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddSrcAtop" composition equation. + /// + public static PixelBlender AddSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractSrcAtop" composition equation. + /// + public static PixelBlender SubtractSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenSrcAtop" composition equation. + /// + public static PixelBlender ScreenSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenSrcAtop" composition equation. + /// + public static PixelBlender DarkenSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenSrcAtop" composition equation. + /// + public static PixelBlender LightenSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlaySrcAtop" composition equation. + /// + public static PixelBlender OverlaySrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightSrcAtop" composition equation. + /// + public static PixelBlender HardLightSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeSrcAtop" composition equation. + /// + public static PixelBlender ColorDodgeSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnSrcAtop" composition equation. + /// + public static PixelBlender ColorBurnSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightSrcAtop" composition equation. + /// + public static PixelBlender SoftLightSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceSrcAtop" composition equation. + /// + public static PixelBlender DifferenceSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionSrcAtop" composition equation. + /// + public static PixelBlender ExclusionSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueSrcAtop" composition equation. + /// + public static PixelBlender HueSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationSrcAtop" composition equation. + /// + public static PixelBlender SaturationSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorSrcAtop" composition equation. + /// + public static PixelBlender ColorSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminositySrcAtop" composition equation. + /// + public static PixelBlender LuminositySrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalSrcOver" composition equation. + /// + public static PixelBlender NormalSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplySrcOver" composition equation. + /// + public static PixelBlender MultiplySrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddSrcOver" composition equation. + /// + public static PixelBlender AddSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractSrcOver" composition equation. + /// + public static PixelBlender SubtractSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenSrcOver" composition equation. + /// + public static PixelBlender ScreenSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenSrcOver" composition equation. + /// + public static PixelBlender DarkenSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenSrcOver" composition equation. + /// + public static PixelBlender LightenSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlaySrcOver" composition equation. + /// + public static PixelBlender OverlaySrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightSrcOver" composition equation. + /// + public static PixelBlender HardLightSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeSrcOver" composition equation. + /// + public static PixelBlender ColorDodgeSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnSrcOver" composition equation. + /// + public static PixelBlender ColorBurnSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightSrcOver" composition equation. + /// + public static PixelBlender SoftLightSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceSrcOver" composition equation. + /// + public static PixelBlender DifferenceSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionSrcOver" composition equation. + /// + public static PixelBlender ExclusionSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueSrcOver" composition equation. + /// + public static PixelBlender HueSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationSrcOver" composition equation. + /// + public static PixelBlender SaturationSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorSrcOver" composition equation. + /// + public static PixelBlender ColorSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminositySrcOver" composition equation. + /// + public static PixelBlender LuminositySrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalSrcIn" composition equation. + /// + public static PixelBlender NormalSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplySrcIn" composition equation. + /// + public static PixelBlender MultiplySrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddSrcIn" composition equation. + /// + public static PixelBlender AddSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractSrcIn" composition equation. + /// + public static PixelBlender SubtractSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenSrcIn" composition equation. + /// + public static PixelBlender ScreenSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenSrcIn" composition equation. + /// + public static PixelBlender DarkenSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenSrcIn" composition equation. + /// + public static PixelBlender LightenSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlaySrcIn" composition equation. + /// + public static PixelBlender OverlaySrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightSrcIn" composition equation. + /// + public static PixelBlender HardLightSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeSrcIn" composition equation. + /// + public static PixelBlender ColorDodgeSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnSrcIn" composition equation. + /// + public static PixelBlender ColorBurnSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightSrcIn" composition equation. + /// + public static PixelBlender SoftLightSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceSrcIn" composition equation. + /// + public static PixelBlender DifferenceSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionSrcIn" composition equation. + /// + public static PixelBlender ExclusionSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueSrcIn" composition equation. + /// + public static PixelBlender HueSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationSrcIn" composition equation. + /// + public static PixelBlender SaturationSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorSrcIn" composition equation. + /// + public static PixelBlender ColorSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminositySrcIn" composition equation. + /// + public static PixelBlender LuminositySrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalSrcOut" composition equation. + /// + public static PixelBlender NormalSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplySrcOut" composition equation. + /// + public static PixelBlender MultiplySrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddSrcOut" composition equation. + /// + public static PixelBlender AddSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractSrcOut" composition equation. + /// + public static PixelBlender SubtractSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenSrcOut" composition equation. + /// + public static PixelBlender ScreenSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenSrcOut" composition equation. + /// + public static PixelBlender DarkenSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenSrcOut" composition equation. + /// + public static PixelBlender LightenSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlaySrcOut" composition equation. + /// + public static PixelBlender OverlaySrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightSrcOut" composition equation. + /// + public static PixelBlender HardLightSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeSrcOut" composition equation. + /// + public static PixelBlender ColorDodgeSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnSrcOut" composition equation. + /// + public static PixelBlender ColorBurnSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightSrcOut" composition equation. + /// + public static PixelBlender SoftLightSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceSrcOut" composition equation. + /// + public static PixelBlender DifferenceSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionSrcOut" composition equation. + /// + public static PixelBlender ExclusionSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueSrcOut" composition equation. + /// + public static PixelBlender HueSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationSrcOut" composition equation. + /// + public static PixelBlender SaturationSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorSrcOut" composition equation. + /// + public static PixelBlender ColorSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminositySrcOut" composition equation. + /// + public static PixelBlender LuminositySrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalDest" composition equation. + /// + public static PixelBlender NormalDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyDest" composition equation. + /// + public static PixelBlender MultiplyDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddDest" composition equation. + /// + public static PixelBlender AddDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractDest" composition equation. + /// + public static PixelBlender SubtractDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenDest" composition equation. + /// + public static PixelBlender ScreenDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenDest" composition equation. + /// + public static PixelBlender DarkenDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenDest" composition equation. + /// + public static PixelBlender LightenDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayDest" composition equation. + /// + public static PixelBlender OverlayDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightDest" composition equation. + /// + public static PixelBlender HardLightDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeDest" composition equation. + /// + public static PixelBlender ColorDodgeDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnDest" composition equation. + /// + public static PixelBlender ColorBurnDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightDest" composition equation. + /// + public static PixelBlender SoftLightDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceDest" composition equation. + /// + public static PixelBlender DifferenceDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionDest" composition equation. + /// + public static PixelBlender ExclusionDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueDest" composition equation. + /// + public static PixelBlender HueDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationDest" composition equation. + /// + public static PixelBlender SaturationDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDest" composition equation. + /// + public static PixelBlender ColorDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityDest" composition equation. + /// + public static PixelBlender LuminosityDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalDestAtop" composition equation. + /// + public static PixelBlender NormalDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyDestAtop" composition equation. + /// + public static PixelBlender MultiplyDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddDestAtop" composition equation. + /// + public static PixelBlender AddDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractDestAtop" composition equation. + /// + public static PixelBlender SubtractDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenDestAtop" composition equation. + /// + public static PixelBlender ScreenDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenDestAtop" composition equation. + /// + public static PixelBlender DarkenDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenDestAtop" composition equation. + /// + public static PixelBlender LightenDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayDestAtop" composition equation. + /// + public static PixelBlender OverlayDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightDestAtop" composition equation. + /// + public static PixelBlender HardLightDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeDestAtop" composition equation. + /// + public static PixelBlender ColorDodgeDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnDestAtop" composition equation. + /// + public static PixelBlender ColorBurnDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightDestAtop" composition equation. + /// + public static PixelBlender SoftLightDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceDestAtop" composition equation. + /// + public static PixelBlender DifferenceDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionDestAtop" composition equation. + /// + public static PixelBlender ExclusionDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueDestAtop" composition equation. + /// + public static PixelBlender HueDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationDestAtop" composition equation. + /// + public static PixelBlender SaturationDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDestAtop" composition equation. + /// + public static PixelBlender ColorDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityDestAtop" composition equation. + /// + public static PixelBlender LuminosityDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalDestOver" composition equation. + /// + public static PixelBlender NormalDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyDestOver" composition equation. + /// + public static PixelBlender MultiplyDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddDestOver" composition equation. + /// + public static PixelBlender AddDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractDestOver" composition equation. + /// + public static PixelBlender SubtractDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenDestOver" composition equation. + /// + public static PixelBlender ScreenDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenDestOver" composition equation. + /// + public static PixelBlender DarkenDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenDestOver" composition equation. + /// + public static PixelBlender LightenDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayDestOver" composition equation. + /// + public static PixelBlender OverlayDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightDestOver" composition equation. + /// + public static PixelBlender HardLightDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeDestOver" composition equation. + /// + public static PixelBlender ColorDodgeDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnDestOver" composition equation. + /// + public static PixelBlender ColorBurnDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightDestOver" composition equation. + /// + public static PixelBlender SoftLightDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceDestOver" composition equation. + /// + public static PixelBlender DifferenceDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionDestOver" composition equation. + /// + public static PixelBlender ExclusionDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueDestOver" composition equation. + /// + public static PixelBlender HueDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationDestOver" composition equation. + /// + public static PixelBlender SaturationDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDestOver" composition equation. + /// + public static PixelBlender ColorDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityDestOver" composition equation. + /// + public static PixelBlender LuminosityDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalDestIn" composition equation. + /// + public static PixelBlender NormalDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyDestIn" composition equation. + /// + public static PixelBlender MultiplyDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddDestIn" composition equation. + /// + public static PixelBlender AddDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractDestIn" composition equation. + /// + public static PixelBlender SubtractDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenDestIn" composition equation. + /// + public static PixelBlender ScreenDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenDestIn" composition equation. + /// + public static PixelBlender DarkenDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenDestIn" composition equation. + /// + public static PixelBlender LightenDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayDestIn" composition equation. + /// + public static PixelBlender OverlayDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightDestIn" composition equation. + /// + public static PixelBlender HardLightDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeDestIn" composition equation. + /// + public static PixelBlender ColorDodgeDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnDestIn" composition equation. + /// + public static PixelBlender ColorBurnDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightDestIn" composition equation. + /// + public static PixelBlender SoftLightDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceDestIn" composition equation. + /// + public static PixelBlender DifferenceDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionDestIn" composition equation. + /// + public static PixelBlender ExclusionDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueDestIn" composition equation. + /// + public static PixelBlender HueDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationDestIn" composition equation. + /// + public static PixelBlender SaturationDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDestIn" composition equation. + /// + public static PixelBlender ColorDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityDestIn" composition equation. + /// + public static PixelBlender LuminosityDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalDestOut" composition equation. + /// + public static PixelBlender NormalDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyDestOut" composition equation. + /// + public static PixelBlender MultiplyDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddDestOut" composition equation. + /// + public static PixelBlender AddDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractDestOut" composition equation. + /// + public static PixelBlender SubtractDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenDestOut" composition equation. + /// + public static PixelBlender ScreenDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenDestOut" composition equation. + /// + public static PixelBlender DarkenDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenDestOut" composition equation. + /// + public static PixelBlender LightenDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayDestOut" composition equation. + /// + public static PixelBlender OverlayDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightDestOut" composition equation. + /// + public static PixelBlender HardLightDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeDestOut" composition equation. + /// + public static PixelBlender ColorDodgeDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnDestOut" composition equation. + /// + public static PixelBlender ColorBurnDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightDestOut" composition equation. + /// + public static PixelBlender SoftLightDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceDestOut" composition equation. + /// + public static PixelBlender DifferenceDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionDestOut" composition equation. + /// + public static PixelBlender ExclusionDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueDestOut" composition equation. + /// + public static PixelBlender HueDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationDestOut" composition equation. + /// + public static PixelBlender SaturationDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDestOut" composition equation. + /// + public static PixelBlender ColorDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityDestOut" composition equation. + /// + public static PixelBlender LuminosityDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalClear" composition equation. + /// + public static PixelBlender NormalClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyClear" composition equation. + /// + public static PixelBlender MultiplyClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddClear" composition equation. + /// + public static PixelBlender AddClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractClear" composition equation. + /// + public static PixelBlender SubtractClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenClear" composition equation. + /// + public static PixelBlender ScreenClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenClear" composition equation. + /// + public static PixelBlender DarkenClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenClear" composition equation. + /// + public static PixelBlender LightenClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayClear" composition equation. + /// + public static PixelBlender OverlayClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightClear" composition equation. + /// + public static PixelBlender HardLightClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeClear" composition equation. + /// + public static PixelBlender ColorDodgeClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnClear" composition equation. + /// + public static PixelBlender ColorBurnClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightClear" composition equation. + /// + public static PixelBlender SoftLightClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceClear" composition equation. + /// + public static PixelBlender DifferenceClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionClear" composition equation. + /// + public static PixelBlender ExclusionClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueClear" composition equation. + /// + public static PixelBlender HueClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationClear" composition equation. + /// + public static PixelBlender SaturationClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorClear" composition equation. + /// + public static PixelBlender ColorClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityClear" composition equation. + /// + public static PixelBlender LuminosityClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalXor" composition equation. + /// + public static PixelBlender NormalXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyXor" composition equation. + /// + public static PixelBlender MultiplyXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddXor" composition equation. + /// + public static PixelBlender AddXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractXor" composition equation. + /// + public static PixelBlender SubtractXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenXor" composition equation. + /// + public static PixelBlender ScreenXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenXor" composition equation. + /// + public static PixelBlender DarkenXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenXor" composition equation. + /// + public static PixelBlender LightenXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayXor" composition equation. + /// + public static PixelBlender OverlayXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightXor" composition equation. + /// + public static PixelBlender HardLightXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeXor" composition equation. + /// + public static PixelBlender ColorDodgeXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnXor" composition equation. + /// + public static PixelBlender ColorBurnXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightXor" composition equation. + /// + public static PixelBlender SoftLightXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceXor" composition equation. + /// + public static PixelBlender DifferenceXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionXor" composition equation. + /// + public static PixelBlender ExclusionXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueXor" composition equation. + /// + public static PixelBlender HueXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationXor" composition equation. + /// + public static PixelBlender SaturationXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorXor" composition equation. + /// + public static PixelBlender ColorXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityXor" composition equation. + /// + public static PixelBlender LuminosityXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalPlus" composition equation. + /// + public static PixelBlender NormalPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyPlus" composition equation. + /// + public static PixelBlender MultiplyPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddPlus" composition equation. + /// + public static PixelBlender AddPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractPlus" composition equation. + /// + public static PixelBlender SubtractPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenPlus" composition equation. + /// + public static PixelBlender ScreenPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenPlus" composition equation. + /// + public static PixelBlender DarkenPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenPlus" composition equation. + /// + public static PixelBlender LightenPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayPlus" composition equation. + /// + public static PixelBlender OverlayPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightPlus" composition equation. + /// + public static PixelBlender HardLightPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgePlus" composition equation. + /// + public static PixelBlender ColorDodgePlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnPlus" composition equation. + /// + public static PixelBlender ColorBurnPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightPlus" composition equation. + /// + public static PixelBlender SoftLightPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferencePlus" composition equation. + /// + public static PixelBlender DifferencePlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionPlus" composition equation. + /// + public static PixelBlender ExclusionPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HuePlus" composition equation. + /// + public static PixelBlender HuePlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationPlus" composition equation. + /// + public static PixelBlender SaturationPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorPlus" composition equation. + /// + public static PixelBlender ColorPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityPlus" composition equation. + /// + public static PixelBlender LuminosityPlus => PixelBlender.Instance; + +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt new file mode 100644 index 000000000..127f45df3 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt @@ -0,0 +1,119 @@ +<# +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. +#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ output extension=".cs" #> +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System.Numerics; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +<# +var composers = new [] +{ + "Src", + "SrcAtop", + "SrcOver", + "SrcIn", + "SrcOut", + "Dest", + "DestAtop", + "DestOver", + "DestIn", + "DestOut", + "Clear", + "Xor", + "Plus", +}; + +var blenders = new [] +{ + "Normal", + "Multiply", + "Add", + "Subtract", + "Screen", + "Darken", + "Lighten", + "Overlay", + "HardLight", + "ColorDodge", + "ColorBurn", + "SoftLight", + "Difference", + "Exclusion", + "Hue", + "Saturation", + "Color", + "Luminosity", +}; +#> +/// +/// Provides the associated-alpha equations consumed by the shared pixel-blending traversal. +/// +internal static class AssociatedAlphaPixelBlenderOperators +{ +<# +foreach (var composer in composers) +{ + foreach (var blender in blenders) + { + var blenderComposer = $"{blender}{composer}"; +#> + /// + /// Applies the "<#= blenderComposer #>" associated-alpha composition equation. + /// + public readonly struct <#= blenderComposer #> : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => true; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => AssociatedAlphaPorterDuffFunctions.<#= blenderComposer #>(background, source, amount); + } + +<# + } +} +#> +} + +/// +/// Provides associated-alpha pixel blenders for each color and alpha composition pair. +/// +/// The associated-alpha destination pixel format. +internal static partial class AssociatedAlphaPixelBlenders + where TPixel : unmanaged, IPixel +{ +<# +foreach (var composer in composers) +{ + foreach (var blender in blenders) + { + var blenderComposer = $"{blender}{composer}"; +#> + /// + /// Gets the shared blender for the "<#= blenderComposer #>" composition equation. + /// + public static PixelBlender <#= blenderComposer #> => PixelBlender>.Instance; + +<# + } +} +#> +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs new file mode 100644 index 000000000..8650cc9f9 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs @@ -0,0 +1,297 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Provides pixel blenders for formats that store associated alpha. +/// +internal static partial class AssociatedAlphaPixelBlenders + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the blender for the requested color blending and alpha composition modes. + /// + /// The color blending mode. + /// The alpha composition mode. + /// The pixel blender. + public static PixelBlender GetPixelBlender(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode) + { + return alphaMode switch + { + PixelAlphaCompositionMode.Src => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplySrc, + PixelColorBlendingMode.Add => AddSrc, + PixelColorBlendingMode.Subtract => SubtractSrc, + PixelColorBlendingMode.Screen => ScreenSrc, + PixelColorBlendingMode.Darken => DarkenSrc, + PixelColorBlendingMode.Lighten => LightenSrc, + PixelColorBlendingMode.Overlay => OverlaySrc, + PixelColorBlendingMode.HardLight => HardLightSrc, + PixelColorBlendingMode.ColorDodge => ColorDodgeSrc, + PixelColorBlendingMode.ColorBurn => ColorBurnSrc, + PixelColorBlendingMode.SoftLight => SoftLightSrc, + PixelColorBlendingMode.Difference => DifferenceSrc, + PixelColorBlendingMode.Exclusion => ExclusionSrc, + PixelColorBlendingMode.Hue => HueSrc, + PixelColorBlendingMode.Saturation => SaturationSrc, + PixelColorBlendingMode.Color => ColorSrc, + PixelColorBlendingMode.Luminosity => LuminositySrc, + _ => NormalSrc, + }, + PixelAlphaCompositionMode.SrcAtop => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplySrcAtop, + PixelColorBlendingMode.Add => AddSrcAtop, + PixelColorBlendingMode.Subtract => SubtractSrcAtop, + PixelColorBlendingMode.Screen => ScreenSrcAtop, + PixelColorBlendingMode.Darken => DarkenSrcAtop, + PixelColorBlendingMode.Lighten => LightenSrcAtop, + PixelColorBlendingMode.Overlay => OverlaySrcAtop, + PixelColorBlendingMode.HardLight => HardLightSrcAtop, + PixelColorBlendingMode.ColorDodge => ColorDodgeSrcAtop, + PixelColorBlendingMode.ColorBurn => ColorBurnSrcAtop, + PixelColorBlendingMode.SoftLight => SoftLightSrcAtop, + PixelColorBlendingMode.Difference => DifferenceSrcAtop, + PixelColorBlendingMode.Exclusion => ExclusionSrcAtop, + PixelColorBlendingMode.Hue => HueSrcAtop, + PixelColorBlendingMode.Saturation => SaturationSrcAtop, + PixelColorBlendingMode.Color => ColorSrcAtop, + PixelColorBlendingMode.Luminosity => LuminositySrcAtop, + _ => NormalSrcAtop, + }, + PixelAlphaCompositionMode.SrcIn => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplySrcIn, + PixelColorBlendingMode.Add => AddSrcIn, + PixelColorBlendingMode.Subtract => SubtractSrcIn, + PixelColorBlendingMode.Screen => ScreenSrcIn, + PixelColorBlendingMode.Darken => DarkenSrcIn, + PixelColorBlendingMode.Lighten => LightenSrcIn, + PixelColorBlendingMode.Overlay => OverlaySrcIn, + PixelColorBlendingMode.HardLight => HardLightSrcIn, + PixelColorBlendingMode.ColorDodge => ColorDodgeSrcIn, + PixelColorBlendingMode.ColorBurn => ColorBurnSrcIn, + PixelColorBlendingMode.SoftLight => SoftLightSrcIn, + PixelColorBlendingMode.Difference => DifferenceSrcIn, + PixelColorBlendingMode.Exclusion => ExclusionSrcIn, + PixelColorBlendingMode.Hue => HueSrcIn, + PixelColorBlendingMode.Saturation => SaturationSrcIn, + PixelColorBlendingMode.Color => ColorSrcIn, + PixelColorBlendingMode.Luminosity => LuminositySrcIn, + _ => NormalSrcIn, + }, + PixelAlphaCompositionMode.SrcOut => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplySrcOut, + PixelColorBlendingMode.Add => AddSrcOut, + PixelColorBlendingMode.Subtract => SubtractSrcOut, + PixelColorBlendingMode.Screen => ScreenSrcOut, + PixelColorBlendingMode.Darken => DarkenSrcOut, + PixelColorBlendingMode.Lighten => LightenSrcOut, + PixelColorBlendingMode.Overlay => OverlaySrcOut, + PixelColorBlendingMode.HardLight => HardLightSrcOut, + PixelColorBlendingMode.ColorDodge => ColorDodgeSrcOut, + PixelColorBlendingMode.ColorBurn => ColorBurnSrcOut, + PixelColorBlendingMode.SoftLight => SoftLightSrcOut, + PixelColorBlendingMode.Difference => DifferenceSrcOut, + PixelColorBlendingMode.Exclusion => ExclusionSrcOut, + PixelColorBlendingMode.Hue => HueSrcOut, + PixelColorBlendingMode.Saturation => SaturationSrcOut, + PixelColorBlendingMode.Color => ColorSrcOut, + PixelColorBlendingMode.Luminosity => LuminositySrcOut, + _ => NormalSrcOut, + }, + PixelAlphaCompositionMode.Dest => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplyDest, + PixelColorBlendingMode.Add => AddDest, + PixelColorBlendingMode.Subtract => SubtractDest, + PixelColorBlendingMode.Screen => ScreenDest, + PixelColorBlendingMode.Darken => DarkenDest, + PixelColorBlendingMode.Lighten => LightenDest, + PixelColorBlendingMode.Overlay => OverlayDest, + PixelColorBlendingMode.HardLight => HardLightDest, + PixelColorBlendingMode.ColorDodge => ColorDodgeDest, + PixelColorBlendingMode.ColorBurn => ColorBurnDest, + PixelColorBlendingMode.SoftLight => SoftLightDest, + PixelColorBlendingMode.Difference => DifferenceDest, + PixelColorBlendingMode.Exclusion => ExclusionDest, + PixelColorBlendingMode.Hue => HueDest, + PixelColorBlendingMode.Saturation => SaturationDest, + PixelColorBlendingMode.Color => ColorDest, + PixelColorBlendingMode.Luminosity => LuminosityDest, + _ => NormalDest, + }, + PixelAlphaCompositionMode.DestAtop => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplyDestAtop, + PixelColorBlendingMode.Add => AddDestAtop, + PixelColorBlendingMode.Subtract => SubtractDestAtop, + PixelColorBlendingMode.Screen => ScreenDestAtop, + PixelColorBlendingMode.Darken => DarkenDestAtop, + PixelColorBlendingMode.Lighten => LightenDestAtop, + PixelColorBlendingMode.Overlay => OverlayDestAtop, + PixelColorBlendingMode.HardLight => HardLightDestAtop, + PixelColorBlendingMode.ColorDodge => ColorDodgeDestAtop, + PixelColorBlendingMode.ColorBurn => ColorBurnDestAtop, + PixelColorBlendingMode.SoftLight => SoftLightDestAtop, + PixelColorBlendingMode.Difference => DifferenceDestAtop, + PixelColorBlendingMode.Exclusion => ExclusionDestAtop, + PixelColorBlendingMode.Hue => HueDestAtop, + PixelColorBlendingMode.Saturation => SaturationDestAtop, + PixelColorBlendingMode.Color => ColorDestAtop, + PixelColorBlendingMode.Luminosity => LuminosityDestAtop, + _ => NormalDestAtop, + }, + PixelAlphaCompositionMode.DestOver => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplyDestOver, + PixelColorBlendingMode.Add => AddDestOver, + PixelColorBlendingMode.Subtract => SubtractDestOver, + PixelColorBlendingMode.Screen => ScreenDestOver, + PixelColorBlendingMode.Darken => DarkenDestOver, + PixelColorBlendingMode.Lighten => LightenDestOver, + PixelColorBlendingMode.Overlay => OverlayDestOver, + PixelColorBlendingMode.HardLight => HardLightDestOver, + PixelColorBlendingMode.ColorDodge => ColorDodgeDestOver, + PixelColorBlendingMode.ColorBurn => ColorBurnDestOver, + PixelColorBlendingMode.SoftLight => SoftLightDestOver, + PixelColorBlendingMode.Difference => DifferenceDestOver, + PixelColorBlendingMode.Exclusion => ExclusionDestOver, + PixelColorBlendingMode.Hue => HueDestOver, + PixelColorBlendingMode.Saturation => SaturationDestOver, + PixelColorBlendingMode.Color => ColorDestOver, + PixelColorBlendingMode.Luminosity => LuminosityDestOver, + _ => NormalDestOver, + }, + PixelAlphaCompositionMode.DestIn => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplyDestIn, + PixelColorBlendingMode.Add => AddDestIn, + PixelColorBlendingMode.Subtract => SubtractDestIn, + PixelColorBlendingMode.Screen => ScreenDestIn, + PixelColorBlendingMode.Darken => DarkenDestIn, + PixelColorBlendingMode.Lighten => LightenDestIn, + PixelColorBlendingMode.Overlay => OverlayDestIn, + PixelColorBlendingMode.HardLight => HardLightDestIn, + PixelColorBlendingMode.ColorDodge => ColorDodgeDestIn, + PixelColorBlendingMode.ColorBurn => ColorBurnDestIn, + PixelColorBlendingMode.SoftLight => SoftLightDestIn, + PixelColorBlendingMode.Difference => DifferenceDestIn, + PixelColorBlendingMode.Exclusion => ExclusionDestIn, + PixelColorBlendingMode.Hue => HueDestIn, + PixelColorBlendingMode.Saturation => SaturationDestIn, + PixelColorBlendingMode.Color => ColorDestIn, + PixelColorBlendingMode.Luminosity => LuminosityDestIn, + _ => NormalDestIn, + }, + PixelAlphaCompositionMode.DestOut => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplyDestOut, + PixelColorBlendingMode.Add => AddDestOut, + PixelColorBlendingMode.Subtract => SubtractDestOut, + PixelColorBlendingMode.Screen => ScreenDestOut, + PixelColorBlendingMode.Darken => DarkenDestOut, + PixelColorBlendingMode.Lighten => LightenDestOut, + PixelColorBlendingMode.Overlay => OverlayDestOut, + PixelColorBlendingMode.HardLight => HardLightDestOut, + PixelColorBlendingMode.ColorDodge => ColorDodgeDestOut, + PixelColorBlendingMode.ColorBurn => ColorBurnDestOut, + PixelColorBlendingMode.SoftLight => SoftLightDestOut, + PixelColorBlendingMode.Difference => DifferenceDestOut, + PixelColorBlendingMode.Exclusion => ExclusionDestOut, + PixelColorBlendingMode.Hue => HueDestOut, + PixelColorBlendingMode.Saturation => SaturationDestOut, + PixelColorBlendingMode.Color => ColorDestOut, + PixelColorBlendingMode.Luminosity => LuminosityDestOut, + _ => NormalDestOut, + }, + PixelAlphaCompositionMode.Clear => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplyClear, + PixelColorBlendingMode.Add => AddClear, + PixelColorBlendingMode.Subtract => SubtractClear, + PixelColorBlendingMode.Screen => ScreenClear, + PixelColorBlendingMode.Darken => DarkenClear, + PixelColorBlendingMode.Lighten => LightenClear, + PixelColorBlendingMode.Overlay => OverlayClear, + PixelColorBlendingMode.HardLight => HardLightClear, + PixelColorBlendingMode.ColorDodge => ColorDodgeClear, + PixelColorBlendingMode.ColorBurn => ColorBurnClear, + PixelColorBlendingMode.SoftLight => SoftLightClear, + PixelColorBlendingMode.Difference => DifferenceClear, + PixelColorBlendingMode.Exclusion => ExclusionClear, + PixelColorBlendingMode.Hue => HueClear, + PixelColorBlendingMode.Saturation => SaturationClear, + PixelColorBlendingMode.Color => ColorClear, + PixelColorBlendingMode.Luminosity => LuminosityClear, + _ => NormalClear, + }, + PixelAlphaCompositionMode.Xor => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplyXor, + PixelColorBlendingMode.Add => AddXor, + PixelColorBlendingMode.Subtract => SubtractXor, + PixelColorBlendingMode.Screen => ScreenXor, + PixelColorBlendingMode.Darken => DarkenXor, + PixelColorBlendingMode.Lighten => LightenXor, + PixelColorBlendingMode.Overlay => OverlayXor, + PixelColorBlendingMode.HardLight => HardLightXor, + PixelColorBlendingMode.ColorDodge => ColorDodgeXor, + PixelColorBlendingMode.ColorBurn => ColorBurnXor, + PixelColorBlendingMode.SoftLight => SoftLightXor, + PixelColorBlendingMode.Difference => DifferenceXor, + PixelColorBlendingMode.Exclusion => ExclusionXor, + PixelColorBlendingMode.Hue => HueXor, + PixelColorBlendingMode.Saturation => SaturationXor, + PixelColorBlendingMode.Color => ColorXor, + PixelColorBlendingMode.Luminosity => LuminosityXor, + _ => NormalXor, + }, + PixelAlphaCompositionMode.Plus => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplyPlus, + PixelColorBlendingMode.Add => AddPlus, + PixelColorBlendingMode.Subtract => SubtractPlus, + PixelColorBlendingMode.Screen => ScreenPlus, + PixelColorBlendingMode.Darken => DarkenPlus, + PixelColorBlendingMode.Lighten => LightenPlus, + PixelColorBlendingMode.Overlay => OverlayPlus, + PixelColorBlendingMode.HardLight => HardLightPlus, + PixelColorBlendingMode.ColorDodge => ColorDodgePlus, + PixelColorBlendingMode.ColorBurn => ColorBurnPlus, + PixelColorBlendingMode.SoftLight => SoftLightPlus, + PixelColorBlendingMode.Difference => DifferencePlus, + PixelColorBlendingMode.Exclusion => ExclusionPlus, + PixelColorBlendingMode.Hue => HuePlus, + PixelColorBlendingMode.Saturation => SaturationPlus, + PixelColorBlendingMode.Color => ColorPlus, + PixelColorBlendingMode.Luminosity => LuminosityPlus, + _ => NormalPlus, + }, + _ => colorMode switch + { + PixelColorBlendingMode.Multiply => MultiplySrcOver, + PixelColorBlendingMode.Add => AddSrcOver, + PixelColorBlendingMode.Subtract => SubtractSrcOver, + PixelColorBlendingMode.Screen => ScreenSrcOver, + PixelColorBlendingMode.Darken => DarkenSrcOver, + PixelColorBlendingMode.Lighten => LightenSrcOver, + PixelColorBlendingMode.Overlay => OverlaySrcOver, + PixelColorBlendingMode.HardLight => HardLightSrcOver, + PixelColorBlendingMode.ColorDodge => ColorDodgeSrcOver, + PixelColorBlendingMode.ColorBurn => ColorBurnSrcOver, + PixelColorBlendingMode.SoftLight => SoftLightSrcOver, + PixelColorBlendingMode.Difference => DifferenceSrcOver, + PixelColorBlendingMode.Exclusion => ExclusionSrcOver, + PixelColorBlendingMode.Hue => HueSrcOver, + PixelColorBlendingMode.Saturation => SaturationSrcOver, + PixelColorBlendingMode.Color => ColorSrcOver, + PixelColorBlendingMode.Luminosity => LuminositySrcOver, + _ => NormalSrcOver, + }, + }; + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs new file mode 100644 index 000000000..1ba8b5dde --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.cs @@ -0,0 +1,10921 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +internal static partial class AssociatedAlphaPorterDuffFunctions +{ + /// + /// Returns the associated-alpha result of the "NormalSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "NormalSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "NormalSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "MultiplySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "MultiplySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "MultiplySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "AddSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "AddSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "AddSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SubtractSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SubtractSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SubtractSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ScreenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ScreenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ScreenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "DarkenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "DarkenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "DarkenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "LightenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "LightenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "LightenSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "OverlaySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "OverlaySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlaySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "OverlaySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "HardLightSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "HardLightSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "HardLightSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "HueSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "HueSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "HueSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SaturationSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SaturationSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "SaturationSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ColorSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorSrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ColorSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "ColorSrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "LuminositySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminositySrc(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "LuminositySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminositySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "LuminositySrc" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminositySrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return source; + } + + /// + /// Returns the associated-alpha result of the "NormalSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlaySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HueSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HueSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HueSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminositySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminositySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminositySrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlaySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HueSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HueSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HueSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminositySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminositySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminositySrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlaySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HueSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HueSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HueSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminositySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminositySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminositySrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlaySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlaySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlaySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SoftLightSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DifferenceSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ExclusionSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HueSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HueSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HueSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SaturationSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorSrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminositySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminositySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LuminositySrcOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminositySrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "NormalDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "NormalDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "MultiplyDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "MultiplyDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "MultiplyDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "AddDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "AddDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "AddDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SubtractDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SubtractDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SubtractDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ScreenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ScreenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ScreenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "DarkenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "DarkenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "DarkenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "LightenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "LightenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "LightenDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "OverlayDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "OverlayDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "OverlayDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "HardLightDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "HardLightDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "HardLightDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SoftLightDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SoftLightDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SoftLightDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "DifferenceDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "DifferenceDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "DifferenceDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ExclusionDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ExclusionDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ExclusionDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "HueDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "HueDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "HueDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SaturationDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SaturationDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "SaturationDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ColorDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ColorDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "ColorDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "LuminosityDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "LuminosityDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "LuminosityDest" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the associated-alpha result of the "NormalDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return AtopNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HueDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HueDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HueDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestAtop" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Atop(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return OverNormal(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "AddDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HueDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HueDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "HueDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "ColorDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestOver" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Over(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the associated-alpha result of the "NormalDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HueDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HueDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HueDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestIn" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return In(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "MultiplyDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "AddDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SubtractDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ScreenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DarkenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LightenDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "OverlayDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HardLightDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SoftLightDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "DifferenceDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ExclusionDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HueDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HueDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "HueDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "SaturationDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "ColorDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "LuminosityDestOut" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Out(source, backdrop); + } + + /// + /// Returns the associated-alpha result of the "NormalClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "NormalClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "NormalClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "MultiplyClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "MultiplyClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "MultiplyClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "AddClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "AddClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "AddClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "SubtractClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "SubtractClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "SubtractClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "ScreenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "ScreenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "ScreenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "DarkenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "DarkenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "DarkenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "LightenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "LightenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "LightenClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "OverlayClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "OverlayClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "OverlayClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "HardLightClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "HardLightClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "HardLightClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "ColorBurnClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "ColorBurnClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "ColorBurnClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "SoftLightClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "SoftLightClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "SoftLightClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "DifferenceClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "DifferenceClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "DifferenceClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "ExclusionClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "ExclusionClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "ExclusionClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "HueClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "HueClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "HueClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "SaturationClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "SaturationClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "SaturationClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "ColorClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "ColorClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "ColorClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "LuminosityClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityClear(Vector4 backdrop, Vector4 source, float opacity) + { + return Vector4.Zero; + } + + /// + /// Returns the associated-alpha result of the "LuminosityClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return Vector256.Zero; + } + + /// + /// Returns the associated-alpha result of the "LuminosityClear" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return Vector512.Zero; + } + + /// + /// Returns the associated-alpha result of the "NormalXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplyXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplyXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplyXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "AddXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SubtractXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ScreenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DarkenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LightenXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlayXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlayXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "OverlayXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HardLightXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgeXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SoftLightXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SoftLightXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SoftLightXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DifferenceXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DifferenceXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "DifferenceXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ExclusionXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ExclusionXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ExclusionXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HueXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HueXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "HueXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SaturationXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SaturationXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "SaturationXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "ColorXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LuminosityXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityXor(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LuminosityXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "LuminosityXor" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Xor(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return PlusNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return PlusNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "NormalPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return PlusNormal(backdrop, source); + } + + /// + /// Returns the associated-alpha result of the "MultiplyPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "MultiplyPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "MultiplyPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "AddPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SubtractPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ScreenPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DarkenPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LightenPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlayPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlayPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "OverlayPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HardLightPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgePlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgePlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgePlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgePlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorDodgePlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgePlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorBurnPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SoftLightPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DifferencePlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferencePlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DifferencePlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferencePlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "DifferencePlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferencePlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ExclusionPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HuePlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HuePlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HuePlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HuePlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "HuePlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HuePlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SaturationPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SaturationPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "SaturationPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "ColorPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LuminosityPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityPlus(Vector4 backdrop, Vector4 source, float opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LuminosityPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the associated-alpha result of the "LuminosityPlus" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + + return Plus(backdrop, source, Luminosity(backdrop, source)); + } + +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.tt new file mode 100644 index 000000000..44f9732fa --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.tt @@ -0,0 +1,152 @@ +<# +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. +#> +<#@ template debug="false" hostspecific="false" language="C#" #> +<#@ assembly name="System.Core" #> +<#@ output extension=".cs" #> +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +internal static partial class AssociatedAlphaPorterDuffFunctions +{ +<# + foreach (string composer in Composers) + { + foreach (string blender in Blenders) + { + string function = blender + composer; + + foreach (string vectorType in VectorTypes) + { + string opacityType = vectorType == "Vector4" ? "float" : vectorType; + string zero = vectorType == "Vector4" ? "Vector4.Zero" : vectorType.Replace("", ".Zero"); +#> + /// + /// Returns the associated-alpha result of the "<#= function #>" compositing equation. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The source opacity in the range 0 through 1. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static <#= vectorType #> <#= function #>(<#= vectorType #> backdrop, <#= vectorType #> source, <#= opacityType #> opacity) + { +<# + if (composer != "Dest" && composer != "Clear") + { +#> + // Associated RGB and alpha are scaled together so opacity cannot change the represented straight colour. + source *= opacity; + +<# + } +#> + return <#= GetComposition(blender, composer, zero) #>; + } + +<# + } + } + } +#> +} +<#+ + private static readonly string[] Composers = + [ + "Src", + "SrcAtop", + "SrcOver", + "SrcIn", + "SrcOut", + "Dest", + "DestAtop", + "DestOver", + "DestIn", + "DestOut", + "Clear", + "Xor", + "Plus" + ]; + + private static readonly string[] Blenders = + [ + "Normal", + "Multiply", + "Add", + "Subtract", + "Screen", + "Darken", + "Lighten", + "Overlay", + "HardLight", + "ColorDodge", + "ColorBurn", + "SoftLight", + "Difference", + "Exclusion", + "Hue", + "Saturation", + "Color", + "Luminosity" + ]; + + private static readonly string[] VectorTypes = + [ + "Vector4", + "Vector256", + "Vector512" + ]; + + private static string GetComposition(string blender, string composer, string zero) + { + bool normal = blender == "Normal"; + + switch (composer) + { + case "Src": + return "source"; + case "SrcAtop": + return normal + ? "AtopNormal(backdrop, source)" + : $"Atop(backdrop, source, {blender}(backdrop, source))"; + case "SrcOver": + return normal + ? "OverNormal(backdrop, source)" + : $"Over(backdrop, source, {blender}(backdrop, source))"; + case "SrcIn": + return "In(backdrop, source)"; + case "SrcOut": + return "Out(backdrop, source)"; + case "Dest": + return "backdrop"; + case "DestAtop": + return normal + ? "AtopNormal(source, backdrop)" + : $"Atop(source, backdrop, {blender}(source, backdrop))"; + case "DestOver": + return normal + ? "OverNormal(source, backdrop)" + : $"Over(source, backdrop, {blender}(source, backdrop))"; + case "DestIn": + return "In(source, backdrop)"; + case "DestOut": + return "Out(source, backdrop)"; + case "Clear": + return zero; + case "Plus": + return normal + ? "PlusNormal(backdrop, source)" + : $"Plus(backdrop, source, {blender}(backdrop, source))"; + default: + return "Xor(backdrop, source)"; + } + } +#> diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs new file mode 100644 index 000000000..3d7175dcb --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs @@ -0,0 +1,999 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Provides Porter-Duff composition functions for associated-alpha vectors. +/// +internal static partial class AssociatedAlphaPorterDuffFunctions +{ + private const int BlendAlphaControl = 0b_10_00_10_00; + private const int ShuffleAlphaControl = 0b_11_11_11_11; + + /// + /// Calculates the associated overlap term for Multiply blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Multiply(Vector4 backdrop, Vector4 source) => backdrop * source; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Multiply(Vector256 backdrop, Vector256 source) => backdrop * source; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Multiply(Vector512 backdrop, Vector512 source) => backdrop * source; + + /// + /// Calculates the associated overlap term for Add blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Add(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + return Vector4.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Add(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return Vector256.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Add(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return Vector512.Min(backdropAlpha * sourceAlpha, (backdrop * sourceAlpha) + (source * backdropAlpha)); + } + + /// + /// Calculates the associated overlap term for Subtract blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Subtract(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + return Vector4.Max(Vector4.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Subtract(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return Vector256.Max(Vector256.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Subtract(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return Vector512.Max(Vector512.Zero, (backdrop * sourceAlpha) - (source * backdropAlpha)); + } + + /// + /// Calculates the associated overlap term for Screen blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Screen(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Screen(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Screen(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return (backdrop * sourceAlpha) + (source * backdropAlpha) - (backdrop * source); + } + + /// + /// Calculates the associated overlap term for Darken blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Darken(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + return Vector4.Min(backdrop * sourceAlpha, source * backdropAlpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Darken(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return Vector256.Min(backdrop * sourceAlpha, source * backdropAlpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Darken(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return Vector512.Min(backdrop * sourceAlpha, source * backdropAlpha); + } + + /// + /// Calculates the associated overlap term for Lighten blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Lighten(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + return Vector4.Max(backdrop * sourceAlpha, source * backdropAlpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Lighten(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return Vector256.Max(backdrop * sourceAlpha, source * backdropAlpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Lighten(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return Vector512.Max(backdrop * sourceAlpha, source * backdropAlpha); + } + + /// + /// Calculates the associated overlap term for Overlay blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Overlay(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + + return new Vector4( + OverlayValue(backdrop.X, backdropAlpha.X, source.X, sourceAlpha.X), + OverlayValue(backdrop.Y, backdropAlpha.Y, source.Y, sourceAlpha.Y), + OverlayValue(backdrop.Z, backdropAlpha.Z, source.Z, sourceAlpha.Z), + 0F); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Overlay(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 left = (backdrop + backdrop) * source; + Vector256 right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector256.Create(2F)); + Vector256 useRight = Avx.CompareGreaterThan(backdrop + backdrop, backdropAlpha); + return Avx.BlendVariable(left, right, useRight); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Overlay(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 left = (backdrop + backdrop) * source; + Vector512 right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector512.Create(2F)); + Vector512 useRight = Avx512F.CompareGreaterThan(backdrop + backdrop, backdropAlpha); + return Vector512.ConditionalSelect(useRight, right, left); + } + + /// + /// Calculates the associated overlap term for HardLight blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLight(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + + return new Vector4( + OverlayValue(source.X, sourceAlpha.X, backdrop.X, backdropAlpha.X), + OverlayValue(source.Y, sourceAlpha.Y, backdrop.Y, backdropAlpha.Y), + OverlayValue(source.Z, sourceAlpha.Z, backdrop.Z, backdropAlpha.Z), + 0F); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLight(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 left = (backdrop + backdrop) * source; + Vector256 right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector256.Create(2F)); + Vector256 useRight = Avx.CompareGreaterThan(source + source, sourceAlpha); + return Avx.BlendVariable(left, right, useRight); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLight(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 left = (backdrop + backdrop) * source; + Vector512 right = (backdropAlpha * sourceAlpha) - (((backdropAlpha - backdrop) * (sourceAlpha - source)) * Vector512.Create(2F)); + Vector512 useRight = Avx512F.CompareGreaterThan(source + source, sourceAlpha); + return Vector512.ConditionalSelect(useRight, right, left); + } + + /// + /// Calculates the associated overlap term for ColorDodge blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodge(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 unassociatedBackdrop = backdrop; + Vector4 unassociatedSource = source; + + // This blend equation is defined in straight RGB, so recover each color before scaling the result by the overlap alpha. + Numerics.UnPremultiply(ref unassociatedBackdrop); + Numerics.UnPremultiply(ref unassociatedSource); + return PorterDuffFunctions.ColorDodge(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodge(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector256 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.ColorDodge(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodge(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector512 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.ColorDodge(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + /// Calculates the associated overlap term for ColorBurn blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurn(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 unassociatedBackdrop = backdrop; + Vector4 unassociatedSource = source; + + // This blend equation is defined in straight RGB, so recover each color before scaling the result by the overlap alpha. + Numerics.UnPremultiply(ref unassociatedBackdrop); + Numerics.UnPremultiply(ref unassociatedSource); + return PorterDuffFunctions.ColorBurn(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurn(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector256 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.ColorBurn(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurn(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector512 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.ColorBurn(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + /// Calculates the associated overlap term for SoftLight blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLight(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 unassociatedBackdrop = backdrop; + Vector4 unassociatedSource = source; + + // This blend equation is defined in straight RGB, so recover each color before scaling the result by the overlap alpha. + Numerics.UnPremultiply(ref unassociatedBackdrop); + Numerics.UnPremultiply(ref unassociatedSource); + return PorterDuffFunctions.SoftLight(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLight(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector256 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.SoftLight(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLight(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector512 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.SoftLight(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + /// Calculates the associated overlap term for Difference blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Difference(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 unassociatedBackdrop = backdrop; + Vector4 unassociatedSource = source; + + // This blend equation is defined in straight RGB, so recover each color before scaling the result by the overlap alpha. + Numerics.UnPremultiply(ref unassociatedBackdrop); + Numerics.UnPremultiply(ref unassociatedSource); + return PorterDuffFunctions.Difference(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Difference(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector256 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Difference(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Difference(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector512 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Difference(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + /// Calculates the associated overlap term for Exclusion blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Exclusion(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 unassociatedBackdrop = backdrop; + Vector4 unassociatedSource = source; + + // This blend equation is defined in straight RGB, so recover each color before scaling the result by the overlap alpha. + Numerics.UnPremultiply(ref unassociatedBackdrop); + Numerics.UnPremultiply(ref unassociatedSource); + return PorterDuffFunctions.Exclusion(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Exclusion(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector256 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Exclusion(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Exclusion(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector512 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Exclusion(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + /// Calculates the associated overlap term for Hue blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Hue(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 unassociatedBackdrop = backdrop; + Vector4 unassociatedSource = source; + + // This blend equation is defined in straight RGB, so recover each color before scaling the result by the overlap alpha. + Numerics.UnPremultiply(ref unassociatedBackdrop); + Numerics.UnPremultiply(ref unassociatedSource); + return PorterDuffFunctions.Hue(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Hue(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector256 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Hue(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Hue(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector512 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Hue(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + /// Calculates the associated overlap term for Saturation blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Saturation(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 unassociatedBackdrop = backdrop; + Vector4 unassociatedSource = source; + + // This blend equation is defined in straight RGB, so recover each color before scaling the result by the overlap alpha. + Numerics.UnPremultiply(ref unassociatedBackdrop); + Numerics.UnPremultiply(ref unassociatedSource); + return PorterDuffFunctions.Saturation(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Saturation(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector256 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Saturation(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Saturation(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector512 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Saturation(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + /// Calculates the associated overlap term for Color blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Color(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 unassociatedBackdrop = backdrop; + Vector4 unassociatedSource = source; + + // This blend equation is defined in straight RGB, so recover each color before scaling the result by the overlap alpha. + Numerics.UnPremultiply(ref unassociatedBackdrop); + Numerics.UnPremultiply(ref unassociatedSource); + return PorterDuffFunctions.Color(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Color(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector256 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Color(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Color(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector512 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Color(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + /// Calculates the associated overlap term for Luminosity blending. + /// + /// The associated backdrop vector. + /// The associated source vector. + /// The associated overlap term. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Luminosity(Vector4 backdrop, Vector4 source) + { + Vector4 backdropAlpha = Numerics.PermuteW(backdrop); + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 unassociatedBackdrop = backdrop; + Vector4 unassociatedSource = source; + + // This blend equation is defined in straight RGB, so recover each color before scaling the result by the overlap alpha. + Numerics.UnPremultiply(ref unassociatedBackdrop); + Numerics.UnPremultiply(ref unassociatedSource); + return PorterDuffFunctions.Luminosity(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Luminosity(Vector256 backdrop, Vector256 source) + { + Vector256 backdropAlpha = Avx.Permute(backdrop, ShuffleAlphaControl); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector256 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Luminosity(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Luminosity(Vector512 backdrop, Vector512 source) + { + Vector512 backdropAlpha = Vector512_.ShuffleNative(backdrop, ShuffleAlphaControl); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 unassociatedBackdrop = Numerics.UnPremultiply(backdrop, backdropAlpha); + Vector512 unassociatedSource = Numerics.UnPremultiply(source, sourceAlpha); + return PorterDuffFunctions.Luminosity(unassociatedBackdrop, unassociatedSource) * backdropAlpha * sourceAlpha; + } + + /// + /// Composites an associated source over an associated destination without a color-blending function. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverNormal(Vector4 destination, Vector4 source) + { + // Associated source-over is Ps + Pb(1 - As); both color and alpha therefore use the same coefficient. + Vector4 sourceAlpha = Numerics.PermuteW(source); + return source + (destination * (Vector4.One - sourceAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverNormal(Vector256 destination, Vector256 source) + { + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + return source + (destination * (Vector256.Create(1F) - sourceAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverNormal(Vector512 destination, Vector512 source) + { + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + return source + (destination * (Vector512.Create(1F) - sourceAlpha)); + } + + /// + /// Adds associated source and destination vectors without a color-blending function. + /// + /// The associated destination vector. + /// The associated source vector. + /// The clamped associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 PlusNormal(Vector4 destination, Vector4 source) + { + Vector4 alpha = Vector4.Min(Vector4.One, Numerics.PermuteW(source) + Numerics.PermuteW(destination)); + return Numerics.WithW(Vector4.Min(Vector4.One, destination + source), alpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 PlusNormal(Vector256 destination, Vector256 source) + { + Vector256 one = Vector256.Create(1F); + Vector256 alpha = Vector256.Min(one, Avx.Permute(source, ShuffleAlphaControl) + Avx.Permute(destination, ShuffleAlphaControl)); + return Avx.Blend(Vector256.Min(one, destination + source), alpha, BlendAlphaControl); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 PlusNormal(Vector512 destination, Vector512 source) + { + Vector512 one = Vector512.Create(1F); + Vector512 alpha = Vector512.Min(one, Vector512_.ShuffleNative(source, ShuffleAlphaControl) + Vector512_.ShuffleNative(destination, ShuffleAlphaControl)); + return Vector512.ConditionalSelect(AlphaMask512(), alpha, Vector512.Min(one, destination + source)); + } + + /// + /// Adds associated source and destination vectors using a color-blended overlap term. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated overlap term produced by the color-blending function. + /// The clamped associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Plus(Vector4 destination, Vector4 source, Vector4 overlap) + { + Vector4 destinationAlpha = Numerics.PermuteW(destination); + Vector4 alpha = Vector4.Min(Vector4.One, Numerics.PermuteW(source) + destinationAlpha); + + // The blended source contributes straight source color outside the destination and the blend result in the overlap. + Vector4 result = destination + (source * (Vector4.One - destinationAlpha)) + overlap; + return Numerics.WithW(Vector4.Min(Vector4.One, result), alpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Plus(Vector256 destination, Vector256 source, Vector256 overlap) + { + Vector256 one = Vector256.Create(1F); + Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); + Vector256 alpha = Vector256.Min(one, Avx.Permute(source, ShuffleAlphaControl) + destinationAlpha); + Vector256 result = destination + (source * (one - destinationAlpha)) + overlap; + return Avx.Blend(Vector256.Min(one, result), alpha, BlendAlphaControl); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Plus(Vector512 destination, Vector512 source, Vector512 overlap) + { + Vector512 one = Vector512.Create(1F); + Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + Vector512 alpha = Vector512.Min(one, Vector512_.ShuffleNative(source, ShuffleAlphaControl) + destinationAlpha); + Vector512 result = destination + (source * (one - destinationAlpha)) + overlap; + return Vector512.ConditionalSelect(AlphaMask512(), alpha, Vector512.Min(one, result)); + } + + /// + /// Composites an associated source atop an associated destination without a color-blending function. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AtopNormal(Vector4 destination, Vector4 source) + { + // Source-atop retains the destination alpha while replacing its covered contribution with the source. + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 destinationAlpha = Numerics.PermuteW(destination); + return (source * destinationAlpha) + (destination * (Vector4.One - sourceAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AtopNormal(Vector256 destination, Vector256 source) + { + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); + return (source * destinationAlpha) + (destination * (Vector256.Create(1F) - sourceAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AtopNormal(Vector512 destination, Vector512 source) + { + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + return (source * destinationAlpha) + (destination * (Vector512.Create(1F) - sourceAlpha)); + } + + /// + /// Composites an associated source over an associated destination using an unassociated blended color. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated overlap term produced by the color-blending function. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Over(Vector4 destination, Vector4 source, Vector4 overlap) + { + // The three terms cover destination-only, source-only, and overlapping color respectively. + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 destinationAlpha = Numerics.PermuteW(destination); + Vector4 result = (destination * (Vector4.One - sourceAlpha)) + (source * (Vector4.One - destinationAlpha)) + overlap; + Vector4 alpha = source + (destination * (Vector4.One - sourceAlpha)); + return Numerics.WithW(result, alpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Over(Vector256 destination, Vector256 source, Vector256 overlap) + { + Vector256 one = Vector256.Create(1F); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); + Vector256 result = (destination * (one - sourceAlpha)) + (source * (one - destinationAlpha)) + overlap; + Vector256 alpha = source + (destination * (one - sourceAlpha)); + return Avx.Blend(result, alpha, BlendAlphaControl); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Over(Vector512 destination, Vector512 source, Vector512 overlap) + { + Vector512 one = Vector512.Create(1F); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + Vector512 result = (destination * (one - sourceAlpha)) + (source * (one - destinationAlpha)) + overlap; + Vector512 alpha = source + (destination * (one - sourceAlpha)); + return Vector512.ConditionalSelect(AlphaMask512(), alpha, result); + } + + /// + /// Composites an associated source atop an associated destination using an unassociated blended color. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated overlap term produced by the color-blending function. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Atop(Vector4 destination, Vector4 source, Vector4 overlap) + { + // Atop discards source-only color and retains the destination alpha unchanged. + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 destinationAlpha = Numerics.PermuteW(destination); + Vector4 coefficient = Vector4.One - sourceAlpha; + Vector4 result = Vector128.FusedMultiplyAdd(destination.AsVector128(), coefficient.AsVector128(), overlap.AsVector128()).AsVector4(); + + return Numerics.WithW(result, destinationAlpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Atop(Vector256 destination, Vector256 source, Vector256 overlap) + { + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); + Vector256 result = Vector256.FusedMultiplyAdd(destination, Vector256.Create(1F) - sourceAlpha, overlap); + + return Avx.Blend(result, destinationAlpha, BlendAlphaControl); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Atop(Vector512 destination, Vector512 source, Vector512 overlap) + { + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + Vector512 result = Vector512.FusedMultiplyAdd(destination, Vector512.Create(1F) - sourceAlpha, overlap); + + return Vector512.ConditionalSelect(AlphaMask512(), destinationAlpha, result); + } + + /// + /// Retains the associated source within the destination coverage. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 In(Vector4 destination, Vector4 source) => source * Numerics.PermuteW(destination); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 In(Vector256 destination, Vector256 source) + => source * Avx.Permute(destination, ShuffleAlphaControl); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 In(Vector512 destination, Vector512 source) + => source * Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + + /// + /// Retains the associated source outside the destination coverage. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Out(Vector4 destination, Vector4 source) + => source * (Vector4.One - Numerics.PermuteW(destination)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Out(Vector256 destination, Vector256 source) + => source * (Vector256.Create(1F) - Avx.Permute(destination, ShuffleAlphaControl)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Out(Vector512 destination, Vector512 source) + => source * (Vector512.Create(1F) - Vector512_.ShuffleNative(destination, ShuffleAlphaControl)); + + /// + /// Retains only the non-overlapping parts of two associated vectors. + /// + /// The associated destination vector. + /// The associated source vector. + /// The associated composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Xor(Vector4 destination, Vector4 source) + { + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 destinationAlpha = Numerics.PermuteW(destination); + return (source * (Vector4.One - destinationAlpha)) + (destination * (Vector4.One - sourceAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Xor(Vector256 destination, Vector256 source) + { + Vector256 one = Vector256.Create(1F); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); + return (source * (one - destinationAlpha)) + (destination * (one - sourceAlpha)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Xor(Vector512 destination, Vector512 source) + { + Vector512 one = Vector512.Create(1F); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + return (source * (one - destinationAlpha)) + (destination * (one - sourceAlpha)); + } + + /// + /// Applies raster coverage to an associated composition result. + /// + /// The associated backdrop vector. + /// The associated composition result. + /// The raster coverage in the range 0 through 1. + /// The covered associated result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 BlendWithCoverage(Vector4 backdrop, Vector4 source, float coverage) + { + // Use the same fused operation as the wider paths so exact midpoints cannot change across vector widths. + return Vector128.FusedMultiplyAdd((source - backdrop).AsVector128(), Vector128.Create(coverage), backdrop.AsVector128()).AsVector4(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 BlendWithCoverage(Vector256 backdrop, Vector256 source, Vector256 coverage) + => Vector256.FusedMultiplyAdd(source - backdrop, coverage, backdrop); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 BlendWithCoverage(Vector512 backdrop, Vector512 source, Vector512 coverage) + => Vector512.FusedMultiplyAdd(source - backdrop, coverage, backdrop); + + /// + /// Calculates one associated Overlay overlap component without recovering either straight component. + /// + /// The associated backdrop component. + /// The backdrop alpha. + /// The associated source component. + /// The source alpha. + /// The associated overlap component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float OverlayValue(float backdrop, float backdropAlpha, float source, float sourceAlpha) + { + // Comparing 2Pb with Ab is equivalent to comparing the straight backdrop component with one half. + return (backdrop + backdrop) <= backdropAlpha + ? (backdrop + backdrop) * source + : (backdropAlpha * sourceAlpha) - (2F * (backdropAlpha - backdrop) * (sourceAlpha - source)); + } + + /// + /// Creates a SIMD lane mask selecting the alpha component of each packed vector. + /// + /// The alpha-component mask. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 AlphaMask512() + => Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs index f62d3c676..de2ccf3a9 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs @@ -3,11150 +3,6106 @@ // using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; /// -/// Collection of Porter Duff alpha blending functions applying different composition models. +/// Provides the straight-alpha equations consumed by the shared pixel-blending traversal. /// -/// -/// These functions are designed to be a general solution for all color cases, -/// that is, they take in account the alpha value of both the backdrop -/// and source, and there's no need to alpha-premultiply neither the backdrop -/// nor the source. -/// Note there are faster functions for when the backdrop color is known -/// to be opaque -/// -internal static class DefaultPixelBlenders - where TPixel : unmanaged, IPixel +internal static class DefaultPixelBlenderOperators { + /// + /// Applies the "NormalSrc" straight-alpha composition equation. + /// + public readonly struct NormalSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalSrc(background, source, amount); + } + + /// + /// Applies the "MultiplySrc" straight-alpha composition equation. + /// + public readonly struct MultiplySrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplySrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplySrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplySrc(background, source, amount); + } + + /// + /// Applies the "AddSrc" straight-alpha composition equation. + /// + public readonly struct AddSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddSrc(background, source, amount); + } + + /// + /// Applies the "SubtractSrc" straight-alpha composition equation. + /// + public readonly struct SubtractSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractSrc(background, source, amount); + } + + /// + /// Applies the "ScreenSrc" straight-alpha composition equation. + /// + public readonly struct ScreenSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenSrc(background, source, amount); + } + + /// + /// Applies the "DarkenSrc" straight-alpha composition equation. + /// + public readonly struct DarkenSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenSrc(background, source, amount); + } + + /// + /// Applies the "LightenSrc" straight-alpha composition equation. + /// + public readonly struct LightenSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenSrc(background, source, amount); + } + + /// + /// Applies the "OverlaySrc" straight-alpha composition equation. + /// + public readonly struct OverlaySrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlaySrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlaySrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlaySrc(background, source, amount); + } + + /// + /// Applies the "HardLightSrc" straight-alpha composition equation. + /// + public readonly struct HardLightSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightSrc(background, source, amount); + } + + /// + /// Applies the "ColorDodgeSrc" straight-alpha composition equation. + /// + public readonly struct ColorDodgeSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeSrc(background, source, amount); + } + + /// + /// Applies the "ColorBurnSrc" straight-alpha composition equation. + /// + public readonly struct ColorBurnSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnSrc(background, source, amount); + } + + /// + /// Applies the "SoftLightSrc" straight-alpha composition equation. + /// + public readonly struct SoftLightSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightSrc(background, source, amount); + } + + /// + /// Applies the "DifferenceSrc" straight-alpha composition equation. + /// + public readonly struct DifferenceSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceSrc(background, source, amount); + } + + /// + /// Applies the "ExclusionSrc" straight-alpha composition equation. + /// + public readonly struct ExclusionSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionSrc(background, source, amount); + } + + /// + /// Applies the "HueSrc" straight-alpha composition equation. + /// + public readonly struct HueSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueSrc(background, source, amount); + } + + /// + /// Applies the "SaturationSrc" straight-alpha composition equation. + /// + public readonly struct SaturationSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationSrc(background, source, amount); + } + + /// + /// Applies the "ColorSrc" straight-alpha composition equation. + /// + public readonly struct ColorSrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorSrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorSrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorSrc(background, source, amount); + } + + /// + /// Applies the "LuminositySrc" straight-alpha composition equation. + /// + public readonly struct LuminositySrc : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminositySrc(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminositySrc(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminositySrc(background, source, amount); + } + + /// + /// Applies the "NormalSrcAtop" straight-alpha composition equation. + /// + public readonly struct NormalSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalSrcAtop(background, source, amount); + } + + /// + /// Applies the "MultiplySrcAtop" straight-alpha composition equation. + /// + public readonly struct MultiplySrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplySrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplySrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplySrcAtop(background, source, amount); + } /// - /// A pixel blender that implements the "NormalSrc" composition equation. - /// - public class NormalSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrc Instance { get; } = new NormalSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrc" composition equation. - /// - public class AddSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrc Instance { get; } = new AddSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrc" composition equation. - /// - public class ScreenSrc : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrc Instance { get; } = new ScreenSrc(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.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)); - } - } - } - } - - /// - /// 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 (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - 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, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.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)); - } - } - } - } - - /// - /// 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 (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - 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, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.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)); - } - } - } - } - - /// - /// 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 (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - 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, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.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)); - } - } - } - } - - /// - /// 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 (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - 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, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.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)); - } - } - } - } - - /// - /// 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 (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - 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, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.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)); - } - } - } - } - - /// - /// 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 (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - 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, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.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)); - } - } - } - } - - /// - /// 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 (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - 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, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcAtop" composition equation. - /// - public class ScreenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcAtop Instance { get; } = new ScreenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcAtop" composition equation. - /// - public class DarkenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcAtop Instance { get; } = new DarkenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcAtop" composition equation. - /// - public class LightenSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcAtop Instance { get; } = new LightenSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcAtop" composition equation. - /// - public class OverlaySrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcAtop Instance { get; } = new OverlaySrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlaySrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcAtop" composition equation. - /// - public class HardLightSrcAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcAtop Instance { get; } = new HardLightSrcAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightSrcAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcOver" composition equation. - /// - public class NormalSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcOver Instance { get; } = new NormalSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcOver" composition equation. - /// - public class MultiplySrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcOver Instance { get; } = new MultiplySrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcOver" composition equation. - /// - public class AddSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcOver Instance { get; } = new AddSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcOver" composition equation. - /// - public class SubtractSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcOver Instance { get; } = new SubtractSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcOver" composition equation. - /// - public class ScreenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcOver Instance { get; } = new ScreenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcOver" composition equation. - /// - public class DarkenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcOver Instance { get; } = new DarkenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcOver" composition equation. - /// - public class LightenSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcOver Instance { get; } = new LightenSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcOver" composition equation. - /// - public class OverlaySrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcOver Instance { get; } = new OverlaySrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlaySrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcOver" composition equation. - /// - public class HardLightSrcOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcOver Instance { get; } = new HardLightSrcOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightSrcOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcIn" composition equation. - /// - public class NormalSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcIn Instance { get; } = new NormalSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcIn" composition equation. - /// - public class MultiplySrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcIn Instance { get; } = new MultiplySrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcIn" composition equation. - /// - public class AddSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcIn Instance { get; } = new AddSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcIn" composition equation. - /// - public class SubtractSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcIn Instance { get; } = new SubtractSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcIn" composition equation. - /// - public class ScreenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcIn Instance { get; } = new ScreenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcIn" composition equation. - /// - public class DarkenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcIn Instance { get; } = new DarkenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcIn" composition equation. - /// - public class LightenSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcIn Instance { get; } = new LightenSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcIn" composition equation. - /// - public class OverlaySrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcIn Instance { get; } = new OverlaySrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlaySrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcIn" composition equation. - /// - public class HardLightSrcIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcIn Instance { get; } = new HardLightSrcIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightSrcIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalSrcOut" composition equation. - /// - public class NormalSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalSrcOut Instance { get; } = new NormalSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplySrcOut" composition equation. - /// - public class MultiplySrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplySrcOut Instance { get; } = new MultiplySrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddSrcOut" composition equation. - /// - public class AddSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddSrcOut Instance { get; } = new AddSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractSrcOut" composition equation. - /// - public class SubtractSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractSrcOut Instance { get; } = new SubtractSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenSrcOut" composition equation. - /// - public class ScreenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenSrcOut Instance { get; } = new ScreenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenSrcOut" composition equation. - /// - public class DarkenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenSrcOut Instance { get; } = new DarkenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenSrcOut" composition equation. - /// - public class LightenSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenSrcOut Instance { get; } = new LightenSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlaySrcOut" composition equation. - /// - public class OverlaySrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlaySrcOut Instance { get; } = new OverlaySrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlaySrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlaySrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightSrcOut" composition equation. - /// - public class HardLightSrcOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightSrcOut Instance { get; } = new HardLightSrcOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightSrcOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightSrcOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightSrcOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDest" composition equation. - /// - public class NormalDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDest Instance { get; } = new NormalDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDest" composition equation. - /// - public class MultiplyDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDest Instance { get; } = new MultiplyDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDest" composition equation. - /// - public class AddDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDest Instance { get; } = new AddDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDest" composition equation. - /// - public class SubtractDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDest Instance { get; } = new SubtractDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDest" composition equation. - /// - public class ScreenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDest Instance { get; } = new ScreenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDest" composition equation. - /// - public class DarkenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDest Instance { get; } = new DarkenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDest" composition equation. - /// - public class LightenDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDest Instance { get; } = new LightenDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDest" composition equation. - /// - public class OverlayDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDest Instance { get; } = new OverlayDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDest" composition equation. - /// - public class HardLightDest : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDest Instance { get; } = new HardLightDest(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDest(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightDest(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDest(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestAtop" composition equation. - /// - public class NormalDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestAtop Instance { get; } = new NormalDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestAtop" composition equation. - /// - public class MultiplyDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestAtop Instance { get; } = new MultiplyDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestAtop" composition equation. - /// - public class AddDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestAtop Instance { get; } = new AddDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestAtop" composition equation. - /// - public class SubtractDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestAtop Instance { get; } = new SubtractDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestAtop" composition equation. - /// - public class ScreenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestAtop Instance { get; } = new ScreenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestAtop" composition equation. - /// - public class DarkenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestAtop Instance { get; } = new DarkenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDestAtop" composition equation. - /// - public class LightenDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestAtop Instance { get; } = new LightenDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestAtop" composition equation. - /// - public class OverlayDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestAtop Instance { get; } = new OverlayDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestAtop" composition equation. - /// - public class HardLightDestAtop : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestAtop Instance { get; } = new HardLightDestAtop(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDestAtop(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightDestAtop(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestAtop(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestOver" composition equation. - /// - public class NormalDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestOver Instance { get; } = new NormalDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestOver" composition equation. - /// - public class MultiplyDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestOver Instance { get; } = new MultiplyDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestOver" composition equation. - /// - public class AddDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestOver Instance { get; } = new AddDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestOver" composition equation. - /// - public class SubtractDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestOver Instance { get; } = new SubtractDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestOver" composition equation. - /// - public class DarkenDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestOver Instance { get; } = new DarkenDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestOver" composition equation. - /// - public class OverlayDestOver : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestOver Instance { get; } = new OverlayDestOver(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestOver(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightDestOver(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOver(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalDestIn" composition equation. - /// - public class NormalDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalDestIn Instance { get; } = new NormalDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddDestIn" composition equation. - /// - public class AddDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddDestIn Instance { get; } = new AddDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenDestIn" composition equation. - /// - public class ScreenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenDestIn Instance { get; } = new ScreenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenDestIn" composition equation. - /// - public class LightenDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenDestIn Instance { get; } = new LightenDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightDestIn" composition equation. - /// - public class HardLightDestIn : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightDestIn Instance { get; } = new HardLightDestIn(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightDestIn(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightDestIn(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestIn(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyDestOut" composition equation. - /// - public class MultiplyDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyDestOut Instance { get; } = new MultiplyDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractDestOut" composition equation. - /// - public class SubtractDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractDestOut Instance { get; } = new SubtractDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenDestOut" composition equation. - /// - public class DarkenDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenDestOut Instance { get; } = new DarkenDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayDestOut" composition equation. - /// - public class OverlayDestOut : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayDestOut Instance { get; } = new OverlayDestOut(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayDestOut(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightDestOut(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightDestOut(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "NormalClear" composition equation. - /// - public class NormalClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static NormalClear Instance { get; } = new NormalClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.NormalClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "AddClear" composition equation. - /// - public class AddClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static AddClear Instance { get; } = new AddClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.AddClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "ScreenClear" composition equation. - /// - public class ScreenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static ScreenClear Instance { get; } = new ScreenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenClear" composition equation. - /// - public class DarkenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenClear Instance { get; } = new DarkenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "LightenClear" composition equation. - /// - public class LightenClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static LightenClear Instance { get; } = new LightenClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.LightenClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "HardLightClear" composition equation. - /// - public class HardLightClear : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static HardLightClear Instance { get; } = new HardLightClear(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.HardLightClear(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightClear(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightClear(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.NormalXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.NormalXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "MultiplyXor" composition equation. - /// - public class MultiplyXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static MultiplyXor Instance { get; } = new MultiplyXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplyXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.MultiplyXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.MultiplyXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.AddXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.AddXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "SubtractXor" composition equation. - /// - public class SubtractXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static SubtractXor Instance { get; } = new SubtractXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.SubtractXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.SubtractXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.ScreenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.ScreenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "DarkenXor" composition equation. - /// - public class DarkenXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static DarkenXor Instance { get; } = new DarkenXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.DarkenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.DarkenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.LightenXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.LightenXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// A pixel blender that implements the "OverlayXor" composition equation. - /// - public class OverlayXor : PixelBlender - { - /// - /// Gets the static instance of this blender. - /// - public static OverlayXor Instance { get; } = new OverlayXor(); - - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.OverlayXor(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.OverlayXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.OverlayXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } - } - - /// - /// 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) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], amount); - } - } - } - - /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.HardLightXor(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.HardLightXor(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// Applies the "AddSrcAtop" straight-alpha composition equation. + /// + public readonly struct AddSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddSrcAtop(background, source, amount); } + /// + /// Applies the "SubtractSrcAtop" straight-alpha composition equation. + /// + public readonly struct SubtractSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractSrcAtop(background, source, amount); + } + + /// + /// Applies the "ScreenSrcAtop" straight-alpha composition equation. + /// + public readonly struct ScreenSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenSrcAtop(background, source, amount); + } + + /// + /// Applies the "DarkenSrcAtop" straight-alpha composition equation. + /// + public readonly struct DarkenSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenSrcAtop(background, source, amount); + } + + /// + /// Applies the "LightenSrcAtop" straight-alpha composition equation. + /// + public readonly struct LightenSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenSrcAtop(background, source, amount); + } + + /// + /// Applies the "OverlaySrcAtop" straight-alpha composition equation. + /// + public readonly struct OverlaySrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlaySrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlaySrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlaySrcAtop(background, source, amount); + } + + /// + /// Applies the "HardLightSrcAtop" straight-alpha composition equation. + /// + public readonly struct HardLightSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightSrcAtop(background, source, amount); + } + + /// + /// Applies the "ColorDodgeSrcAtop" straight-alpha composition equation. + /// + public readonly struct ColorDodgeSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeSrcAtop(background, source, amount); + } + + /// + /// Applies the "ColorBurnSrcAtop" straight-alpha composition equation. + /// + public readonly struct ColorBurnSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnSrcAtop(background, source, amount); + } + + /// + /// Applies the "SoftLightSrcAtop" straight-alpha composition equation. + /// + public readonly struct SoftLightSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightSrcAtop(background, source, amount); + } + + /// + /// Applies the "DifferenceSrcAtop" straight-alpha composition equation. + /// + public readonly struct DifferenceSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceSrcAtop(background, source, amount); + } + + /// + /// Applies the "ExclusionSrcAtop" straight-alpha composition equation. + /// + public readonly struct ExclusionSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionSrcAtop(background, source, amount); + } + + /// + /// Applies the "HueSrcAtop" straight-alpha composition equation. + /// + public readonly struct HueSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueSrcAtop(background, source, amount); + } + + /// + /// Applies the "SaturationSrcAtop" straight-alpha composition equation. + /// + public readonly struct SaturationSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationSrcAtop(background, source, amount); + } + + /// + /// Applies the "ColorSrcAtop" straight-alpha composition equation. + /// + public readonly struct ColorSrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorSrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorSrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorSrcAtop(background, source, amount); + } + + /// + /// Applies the "LuminositySrcAtop" straight-alpha composition equation. + /// + public readonly struct LuminositySrcAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminositySrcAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminositySrcAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminositySrcAtop(background, source, amount); + } + + /// + /// Applies the "NormalSrcOver" straight-alpha composition equation. + /// + public readonly struct NormalSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalSrcOver(background, source, amount); + } + + /// + /// Applies the "MultiplySrcOver" straight-alpha composition equation. + /// + public readonly struct MultiplySrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplySrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplySrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplySrcOver(background, source, amount); + } + + /// + /// Applies the "AddSrcOver" straight-alpha composition equation. + /// + public readonly struct AddSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddSrcOver(background, source, amount); + } + + /// + /// Applies the "SubtractSrcOver" straight-alpha composition equation. + /// + public readonly struct SubtractSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractSrcOver(background, source, amount); + } + + /// + /// Applies the "ScreenSrcOver" straight-alpha composition equation. + /// + public readonly struct ScreenSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenSrcOver(background, source, amount); + } + + /// + /// Applies the "DarkenSrcOver" straight-alpha composition equation. + /// + public readonly struct DarkenSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenSrcOver(background, source, amount); + } + + /// + /// Applies the "LightenSrcOver" straight-alpha composition equation. + /// + public readonly struct LightenSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenSrcOver(background, source, amount); + } + + /// + /// Applies the "OverlaySrcOver" straight-alpha composition equation. + /// + public readonly struct OverlaySrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlaySrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlaySrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlaySrcOver(background, source, amount); + } + + /// + /// Applies the "HardLightSrcOver" straight-alpha composition equation. + /// + public readonly struct HardLightSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightSrcOver(background, source, amount); + } + + /// + /// Applies the "ColorDodgeSrcOver" straight-alpha composition equation. + /// + public readonly struct ColorDodgeSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeSrcOver(background, source, amount); + } + + /// + /// Applies the "ColorBurnSrcOver" straight-alpha composition equation. + /// + public readonly struct ColorBurnSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnSrcOver(background, source, amount); + } + + /// + /// Applies the "SoftLightSrcOver" straight-alpha composition equation. + /// + public readonly struct SoftLightSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightSrcOver(background, source, amount); + } + + /// + /// Applies the "DifferenceSrcOver" straight-alpha composition equation. + /// + public readonly struct DifferenceSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceSrcOver(background, source, amount); + } + + /// + /// Applies the "ExclusionSrcOver" straight-alpha composition equation. + /// + public readonly struct ExclusionSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionSrcOver(background, source, amount); + } + + /// + /// Applies the "HueSrcOver" straight-alpha composition equation. + /// + public readonly struct HueSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueSrcOver(background, source, amount); + } + + /// + /// Applies the "SaturationSrcOver" straight-alpha composition equation. + /// + public readonly struct SaturationSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationSrcOver(background, source, amount); + } + + /// + /// Applies the "ColorSrcOver" straight-alpha composition equation. + /// + public readonly struct ColorSrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorSrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorSrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorSrcOver(background, source, amount); + } + + /// + /// Applies the "LuminositySrcOver" straight-alpha composition equation. + /// + public readonly struct LuminositySrcOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminositySrcOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminositySrcOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminositySrcOver(background, source, amount); + } + + /// + /// Applies the "NormalSrcIn" straight-alpha composition equation. + /// + public readonly struct NormalSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalSrcIn(background, source, amount); + } + + /// + /// Applies the "MultiplySrcIn" straight-alpha composition equation. + /// + public readonly struct MultiplySrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplySrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplySrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplySrcIn(background, source, amount); + } + + /// + /// Applies the "AddSrcIn" straight-alpha composition equation. + /// + public readonly struct AddSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddSrcIn(background, source, amount); + } + + /// + /// Applies the "SubtractSrcIn" straight-alpha composition equation. + /// + public readonly struct SubtractSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractSrcIn(background, source, amount); + } + + /// + /// Applies the "ScreenSrcIn" straight-alpha composition equation. + /// + public readonly struct ScreenSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenSrcIn(background, source, amount); + } + + /// + /// Applies the "DarkenSrcIn" straight-alpha composition equation. + /// + public readonly struct DarkenSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenSrcIn(background, source, amount); + } + + /// + /// Applies the "LightenSrcIn" straight-alpha composition equation. + /// + public readonly struct LightenSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenSrcIn(background, source, amount); + } + + /// + /// Applies the "OverlaySrcIn" straight-alpha composition equation. + /// + public readonly struct OverlaySrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlaySrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlaySrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlaySrcIn(background, source, amount); + } + + /// + /// Applies the "HardLightSrcIn" straight-alpha composition equation. + /// + public readonly struct HardLightSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightSrcIn(background, source, amount); + } + + /// + /// Applies the "ColorDodgeSrcIn" straight-alpha composition equation. + /// + public readonly struct ColorDodgeSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeSrcIn(background, source, amount); + } + + /// + /// Applies the "ColorBurnSrcIn" straight-alpha composition equation. + /// + public readonly struct ColorBurnSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnSrcIn(background, source, amount); + } + + /// + /// Applies the "SoftLightSrcIn" straight-alpha composition equation. + /// + public readonly struct SoftLightSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightSrcIn(background, source, amount); + } + + /// + /// Applies the "DifferenceSrcIn" straight-alpha composition equation. + /// + public readonly struct DifferenceSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceSrcIn(background, source, amount); + } + + /// + /// Applies the "ExclusionSrcIn" straight-alpha composition equation. + /// + public readonly struct ExclusionSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionSrcIn(background, source, amount); + } + + /// + /// Applies the "HueSrcIn" straight-alpha composition equation. + /// + public readonly struct HueSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueSrcIn(background, source, amount); + } + + /// + /// Applies the "SaturationSrcIn" straight-alpha composition equation. + /// + public readonly struct SaturationSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationSrcIn(background, source, amount); + } + + /// + /// Applies the "ColorSrcIn" straight-alpha composition equation. + /// + public readonly struct ColorSrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorSrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorSrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorSrcIn(background, source, amount); + } + + /// + /// Applies the "LuminositySrcIn" straight-alpha composition equation. + /// + public readonly struct LuminositySrcIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminositySrcIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminositySrcIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminositySrcIn(background, source, amount); + } + + /// + /// Applies the "NormalSrcOut" straight-alpha composition equation. + /// + public readonly struct NormalSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalSrcOut(background, source, amount); + } + + /// + /// Applies the "MultiplySrcOut" straight-alpha composition equation. + /// + public readonly struct MultiplySrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplySrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplySrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplySrcOut(background, source, amount); + } + + /// + /// Applies the "AddSrcOut" straight-alpha composition equation. + /// + public readonly struct AddSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddSrcOut(background, source, amount); + } + + /// + /// Applies the "SubtractSrcOut" straight-alpha composition equation. + /// + public readonly struct SubtractSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractSrcOut(background, source, amount); + } + + /// + /// Applies the "ScreenSrcOut" straight-alpha composition equation. + /// + public readonly struct ScreenSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenSrcOut(background, source, amount); + } + + /// + /// Applies the "DarkenSrcOut" straight-alpha composition equation. + /// + public readonly struct DarkenSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenSrcOut(background, source, amount); + } + + /// + /// Applies the "LightenSrcOut" straight-alpha composition equation. + /// + public readonly struct LightenSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenSrcOut(background, source, amount); + } + + /// + /// Applies the "OverlaySrcOut" straight-alpha composition equation. + /// + public readonly struct OverlaySrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlaySrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlaySrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlaySrcOut(background, source, amount); + } + + /// + /// Applies the "HardLightSrcOut" straight-alpha composition equation. + /// + public readonly struct HardLightSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightSrcOut(background, source, amount); + } + + /// + /// Applies the "ColorDodgeSrcOut" straight-alpha composition equation. + /// + public readonly struct ColorDodgeSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeSrcOut(background, source, amount); + } + + /// + /// Applies the "ColorBurnSrcOut" straight-alpha composition equation. + /// + public readonly struct ColorBurnSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnSrcOut(background, source, amount); + } + + /// + /// Applies the "SoftLightSrcOut" straight-alpha composition equation. + /// + public readonly struct SoftLightSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightSrcOut(background, source, amount); + } + + /// + /// Applies the "DifferenceSrcOut" straight-alpha composition equation. + /// + public readonly struct DifferenceSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceSrcOut(background, source, amount); + } + + /// + /// Applies the "ExclusionSrcOut" straight-alpha composition equation. + /// + public readonly struct ExclusionSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionSrcOut(background, source, amount); + } + + /// + /// Applies the "HueSrcOut" straight-alpha composition equation. + /// + public readonly struct HueSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueSrcOut(background, source, amount); + } + + /// + /// Applies the "SaturationSrcOut" straight-alpha composition equation. + /// + public readonly struct SaturationSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationSrcOut(background, source, amount); + } + + /// + /// Applies the "ColorSrcOut" straight-alpha composition equation. + /// + public readonly struct ColorSrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorSrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorSrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorSrcOut(background, source, amount); + } + + /// + /// Applies the "LuminositySrcOut" straight-alpha composition equation. + /// + public readonly struct LuminositySrcOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminositySrcOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminositySrcOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminositySrcOut(background, source, amount); + } + + /// + /// Applies the "NormalDest" straight-alpha composition equation. + /// + public readonly struct NormalDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalDest(background, source, amount); + } + + /// + /// Applies the "MultiplyDest" straight-alpha composition equation. + /// + public readonly struct MultiplyDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplyDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplyDest(background, source, amount); + } + + /// + /// Applies the "AddDest" straight-alpha composition equation. + /// + public readonly struct AddDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddDest(background, source, amount); + } + + /// + /// Applies the "SubtractDest" straight-alpha composition equation. + /// + public readonly struct SubtractDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractDest(background, source, amount); + } + + /// + /// Applies the "ScreenDest" straight-alpha composition equation. + /// + public readonly struct ScreenDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenDest(background, source, amount); + } + + /// + /// Applies the "DarkenDest" straight-alpha composition equation. + /// + public readonly struct DarkenDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenDest(background, source, amount); + } + + /// + /// Applies the "LightenDest" straight-alpha composition equation. + /// + public readonly struct LightenDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenDest(background, source, amount); + } + + /// + /// Applies the "OverlayDest" straight-alpha composition equation. + /// + public readonly struct OverlayDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlayDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlayDest(background, source, amount); + } + + /// + /// Applies the "HardLightDest" straight-alpha composition equation. + /// + public readonly struct HardLightDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightDest(background, source, amount); + } + + /// + /// Applies the "ColorDodgeDest" straight-alpha composition equation. + /// + public readonly struct ColorDodgeDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeDest(background, source, amount); + } + + /// + /// Applies the "ColorBurnDest" straight-alpha composition equation. + /// + public readonly struct ColorBurnDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnDest(background, source, amount); + } + + /// + /// Applies the "SoftLightDest" straight-alpha composition equation. + /// + public readonly struct SoftLightDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightDest(background, source, amount); + } + + /// + /// Applies the "DifferenceDest" straight-alpha composition equation. + /// + public readonly struct DifferenceDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceDest(background, source, amount); + } + + /// + /// Applies the "ExclusionDest" straight-alpha composition equation. + /// + public readonly struct ExclusionDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionDest(background, source, amount); + } + + /// + /// Applies the "HueDest" straight-alpha composition equation. + /// + public readonly struct HueDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueDest(background, source, amount); + } + + /// + /// Applies the "SaturationDest" straight-alpha composition equation. + /// + public readonly struct SaturationDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationDest(background, source, amount); + } + + /// + /// Applies the "ColorDest" straight-alpha composition equation. + /// + public readonly struct ColorDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDest(background, source, amount); + } + + /// + /// Applies the "LuminosityDest" straight-alpha composition equation. + /// + public readonly struct LuminosityDest : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminosityDest(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminosityDest(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminosityDest(background, source, amount); + } + + /// + /// Applies the "NormalDestAtop" straight-alpha composition equation. + /// + public readonly struct NormalDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalDestAtop(background, source, amount); + } + + /// + /// Applies the "MultiplyDestAtop" straight-alpha composition equation. + /// + public readonly struct MultiplyDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplyDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplyDestAtop(background, source, amount); + } + + /// + /// Applies the "AddDestAtop" straight-alpha composition equation. + /// + public readonly struct AddDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddDestAtop(background, source, amount); + } + + /// + /// Applies the "SubtractDestAtop" straight-alpha composition equation. + /// + public readonly struct SubtractDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractDestAtop(background, source, amount); + } + + /// + /// Applies the "ScreenDestAtop" straight-alpha composition equation. + /// + public readonly struct ScreenDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenDestAtop(background, source, amount); + } + + /// + /// Applies the "DarkenDestAtop" straight-alpha composition equation. + /// + public readonly struct DarkenDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenDestAtop(background, source, amount); + } + + /// + /// Applies the "LightenDestAtop" straight-alpha composition equation. + /// + public readonly struct LightenDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenDestAtop(background, source, amount); + } + + /// + /// Applies the "OverlayDestAtop" straight-alpha composition equation. + /// + public readonly struct OverlayDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlayDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlayDestAtop(background, source, amount); + } + + /// + /// Applies the "HardLightDestAtop" straight-alpha composition equation. + /// + public readonly struct HardLightDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightDestAtop(background, source, amount); + } + + /// + /// Applies the "ColorDodgeDestAtop" straight-alpha composition equation. + /// + public readonly struct ColorDodgeDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeDestAtop(background, source, amount); + } + + /// + /// Applies the "ColorBurnDestAtop" straight-alpha composition equation. + /// + public readonly struct ColorBurnDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnDestAtop(background, source, amount); + } + + /// + /// Applies the "SoftLightDestAtop" straight-alpha composition equation. + /// + public readonly struct SoftLightDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightDestAtop(background, source, amount); + } + + /// + /// Applies the "DifferenceDestAtop" straight-alpha composition equation. + /// + public readonly struct DifferenceDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceDestAtop(background, source, amount); + } + + /// + /// Applies the "ExclusionDestAtop" straight-alpha composition equation. + /// + public readonly struct ExclusionDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionDestAtop(background, source, amount); + } + + /// + /// Applies the "HueDestAtop" straight-alpha composition equation. + /// + public readonly struct HueDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueDestAtop(background, source, amount); + } + + /// + /// Applies the "SaturationDestAtop" straight-alpha composition equation. + /// + public readonly struct SaturationDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationDestAtop(background, source, amount); + } + + /// + /// Applies the "ColorDestAtop" straight-alpha composition equation. + /// + public readonly struct ColorDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDestAtop(background, source, amount); + } + + /// + /// Applies the "LuminosityDestAtop" straight-alpha composition equation. + /// + public readonly struct LuminosityDestAtop : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminosityDestAtop(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminosityDestAtop(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminosityDestAtop(background, source, amount); + } + + /// + /// Applies the "NormalDestOver" straight-alpha composition equation. + /// + public readonly struct NormalDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalDestOver(background, source, amount); + } + + /// + /// Applies the "MultiplyDestOver" straight-alpha composition equation. + /// + public readonly struct MultiplyDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplyDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplyDestOver(background, source, amount); + } + + /// + /// Applies the "AddDestOver" straight-alpha composition equation. + /// + public readonly struct AddDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddDestOver(background, source, amount); + } + + /// + /// Applies the "SubtractDestOver" straight-alpha composition equation. + /// + public readonly struct SubtractDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractDestOver(background, source, amount); + } + + /// + /// Applies the "ScreenDestOver" straight-alpha composition equation. + /// + public readonly struct ScreenDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenDestOver(background, source, amount); + } + + /// + /// Applies the "DarkenDestOver" straight-alpha composition equation. + /// + public readonly struct DarkenDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenDestOver(background, source, amount); + } + + /// + /// Applies the "LightenDestOver" straight-alpha composition equation. + /// + public readonly struct LightenDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenDestOver(background, source, amount); + } + + /// + /// Applies the "OverlayDestOver" straight-alpha composition equation. + /// + public readonly struct OverlayDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlayDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlayDestOver(background, source, amount); + } + + /// + /// Applies the "HardLightDestOver" straight-alpha composition equation. + /// + public readonly struct HardLightDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightDestOver(background, source, amount); + } + + /// + /// Applies the "ColorDodgeDestOver" straight-alpha composition equation. + /// + public readonly struct ColorDodgeDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeDestOver(background, source, amount); + } + + /// + /// Applies the "ColorBurnDestOver" straight-alpha composition equation. + /// + public readonly struct ColorBurnDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnDestOver(background, source, amount); + } + + /// + /// Applies the "SoftLightDestOver" straight-alpha composition equation. + /// + public readonly struct SoftLightDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightDestOver(background, source, amount); + } + + /// + /// Applies the "DifferenceDestOver" straight-alpha composition equation. + /// + public readonly struct DifferenceDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceDestOver(background, source, amount); + } + + /// + /// Applies the "ExclusionDestOver" straight-alpha composition equation. + /// + public readonly struct ExclusionDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionDestOver(background, source, amount); + } + + /// + /// Applies the "HueDestOver" straight-alpha composition equation. + /// + public readonly struct HueDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueDestOver(background, source, amount); + } + + /// + /// Applies the "SaturationDestOver" straight-alpha composition equation. + /// + public readonly struct SaturationDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationDestOver(background, source, amount); + } + + /// + /// Applies the "ColorDestOver" straight-alpha composition equation. + /// + public readonly struct ColorDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDestOver(background, source, amount); + } + + /// + /// Applies the "LuminosityDestOver" straight-alpha composition equation. + /// + public readonly struct LuminosityDestOver : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminosityDestOver(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminosityDestOver(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminosityDestOver(background, source, amount); + } + + /// + /// Applies the "NormalDestIn" straight-alpha composition equation. + /// + public readonly struct NormalDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalDestIn(background, source, amount); + } + + /// + /// Applies the "MultiplyDestIn" straight-alpha composition equation. + /// + public readonly struct MultiplyDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplyDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplyDestIn(background, source, amount); + } + + /// + /// Applies the "AddDestIn" straight-alpha composition equation. + /// + public readonly struct AddDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddDestIn(background, source, amount); + } + + /// + /// Applies the "SubtractDestIn" straight-alpha composition equation. + /// + public readonly struct SubtractDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractDestIn(background, source, amount); + } + + /// + /// Applies the "ScreenDestIn" straight-alpha composition equation. + /// + public readonly struct ScreenDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenDestIn(background, source, amount); + } + + /// + /// Applies the "DarkenDestIn" straight-alpha composition equation. + /// + public readonly struct DarkenDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenDestIn(background, source, amount); + } + + /// + /// Applies the "LightenDestIn" straight-alpha composition equation. + /// + public readonly struct LightenDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenDestIn(background, source, amount); + } + + /// + /// Applies the "OverlayDestIn" straight-alpha composition equation. + /// + public readonly struct OverlayDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlayDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlayDestIn(background, source, amount); + } + + /// + /// Applies the "HardLightDestIn" straight-alpha composition equation. + /// + public readonly struct HardLightDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightDestIn(background, source, amount); + } + + /// + /// Applies the "ColorDodgeDestIn" straight-alpha composition equation. + /// + public readonly struct ColorDodgeDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeDestIn(background, source, amount); + } + + /// + /// Applies the "ColorBurnDestIn" straight-alpha composition equation. + /// + public readonly struct ColorBurnDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnDestIn(background, source, amount); + } + + /// + /// Applies the "SoftLightDestIn" straight-alpha composition equation. + /// + public readonly struct SoftLightDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightDestIn(background, source, amount); + } + + /// + /// Applies the "DifferenceDestIn" straight-alpha composition equation. + /// + public readonly struct DifferenceDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceDestIn(background, source, amount); + } + + /// + /// Applies the "ExclusionDestIn" straight-alpha composition equation. + /// + public readonly struct ExclusionDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionDestIn(background, source, amount); + } + + /// + /// Applies the "HueDestIn" straight-alpha composition equation. + /// + public readonly struct HueDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueDestIn(background, source, amount); + } + + /// + /// Applies the "SaturationDestIn" straight-alpha composition equation. + /// + public readonly struct SaturationDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationDestIn(background, source, amount); + } + + /// + /// Applies the "ColorDestIn" straight-alpha composition equation. + /// + public readonly struct ColorDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDestIn(background, source, amount); + } + + /// + /// Applies the "LuminosityDestIn" straight-alpha composition equation. + /// + public readonly struct LuminosityDestIn : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminosityDestIn(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminosityDestIn(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminosityDestIn(background, source, amount); + } + + /// + /// Applies the "NormalDestOut" straight-alpha composition equation. + /// + public readonly struct NormalDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalDestOut(background, source, amount); + } + + /// + /// Applies the "MultiplyDestOut" straight-alpha composition equation. + /// + public readonly struct MultiplyDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplyDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplyDestOut(background, source, amount); + } + + /// + /// Applies the "AddDestOut" straight-alpha composition equation. + /// + public readonly struct AddDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddDestOut(background, source, amount); + } + + /// + /// Applies the "SubtractDestOut" straight-alpha composition equation. + /// + public readonly struct SubtractDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractDestOut(background, source, amount); + } + + /// + /// Applies the "ScreenDestOut" straight-alpha composition equation. + /// + public readonly struct ScreenDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenDestOut(background, source, amount); + } + + /// + /// Applies the "DarkenDestOut" straight-alpha composition equation. + /// + public readonly struct DarkenDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenDestOut(background, source, amount); + } + + /// + /// Applies the "LightenDestOut" straight-alpha composition equation. + /// + public readonly struct LightenDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenDestOut(background, source, amount); + } + + /// + /// Applies the "OverlayDestOut" straight-alpha composition equation. + /// + public readonly struct OverlayDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlayDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlayDestOut(background, source, amount); + } + + /// + /// Applies the "HardLightDestOut" straight-alpha composition equation. + /// + public readonly struct HardLightDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightDestOut(background, source, amount); + } + + /// + /// Applies the "ColorDodgeDestOut" straight-alpha composition equation. + /// + public readonly struct ColorDodgeDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeDestOut(background, source, amount); + } + + /// + /// Applies the "ColorBurnDestOut" straight-alpha composition equation. + /// + public readonly struct ColorBurnDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnDestOut(background, source, amount); + } + + /// + /// Applies the "SoftLightDestOut" straight-alpha composition equation. + /// + public readonly struct SoftLightDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightDestOut(background, source, amount); + } + + /// + /// Applies the "DifferenceDestOut" straight-alpha composition equation. + /// + public readonly struct DifferenceDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceDestOut(background, source, amount); + } + + /// + /// Applies the "ExclusionDestOut" straight-alpha composition equation. + /// + public readonly struct ExclusionDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionDestOut(background, source, amount); + } + + /// + /// Applies the "HueDestOut" straight-alpha composition equation. + /// + public readonly struct HueDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueDestOut(background, source, amount); + } + + /// + /// Applies the "SaturationDestOut" straight-alpha composition equation. + /// + public readonly struct SaturationDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationDestOut(background, source, amount); + } + + /// + /// Applies the "ColorDestOut" straight-alpha composition equation. + /// + public readonly struct ColorDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDestOut(background, source, amount); + } + + /// + /// Applies the "LuminosityDestOut" straight-alpha composition equation. + /// + public readonly struct LuminosityDestOut : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminosityDestOut(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminosityDestOut(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminosityDestOut(background, source, amount); + } + + /// + /// Applies the "NormalClear" straight-alpha composition equation. + /// + public readonly struct NormalClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalClear(background, source, amount); + } + + /// + /// Applies the "MultiplyClear" straight-alpha composition equation. + /// + public readonly struct MultiplyClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplyClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplyClear(background, source, amount); + } + + /// + /// Applies the "AddClear" straight-alpha composition equation. + /// + public readonly struct AddClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddClear(background, source, amount); + } + + /// + /// Applies the "SubtractClear" straight-alpha composition equation. + /// + public readonly struct SubtractClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractClear(background, source, amount); + } + + /// + /// Applies the "ScreenClear" straight-alpha composition equation. + /// + public readonly struct ScreenClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenClear(background, source, amount); + } + + /// + /// Applies the "DarkenClear" straight-alpha composition equation. + /// + public readonly struct DarkenClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenClear(background, source, amount); + } + + /// + /// Applies the "LightenClear" straight-alpha composition equation. + /// + public readonly struct LightenClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenClear(background, source, amount); + } + + /// + /// Applies the "OverlayClear" straight-alpha composition equation. + /// + public readonly struct OverlayClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlayClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlayClear(background, source, amount); + } + + /// + /// Applies the "HardLightClear" straight-alpha composition equation. + /// + public readonly struct HardLightClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightClear(background, source, amount); + } + + /// + /// Applies the "ColorDodgeClear" straight-alpha composition equation. + /// + public readonly struct ColorDodgeClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeClear(background, source, amount); + } + + /// + /// Applies the "ColorBurnClear" straight-alpha composition equation. + /// + public readonly struct ColorBurnClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnClear(background, source, amount); + } + + /// + /// Applies the "SoftLightClear" straight-alpha composition equation. + /// + public readonly struct SoftLightClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightClear(background, source, amount); + } + + /// + /// Applies the "DifferenceClear" straight-alpha composition equation. + /// + public readonly struct DifferenceClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceClear(background, source, amount); + } + + /// + /// Applies the "ExclusionClear" straight-alpha composition equation. + /// + public readonly struct ExclusionClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionClear(background, source, amount); + } + + /// + /// Applies the "HueClear" straight-alpha composition equation. + /// + public readonly struct HueClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueClear(background, source, amount); + } + + /// + /// Applies the "SaturationClear" straight-alpha composition equation. + /// + public readonly struct SaturationClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationClear(background, source, amount); + } + + /// + /// Applies the "ColorClear" straight-alpha composition equation. + /// + public readonly struct ColorClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorClear(background, source, amount); + } + + /// + /// Applies the "LuminosityClear" straight-alpha composition equation. + /// + public readonly struct LuminosityClear : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminosityClear(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminosityClear(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminosityClear(background, source, amount); + } + + /// + /// Applies the "NormalXor" straight-alpha composition equation. + /// + public readonly struct NormalXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalXor(background, source, amount); + } + + /// + /// Applies the "MultiplyXor" straight-alpha composition equation. + /// + public readonly struct MultiplyXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplyXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplyXor(background, source, amount); + } + + /// + /// Applies the "AddXor" straight-alpha composition equation. + /// + public readonly struct AddXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddXor(background, source, amount); + } + + /// + /// Applies the "SubtractXor" straight-alpha composition equation. + /// + public readonly struct SubtractXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractXor(background, source, amount); + } + + /// + /// Applies the "ScreenXor" straight-alpha composition equation. + /// + public readonly struct ScreenXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenXor(background, source, amount); + } + + /// + /// Applies the "DarkenXor" straight-alpha composition equation. + /// + public readonly struct DarkenXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenXor(background, source, amount); + } + + /// + /// Applies the "LightenXor" straight-alpha composition equation. + /// + public readonly struct LightenXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenXor(background, source, amount); + } + + /// + /// Applies the "OverlayXor" straight-alpha composition equation. + /// + public readonly struct OverlayXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlayXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlayXor(background, source, amount); + } + + /// + /// Applies the "HardLightXor" straight-alpha composition equation. + /// + public readonly struct HardLightXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightXor(background, source, amount); + } + + /// + /// Applies the "ColorDodgeXor" straight-alpha composition equation. + /// + public readonly struct ColorDodgeXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgeXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgeXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgeXor(background, source, amount); + } + + /// + /// Applies the "ColorBurnXor" straight-alpha composition equation. + /// + public readonly struct ColorBurnXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnXor(background, source, amount); + } + + /// + /// Applies the "SoftLightXor" straight-alpha composition equation. + /// + public readonly struct SoftLightXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightXor(background, source, amount); + } + + /// + /// Applies the "DifferenceXor" straight-alpha composition equation. + /// + public readonly struct DifferenceXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferenceXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferenceXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferenceXor(background, source, amount); + } + + /// + /// Applies the "ExclusionXor" straight-alpha composition equation. + /// + public readonly struct ExclusionXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionXor(background, source, amount); + } + + /// + /// Applies the "HueXor" straight-alpha composition equation. + /// + public readonly struct HueXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HueXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HueXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HueXor(background, source, amount); + } + + /// + /// Applies the "SaturationXor" straight-alpha composition equation. + /// + public readonly struct SaturationXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationXor(background, source, amount); + } + + /// + /// Applies the "ColorXor" straight-alpha composition equation. + /// + public readonly struct ColorXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorXor(background, source, amount); + } + + /// + /// Applies the "LuminosityXor" straight-alpha composition equation. + /// + public readonly struct LuminosityXor : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminosityXor(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminosityXor(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminosityXor(background, source, amount); + } + + /// + /// Applies the "NormalPlus" straight-alpha composition equation. + /// + public readonly struct NormalPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.NormalPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.NormalPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.NormalPlus(background, source, amount); + } + + /// + /// Applies the "MultiplyPlus" straight-alpha composition equation. + /// + public readonly struct MultiplyPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.MultiplyPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.MultiplyPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.MultiplyPlus(background, source, amount); + } + + /// + /// Applies the "AddPlus" straight-alpha composition equation. + /// + public readonly struct AddPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.AddPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.AddPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.AddPlus(background, source, amount); + } + + /// + /// Applies the "SubtractPlus" straight-alpha composition equation. + /// + public readonly struct SubtractPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SubtractPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SubtractPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SubtractPlus(background, source, amount); + } + + /// + /// Applies the "ScreenPlus" straight-alpha composition equation. + /// + public readonly struct ScreenPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ScreenPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ScreenPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ScreenPlus(background, source, amount); + } + + /// + /// Applies the "DarkenPlus" straight-alpha composition equation. + /// + public readonly struct DarkenPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DarkenPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DarkenPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DarkenPlus(background, source, amount); + } + + /// + /// Applies the "LightenPlus" straight-alpha composition equation. + /// + public readonly struct LightenPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LightenPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LightenPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LightenPlus(background, source, amount); + } + + /// + /// Applies the "OverlayPlus" straight-alpha composition equation. + /// + public readonly struct OverlayPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.OverlayPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.OverlayPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.OverlayPlus(background, source, amount); + } + + /// + /// Applies the "HardLightPlus" straight-alpha composition equation. + /// + public readonly struct HardLightPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HardLightPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HardLightPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HardLightPlus(background, source, amount); + } + + /// + /// Applies the "ColorDodgePlus" straight-alpha composition equation. + /// + public readonly struct ColorDodgePlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorDodgePlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorDodgePlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorDodgePlus(background, source, amount); + } + + /// + /// Applies the "ColorBurnPlus" straight-alpha composition equation. + /// + public readonly struct ColorBurnPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorBurnPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorBurnPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorBurnPlus(background, source, amount); + } + + /// + /// Applies the "SoftLightPlus" straight-alpha composition equation. + /// + public readonly struct SoftLightPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SoftLightPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SoftLightPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SoftLightPlus(background, source, amount); + } + + /// + /// Applies the "DifferencePlus" straight-alpha composition equation. + /// + public readonly struct DifferencePlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.DifferencePlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.DifferencePlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.DifferencePlus(background, source, amount); + } + + /// + /// Applies the "ExclusionPlus" straight-alpha composition equation. + /// + public readonly struct ExclusionPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ExclusionPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ExclusionPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ExclusionPlus(background, source, amount); + } + + /// + /// Applies the "HuePlus" straight-alpha composition equation. + /// + public readonly struct HuePlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.HuePlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.HuePlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.HuePlus(background, source, amount); + } + + /// + /// Applies the "SaturationPlus" straight-alpha composition equation. + /// + public readonly struct SaturationPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.SaturationPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.SaturationPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.SaturationPlus(background, source, amount); + } + + /// + /// Applies the "ColorPlus" straight-alpha composition equation. + /// + public readonly struct ColorPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.ColorPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.ColorPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.ColorPlus(background, source, amount); + } + + /// + /// Applies the "LuminosityPlus" straight-alpha composition equation. + /// + public readonly struct LuminosityPlus : IPixelBlenderOperator + { + /// + public static bool IsAssociatedAlpha => false; + + /// + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.LuminosityPlus(background, source, amount); + + /// + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.LuminosityPlus(background, source, amount); + + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.LuminosityPlus(background, source, amount); + } + +} + +/// +/// Provides straight-alpha pixel blenders for each color and alpha composition pair. +/// +/// The destination pixel format. +internal static class DefaultPixelBlenders + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the shared blender for the "NormalSrc" composition equation. + /// + public static PixelBlender NormalSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplySrc" composition equation. + /// + public static PixelBlender MultiplySrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddSrc" composition equation. + /// + public static PixelBlender AddSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractSrc" composition equation. + /// + public static PixelBlender SubtractSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenSrc" composition equation. + /// + public static PixelBlender ScreenSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenSrc" composition equation. + /// + public static PixelBlender DarkenSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenSrc" composition equation. + /// + public static PixelBlender LightenSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlaySrc" composition equation. + /// + public static PixelBlender OverlaySrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightSrc" composition equation. + /// + public static PixelBlender HardLightSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeSrc" composition equation. + /// + public static PixelBlender ColorDodgeSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnSrc" composition equation. + /// + public static PixelBlender ColorBurnSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightSrc" composition equation. + /// + public static PixelBlender SoftLightSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceSrc" composition equation. + /// + public static PixelBlender DifferenceSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionSrc" composition equation. + /// + public static PixelBlender ExclusionSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueSrc" composition equation. + /// + public static PixelBlender HueSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationSrc" composition equation. + /// + public static PixelBlender SaturationSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorSrc" composition equation. + /// + public static PixelBlender ColorSrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminositySrc" composition equation. + /// + public static PixelBlender LuminositySrc => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalSrcAtop" composition equation. + /// + public static PixelBlender NormalSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplySrcAtop" composition equation. + /// + public static PixelBlender MultiplySrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddSrcAtop" composition equation. + /// + public static PixelBlender AddSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractSrcAtop" composition equation. + /// + public static PixelBlender SubtractSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenSrcAtop" composition equation. + /// + public static PixelBlender ScreenSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenSrcAtop" composition equation. + /// + public static PixelBlender DarkenSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenSrcAtop" composition equation. + /// + public static PixelBlender LightenSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlaySrcAtop" composition equation. + /// + public static PixelBlender OverlaySrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightSrcAtop" composition equation. + /// + public static PixelBlender HardLightSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeSrcAtop" composition equation. + /// + public static PixelBlender ColorDodgeSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnSrcAtop" composition equation. + /// + public static PixelBlender ColorBurnSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightSrcAtop" composition equation. + /// + public static PixelBlender SoftLightSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceSrcAtop" composition equation. + /// + public static PixelBlender DifferenceSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionSrcAtop" composition equation. + /// + public static PixelBlender ExclusionSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueSrcAtop" composition equation. + /// + public static PixelBlender HueSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationSrcAtop" composition equation. + /// + public static PixelBlender SaturationSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorSrcAtop" composition equation. + /// + public static PixelBlender ColorSrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminositySrcAtop" composition equation. + /// + public static PixelBlender LuminositySrcAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalSrcOver" composition equation. + /// + public static PixelBlender NormalSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplySrcOver" composition equation. + /// + public static PixelBlender MultiplySrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddSrcOver" composition equation. + /// + public static PixelBlender AddSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractSrcOver" composition equation. + /// + public static PixelBlender SubtractSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenSrcOver" composition equation. + /// + public static PixelBlender ScreenSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenSrcOver" composition equation. + /// + public static PixelBlender DarkenSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenSrcOver" composition equation. + /// + public static PixelBlender LightenSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlaySrcOver" composition equation. + /// + public static PixelBlender OverlaySrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightSrcOver" composition equation. + /// + public static PixelBlender HardLightSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeSrcOver" composition equation. + /// + public static PixelBlender ColorDodgeSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnSrcOver" composition equation. + /// + public static PixelBlender ColorBurnSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightSrcOver" composition equation. + /// + public static PixelBlender SoftLightSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceSrcOver" composition equation. + /// + public static PixelBlender DifferenceSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionSrcOver" composition equation. + /// + public static PixelBlender ExclusionSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueSrcOver" composition equation. + /// + public static PixelBlender HueSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationSrcOver" composition equation. + /// + public static PixelBlender SaturationSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorSrcOver" composition equation. + /// + public static PixelBlender ColorSrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminositySrcOver" composition equation. + /// + public static PixelBlender LuminositySrcOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalSrcIn" composition equation. + /// + public static PixelBlender NormalSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplySrcIn" composition equation. + /// + public static PixelBlender MultiplySrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddSrcIn" composition equation. + /// + public static PixelBlender AddSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractSrcIn" composition equation. + /// + public static PixelBlender SubtractSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenSrcIn" composition equation. + /// + public static PixelBlender ScreenSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenSrcIn" composition equation. + /// + public static PixelBlender DarkenSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenSrcIn" composition equation. + /// + public static PixelBlender LightenSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlaySrcIn" composition equation. + /// + public static PixelBlender OverlaySrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightSrcIn" composition equation. + /// + public static PixelBlender HardLightSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeSrcIn" composition equation. + /// + public static PixelBlender ColorDodgeSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnSrcIn" composition equation. + /// + public static PixelBlender ColorBurnSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightSrcIn" composition equation. + /// + public static PixelBlender SoftLightSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceSrcIn" composition equation. + /// + public static PixelBlender DifferenceSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionSrcIn" composition equation. + /// + public static PixelBlender ExclusionSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueSrcIn" composition equation. + /// + public static PixelBlender HueSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationSrcIn" composition equation. + /// + public static PixelBlender SaturationSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorSrcIn" composition equation. + /// + public static PixelBlender ColorSrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminositySrcIn" composition equation. + /// + public static PixelBlender LuminositySrcIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalSrcOut" composition equation. + /// + public static PixelBlender NormalSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplySrcOut" composition equation. + /// + public static PixelBlender MultiplySrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddSrcOut" composition equation. + /// + public static PixelBlender AddSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractSrcOut" composition equation. + /// + public static PixelBlender SubtractSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenSrcOut" composition equation. + /// + public static PixelBlender ScreenSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenSrcOut" composition equation. + /// + public static PixelBlender DarkenSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenSrcOut" composition equation. + /// + public static PixelBlender LightenSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlaySrcOut" composition equation. + /// + public static PixelBlender OverlaySrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightSrcOut" composition equation. + /// + public static PixelBlender HardLightSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeSrcOut" composition equation. + /// + public static PixelBlender ColorDodgeSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnSrcOut" composition equation. + /// + public static PixelBlender ColorBurnSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightSrcOut" composition equation. + /// + public static PixelBlender SoftLightSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceSrcOut" composition equation. + /// + public static PixelBlender DifferenceSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionSrcOut" composition equation. + /// + public static PixelBlender ExclusionSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueSrcOut" composition equation. + /// + public static PixelBlender HueSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationSrcOut" composition equation. + /// + public static PixelBlender SaturationSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorSrcOut" composition equation. + /// + public static PixelBlender ColorSrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminositySrcOut" composition equation. + /// + public static PixelBlender LuminositySrcOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalDest" composition equation. + /// + public static PixelBlender NormalDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyDest" composition equation. + /// + public static PixelBlender MultiplyDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddDest" composition equation. + /// + public static PixelBlender AddDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractDest" composition equation. + /// + public static PixelBlender SubtractDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenDest" composition equation. + /// + public static PixelBlender ScreenDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenDest" composition equation. + /// + public static PixelBlender DarkenDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenDest" composition equation. + /// + public static PixelBlender LightenDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayDest" composition equation. + /// + public static PixelBlender OverlayDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightDest" composition equation. + /// + public static PixelBlender HardLightDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeDest" composition equation. + /// + public static PixelBlender ColorDodgeDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnDest" composition equation. + /// + public static PixelBlender ColorBurnDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightDest" composition equation. + /// + public static PixelBlender SoftLightDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceDest" composition equation. + /// + public static PixelBlender DifferenceDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionDest" composition equation. + /// + public static PixelBlender ExclusionDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueDest" composition equation. + /// + public static PixelBlender HueDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationDest" composition equation. + /// + public static PixelBlender SaturationDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDest" composition equation. + /// + public static PixelBlender ColorDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityDest" composition equation. + /// + public static PixelBlender LuminosityDest => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalDestAtop" composition equation. + /// + public static PixelBlender NormalDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyDestAtop" composition equation. + /// + public static PixelBlender MultiplyDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddDestAtop" composition equation. + /// + public static PixelBlender AddDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractDestAtop" composition equation. + /// + public static PixelBlender SubtractDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenDestAtop" composition equation. + /// + public static PixelBlender ScreenDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenDestAtop" composition equation. + /// + public static PixelBlender DarkenDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenDestAtop" composition equation. + /// + public static PixelBlender LightenDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayDestAtop" composition equation. + /// + public static PixelBlender OverlayDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightDestAtop" composition equation. + /// + public static PixelBlender HardLightDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeDestAtop" composition equation. + /// + public static PixelBlender ColorDodgeDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnDestAtop" composition equation. + /// + public static PixelBlender ColorBurnDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightDestAtop" composition equation. + /// + public static PixelBlender SoftLightDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceDestAtop" composition equation. + /// + public static PixelBlender DifferenceDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionDestAtop" composition equation. + /// + public static PixelBlender ExclusionDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueDestAtop" composition equation. + /// + public static PixelBlender HueDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationDestAtop" composition equation. + /// + public static PixelBlender SaturationDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDestAtop" composition equation. + /// + public static PixelBlender ColorDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityDestAtop" composition equation. + /// + public static PixelBlender LuminosityDestAtop => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalDestOver" composition equation. + /// + public static PixelBlender NormalDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyDestOver" composition equation. + /// + public static PixelBlender MultiplyDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddDestOver" composition equation. + /// + public static PixelBlender AddDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractDestOver" composition equation. + /// + public static PixelBlender SubtractDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenDestOver" composition equation. + /// + public static PixelBlender ScreenDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenDestOver" composition equation. + /// + public static PixelBlender DarkenDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenDestOver" composition equation. + /// + public static PixelBlender LightenDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayDestOver" composition equation. + /// + public static PixelBlender OverlayDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightDestOver" composition equation. + /// + public static PixelBlender HardLightDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeDestOver" composition equation. + /// + public static PixelBlender ColorDodgeDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnDestOver" composition equation. + /// + public static PixelBlender ColorBurnDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightDestOver" composition equation. + /// + public static PixelBlender SoftLightDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceDestOver" composition equation. + /// + public static PixelBlender DifferenceDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionDestOver" composition equation. + /// + public static PixelBlender ExclusionDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueDestOver" composition equation. + /// + public static PixelBlender HueDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationDestOver" composition equation. + /// + public static PixelBlender SaturationDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDestOver" composition equation. + /// + public static PixelBlender ColorDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityDestOver" composition equation. + /// + public static PixelBlender LuminosityDestOver => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalDestIn" composition equation. + /// + public static PixelBlender NormalDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyDestIn" composition equation. + /// + public static PixelBlender MultiplyDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddDestIn" composition equation. + /// + public static PixelBlender AddDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractDestIn" composition equation. + /// + public static PixelBlender SubtractDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenDestIn" composition equation. + /// + public static PixelBlender ScreenDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenDestIn" composition equation. + /// + public static PixelBlender DarkenDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenDestIn" composition equation. + /// + public static PixelBlender LightenDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayDestIn" composition equation. + /// + public static PixelBlender OverlayDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightDestIn" composition equation. + /// + public static PixelBlender HardLightDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeDestIn" composition equation. + /// + public static PixelBlender ColorDodgeDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnDestIn" composition equation. + /// + public static PixelBlender ColorBurnDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightDestIn" composition equation. + /// + public static PixelBlender SoftLightDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceDestIn" composition equation. + /// + public static PixelBlender DifferenceDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionDestIn" composition equation. + /// + public static PixelBlender ExclusionDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueDestIn" composition equation. + /// + public static PixelBlender HueDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationDestIn" composition equation. + /// + public static PixelBlender SaturationDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDestIn" composition equation. + /// + public static PixelBlender ColorDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityDestIn" composition equation. + /// + public static PixelBlender LuminosityDestIn => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalDestOut" composition equation. + /// + public static PixelBlender NormalDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyDestOut" composition equation. + /// + public static PixelBlender MultiplyDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddDestOut" composition equation. + /// + public static PixelBlender AddDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractDestOut" composition equation. + /// + public static PixelBlender SubtractDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenDestOut" composition equation. + /// + public static PixelBlender ScreenDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenDestOut" composition equation. + /// + public static PixelBlender DarkenDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenDestOut" composition equation. + /// + public static PixelBlender LightenDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayDestOut" composition equation. + /// + public static PixelBlender OverlayDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightDestOut" composition equation. + /// + public static PixelBlender HardLightDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeDestOut" composition equation. + /// + public static PixelBlender ColorDodgeDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnDestOut" composition equation. + /// + public static PixelBlender ColorBurnDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightDestOut" composition equation. + /// + public static PixelBlender SoftLightDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceDestOut" composition equation. + /// + public static PixelBlender DifferenceDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionDestOut" composition equation. + /// + public static PixelBlender ExclusionDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueDestOut" composition equation. + /// + public static PixelBlender HueDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationDestOut" composition equation. + /// + public static PixelBlender SaturationDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDestOut" composition equation. + /// + public static PixelBlender ColorDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityDestOut" composition equation. + /// + public static PixelBlender LuminosityDestOut => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalClear" composition equation. + /// + public static PixelBlender NormalClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyClear" composition equation. + /// + public static PixelBlender MultiplyClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddClear" composition equation. + /// + public static PixelBlender AddClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractClear" composition equation. + /// + public static PixelBlender SubtractClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenClear" composition equation. + /// + public static PixelBlender ScreenClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenClear" composition equation. + /// + public static PixelBlender DarkenClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenClear" composition equation. + /// + public static PixelBlender LightenClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayClear" composition equation. + /// + public static PixelBlender OverlayClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightClear" composition equation. + /// + public static PixelBlender HardLightClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeClear" composition equation. + /// + public static PixelBlender ColorDodgeClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnClear" composition equation. + /// + public static PixelBlender ColorBurnClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightClear" composition equation. + /// + public static PixelBlender SoftLightClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceClear" composition equation. + /// + public static PixelBlender DifferenceClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionClear" composition equation. + /// + public static PixelBlender ExclusionClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueClear" composition equation. + /// + public static PixelBlender HueClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationClear" composition equation. + /// + public static PixelBlender SaturationClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorClear" composition equation. + /// + public static PixelBlender ColorClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityClear" composition equation. + /// + public static PixelBlender LuminosityClear => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalXor" composition equation. + /// + public static PixelBlender NormalXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyXor" composition equation. + /// + public static PixelBlender MultiplyXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddXor" composition equation. + /// + public static PixelBlender AddXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractXor" composition equation. + /// + public static PixelBlender SubtractXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenXor" composition equation. + /// + public static PixelBlender ScreenXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenXor" composition equation. + /// + public static PixelBlender DarkenXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenXor" composition equation. + /// + public static PixelBlender LightenXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayXor" composition equation. + /// + public static PixelBlender OverlayXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightXor" composition equation. + /// + public static PixelBlender HardLightXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgeXor" composition equation. + /// + public static PixelBlender ColorDodgeXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnXor" composition equation. + /// + public static PixelBlender ColorBurnXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightXor" composition equation. + /// + public static PixelBlender SoftLightXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferenceXor" composition equation. + /// + public static PixelBlender DifferenceXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionXor" composition equation. + /// + public static PixelBlender ExclusionXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HueXor" composition equation. + /// + public static PixelBlender HueXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationXor" composition equation. + /// + public static PixelBlender SaturationXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorXor" composition equation. + /// + public static PixelBlender ColorXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityXor" composition equation. + /// + public static PixelBlender LuminosityXor => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "NormalPlus" composition equation. + /// + public static PixelBlender NormalPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "MultiplyPlus" composition equation. + /// + public static PixelBlender MultiplyPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "AddPlus" composition equation. + /// + public static PixelBlender AddPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SubtractPlus" composition equation. + /// + public static PixelBlender SubtractPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ScreenPlus" composition equation. + /// + public static PixelBlender ScreenPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DarkenPlus" composition equation. + /// + public static PixelBlender DarkenPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LightenPlus" composition equation. + /// + public static PixelBlender LightenPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "OverlayPlus" composition equation. + /// + public static PixelBlender OverlayPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HardLightPlus" composition equation. + /// + public static PixelBlender HardLightPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorDodgePlus" composition equation. + /// + public static PixelBlender ColorDodgePlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorBurnPlus" composition equation. + /// + public static PixelBlender ColorBurnPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SoftLightPlus" composition equation. + /// + public static PixelBlender SoftLightPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "DifferencePlus" composition equation. + /// + public static PixelBlender DifferencePlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ExclusionPlus" composition equation. + /// + public static PixelBlender ExclusionPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "HuePlus" composition equation. + /// + public static PixelBlender HuePlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "SaturationPlus" composition equation. + /// + public static PixelBlender SaturationPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "ColorPlus" composition equation. + /// + public static PixelBlender ColorPlus => PixelBlender.Instance; + + /// + /// Gets the shared blender for the "LuminosityPlus" composition equation. + /// + public static PixelBlender LuminosityPlus => PixelBlender.Instance; + } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt index 5b71bb0a6..8dc9fe564 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt @@ -4,39 +4,19 @@ #> <#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. // using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; -/// -/// Collection of Porter Duff alpha blending functions applying different composition models. -/// -/// -/// These functions are designed to be a general solution for all color cases, -/// that is, they take in account the alpha value of both the backdrop -/// and source, and there's no need to alpha-premultiply neither the backdrop -/// nor the source. -/// Note there are faster functions for when the backdrop color is known -/// to be opaque -/// -internal static class DefaultPixelBlenders - where TPixel : unmanaged, IPixel -{ - <# -var composers = new []{ +var composers = new [] +{ "Src", "SrcAtop", "SrcOver", @@ -49,9 +29,11 @@ var composers = new []{ "DestOut", "Clear", "Xor", + "Plus", }; -var blenders = new []{ +var blenders = new [] +{ "Normal", "Multiply", "Add", @@ -60,120 +42,78 @@ var blenders = new []{ "Darken", "Lighten", "Overlay", - "HardLight" + "HardLight", + "ColorDodge", + "ColorBurn", + "SoftLight", + "Difference", + "Exclusion", + "Hue", + "Saturation", + "Color", + "Luminosity", }; - - foreach(var composer in composers) { - foreach(var blender in blenders) { - - var blender_composer= $"{blender}{composer}"; +#> +/// +/// Provides the straight-alpha equations consumed by the shared pixel-blending traversal. +/// +internal static class DefaultPixelBlenderOperators +{ +<# +foreach (var composer in composers) +{ + foreach (var blender in blenders) + { + var blenderComposer = $"{blender}{composer}"; #> /// - /// A pixel blender that implements the "<#= blender_composer#>" composition equation. + /// Applies the "<#= blenderComposer #>" straight-alpha composition equation. /// - public class <#= blender_composer#> : PixelBlender + public readonly struct <#= blenderComposer #> : IPixelBlenderOperator { - /// - /// Gets the static instance of this blender. - /// - public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>(); - /// - public override TPixel Blend(TPixel background, TPixel source, float amount) - { - return TPixel.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1))); - } + public static bool IsAssociatedAlpha => false; /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) - { - amount = Numerics.Clamp(amount, 0, 1); - - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); - - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - Vector256 opacity = Vector256.Create(amount); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount); - } - } - } + public static Vector4 Invoke(Vector4 background, Vector4 source, float amount) + => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount); /// - protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) - { - if (Avx2.IsSupported && destination.Length >= 2) - { - // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 - ref Vector256 destinationBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u); + public static Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount) + => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount); - ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background)); - ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref float amountBase = ref MemoryMarshal.GetReference(amount); - - Vector256 vOne = Vector256.Create(1F); - - while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast)) - { - // We need to create a Vector256 containing the current and next amount values - // taking up each half of the Vector256 and then clamp them. - Vector256 opacity = Vector256.Create( - Vector128.Create(amountBase), - Vector128.Create(Unsafe.Add(ref amountBase, 1))); - opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne); - - destinationBase = PorterDuffFunctions.<#=blender_composer#>(backgroundBase, sourceBase, opacity); - destinationBase = ref Unsafe.Add(ref destinationBase, 1); - backgroundBase = ref Unsafe.Add(ref backgroundBase, 1); - sourceBase = ref Unsafe.Add(ref sourceBase, 1); - amountBase = ref Unsafe.Add(ref amountBase, 2); - } - - if (Numerics.Modulo2(destination.Length) != 0) - { - // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. - int i = destination.Length - 1; - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - else - { - for (int i = 0; i < destination.Length; i++) - { - destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F)); - } - } - } + /// + public static Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount) + => PorterDuffFunctions.<#= blenderComposer #>(background, source, amount); } <# } } +#> +} + +/// +/// Provides straight-alpha pixel blenders for each color and alpha composition pair. +/// +/// The destination pixel format. +internal static class DefaultPixelBlenders + where TPixel : unmanaged, IPixel +{ +<# +foreach (var composer in composers) +{ + foreach (var blender in blenders) + { + var blenderComposer = $"{blender}{composer}"; +#> + /// + /// Gets the shared blender for the "<#= blenderComposer #>" composition equation. + /// + public static PixelBlender <#= blenderComposer #> => PixelBlender>.Instance; +<# + } +} #> } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs b/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs new file mode 100644 index 000000000..4b06e9523 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/IPixelBlenderOperator.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Defines a Porter-Duff equation that can be applied by the shared pixel-blending traversal. +/// +internal interface IPixelBlenderOperator +{ + /// + /// Gets a value indicating whether pixel conversion uses associated-alpha vectors. + /// + public static abstract bool IsAssociatedAlpha { get; } + + /// + /// Blends one pixel represented by four RGBA lanes. + /// + /// The background RGBA lanes. + /// The source RGBA lanes. + /// The source opacity in the range 0 through 1. + /// The blended RGBA lanes. + public static abstract Vector4 Invoke(Vector4 background, Vector4 source, float amount); + + /// + /// Blends two pixels represented by two consecutive groups of four RGBA lanes. + /// + /// The background RGBA lanes. + /// The source RGBA lanes. + /// The source opacity repeated across each pixel's four lanes. + /// The blended RGBA lanes. + public static abstract Vector256 Invoke(Vector256 background, Vector256 source, Vector256 amount); + + /// + /// Blends four pixels represented by four consecutive groups of four RGBA lanes. + /// + /// The background RGBA lanes. + /// The source RGBA lanes. + /// The source opacity repeated across each pixel's four lanes. + /// The blended RGBA lanes. + public static abstract Vector512 Invoke(Vector512 background, Vector512 source, Vector512 amount); +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs new file mode 100644 index 000000000..d2b26db80 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PixelBlender{TPixel,TOperator}.cs @@ -0,0 +1,614 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; + +/// +/// Applies a statically selected Porter-Duff equation across pixel-vector rows. +/// +/// The destination pixel format. +/// The Porter-Duff equation. +internal sealed class PixelBlender : PixelBlender + where TPixel : unmanaged, IPixel + where TOperator : struct, IPixelBlenderOperator +{ + /// + /// Initializes a new instance of the class. + /// + private PixelBlender() + { + } + + /// + /// Gets the shared blender instance for this exact pixel and equation combination. + /// + // Keeping the singleton on the already-required closed blender type preserves per-mode lazy initialization + // without introducing another NativeAOT holder type or eagerly allocating every blender for a pixel format. + public static PixelBlender Instance { get; } = new PixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + // The operator type uniquely determines the alpha representation. NativeAOT therefore needs only the + // pixel/operator closure, while the JIT can remove this static choice from the generated hot path. + Vector4 backgroundVector = TOperator.IsAssociatedAlpha + ? background.ToAssociatedScaledVector4() + : background.ToUnassociatedScaledVector4(); + Vector4 sourceVector = TOperator.IsAssociatedAlpha + ? source.ToAssociatedScaledVector4() + : source.ToUnassociatedScaledVector4(); + + Vector4 result = TOperator.Invoke(backgroundVector, sourceVector, Numerics.Clamp(amount, 0, 1F)); + + return TOperator.IsAssociatedAlpha + ? TPixel.FromAssociatedScaledVector4(result) + : TPixel.FromUnassociatedScaledVector4(result); + } + + /// + protected sealed override void ToBlendVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + { + PixelConversionModifiers modifiers = TOperator.IsAssociatedAlpha + ? PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply + : PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply; + + PixelOperations.Instance.ToVector4(configuration, source, destination, modifiers); + } + + /// + protected sealed override Vector4 ToBlendVector4(TPixel source) + => TOperator.IsAssociatedAlpha + ? source.ToAssociatedScaledVector4() + : source.ToUnassociatedScaledVector4(); + + /// + protected sealed override void FromBlendVector4( + Configuration configuration, + Span source, + Span destination) + { + PixelConversionModifiers modifiers = TOperator.IsAssociatedAlpha + ? PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply + : PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply; + + PixelOperations.Instance.FromVector4Destructive(configuration, source, destination, modifiers); + } + + /// + protected sealed override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount) + { + // Public entry points validate the row lengths, so all three references can advance in lockstep. + int scalarStart = 0; + amount = Numerics.Clamp(amount, 0, 1F); + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); + + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) + { + // A 512-bit register holds four complete RGBA pixels in [R,G,B,A] groups. + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector512 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 4; + Vector512 amountVector = Vector512.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); + } + + scalarStart = vectorCount * 4; + } + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) + { + // A 256-bit register holds two complete RGBA pixels in [R,G,B,A] groups. + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector256 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 2; + Vector256 amountVector = Vector256.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); + } + + scalarStart = vectorCount * 2; + } + + // Vector4 is both the scalar pixel representation and the portable SIMD fallback. + for (int i = scalarStart; i < destination.Length; i++) + { + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), Unsafe.Add(ref sourceRef, (uint)i), amount); + } + } + + /// + protected sealed override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount) + { + // Public entry points validate the row lengths, so the destination and background advance together. + int scalarStart = 0; + amount = Numerics.Clamp(amount, 0, 1F); + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) + { + // Repeat one [R,G,B,A] source group four times to match the four background pixels. + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 4; + Vector512 sourceVector = CreateVector512(source); + Vector512 amountVector = Vector512.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); + } + + scalarStart = vectorCount * 4; + } + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) + { + // Repeat one [R,G,B,A] source group twice to match the two background pixels. + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 2; + Vector256 sourceVector = CreateVector256(source); + Vector256 amountVector = Vector256.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); + } + + scalarStart = vectorCount * 2; + } + + // The remaining pixel count is at most three after AVX-512 or one after AVX2. + for (int i = scalarStart; i < destination.Length; i++) + { + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), source, amount); + } + } + + /// + protected sealed override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount) + { + // Each amount belongs to one pixel and must be repeated across that pixel's four RGBA lanes. + int scalarStart = 0; + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); + ref float amountRef = ref MemoryMarshal.GetReference(amount); + + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector512 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 4; + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + // Four scalar amounts become four contiguous [a,a,a,a] groups before clamping. + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); + Vector512 amountVector = CreateClampedVector512(ref amountBase); + + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); + } + + scalarStart = vectorCount * 4; + } + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector256 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 2; + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + // Two scalar amounts become two contiguous [a,a,a,a] groups before clamping. + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); + Vector256 amountVector = CreateClampedVector256(ref amountBase); + + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), Unsafe.Add(ref sourceBase, i), amountVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), Unsafe.Add(ref sourceRef, (uint)i), Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + } + } + + /// + protected sealed override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount) + { + // The source is invariant, while each background pixel has its own independently clamped amount. + int scalarStart = 0; + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref float amountRef = ref MemoryMarshal.GetReference(amount); + + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 4; + Vector512 sourceVector = CreateVector512(source); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); + Vector512 amountVector = CreateClampedVector512(ref amountBase); + + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); + } + + scalarStart = vectorCount * 4; + } + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 2; + Vector256 sourceVector = CreateVector256(source); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); + Vector256 amountVector = CreateClampedVector256(ref amountBase); + + Unsafe.Add(ref destinationBase, i) = TOperator.Invoke(Unsafe.Add(ref backgroundBase, i), sourceVector, amountVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.Invoke(Unsafe.Add(ref backgroundRef, (uint)i), source, Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + } + } + + /// + protected sealed override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount, ReadOnlySpan coverage) + { + // Coverage mixes the composed result back toward the original background, so it is fused into this pass. + int scalarStart = 0; + amount = Numerics.Clamp(amount, 0, 1F); + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); + ref float coverageRef = ref MemoryMarshal.GetReference(coverage); + + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector512 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 4; + Vector512 amountVector = Vector512.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector512 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); + Vector512 coverageVector = CreateClampedVector512(ref coverageBase); + Vector512 blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); + + Unsafe.Add(ref destinationBase, i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector) + : PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); + } + + scalarStart = vectorCount * 4; + } + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector256 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 2; + Vector256 amountVector = Vector256.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector256 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); + Vector256 coverageVector = CreateClampedVector256(ref coverageBase); + Vector256 blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); + + Unsafe.Add(ref destinationBase, i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector) + : PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); + Vector4 blended = TOperator.Invoke(backgroundPixel, Unsafe.Add(ref sourceRef, (uint)i), amount); + + float clampedCoverage = Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F); + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, clampedCoverage) + : PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, clampedCoverage); + } + } + + /// + protected sealed override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount, ReadOnlySpan coverage) + { + // The constant source is expanded once per selected width and reused for the complete row. + int scalarStart = 0; + amount = Numerics.Clamp(amount, 0, 1F); + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref float coverageRef = ref MemoryMarshal.GetReference(coverage); + + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 4; + Vector512 sourceVector = CreateVector512(source); + Vector512 amountVector = Vector512.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector512 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); + Vector512 coverageVector = CreateClampedVector512(ref coverageBase); + Vector512 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); + + Unsafe.Add(ref destinationBase, i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector) + : PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); + } + + scalarStart = vectorCount * 4; + } + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 2; + Vector256 sourceVector = CreateVector256(source); + Vector256 amountVector = Vector256.Create(amount); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector256 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); + Vector256 coverageVector = CreateClampedVector256(ref coverageBase); + Vector256 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); + + Unsafe.Add(ref destinationBase, i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector) + : PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); + Vector4 blended = TOperator.Invoke(backgroundPixel, source, amount); + + float clampedCoverage = Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F); + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, clampedCoverage) + : PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, clampedCoverage); + } + } + + /// + protected sealed override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + // Amount controls composition while coverage controls the final mix with the untouched background. + int scalarStart = 0; + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref Vector4 sourceRef = ref MemoryMarshal.GetReference(source); + ref float amountRef = ref MemoryMarshal.GetReference(amount); + ref float coverageRef = ref MemoryMarshal.GetReference(coverage); + + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector512 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 4; + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector512 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); + Vector512 amountVector = CreateClampedVector512(ref amountBase); + Vector512 coverageVector = CreateClampedVector512(ref coverageBase); + Vector512 blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); + + Unsafe.Add(ref destinationBase, i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector) + : PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); + } + + scalarStart = vectorCount * 4; + } + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + ref Vector256 sourceBase = ref Unsafe.As>(ref sourceRef); + int vectorCount = destination.Length / 2; + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector256 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); + Vector256 amountVector = CreateClampedVector256(ref amountBase); + Vector256 coverageVector = CreateClampedVector256(ref coverageBase); + Vector256 blended = TOperator.Invoke(backgroundVector, Unsafe.Add(ref sourceBase, i), amountVector); + + Unsafe.Add(ref destinationBase, i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector) + : PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); + Vector4 blended = TOperator.Invoke(backgroundPixel, Unsafe.Add(ref sourceRef, (uint)i), Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + + float clampedCoverage = Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F); + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, clampedCoverage) + : PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, clampedCoverage); + } + } + + /// + protected sealed override void BlendWithCoverageFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount, ReadOnlySpan coverage) + { + // The invariant source is expanded once; only amount and coverage are gathered for each vector batch. + int scalarStart = 0; + + ref Vector4 destinationRef = ref MemoryMarshal.GetReference(destination); + ref Vector4 backgroundRef = ref MemoryMarshal.GetReference(background); + ref float amountRef = ref MemoryMarshal.GetReference(amount); + ref float coverageRef = ref MemoryMarshal.GetReference(coverage); + + if (Vector512.IsHardwareAccelerated && destination.Length >= 4) + { + ref Vector512 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector512 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 4; + Vector512 sourceVector = CreateVector512(source); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector512 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 4); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 4); + Vector512 amountVector = CreateClampedVector512(ref amountBase); + Vector512 coverageVector = CreateClampedVector512(ref coverageBase); + Vector512 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); + + Unsafe.Add(ref destinationBase, i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector) + : PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); + } + + scalarStart = vectorCount * 4; + } + else if (Vector256.IsHardwareAccelerated && destination.Length >= 2) + { + ref Vector256 destinationBase = ref Unsafe.As>(ref destinationRef); + ref Vector256 backgroundBase = ref Unsafe.As>(ref backgroundRef); + int vectorCount = destination.Length / 2; + Vector256 sourceVector = CreateVector256(source); + + for (nuint i = 0; i < (uint)vectorCount; i++) + { + ref Vector256 backgroundVector = ref Unsafe.Add(ref backgroundBase, i); + ref float amountBase = ref Unsafe.Add(ref amountRef, i * 2); + ref float coverageBase = ref Unsafe.Add(ref coverageRef, i * 2); + Vector256 amountVector = CreateClampedVector256(ref amountBase); + Vector256 coverageVector = CreateClampedVector256(ref coverageBase); + Vector256 blended = TOperator.Invoke(backgroundVector, sourceVector, amountVector); + + Unsafe.Add(ref destinationBase, i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector) + : PorterDuffFunctions.BlendWithCoverage(backgroundVector, blended, coverageVector); + } + + scalarStart = vectorCount * 2; + } + + for (int i = scalarStart; i < destination.Length; i++) + { + Vector4 backgroundPixel = Unsafe.Add(ref backgroundRef, (uint)i); + Vector4 blended = TOperator.Invoke(backgroundPixel, source, Numerics.Clamp(Unsafe.Add(ref amountRef, (uint)i), 0, 1F)); + + float clampedCoverage = Numerics.Clamp(Unsafe.Add(ref coverageRef, (uint)i), 0, 1F); + Unsafe.Add(ref destinationRef, (uint)i) = TOperator.IsAssociatedAlpha + ? AssociatedAlphaPorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, clampedCoverage) + : PorterDuffFunctions.BlendWithCoverage(backgroundPixel, blended, clampedCoverage); + } + } + + /// + /// Repeats one RGBA pixel twice to fill a 256-bit register. + /// + /// The pixel represented by ordered RGBA lanes. + /// Two consecutive copies of the pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 CreateVector256(Vector4 pixel) + => Vector256.Create(pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W); + + /// + /// Repeats one RGBA pixel four times to fill a 512-bit register. + /// + /// The pixel represented by ordered RGBA lanes. + /// Four consecutive copies of the pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 CreateVector512(Vector4 pixel) + => Vector512.Create(pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W, pixel.X, pixel.Y, pixel.Z, pixel.W); + + /// + /// Expands and clamps two per-pixel scalar values for two packed RGBA pixels. + /// + /// The first of two contiguous scalar values. + /// Two consecutive groups containing one clamped value in each group's four lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 CreateClampedVector256(ref float values) + { + Vector256 result = Vector256.Create(Vector128.Create(values), Vector128.Create(Unsafe.Add(ref values, 1))); + + // Amount and coverage share the same public 0..1 contract and therefore the same packed clamp. + return Vector256.Min(Vector256.Max(Vector256.Zero, result), Vector256.Create(1F)); + } + + /// + /// Expands and clamps four per-pixel scalar values for four packed RGBA pixels. + /// + /// The first of four contiguous scalar values. + /// Four consecutive groups containing one clamped value in each group's four lanes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 CreateClampedVector512(ref float values) + { + Vector512 result = Vector512.Create(values, values, values, values, Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 1), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 2), Unsafe.Add(ref values, 3), Unsafe.Add(ref values, 3), Unsafe.Add(ref values, 3), Unsafe.Add(ref values, 3)); + + // Amount and coverage share the same public 0..1 contract and therefore the same packed clamp. + return Vector512.Min(Vector512.Max(Vector512.Zero, result), Vector512.Create(1F)); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs index 255bafc79..138249fbd 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs @@ -37,7 +37,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "NormalSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); /// /// Returns the result of the "NormalSrcAtop" compositing equation. @@ -64,7 +75,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Normal(backdrop, source)); + } + + /// + /// Returns the result of the "NormalSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(backdrop, source, Normal(backdrop, source)); } @@ -94,7 +120,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Normal(backdrop, source)); + } + + /// + /// Returns the result of the "NormalSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(backdrop, source, Normal(backdrop, source)); } @@ -123,7 +164,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "NormalSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "NormalSrcOut" compositing equation. @@ -149,7 +201,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "NormalSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "NormalDest" compositing equation. @@ -177,6 +240,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "NormalDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "NormalDestAtop" compositing equation. /// @@ -202,7 +278,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Normal(source, backdrop)); + } + + /// + /// Returns the result of the "NormalDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, Normal(source, backdrop)); } @@ -232,7 +323,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Normal(source, backdrop)); + } + + /// + /// Returns the result of the "NormalDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(source, backdrop, Normal(source, backdrop)); } @@ -261,7 +367,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "NormalDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "NormalDestOut" compositing equation. @@ -287,7 +404,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "NormalDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "NormalXor" compositing equation. @@ -313,7 +441,63 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "NormalXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "NormalPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Normal(backdrop, source)); + } + + /// + /// Returns the result of the "NormalPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 NormalPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Normal(backdrop, source)); + } + + /// + /// Returns the result of the "NormalPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Normal(backdrop, source)); + } /// /// Returns the result of the "NormalClear" compositing equation. @@ -339,7 +523,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 NormalClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "NormalClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 NormalClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// @@ -355,7 +550,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -371,7 +566,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -387,7 +582,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -403,7 +598,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -419,7 +614,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -435,7 +630,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -451,7 +646,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -467,7 +662,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -483,7 +678,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -499,7 +694,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -515,7 +710,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -531,7 +726,23 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(NormalXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(NormalXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "NormalPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(NormalPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -558,7 +769,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "MultiplySrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); /// /// Returns the result of the "MultiplySrcAtop" compositing equation. @@ -585,7 +807,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the result of the "MultiplySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(backdrop, source, Multiply(backdrop, source)); } @@ -615,7 +852,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the result of the "MultiplySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(backdrop, source, Multiply(backdrop, source)); } @@ -644,7 +896,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "MultiplySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "MultiplySrcOut" compositing equation. @@ -670,7 +933,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "MultiplySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplySrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "MultiplyDest" compositing equation. @@ -698,6 +972,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "MultiplyDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "MultiplyDestAtop" compositing equation. /// @@ -723,7 +1010,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the result of the "MultiplyDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, Multiply(source, backdrop)); } @@ -753,7 +1055,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Multiply(source, backdrop)); + } + + /// + /// Returns the result of the "MultiplyDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(source, backdrop, Multiply(source, backdrop)); } @@ -782,7 +1099,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "MultiplyDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "MultiplyDestOut" compositing equation. @@ -808,17 +1136,28 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); /// - /// Returns the result of the "MultiplyXor" compositing equation. + /// Returns the result of the "MultiplyDestOut" compositing equation. /// /// The backdrop vector. /// The source vector. /// The source opacity. Range 0..1 - /// The . + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) + public static Vector512 MultiplyDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "MultiplyXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyXor(Vector4 backdrop, Vector4 source, float opacity) { source = Numerics.WithW(source, source * opacity); @@ -834,7 +1173,63 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "MultiplyXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "MultiplyPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the result of the "MultiplyPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 MultiplyPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Multiply(backdrop, source)); + } + + /// + /// Returns the result of the "MultiplyPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Multiply(backdrop, source)); + } /// /// Returns the result of the "MultiplyClear" compositing equation. @@ -860,7 +1255,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 MultiplyClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "MultiplyClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 MultiplyClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// @@ -876,7 +1282,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplySrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -892,7 +1298,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplySrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -908,7 +1314,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplySrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -924,7 +1330,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplySrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -940,7 +1346,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplySrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -956,7 +1362,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -972,7 +1378,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -988,7 +1394,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1004,7 +1410,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1020,7 +1426,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1036,7 +1442,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1052,7 +1458,23 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(MultiplyXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(MultiplyXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "MultiplyPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(MultiplyPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1079,7 +1501,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "AddSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); /// /// Returns the result of the "AddSrcAtop" compositing equation. @@ -1106,7 +1539,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the result of the "AddSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(backdrop, source, Add(backdrop, source)); } @@ -1136,7 +1584,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the result of the "AddSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(backdrop, source, Add(backdrop, source)); } @@ -1165,7 +1628,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "AddSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "AddSrcOut" compositing equation. @@ -1191,7 +1665,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "AddSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "AddDest" compositing equation. @@ -1219,6 +1704,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "AddDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "AddDestAtop" compositing equation. /// @@ -1244,7 +1742,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the result of the "AddDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, Add(source, backdrop)); } @@ -1274,7 +1787,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Add(source, backdrop)); + } + + /// + /// Returns the result of the "AddDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(source, backdrop, Add(source, backdrop)); } @@ -1303,7 +1831,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "AddDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "AddDestOut" compositing equation. @@ -1329,7 +1868,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "AddDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "AddXor" compositing equation. @@ -1355,7 +1905,63 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "AddXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "AddPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the result of the "AddPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 AddPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Add(backdrop, source)); + } + + /// + /// Returns the result of the "AddPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Add(backdrop, source)); + } /// /// Returns the result of the "AddClear" compositing equation. @@ -1381,7 +1987,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 AddClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "AddClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 AddClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// @@ -1397,7 +2014,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1413,7 +2030,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1429,7 +2046,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1445,7 +2062,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1461,7 +2078,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1477,7 +2094,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1493,7 +2110,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1509,7 +2126,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1525,7 +2142,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1541,7 +2158,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1557,7 +2174,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1573,7 +2190,23 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(AddXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(AddXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "AddPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(AddPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1600,7 +2233,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "SubtractSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); /// /// Returns the result of the "SubtractSrcAtop" compositing equation. @@ -1627,7 +2271,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the result of the "SubtractSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(backdrop, source, Subtract(backdrop, source)); } @@ -1657,18 +2316,33 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); return Over(backdrop, source, Subtract(backdrop, source)); } /// - /// Returns the result of the "SubtractSrcIn" compositing equation. + /// Returns the result of the "SubtractSrcOver" compositing equation. /// /// The backdrop vector. /// The source vector. /// The source opacity. Range 0..1 - /// The . + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the result of the "SubtractSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 SubtractSrcIn(Vector4 backdrop, Vector4 source, float opacity) { @@ -1686,7 +2360,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SubtractSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "SubtractSrcOut" compositing equation. @@ -1712,7 +2397,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SubtractSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "SubtractDest" compositing equation. @@ -1740,6 +2436,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "SubtractDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "SubtractDestAtop" compositing equation. /// @@ -1765,7 +2474,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the result of the "SubtractDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, Subtract(source, backdrop)); } @@ -1795,7 +2519,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Subtract(source, backdrop)); + } + + /// + /// Returns the result of the "SubtractDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(source, backdrop, Subtract(source, backdrop)); } @@ -1824,7 +2563,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "SubtractDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "SubtractDestOut" compositing equation. @@ -1850,7 +2600,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "SubtractDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "SubtractXor" compositing equation. @@ -1876,7 +2637,63 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SubtractXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "SubtractPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubtractPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the result of the "SubtractPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SubtractPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Subtract(backdrop, source)); + } + + /// + /// Returns the result of the "SubtractPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Subtract(backdrop, source)); + } /// /// Returns the result of the "SubtractClear" compositing equation. @@ -1902,7 +2719,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 SubtractClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SubtractClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SubtractClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// @@ -1918,7 +2746,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1934,7 +2762,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1950,7 +2778,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1966,7 +2794,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1982,7 +2810,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -1998,7 +2826,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2014,7 +2842,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2030,7 +2858,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2046,7 +2874,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2062,7 +2890,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2078,7 +2906,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2094,7 +2922,23 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(SubtractXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(SubtractXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SubtractPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubtractPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SubtractPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2121,7 +2965,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "ScreenSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); /// /// Returns the result of the "ScreenSrcAtop" compositing equation. @@ -2148,7 +3003,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the result of the "ScreenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(backdrop, source, Screen(backdrop, source)); } @@ -2178,7 +3048,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the result of the "ScreenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(backdrop, source, Screen(backdrop, source)); } @@ -2207,7 +3092,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ScreenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "ScreenSrcOut" compositing equation. @@ -2233,7 +3129,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ScreenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "ScreenDest" compositing equation. @@ -2261,6 +3168,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "ScreenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "ScreenDestAtop" compositing equation. /// @@ -2286,7 +3206,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the result of the "ScreenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, Screen(source, backdrop)); } @@ -2316,7 +3251,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Screen(source, backdrop)); + } + + /// + /// Returns the result of the "ScreenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(source, backdrop, Screen(source, backdrop)); } @@ -2345,7 +3295,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "ScreenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "ScreenDestOut" compositing equation. @@ -2371,7 +3332,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "ScreenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "ScreenXor" compositing equation. @@ -2397,33 +3369,100 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 ScreenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// - /// Returns the result of the "ScreenClear" compositing equation. + /// Returns the result of the "ScreenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ScreenPlus" compositing equation. /// /// The backdrop vector. /// The source vector. /// The source opacity. Range 0..1 /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 ScreenPlus(Vector4 backdrop, Vector4 source, float opacity) { source = Numerics.WithW(source, source * opacity); - return Clear(backdrop, source); + return Plus(backdrop, source, Screen(backdrop, source)); } /// - /// Returns the result of the "ScreenClear" compositing equation. + /// Returns the result of the "ScreenPlus" compositing equation. /// /// The backdrop vector. /// The source vector. /// The source opacity. Range 0..1 /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 ScreenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + public static Vector256 ScreenPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the result of the "ScreenPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Screen(backdrop, source)); + } + + /// + /// Returns the result of the "ScreenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "ScreenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ScreenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ScreenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ScreenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// @@ -2439,7 +3478,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2455,7 +3494,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2471,7 +3510,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2487,7 +3526,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2503,7 +3542,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2519,7 +3558,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2535,7 +3574,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2551,7 +3590,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2567,7 +3606,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2583,7 +3622,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2599,7 +3638,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2615,7 +3654,23 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(ScreenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(ScreenXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ScreenPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ScreenPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2642,7 +3697,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "DarkenSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); /// /// Returns the result of the "DarkenSrcAtop" compositing equation. @@ -2669,7 +3735,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the result of the "DarkenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(backdrop, source, Darken(backdrop, source)); } @@ -2699,7 +3780,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the result of the "DarkenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(backdrop, source, Darken(backdrop, source)); } @@ -2728,7 +3824,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "DarkenSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "DarkenSrcOut" compositing equation. @@ -2754,7 +3861,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "DarkenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "DarkenDest" compositing equation. @@ -2782,6 +3900,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "DarkenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "DarkenDestAtop" compositing equation. /// @@ -2807,7 +3938,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the result of the "DarkenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, Darken(source, backdrop)); } @@ -2837,7 +3983,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Darken(source, backdrop)); + } + + /// + /// Returns the result of the "DarkenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(source, backdrop, Darken(source, backdrop)); } @@ -2866,7 +4027,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "DarkenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "DarkenDestOut" compositing equation. @@ -2892,7 +4064,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "DarkenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "DarkenXor" compositing equation. @@ -2918,7 +4101,63 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "DarkenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "DarkenPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the result of the "DarkenPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DarkenPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Darken(backdrop, source)); + } + + /// + /// Returns the result of the "DarkenPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Darken(backdrop, source)); + } /// /// Returns the result of the "DarkenClear" compositing equation. @@ -2944,7 +4183,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 DarkenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "DarkenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DarkenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// @@ -2960,7 +4210,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2976,7 +4226,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -2992,7 +4242,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3008,7 +4258,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3024,7 +4274,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3040,7 +4290,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3056,7 +4306,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3072,7 +4322,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3088,7 +4338,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3104,7 +4354,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3120,7 +4370,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3136,7 +4386,23 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(DarkenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(DarkenXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DarkenPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DarkenPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3163,7 +4429,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "LightenSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); /// /// Returns the result of the "LightenSrcAtop" compositing equation. @@ -3190,7 +4467,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the result of the "LightenSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(backdrop, source, Lighten(backdrop, source)); } @@ -3220,7 +4512,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the result of the "LightenSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(backdrop, source, Lighten(backdrop, source)); } @@ -3249,19 +4556,30 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// - /// Returns the result of the "LightenSrcOut" compositing equation. + /// Returns the result of the "LightenSrcIn" compositing equation. /// /// The backdrop vector. /// The source vector. /// The source opacity. Range 0..1 - /// The . + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) - { - source = Numerics.WithW(source, source * opacity); + public static Vector512 LightenSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "LightenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); return Out(backdrop, source); } @@ -3275,7 +4593,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "LightenSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "LightenDest" compositing equation. @@ -3303,6 +4632,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "LightenDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "LightenDestAtop" compositing equation. /// @@ -3328,7 +4670,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the result of the "LightenDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, Lighten(source, backdrop)); } @@ -3358,7 +4715,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Lighten(source, backdrop)); + } + + /// + /// Returns the result of the "LightenDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(source, backdrop, Lighten(source, backdrop)); } @@ -3387,7 +4759,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "LightenDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "LightenDestOut" compositing equation. @@ -3413,7 +4796,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "LightenDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "LightenXor" compositing equation. @@ -3439,7 +4833,63 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "LightenXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "LightenPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the result of the "LightenPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LightenPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Lighten(backdrop, source)); + } + + /// + /// Returns the result of the "LightenPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Lighten(backdrop, source)); + } /// /// Returns the result of the "LightenClear" compositing equation. @@ -3465,7 +4915,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 LightenClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "LightenClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LightenClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// @@ -3481,7 +4942,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3497,7 +4958,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3513,7 +4974,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3529,7 +4990,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3545,7 +5006,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3561,7 +5022,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3577,7 +5038,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3593,7 +5054,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3609,7 +5070,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3625,7 +5086,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3641,7 +5102,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3657,7 +5118,23 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(LightenXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(LightenXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LightenPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LightenPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -3684,7 +5161,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlaySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "OverlaySrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); /// /// Returns the result of the "OverlaySrcAtop" compositing equation. @@ -3711,7 +5199,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlaySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the result of the "OverlaySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(backdrop, source, Overlay(backdrop, source)); } @@ -3741,7 +5244,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlaySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Overlay(backdrop, source)); + } + + /// + /// Returns the result of the "OverlaySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(backdrop, source, Overlay(backdrop, source)); } @@ -3770,7 +5288,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlaySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "OverlaySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "OverlaySrcOut" compositing equation. @@ -3796,7 +5325,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlaySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "OverlaySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlaySrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "OverlayDest" compositing equation. @@ -3824,6 +5364,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "OverlayDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "OverlayDestAtop" compositing equation. /// @@ -3849,7 +5402,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the result of the "OverlayDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, Overlay(source, backdrop)); } @@ -3879,7 +5447,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Overlay(source, backdrop)); + } + + /// + /// Returns the result of the "OverlayDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(source, backdrop, Overlay(source, backdrop)); } @@ -3908,7 +5491,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "OverlayDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "OverlayDestOut" compositing equation. @@ -3934,7 +5528,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "OverlayDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "OverlayXor" compositing equation. @@ -3960,53 +5565,120 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 OverlayXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); /// - /// Returns the result of the "OverlayClear" compositing equation. + /// Returns the result of the "OverlayXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "OverlayPlus" compositing equation. /// /// The backdrop vector. /// The source vector. /// The source opacity. Range 0..1 /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) + public static Vector4 OverlayPlus(Vector4 backdrop, Vector4 source, float opacity) { source = Numerics.WithW(source, source * opacity); - return Clear(backdrop, source); + return Plus(backdrop, source, Overlay(backdrop, source)); } /// - /// Returns the result of the "OverlayClear" compositing equation. + /// Returns the result of the "OverlayPlus" compositing equation. /// /// The backdrop vector. /// The source vector. /// The source opacity. Range 0..1 /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Vector256 OverlayClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + public static Vector256 OverlayPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + return Plus(backdrop, source, Overlay(backdrop, source)); + } /// - /// Returns the result of the "OverlaySrc" compositing equation. + /// Returns the result of the "OverlayPlus" compositing equation. /// - /// The pixel format. /// The backdrop vector. /// The source vector. /// The source opacity. Range 0..1 - /// The . + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlaySrc(TPixel backdrop, TPixel source, float opacity) - where TPixel : unmanaged, IPixel + public static Vector512 OverlayPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) { - opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Overlay(backdrop, source)); } /// - /// Returns the result of the "OverlaySrcAtop" compositing equation. + /// Returns the result of the "OverlayClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "OverlayClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 OverlayClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "OverlayClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + + /// + /// Returns the result of the "OverlaySrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlaySrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(OverlaySrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "OverlaySrcAtop" compositing equation. /// /// The pixel format. /// The backdrop vector. @@ -4018,7 +5690,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlaySrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4034,7 +5706,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlaySrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4050,7 +5722,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlaySrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4066,7 +5738,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlaySrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlaySrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4082,7 +5754,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4098,7 +5770,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4114,7 +5786,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4130,7 +5802,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4146,7 +5818,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4162,7 +5834,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4178,7 +5850,23 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(OverlayXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(OverlayXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "OverlayPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(OverlayPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4205,7 +5893,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "HardLightSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); /// /// Returns the result of the "HardLightSrcAtop" compositing equation. @@ -4232,7 +5931,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the result of the "HardLightSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(backdrop, source, HardLight(backdrop, source)); } @@ -4262,7 +5976,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the result of the "HardLightSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(backdrop, source, HardLight(backdrop, source)); } @@ -4291,7 +6020,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "HardLightSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "HardLightSrcOut" compositing equation. @@ -4317,7 +6057,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "HardLightSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "HardLightDest" compositing equation. @@ -4345,6 +6096,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "HardLightDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "HardLightDestAtop" compositing equation. /// @@ -4370,7 +6134,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the result of the "HardLightDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, HardLight(source, backdrop)); } @@ -4400,7 +6179,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, HardLight(source, backdrop)); + } + + /// + /// Returns the result of the "HardLightDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(source, backdrop, HardLight(source, backdrop)); } @@ -4429,7 +6223,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "HardLightDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "HardLightDestOut" compositing equation. @@ -4455,7 +6260,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "HardLightDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "HardLightXor" compositing equation. @@ -4481,7 +6297,63 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightXor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "HardLightXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "HardLightPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the result of the "HardLightPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HardLightPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, HardLight(backdrop, source)); + } + + /// + /// Returns the result of the "HardLightPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, HardLight(backdrop, source)); + } /// /// Returns the result of the "HardLightClear" compositing equation. @@ -4507,7 +6379,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 HardLightClear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "HardLightClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLightClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// @@ -4523,7 +6406,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrc(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4539,7 +6422,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4555,7 +6438,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4571,7 +6454,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4587,7 +6470,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightSrcOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4603,7 +6486,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDest(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4619,7 +6502,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestAtop(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4635,7 +6518,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestOver(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4651,7 +6534,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestIn(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4667,7 +6550,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightDestOut(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4683,7 +6566,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightClear(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } /// @@ -4699,6 +6582,6610 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(HardLightXor(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(HardLightXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HardLightPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HardLightPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return source; + } + + /// + /// Returns the result of the "ColorDodgeSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "ColorDodgeSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + /// + /// Returns the result of the "ColorDodgeSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the result of the "ColorDodgeSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the result of the "ColorDodgeSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the result of the "ColorDodgeSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the result of the "ColorDodgeSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the result of the "ColorDodgeSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the result of the "ColorDodgeSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(backdrop, source); + } + + /// + /// Returns the result of the "ColorDodgeSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorDodgeSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ColorDodgeSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "ColorDodgeSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorDodgeSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ColorDodgeDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ColorDodgeDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ColorDodgeDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ColorDodgeDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDodgeDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDodgeDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDodgeDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDodgeDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDodgeDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, ColorDodge(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDodgeDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(source, backdrop); + } + + /// + /// Returns the result of the "ColorDodgeDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "ColorDodgeDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "ColorDodgeDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "ColorDodgeDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "ColorDodgeDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "ColorDodgeXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeXor(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "ColorDodgeXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorDodgeXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ColorDodgePlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgePlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the result of the "ColorDodgePlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgePlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the result of the "ColorDodgePlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgePlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, ColorDodge(backdrop, source)); + } + + /// + /// Returns the result of the "ColorDodgeClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodgeClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "ColorDodgeClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodgeClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorDodgeClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodgeClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + + /// + /// Returns the result of the "ColorDodgeSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgeXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgeXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgeXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDodgePlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDodgePlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDodgePlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return source; + } + + /// + /// Returns the result of the "ColorBurnSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "ColorBurnSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + /// + /// Returns the result of the "ColorBurnSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the result of the "ColorBurnSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the result of the "ColorBurnSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the result of the "ColorBurnSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the result of the "ColorBurnSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the result of the "ColorBurnSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the result of the "ColorBurnSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(backdrop, source); + } + + /// + /// Returns the result of the "ColorBurnSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorBurnSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ColorBurnSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "ColorBurnSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorBurnSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ColorBurnDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ColorBurnDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ColorBurnDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ColorBurnDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the result of the "ColorBurnDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the result of the "ColorBurnDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the result of the "ColorBurnDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the result of the "ColorBurnDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the result of the "ColorBurnDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, ColorBurn(source, backdrop)); + } + + /// + /// Returns the result of the "ColorBurnDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(source, backdrop); + } + + /// + /// Returns the result of the "ColorBurnDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "ColorBurnDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "ColorBurnDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "ColorBurnDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "ColorBurnDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "ColorBurnXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnXor(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "ColorBurnXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorBurnXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ColorBurnPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the result of the "ColorBurnPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the result of the "ColorBurnPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, ColorBurn(backdrop, source)); + } + + /// + /// Returns the result of the "ColorBurnClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurnClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "ColorBurnClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurnClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorBurnClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurnClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + + /// + /// Returns the result of the "ColorBurnSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorBurnPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorBurnPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorBurnPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return source; + } + + /// + /// Returns the result of the "SoftLightSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "SoftLightSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + /// + /// Returns the result of the "SoftLightSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the result of the "SoftLightSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the result of the "SoftLightSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the result of the "SoftLightSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the result of the "SoftLightSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the result of the "SoftLightSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the result of the "SoftLightSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(backdrop, source); + } + + /// + /// Returns the result of the "SoftLightSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SoftLightSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "SoftLightSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "SoftLightSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SoftLightSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "SoftLightDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "SoftLightDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "SoftLightDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "SoftLightDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the result of the "SoftLightDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the result of the "SoftLightDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the result of the "SoftLightDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the result of the "SoftLightDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the result of the "SoftLightDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, SoftLight(source, backdrop)); + } + + /// + /// Returns the result of the "SoftLightDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(source, backdrop); + } + + /// + /// Returns the result of the "SoftLightDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "SoftLightDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "SoftLightDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "SoftLightDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "SoftLightDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "SoftLightXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightXor(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "SoftLightXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SoftLightXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "SoftLightPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the result of the "SoftLightPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the result of the "SoftLightPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, SoftLight(backdrop, source)); + } + + /// + /// Returns the result of the "SoftLightClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLightClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "SoftLightClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLightClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SoftLightClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLightClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + + /// + /// Returns the result of the "SoftLightSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SoftLightPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SoftLightPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SoftLightPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return source; + } + + /// + /// Returns the result of the "DifferenceSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "DifferenceSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + /// + /// Returns the result of the "DifferenceSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the result of the "DifferenceSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the result of the "DifferenceSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the result of the "DifferenceSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the result of the "DifferenceSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the result of the "DifferenceSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the result of the "DifferenceSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(backdrop, source); + } + + /// + /// Returns the result of the "DifferenceSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "DifferenceSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "DifferenceSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "DifferenceSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "DifferenceSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "DifferenceDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "DifferenceDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "DifferenceDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "DifferenceDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the result of the "DifferenceDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the result of the "DifferenceDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the result of the "DifferenceDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the result of the "DifferenceDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the result of the "DifferenceDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Difference(source, backdrop)); + } + + /// + /// Returns the result of the "DifferenceDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(source, backdrop); + } + + /// + /// Returns the result of the "DifferenceDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "DifferenceDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "DifferenceDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "DifferenceDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "DifferenceDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "DifferenceXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceXor(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "DifferenceXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "DifferenceXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "DifferencePlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferencePlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the result of the "DifferencePlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferencePlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the result of the "DifferencePlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferencePlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Difference(backdrop, source)); + } + + /// + /// Returns the result of the "DifferenceClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DifferenceClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "DifferenceClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 DifferenceClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "DifferenceClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 DifferenceClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + + /// + /// Returns the result of the "DifferenceSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferenceXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferenceXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferenceXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "DifferencePlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DifferencePlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(DifferencePlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return source; + } + + /// + /// Returns the result of the "ExclusionSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "ExclusionSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + /// + /// Returns the result of the "ExclusionSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the result of the "ExclusionSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the result of the "ExclusionSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the result of the "ExclusionSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the result of the "ExclusionSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the result of the "ExclusionSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the result of the "ExclusionSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(backdrop, source); + } + + /// + /// Returns the result of the "ExclusionSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ExclusionSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ExclusionSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "ExclusionSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ExclusionSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ExclusionDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ExclusionDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ExclusionDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ExclusionDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the result of the "ExclusionDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the result of the "ExclusionDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the result of the "ExclusionDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the result of the "ExclusionDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the result of the "ExclusionDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Exclusion(source, backdrop)); + } + + /// + /// Returns the result of the "ExclusionDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(source, backdrop); + } + + /// + /// Returns the result of the "ExclusionDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "ExclusionDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "ExclusionDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "ExclusionDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "ExclusionDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "ExclusionXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionXor(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "ExclusionXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ExclusionXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ExclusionPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the result of the "ExclusionPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the result of the "ExclusionPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Exclusion(backdrop, source)); + } + + /// + /// Returns the result of the "ExclusionClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ExclusionClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "ExclusionClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ExclusionClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ExclusionClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ExclusionClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + + /// + /// Returns the result of the "ExclusionSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ExclusionPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ExclusionPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ExclusionPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return source; + } + + /// + /// Returns the result of the "HueSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "HueSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + /// + /// Returns the result of the "HueSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the result of the "HueSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the result of the "HueSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the result of the "HueSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the result of the "HueSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the result of the "HueSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the result of the "HueSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(backdrop, source); + } + + /// + /// Returns the result of the "HueSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "HueSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "HueSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "HueSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "HueSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "HueDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "HueDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "HueDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "HueDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the result of the "HueDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the result of the "HueDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the result of the "HueDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the result of the "HueDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the result of the "HueDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Hue(source, backdrop)); + } + + /// + /// Returns the result of the "HueDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(source, backdrop); + } + + /// + /// Returns the result of the "HueDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "HueDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "HueDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "HueDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "HueDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "HueXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueXor(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "HueXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "HueXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "HuePlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HuePlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the result of the "HuePlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HuePlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the result of the "HuePlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HuePlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Hue(backdrop, source)); + } + + /// + /// Returns the result of the "HueClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HueClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "HueClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 HueClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "HueClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HueClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + + /// + /// Returns the result of the "HueSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HueXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HueXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HueXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "HuePlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HuePlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(HuePlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return source; + } + + /// + /// Returns the result of the "SaturationSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "SaturationSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + /// + /// Returns the result of the "SaturationSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the result of the "SaturationSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the result of the "SaturationSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the result of the "SaturationSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the result of the "SaturationSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the result of the "SaturationSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the result of the "SaturationSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(backdrop, source); + } + + /// + /// Returns the result of the "SaturationSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SaturationSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "SaturationSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "SaturationSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SaturationSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "SaturationDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "SaturationDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "SaturationDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "SaturationDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the result of the "SaturationDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the result of the "SaturationDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the result of the "SaturationDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the result of the "SaturationDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the result of the "SaturationDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Saturation(source, backdrop)); + } + + /// + /// Returns the result of the "SaturationDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(source, backdrop); + } + + /// + /// Returns the result of the "SaturationDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "SaturationDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "SaturationDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "SaturationDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "SaturationDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "SaturationXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationXor(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "SaturationXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SaturationXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "SaturationPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the result of the "SaturationPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the result of the "SaturationPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Saturation(backdrop, source)); + } + + /// + /// Returns the result of the "SaturationClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SaturationClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "SaturationClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SaturationClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "SaturationClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SaturationClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + + /// + /// Returns the result of the "SaturationSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "SaturationPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SaturationPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(SaturationPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorSrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorSrc(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return source; + } + + /// + /// Returns the result of the "ColorSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorSrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "ColorSrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorSrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + /// + /// Returns the result of the "ColorSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorSrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the result of the "ColorSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorSrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the result of the "ColorSrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorSrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the result of the "ColorSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorSrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the result of the "ColorSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorSrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the result of the "ColorSrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorSrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the result of the "ColorSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorSrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(backdrop, source); + } + + /// + /// Returns the result of the "ColorSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorSrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorSrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorSrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ColorSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorSrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "ColorSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorSrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorSrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorSrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ColorDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ColorDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ColorDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "ColorDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Color(source, backdrop)); + } + + /// + /// Returns the result of the "ColorDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(source, backdrop); + } + + /// + /// Returns the result of the "ColorDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "ColorDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "ColorDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "ColorDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "ColorDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "ColorXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorXor(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "ColorXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "ColorPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the result of the "ColorPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the result of the "ColorPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Color(backdrop, source)); + } + + /// + /// Returns the result of the "ColorClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "ColorClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "ColorClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + + /// + /// Returns the result of the "ColorSrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorSrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorSrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorSrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorSrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorSrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorSrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorSrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorSrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorSrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorSrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorSrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorSrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorSrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorSrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "ColorPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ColorPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(ColorPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminositySrc" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminositySrc(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return source; + } + + /// + /// Returns the result of the "LuminositySrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminositySrc(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "LuminositySrc compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminositySrc(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + /// + /// Returns the result of the "LuminositySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminositySrcAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the result of the "LuminositySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminositySrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the result of the "LuminositySrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminositySrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the result of the "LuminositySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminositySrcOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the result of the "LuminositySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminositySrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the result of the "LuminositySrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminositySrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the result of the "LuminositySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminositySrcIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(backdrop, source); + } + + /// + /// Returns the result of the "LuminositySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminositySrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "LuminositySrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminositySrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "LuminositySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminositySrcOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(backdrop, source); + } + + /// + /// Returns the result of the "LuminositySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminositySrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "LuminositySrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminositySrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "LuminosityDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityDest(Vector4 backdrop, Vector4 source, float opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "LuminosityDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityDest(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "LuminosityDest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityDest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + + /// + /// Returns the result of the "LuminosityDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityDestAtop(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Atop(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the result of the "LuminosityDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityDestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the result of the "LuminosityDestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityDestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Atop(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the result of the "LuminosityDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityDestOver(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Over(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the result of the "LuminosityDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityDestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the result of the "LuminosityDestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityDestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Over(source, backdrop, Luminosity(source, backdrop)); + } + + /// + /// Returns the result of the "LuminosityDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityDestIn(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return In(source, backdrop); + } + + /// + /// Returns the result of the "LuminosityDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityDestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "LuminosityDestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityDestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "LuminosityDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityDestOut(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Out(source, backdrop); + } + + /// + /// Returns the result of the "LuminosityDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityDestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "LuminosityDestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityDestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); + + /// + /// Returns the result of the "LuminosityXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityXor(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Xor(backdrop, source); + } + + /// + /// Returns the result of the "LuminosityXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityXor(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "LuminosityXor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityXor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "LuminosityPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityPlus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the result of the "LuminosityPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityPlus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the result of the "LuminosityPlus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityPlus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, Luminosity(backdrop, source)); + } + + /// + /// Returns the result of the "LuminosityClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LuminosityClear(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Clear(backdrop, source); + } + + /// + /// Returns the result of the "LuminosityClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 LuminosityClear(Vector256 backdrop, Vector256 source, Vector256 opacity) + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "LuminosityClear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 LuminosityClear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + + /// + /// Returns the result of the "LuminositySrc" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminositySrc(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminositySrc(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminositySrcAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminositySrcAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminositySrcAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminositySrcOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminositySrcOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminositySrcOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminositySrcIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminositySrcIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminositySrcIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminositySrcOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminositySrcOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminositySrcOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminosityDest" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminosityDest(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminosityDest(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminosityDestAtop" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminosityDestAtop(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminosityDestAtop(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminosityDestOver" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminosityDestOver(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminosityDestOver(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminosityDestIn" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminosityDestIn(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminosityDestIn(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminosityDestOut" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminosityDestOut(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminosityDestOut(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminosityClear" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminosityClear(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminosityClear(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminosityXor" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminosityXor(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminosityXor(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); + } + + /// + /// Returns the result of the "LuminosityPlus" compositing equation. + /// + /// The pixel format. + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LuminosityPlus(TPixel backdrop, TPixel source, float opacity) + where TPixel : unmanaged, IPixel + { + opacity = Numerics.Clamp(opacity, 0, 1); + return TPixel.FromUnassociatedScaledVector4(LuminosityPlus(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt index 150adb33a..3ee0e133c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt @@ -47,7 +47,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>Src(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + => Avx.Blend(source, source * opacity, BlendAlphaControl); + + /// + /// Returns the result of the "<#=blender#>Src compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>Src(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); /// /// Returns the result of the "<#=blender#>SrcAtop" compositing equation. @@ -74,7 +85,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>SrcAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(backdrop, source, <#=blender#>(backdrop, source)); + } + + /// + /// Returns the result of the "<#=blender#>SrcAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>SrcAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(backdrop, source, <#=blender#>(backdrop, source)); } @@ -104,7 +130,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>SrcOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(backdrop, source, <#=blender#>(backdrop, source)); + } + + /// + /// Returns the result of the "<#=blender#>SrcOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>SrcOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(backdrop, source, <#=blender#>(backdrop, source)); } @@ -133,7 +174,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>SrcIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => In(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "<#=blender#>SrcIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>SrcIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "<#=blender#>SrcOut" compositing equation. @@ -159,7 +211,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>SrcOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Out(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "<#=blender#>SrcOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>SrcOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); /// /// Returns the result of the "<#=blender#>Dest" compositing equation. @@ -187,6 +250,19 @@ internal static partial class PorterDuffFunctions return backdrop; } + /// + /// Returns the result of the "<#=blender#>Dest" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>Dest(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + return backdrop; + } + /// /// Returns the result of the "<#=blender#>DestAtop" compositing equation. /// @@ -212,7 +288,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>DestAtop(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Atop(source, backdrop, <#=blender#>(source, backdrop)); + } + + /// + /// Returns the result of the "<#=blender#>DestAtop" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>DestAtop(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Atop(source, backdrop, <#=blender#>(source, backdrop)); } @@ -242,7 +333,22 @@ internal static partial class PorterDuffFunctions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>DestOver(Vector256 backdrop, Vector256 source, Vector256 opacity) { - source = Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl); + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Over(source, backdrop, <#=blender#>(source, backdrop)); + } + + /// + /// Returns the result of the "<#=blender#>DestOver" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>DestOver(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); return Over(source, backdrop, <#=blender#>(source, backdrop)); } @@ -271,7 +377,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>DestIn(Vector256 backdrop, Vector256 source, Vector256 opacity) - => In(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => In(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "<#=blender#>DestIn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>DestIn(Vector512 backdrop, Vector512 source, Vector512 opacity) + => In(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "<#=blender#>DestOut" compositing equation. @@ -297,7 +414,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>DestOut(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Out(Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl), backdrop); + => Out(Avx.Blend(source, source * opacity, BlendAlphaControl), backdrop); + + /// + /// Returns the result of the "<#=blender#>DestOut" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>DestOut(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Out(Avx512F.BlendVariable(source, source * opacity, AlphaMask512()), backdrop); /// /// Returns the result of the "<#=blender#>Xor" compositing equation. @@ -323,7 +451,63 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>Xor(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Xor(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Xor(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "<#=blender#>Xor" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>Xor(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Xor(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); + + /// + /// Returns the result of the "<#=blender#>Plus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 <#=blender#>Plus(Vector4 backdrop, Vector4 source, float opacity) + { + source = Numerics.WithW(source, source * opacity); + + return Plus(backdrop, source, <#=blender#>(backdrop, source)); + } + + /// + /// Returns the result of the "<#=blender#>Plus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 <#=blender#>Plus(Vector256 backdrop, Vector256 source, Vector256 opacity) + { + source = Avx.Blend(source, source * opacity, BlendAlphaControl); + + return Plus(backdrop, source, <#=blender#>(backdrop, source)); + } + + /// + /// Returns the result of the "<#=blender#>Plus" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>Plus(Vector512 backdrop, Vector512 source, Vector512 opacity) + { + source = Avx512F.BlendVariable(source, source * opacity, AlphaMask512()); + + return Plus(backdrop, source, <#=blender#>(backdrop, source)); + } /// /// Returns the result of the "<#=blender#>Clear" compositing equation. @@ -349,7 +533,18 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 <#=blender#>Clear(Vector256 backdrop, Vector256 source, Vector256 opacity) - => Clear(backdrop, Avx.Blend(source, Avx.Multiply(source, opacity), BlendAlphaControl)); + => Clear(backdrop, Avx.Blend(source, source * opacity, BlendAlphaControl)); + + /// + /// Returns the result of the "<#=blender#>Clear" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The source opacity. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 <#=blender#>Clear(Vector512 backdrop, Vector512 source, Vector512 opacity) + => Clear(backdrop, Avx512F.BlendVariable(source, source * opacity, AlphaMask512())); <#} #> @@ -368,7 +563,7 @@ internal static partial class PorterDuffFunctions where TPixel : unmanaged, IPixel { opacity = Numerics.Clamp(opacity, 0, 1); - return TPixel.FromScaledVector4(<#=blender#><#=composer#>(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity)); + return TPixel.FromUnassociatedScaledVector4(<#=blender#><#=composer#>(backdrop.ToUnassociatedScaledVector4(), source.ToUnassociatedScaledVector4(), opacity)); } <# } #> <# @@ -385,6 +580,7 @@ var composers = new []{ "DestOut", "Clear", "Xor", + "Plus", }; var blenders = new []{ @@ -396,7 +592,16 @@ var blenders = new []{ "Darken", "Lighten", "Overlay", - "HardLight" + "HardLight", + "ColorDodge", + "ColorBurn", + "SoftLight", + "Difference", + "Exclusion", + "Hue", + "Saturation", + "Color", + "Luminosity", }; foreach(var blender in blenders) diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index ca358be31..c319b9662 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -5,6 +5,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders; @@ -44,6 +45,16 @@ internal static partial class PorterDuffFunctions public static Vector256 Normal(Vector256 backdrop, Vector256 source) => source; + /// + /// Returns the result of the "Normal" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Normal(Vector512 backdrop, Vector512 source) + => source; + /// /// Returns the result of the "Multiply" compositing equation. /// @@ -62,7 +73,17 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Multiply(Vector256 backdrop, Vector256 source) - => Avx.Multiply(backdrop, source); + => backdrop * source; + + /// + /// Returns the result of the "Multiply" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Multiply(Vector512 backdrop, Vector512 source) + => backdrop * source; /// /// Returns the result of the "Add" compositing equation. @@ -82,7 +103,17 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Add(Vector256 backdrop, Vector256 source) - => Avx.Min(Vector256.Create(1F), Avx.Add(backdrop, source)); + => Vector256.Min(Vector256.Create(1F), backdrop + source); + + /// + /// Returns the result of the "Add" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Add(Vector512 backdrop, Vector512 source) + => Vector512.Min(Vector512.Create(1F), backdrop + source); /// /// Returns the result of the "Subtract" compositing equation. @@ -102,7 +133,17 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Subtract(Vector256 backdrop, Vector256 source) - => Avx.Max(Vector256.Zero, Avx.Subtract(backdrop, source)); + => Vector256.Max(Vector256.Zero, backdrop - source); + + /// + /// Returns the result of the "Subtract" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Subtract(Vector512 backdrop, Vector512 source) + => Vector512.Max(Vector512.Zero, backdrop - source); /// /// Returns the result of the "Screen" compositing equation. @@ -124,7 +165,20 @@ internal static partial class PorterDuffFunctions public static Vector256 Screen(Vector256 backdrop, Vector256 source) { Vector256 vOne = Vector256.Create(1F); - return SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Subtract(vOne, backdrop), Avx.Subtract(vOne, source), vOne); + return Vector256_.MultiplyAddNegated(vOne, vOne - backdrop, vOne - source); + } + + /// + /// Returns the result of the "Screen" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Screen(Vector512 backdrop, Vector512 source) + { + Vector512 vOne = Vector512.Create(1F); + return Vector512_.MultiplyAddNegated(vOne, vOne - backdrop, vOne - source); } /// @@ -145,7 +199,17 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Darken(Vector256 backdrop, Vector256 source) - => Avx.Min(backdrop, source); + => Vector256.Min(backdrop, source); + + /// + /// Returns the result of the "Darken" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Darken(Vector512 backdrop, Vector512 source) + => Vector512.Min(backdrop, source); /// /// Returns the result of the "Lighten" compositing equation. @@ -164,7 +228,17 @@ internal static partial class PorterDuffFunctions /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector256 Lighten(Vector256 backdrop, Vector256 source) - => Avx.Max(backdrop, source); + => Vector256.Max(backdrop, source); + + /// + /// Returns the result of the "Lighten" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Lighten(Vector512 backdrop, Vector512 source) + => Vector512.Max(backdrop, source); /// /// Returns the result of the "Overlay" compositing equation. @@ -192,7 +266,20 @@ internal static partial class PorterDuffFunctions public static Vector256 Overlay(Vector256 backdrop, Vector256 source) { Vector256 color = OverlayValueFunction(backdrop, source); - return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); + return Vector256.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); + } + + /// + /// Returns the result of the "Overlay" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Overlay(Vector512 backdrop, Vector512 source) + { + Vector512 color = OverlayValueFunction(backdrop, source); + return Vector512.Min(Vector512.Create(1F), Vector512.ConditionalSelect(AlphaMask512(), Vector512.Zero, color)); } /// @@ -221,7 +308,434 @@ internal static partial class PorterDuffFunctions public static Vector256 HardLight(Vector256 backdrop, Vector256 source) { Vector256 color = OverlayValueFunction(source, backdrop); - return Avx.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); + return Vector256.Min(Vector256.Create(1F), Avx.Blend(color, Vector256.Zero, BlendAlphaControl)); + } + + /// + /// Returns the result of the "HardLight" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 HardLight(Vector512 backdrop, Vector512 source) + { + Vector512 color = OverlayValueFunction(source, backdrop); + return Vector512.Min(Vector512.Create(1F), Vector512.ConditionalSelect(AlphaMask512(), Vector512.Zero, color)); + } + + /// + /// Returns the result of the "ColorDodge" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The blended color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorDodge(Vector4 backdrop, Vector4 source) + => new( + ColorDodgeValue(backdrop.X, source.X), + ColorDodgeValue(backdrop.Y, source.Y), + ColorDodgeValue(backdrop.Z, source.Z), + 0F); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorDodge(Vector256 backdrop, Vector256 source) + { + Vector256 one = Vector256.Create(1F); + Vector256 result = Vector256.Min(one, backdrop / (one - source)); + + // The explicit singular cases avoid the undefined 0 / 0 result while preserving the order required by the specification. + result = Avx.BlendVariable(result, one, Avx.CompareEqual(source, one)); + result = Avx.BlendVariable(result, Vector256.Zero, Avx.CompareEqual(backdrop, Vector256.Zero)); + return Avx.Blend(result, Vector256.Zero, BlendAlphaControl); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorDodge(Vector512 backdrop, Vector512 source) + { + Vector512 one = Vector512.Create(1F); + Vector512 result = Vector512.Min(one, backdrop / (one - source)); + result = Vector512.ConditionalSelect(Vector512.Equals(source, one), one, result); + result = Vector512.ConditionalSelect(Vector512.Equals(backdrop, Vector512.Zero), Vector512.Zero, result); + return Vector512.ConditionalSelect(AlphaMask512(), Vector512.Zero, result); + } + + /// + /// Returns the result of the "ColorBurn" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The blended color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ColorBurn(Vector4 backdrop, Vector4 source) + => new( + ColorBurnValue(backdrop.X, source.X), + ColorBurnValue(backdrop.Y, source.Y), + ColorBurnValue(backdrop.Z, source.Z), + 0F); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 ColorBurn(Vector256 backdrop, Vector256 source) + { + Vector256 one = Vector256.Create(1F); + Vector256 result = one - Vector256.Min(one, (one - backdrop) / source); + + // As with ColorDodge, resolve the branch singularities explicitly so transparent lanes never produce NaN. + result = Avx.BlendVariable(result, Vector256.Zero, Avx.CompareEqual(source, Vector256.Zero)); + result = Avx.BlendVariable(result, one, Avx.CompareEqual(backdrop, one)); + return Avx.Blend(result, Vector256.Zero, BlendAlphaControl); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 ColorBurn(Vector512 backdrop, Vector512 source) + { + Vector512 one = Vector512.Create(1F); + Vector512 result = one - Vector512.Min(one, (one - backdrop) / source); + result = Vector512.ConditionalSelect(Vector512.Equals(source, Vector512.Zero), Vector512.Zero, result); + result = Vector512.ConditionalSelect(Vector512.Equals(backdrop, one), one, result); + return Vector512.ConditionalSelect(AlphaMask512(), Vector512.Zero, result); + } + + /// + /// Returns the result of the "SoftLight" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The blended color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SoftLight(Vector4 backdrop, Vector4 source) + => new( + SoftLightValue(backdrop.X, source.X), + SoftLightValue(backdrop.Y, source.Y), + SoftLightValue(backdrop.Z, source.Z), + 0F); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 SoftLight(Vector256 backdrop, Vector256 source) + { + Vector256 one = Vector256.Create(1F); + Vector256 two = Vector256.Create(2F); + Vector256 dark = backdrop - ((one - (two * source)) * backdrop * (one - backdrop)); + Vector256 polynomial = ((((Vector256.Create(16F) * backdrop) - Vector256.Create(12F)) * backdrop) + Vector256.Create(4F)) * backdrop; + Vector256 d = Avx.BlendVariable(polynomial, Avx.Sqrt(backdrop), Avx.CompareGreaterThan(backdrop, Vector256.Create(.25F))); + Vector256 light = backdrop + (((two * source) - one) * (d - backdrop)); + Vector256 result = Avx.BlendVariable(dark, light, Avx.CompareGreaterThan(source, Vector256.Create(.5F))); + return Avx.Blend(result, Vector256.Zero, BlendAlphaControl); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 SoftLight(Vector512 backdrop, Vector512 source) + { + Vector512 one = Vector512.Create(1F); + Vector512 two = Vector512.Create(2F); + Vector512 dark = backdrop - ((one - (two * source)) * backdrop * (one - backdrop)); + Vector512 polynomial = ((((Vector512.Create(16F) * backdrop) - Vector512.Create(12F)) * backdrop) + Vector512.Create(4F)) * backdrop; + Vector512 d = Vector512.ConditionalSelect(Avx512F.CompareGreaterThan(backdrop, Vector512.Create(.25F)), Vector512.Sqrt(backdrop), polynomial); + Vector512 light = backdrop + (((two * source) - one) * (d - backdrop)); + Vector512 result = Vector512.ConditionalSelect(Avx512F.CompareGreaterThan(source, Vector512.Create(.5F)), light, dark); + return Vector512.ConditionalSelect(AlphaMask512(), Vector512.Zero, result); + } + + /// + /// Returns the result of the "Difference" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The blended color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Difference(Vector4 backdrop, Vector4 source) + => Vector4.Abs(backdrop - source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Difference(Vector256 backdrop, Vector256 source) + => Vector256.Abs(backdrop - source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Difference(Vector512 backdrop, Vector512 source) + => Vector512.Abs(backdrop - source); + + /// + /// Returns the result of the "Exclusion" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The blended color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Exclusion(Vector4 backdrop, Vector4 source) + => backdrop + source - (2F * backdrop * source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Exclusion(Vector256 backdrop, Vector256 source) + => backdrop + source - (Vector256.Create(2F) * backdrop * source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Exclusion(Vector512 backdrop, Vector512 source) + => backdrop + source - (Vector512.Create(2F) * backdrop * source); + + /// + /// Returns the result of the "Hue" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The blended color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Hue(Vector4 backdrop, Vector4 source) + => SetLuminosity(SetSaturation(source, Saturation(backdrop)), Luminosity(backdrop)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Hue(Vector256 backdrop, Vector256 source) + => Vector256.Create( + Hue(backdrop.GetLower().AsVector4(), source.GetLower().AsVector4()).AsVector128(), + Hue(backdrop.GetUpper().AsVector4(), source.GetUpper().AsVector4()).AsVector128()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Hue(Vector512 backdrop, Vector512 source) + => Vector512.Create(Hue(backdrop.GetLower(), source.GetLower()), Hue(backdrop.GetUpper(), source.GetUpper())); + + /// + /// Returns the result of the "Saturation" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The blended color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Saturation(Vector4 backdrop, Vector4 source) + => SetLuminosity(SetSaturation(backdrop, Saturation(source)), Luminosity(backdrop)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Saturation(Vector256 backdrop, Vector256 source) + => Vector256.Create( + Saturation(backdrop.GetLower().AsVector4(), source.GetLower().AsVector4()).AsVector128(), + Saturation(backdrop.GetUpper().AsVector4(), source.GetUpper().AsVector4()).AsVector128()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Saturation(Vector512 backdrop, Vector512 source) + => Vector512.Create(Saturation(backdrop.GetLower(), source.GetLower()), Saturation(backdrop.GetUpper(), source.GetUpper())); + + /// + /// Returns the result of the "Color" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The blended color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Color(Vector4 backdrop, Vector4 source) + => SetLuminosity(source, Luminosity(backdrop)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Color(Vector256 backdrop, Vector256 source) + => Vector256.Create( + Color(backdrop.GetLower().AsVector4(), source.GetLower().AsVector4()).AsVector128(), + Color(backdrop.GetUpper().AsVector4(), source.GetUpper().AsVector4()).AsVector128()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Color(Vector512 backdrop, Vector512 source) + => Vector512.Create(Color(backdrop.GetLower(), source.GetLower()), Color(backdrop.GetUpper(), source.GetUpper())); + + /// + /// Returns the result of the "Luminosity" compositing equation. + /// + /// The backdrop vector. + /// The source vector. + /// The blended color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Luminosity(Vector4 backdrop, Vector4 source) + => SetLuminosity(backdrop, Luminosity(source)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Luminosity(Vector256 backdrop, Vector256 source) + => Vector256.Create( + Luminosity(backdrop.GetLower().AsVector4(), source.GetLower().AsVector4()).AsVector128(), + Luminosity(backdrop.GetUpper().AsVector4(), source.GetUpper().AsVector4()).AsVector128()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Luminosity(Vector512 backdrop, Vector512 source) + => Vector512.Create(Luminosity(backdrop.GetLower(), source.GetLower()), Luminosity(backdrop.GetUpper(), source.GetUpper())); + + /// + /// 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); + + // Use the same fused operation as the wider paths so exact midpoints cannot change across vector widths. + Vector4 result = Vector128.MultiplyAddEstimate((sourcePremultiplied - backdropPremultiplied).AsVector128(), Vector128.Create(coverage), backdropPremultiplied.AsVector128()).AsVector4(); + + Numerics.UnPremultiply(ref result); + return result; + } + + /// + /// 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.MultiplyAddEstimate(sourcePremultiplied - backdropPremultiplied, coverage, backdropPremultiplied); + + 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.MultiplyAddEstimate(sourcePremultiplied - backdropPremultiplied, coverage, backdropPremultiplied); + + return Numerics.UnPremultiply(result, Vector512_.ShuffleNative(result, ShuffleAlphaControl)); + } + + /// + /// Calculates one ColorDodge component, including the singular cases defined by the blending model. + /// + /// The backdrop component. + /// The source component. + /// The blended component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ColorDodgeValue(float backdrop, float source) + => backdrop == 0F ? 0F : source == 1F ? 1F : MathF.Min(1F, backdrop / (1F - source)); + + /// + /// Calculates one ColorBurn component, including the singular cases defined by the blending model. + /// + /// The backdrop component. + /// The source component. + /// The blended component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float ColorBurnValue(float backdrop, float source) + => backdrop == 1F ? 1F : source == 0F ? 0F : 1F - MathF.Min(1F, (1F - backdrop) / source); + + /// + /// Calculates one SoftLight component. + /// + /// The backdrop component. + /// The source component. + /// The blended component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float SoftLightValue(float backdrop, float source) + { + if (source <= .5F) + { + return backdrop - ((1F - (2F * source)) * backdrop * (1F - backdrop)); + } + + // The cubic segment joins the square-root segment smoothly while avoiding its steep slope near zero. + float d = backdrop <= .25F + ? (((((16F * backdrop) - 12F) * backdrop) + 4F) * backdrop) + : MathF.Sqrt(backdrop); + + return backdrop + (((2F * source) - 1F) * (d - backdrop)); + } + + /// + /// Calculates the saturation of an RGB color. + /// + /// The color. + /// The difference between the maximum and minimum RGB components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Saturation(Vector4 color) + => MathF.Max(color.X, MathF.Max(color.Y, color.Z)) - MathF.Min(color.X, MathF.Min(color.Y, color.Z)); + + /// + /// Calculates the luminosity of an RGB color. + /// + /// The color. + /// The weighted RGB luminosity. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Luminosity(Vector4 color) + => (.3F * color.X) + (.59F * color.Y) + (.11F * color.Z); + + /// + /// Replaces the saturation of an RGB color while retaining its channel ordering. + /// + /// The color. + /// The required saturation. + /// The adjusted color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 SetSaturation(Vector4 color, float saturation) + { + float minimum = MathF.Min(color.X, MathF.Min(color.Y, color.Z)); + float range = MathF.Max(color.X, MathF.Max(color.Y, color.Z)) - minimum; + if (range == 0F) + { + return Vector4.Zero; + } + + // Translating the minimum to zero and scaling the range is equivalent to the specification's ordered-channel construction. + return (color - new Vector4(minimum)) * (saturation / range); + } + + /// + /// Replaces the luminosity of an RGB color and clips the result to the representable gamut. + /// + /// The color. + /// The required luminosity. + /// The adjusted color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 SetLuminosity(Vector4 color, float luminosity) + { + color += new Vector4(luminosity - Luminosity(color)); + + float minimum = MathF.Min(color.X, MathF.Min(color.Y, color.Z)); + if (minimum < 0F) + { + // Pull every component toward the requested luminosity by the same ratio so hue is preserved. + color = new Vector4(luminosity) + ((color - new Vector4(luminosity)) * (luminosity / (luminosity - minimum))); + } + + float maximum = MathF.Max(color.X, MathF.Max(color.Y, color.Z)); + if (maximum > 1F) + { + color = new Vector4(luminosity) + ((color - new Vector4(luminosity)) * ((1F - luminosity) / (maximum - luminosity))); + } + + return Numerics.WithW(color, Vector4.Zero); } /// @@ -244,14 +758,32 @@ internal static partial class PorterDuffFunctions public static Vector256 OverlayValueFunction(Vector256 backdrop, Vector256 source) { Vector256 vOne = Vector256.Create(1F); - Vector256 left = Avx.Multiply(Avx.Add(backdrop, backdrop), source); + Vector256 left = (backdrop + backdrop) * source; Vector256 vOneMinusSource = Avx.Subtract(vOne, source); - Vector256 right = SimdUtils.HwIntrinsics.MultiplyAddNegated(Avx.Add(vOneMinusSource, vOneMinusSource), Avx.Subtract(vOne, backdrop), vOne); + Vector256 right = Vector256_.MultiplyAddNegated(vOne, vOneMinusSource + vOneMinusSource, vOne - backdrop); Vector256 cmp = Avx.CompareGreaterThan(backdrop, Vector256.Create(.5F)); return Avx.BlendVariable(left, right, cmp); } + /// + /// Helper function for Overlay and HardLight modes + /// + /// Backdrop color element + /// Source color element + /// Overlay value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 OverlayValueFunction(Vector512 backdrop, Vector512 source) + { + Vector512 vOne = Vector512.Create(1F); + Vector512 left = (backdrop + backdrop) * source; + + Vector512 vOneMinusSource = vOne - source; + Vector512 right = Vector512_.MultiplyAddNegated(vOne, vOneMinusSource + vOneMinusSource, vOne - backdrop); + Vector512 cmp = Avx512F.CompareGreaterThan(backdrop, Vector512.Create(.5F)); + return Vector512.ConditionalSelect(cmp, right, left); + } + /// /// Returns the result of the "Over" compositing equation. /// @@ -295,17 +827,47 @@ internal static partial class PorterDuffFunctions Vector256 sW = Avx.Permute(source, ShuffleAlphaControl); Vector256 dW = Avx.Permute(destination, ShuffleAlphaControl); - Vector256 blendW = Avx.Multiply(sW, dW); - Vector256 dstW = Avx.Subtract(dW, blendW); - Vector256 srcW = Avx.Subtract(sW, blendW); + Vector256 blendW = sW * dW; + Vector256 dstW = dW - blendW; + Vector256 srcW = sW - blendW; + + // calculate final alpha + Vector256 alpha = dstW + sW; + + // calculate final color + Vector256 color = destination * dstW; + color = Vector256.MultiplyAddEstimate(source, srcW, color); + color = Vector256.MultiplyAddEstimate(blend, blendW, color); + + // unpremultiply + return Numerics.UnPremultiply(color, alpha); + } + + /// + /// Returns the result of the "Over" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The amount to blend. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Over(Vector512 destination, Vector512 source, Vector512 blend) + { + // calculate weights + Vector512 sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 dW = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + + Vector512 blendW = sW * dW; + Vector512 dstW = dW - blendW; + Vector512 srcW = sW - blendW; // calculate final alpha - Vector256 alpha = Avx.Add(dstW, sW); + Vector512 alpha = dstW + sW; // calculate final color - Vector256 color = Avx.Multiply(destination, dstW); - color = SimdUtils.HwIntrinsics.MultiplyAdd(color, source, srcW); - color = SimdUtils.HwIntrinsics.MultiplyAdd(color, blend, blendW); + Vector512 color = destination * dstW; + color = Vector512.MultiplyAddEstimate(source, srcW, color); + color = Vector512.MultiplyAddEstimate(blend, blendW, color); // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -354,11 +916,36 @@ internal static partial class PorterDuffFunctions // calculate weights Vector256 sW = Avx.Permute(source, ShuffleAlphaControl); - Vector256 blendW = Avx.Multiply(sW, alpha); - Vector256 dstW = Avx.Subtract(alpha, blendW); + Vector256 blendW = sW * alpha; + Vector256 dstW = alpha - blendW; // calculate final color - Vector256 color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(blend, blendW), destination, dstW); + Vector256 color = Vector256.MultiplyAddEstimate(destination, dstW, Avx.Multiply(blend, blendW)); + + // unpremultiply + return Numerics.UnPremultiply(color, alpha); + } + + /// + /// Returns the result of the "Atop" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The amount to blend. Range 0..1 + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Atop(Vector512 destination, Vector512 source, Vector512 blend) + { + // calculate final alpha + Vector512 alpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + + // calculate weights + Vector512 sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 blendW = sW * alpha; + Vector512 dstW = alpha - blendW; + + // calculate final color + Vector512 color = Vector512.MultiplyAddEstimate(destination, dstW, blend * blendW); // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -392,10 +979,29 @@ internal static partial class PorterDuffFunctions public static Vector256 In(Vector256 destination, Vector256 source) { // calculate alpha - Vector256 alpha = Avx.Permute(Avx.Multiply(source, destination), ShuffleAlphaControl); + Vector256 alpha = Avx.Permute(source * destination, ShuffleAlphaControl); + + // premultiply + Vector256 color = source * alpha; + + // unpremultiply + return Numerics.UnPremultiply(color, alpha); + } + + /// + /// Returns the result of the "In" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 In(Vector512 destination, Vector512 source) + { + // calculate alpha + Vector512 alpha = Vector512_.ShuffleNative(source * destination, ShuffleAlphaControl); // premultiply - Vector256 color = Avx.Multiply(source, alpha); + Vector512 color = source * alpha; // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -429,10 +1035,29 @@ internal static partial class PorterDuffFunctions public static Vector256 Out(Vector256 destination, Vector256 source) { // calculate alpha - Vector256 alpha = Avx.Permute(Avx.Multiply(source, Avx.Subtract(Vector256.Create(1F), destination)), ShuffleAlphaControl); + Vector256 alpha = Avx.Permute(source * (Vector256.Create(1F) - destination), ShuffleAlphaControl); + + // premultiply + Vector256 color = source * alpha; + + // unpremultiply + return Numerics.UnPremultiply(color, alpha); + } + + /// + /// Returns the result of the "Out" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Out(Vector512 destination, Vector512 source) + { + // calculate alpha + Vector512 alpha = Vector512_.ShuffleNative(source * (Vector512.Create(1F) - destination), ShuffleAlphaControl); // premultiply - Vector256 color = Avx.Multiply(source, alpha); + Vector512 color = source * alpha; // unpremultiply return Numerics.UnPremultiply(color, alpha); @@ -475,20 +1100,103 @@ internal static partial class PorterDuffFunctions Vector256 dW = Avx.Shuffle(destination, destination, ShuffleAlphaControl); Vector256 vOne = Vector256.Create(1F); - Vector256 srcW = Avx.Subtract(vOne, dW); - Vector256 dstW = Avx.Subtract(vOne, sW); + Vector256 srcW = vOne - dW; + Vector256 dstW = vOne - sW; + + // calculate alpha + Vector256 alpha = Vector256.MultiplyAddEstimate(sW, srcW, Avx.Multiply(dW, dstW)); + Vector256 color = Vector256.MultiplyAddEstimate(Avx.Multiply(sW, source), srcW, Avx.Multiply(Avx.Multiply(dW, destination), dstW)); + + // unpremultiply + return Numerics.UnPremultiply(color, alpha); + } + + /// + /// Returns the result of the "XOr" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Xor(Vector512 destination, Vector512 source) + { + // calculate weights + Vector512 sW = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 dW = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + + Vector512 vOne = Vector512.Create(1F); + Vector512 srcW = vOne - dW; + Vector512 dstW = vOne - sW; // calculate alpha - Vector256 alpha = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(dW, dstW), sW, srcW); - Vector256 color = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(Avx.Multiply(dW, destination), dstW), Avx.Multiply(sW, source), srcW); + Vector512 alpha = Vector512.MultiplyAddEstimate(sW, srcW, dW * dstW); + Vector512 color = Vector512.MultiplyAddEstimate(sW * source, srcW, (dW * destination) * dstW); // unpremultiply return Numerics.UnPremultiply(color, alpha); } + /// + /// Returns the result of the "Plus" compositing equation. + /// + /// The destination vector. + /// The source vector. + /// The blended source and destination color. + /// The composition result. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Plus(Vector4 destination, Vector4 source, Vector4 blend) + { + Vector4 sourceAlpha = Numerics.PermuteW(source); + Vector4 destinationAlpha = Numerics.PermuteW(destination); + Vector4 overlap = sourceAlpha * destinationAlpha; + + // Plus retains both non-overlapping regions and uses the selected blend mode in their overlap. + Vector4 color = (destination * destinationAlpha) + (source * (sourceAlpha - overlap)) + (blend * overlap); + Vector4 alpha = Vector4.Min(Vector4.One, sourceAlpha + destinationAlpha); + color = Vector4.Min(Vector4.One, color); + + Numerics.UnPremultiply(ref color, alpha); + return color; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Plus(Vector256 destination, Vector256 source, Vector256 blend) + { + Vector256 one = Vector256.Create(1F); + Vector256 sourceAlpha = Avx.Permute(source, ShuffleAlphaControl); + Vector256 destinationAlpha = Avx.Permute(destination, ShuffleAlphaControl); + Vector256 overlap = sourceAlpha * destinationAlpha; + Vector256 color = (destination * destinationAlpha) + (source * (sourceAlpha - overlap)) + (blend * overlap); + Vector256 alpha = Vector256.Min(one, sourceAlpha + destinationAlpha); + + return Numerics.UnPremultiply(Vector256.Min(one, color), alpha); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector512 Plus(Vector512 destination, Vector512 source, Vector512 blend) + { + Vector512 one = Vector512.Create(1F); + Vector512 sourceAlpha = Vector512_.ShuffleNative(source, ShuffleAlphaControl); + Vector512 destinationAlpha = Vector512_.ShuffleNative(destination, ShuffleAlphaControl); + Vector512 overlap = sourceAlpha * destinationAlpha; + Vector512 color = (destination * destinationAlpha) + (source * (sourceAlpha - overlap)) + (blend * overlap); + Vector512 alpha = Vector512.Min(one, sourceAlpha + destinationAlpha); + + return Numerics.UnPremultiply(Vector512.Min(one, color), alpha); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 Clear(Vector4 backdrop, Vector4 source) => Vector4.Zero; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector256 Clear(Vector256 backdrop, Vector256 source) => Vector256.Zero; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Clear(Vector512 backdrop, Vector512 source) => Vector512.Zero; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 AlphaMask512() + => Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); } diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index 4ed617438..bb519c174 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -3,7 +3,7 @@ using System.Buffers; using System.Numerics; -using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats.PixelBlenders; namespace SixLabors.ImageSharp.PixelFormats; @@ -52,16 +52,276 @@ public abstract class PixelBlender Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); - Span destinationVectors = buffer.Slice(0, maxLength); - Span backgroundVectors = buffer.Slice(maxLength, maxLength); - Span sourceVectors = buffer.Slice(maxLength * 2, maxLength); + this.Blend( + configuration, + destination, + background, + source, + amount, + buffer.Memory.Span[..(maxLength * 3)]); + } + + /// + /// Blends 2 rows together using caller-provided temporary vector scratch. + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// Reusable temporary vector scratch with capacity for at least 3 rows. + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + float amount, + Span workingBuffer) + where TPixelSrc : unmanaged, IPixel + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 3, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); + Span sourceVectors = workingBuffer.Slice(maxLength * 2, maxLength); - PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); - PixelOperations.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale); + this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors); + this.ToBlendVector4(configuration, source[..maxLength], sourceVectors); this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount); - PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); + this.FromBlendVector4(configuration, destinationVectors, destination); + } + + /// + /// Blends a row against a constant source color. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + float amount) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 2); + this.Blend( + configuration, + destination, + background, + source, + amount, + buffer.Memory.Span[..(maxLength * 2)]); + } + + /// + /// Blends a row against a constant source color using caller-provided temporary vector scratch. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// Reusable temporary vector scratch with capacity for at least 2 rows. + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + float amount, + Span workingBuffer) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 2, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); + + this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors); + + this.BlendFunction(destinationVectors, backgroundVectors, this.ToBlendVector4(source), amount); + + this.FromBlendVector4(configuration, destinationVectors, destination); + } + + /// + /// 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); + + this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors); + this.ToBlendVector4(configuration, source[..maxLength], sourceVectors); + + this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, sourceVectors, amount, coverage); + + this.FromBlendVector4(configuration, destinationVectors, destination); + } + + /// + /// 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); + + this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors); + + this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, this.ToBlendVector4(source), amount, coverage); + + this.FromBlendVector4(configuration, destinationVectors, destination); } /// @@ -83,6 +343,27 @@ public abstract class PixelBlender ReadOnlySpan amount) => this.Blend(configuration, destination, background, source, amount); + /// + /// Blends 2 rows together using caller-provided temporary vector scratch. + /// + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// Reusable temporary vector scratch with capacity for at least 3 rows. + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + Span workingBuffer) + => this.Blend(configuration, destination, background, source, amount, workingBuffer); + /// /// Blends 2 rows together /// @@ -109,18 +390,356 @@ public abstract class PixelBlender Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 3); - Span destinationVectors = buffer.Slice(0, maxLength); - Span backgroundVectors = buffer.Slice(maxLength, maxLength); - Span sourceVectors = buffer.Slice(maxLength * 2, maxLength); + this.Blend( + configuration, + destination, + background, + source, + amount, + buffer.Memory.Span[..(maxLength * 3)]); + } + + /// + /// Blends a row against a constant source color. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + ReadOnlySpan amount) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + + using IMemoryOwner buffer = configuration.MemoryAllocator.Allocate(maxLength * 2); + this.Blend( + configuration, + destination, + background, + source, + amount, + buffer.Memory.Span[..(maxLength * 2)]); + } + + /// + /// Blends 2 rows together using caller-provided temporary vector scratch. + /// + /// the pixel format of the source span + /// to use internally + /// the destination span + /// the background span + /// the source span + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// Reusable temporary vector scratch with capacity for at least 3 rows. + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + ReadOnlySpan source, + ReadOnlySpan amount, + Span workingBuffer) + where TPixelSrc : unmanaged, IPixel + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, maxLength, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 3, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); + Span sourceVectors = workingBuffer.Slice(maxLength * 2, maxLength); - PixelOperations.Instance.ToVector4(configuration, background[..maxLength], backgroundVectors, PixelConversionModifiers.Scale); - PixelOperations.Instance.ToVector4(configuration, source[..maxLength], sourceVectors, PixelConversionModifiers.Scale); + this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors); + this.ToBlendVector4(configuration, source[..maxLength], sourceVectors); this.BlendFunction(destinationVectors, backgroundVectors, sourceVectors, amount); - PixelOperations.Instance.FromVector4Destructive(configuration, destinationVectors[..maxLength], destination, PixelConversionModifiers.Scale); + this.FromBlendVector4(configuration, destinationVectors, destination); } + /// + /// Blends a row against a constant source color using caller-provided temporary vector scratch. + /// + /// to use internally + /// the destination span + /// the background span + /// the source color + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + /// Reusable temporary vector scratch with capacity for at least 2 rows. + public void Blend( + Configuration configuration, + Span destination, + ReadOnlySpan background, + TPixel source, + ReadOnlySpan amount, + Span workingBuffer) + { + int maxLength = destination.Length; + Guard.MustBeGreaterThanOrEqualTo(background.Length, maxLength, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, maxLength, nameof(amount.Length)); + Guard.MustBeGreaterThanOrEqualTo(workingBuffer.Length, maxLength * 2, nameof(workingBuffer.Length)); + + Span destinationVectors = workingBuffer[..maxLength]; + Span backgroundVectors = workingBuffer.Slice(maxLength, maxLength); + + this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors); + + this.BlendFunction(destinationVectors, backgroundVectors, this.ToBlendVector4(source), amount); + + this.FromBlendVector4(configuration, destinationVectors, destination); + } + + /// + /// 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); + + this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors); + this.ToBlendVector4(configuration, source[..maxLength], sourceVectors); + + this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, sourceVectors, amount, coverage); + + this.FromBlendVector4(configuration, destinationVectors, destination); + } + + /// + /// 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); + + this.ToBlendVector4(configuration, background[..maxLength], backgroundVectors); + + this.BlendWithCoverageFunction(destinationVectors, backgroundVectors, this.ToBlendVector4(source), amount, coverage); + + this.FromBlendVector4(configuration, destinationVectors, destination); + } + + /// + /// Converts source pixels to the scaled-vector representation consumed by this blender. + /// + /// The source pixel format. + /// The configuration. + /// The source pixels. + /// The destination vectors. + protected virtual void ToBlendVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + where TPixelSource : unmanaged, IPixel + => PixelOperations.Instance.ToVector4(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + /// + /// Converts a source pixel to the vector representation consumed by the blend functions. + /// + /// The source pixel. + /// The source vector. + protected virtual Vector4 ToBlendVector4(TPixel source) + => source.ToUnassociatedScaledVector4(); + + /// + /// Converts blend results from this blender's scaled-vector representation to destination pixels. + /// + /// The configuration. + /// The source vectors. + /// The destination pixels. + protected virtual void FromBlendVector4( + Configuration configuration, + Span source, + Span destination) + => PixelOperations.Instance.FromVector4Destructive(configuration, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + /// /// Blend 2 rows together. /// @@ -137,6 +756,22 @@ public abstract class PixelBlender ReadOnlySpan source, float amount); + /// + /// Blend a row against a constant source color. + /// + /// destination span + /// the background span + /// the source color vector + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + Vector4 source, + float amount); + /// /// Blend 2 rows together. /// @@ -152,4 +787,124 @@ public abstract class PixelBlender ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount); + + /// + /// Blend a row against a constant source color. + /// + /// destination span + /// the background span + /// the source color vector + /// + /// A span with values between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "background" is returned, at amount = 1, "source" is returned. + /// + protected abstract void BlendFunction( + Span destination, + ReadOnlySpan background, + Vector4 source, + ReadOnlySpan amount); + + /// + /// 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/PixelColorBlendingMode.cs b/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs index 7aa07cf51..aa74f6172 100644 --- a/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs +++ b/src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs @@ -52,4 +52,49 @@ public enum PixelColorBlendingMode /// Multiplies or screens the colors, depending on the source value. /// HardLight, + + /// + /// Brightens the backdrop color to reflect the source color. + /// + ColorDodge, + + /// + /// Darkens the backdrop color to reflect the source color. + /// + ColorBurn, + + /// + /// Darkens or lightens the colors, depending on the source color. + /// + SoftLight, + + /// + /// Subtracts the darker color from the lighter color. + /// + Difference, + + /// + /// Produces an effect similar to Difference with lower contrast. + /// + Exclusion, + + /// + /// Uses the hue of the source color with the saturation and luminosity of the backdrop color. + /// + Hue, + + /// + /// Uses the saturation of the source color with the hue and luminosity of the backdrop color. + /// + Saturation, + + /// + /// Uses the hue and saturation of the source color with the luminosity of the backdrop color. + /// + Color, + + /// + /// Uses the luminosity of the source color with the hue and saturation of the backdrop color. + /// + Luminosity, } diff --git a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs index edc04fa7c..8cee6057d 100644 --- a/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs +++ b/src/ImageSharp/PixelFormats/PixelConversionModifiers.cs @@ -6,32 +6,38 @@ using SixLabors.ImageSharp.ColorProfiles.Companding; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Flags responsible to select additional operations which could be efficiently applied in +/// Specifies vector representation transformations applied by /// -/// or +/// and /// -/// knowing the pixel type. +/// during bulk pixel conversion. /// [Flags] public enum PixelConversionModifiers { /// - /// No special operation is selected + /// Preserves the pixel format's native numeric range and alpha representation. /// None = 0, /// - /// Select and instead the standard (non scaled) variants. + /// Requests the scaled numeric range represented by and . /// Scale = 1 << 0, /// - /// Enable alpha premultiplication / unpremultiplication + /// Requests associated alpha for the vector representation. + /// When combined with , associated alpha takes precedence. /// Premultiply = 1 << 1, /// - /// Enable SRGB companding (defined in ). + /// Expands sRGB color components when converting pixels to vectors and compresses linear RGB color components when converting vectors to pixels. /// SRgbCompand = 1 << 2, + + /// + /// Requests unassociated alpha for the vector representation unless is also specified. + /// + UnPremultiply = 1 << 3, } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs index 94fbf7eb7..8a17831a1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs @@ -8,10 +8,11 @@ namespace SixLabors.ImageSharp.PixelFormats; /// /// Packed pixel type containing a single 8-bit normalized alpha value. -/// -/// Ranges from [0, 0, 0, 0] to [0, 0, 0, 1] in vector form. -/// /// +/// +/// and scaled vector conversions return alpha in [0, 1] with zero color components. +/// The storage layout matches DXGI_FORMAT_A8_UNORM. +/// public partial struct A8 : IPixel, IPackedVector { /// @@ -77,6 +78,38 @@ public partial struct A8 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static A8 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static A8 FromAssociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static A8 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static A8 FromAssociatedVector4(Vector4 source) => FromVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static A8 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs index 1190d307a..acd354b7f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs @@ -182,7 +182,7 @@ public partial struct Abgr32 : IPixel, IPackedVector /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public static PixelTypeInfo GetPixelTypeInfo() @@ -194,6 +194,47 @@ public partial struct Abgr32 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Abgr32 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs new file mode 100644 index 000000000..9105a99f7 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32P.cs @@ -0,0 +1,273 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing alpha and associated blue, green, and red components as 8-bit unsigned normalized values. +/// Components are stored in alpha, blue, green, and red order from least to most significant byte. +/// +/// +/// The native component, packed, and vector representations use associated alpha. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Abgr32P : IPixel, IPackedVector +{ + private static readonly Vector4 Half = new(0.5F); + private static readonly Vector4 MaxBytes = new(byte.MaxValue); + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Gets or sets the associated blue component. + /// + public byte B; + + /// + /// Gets or sets the associated green component. + /// + public byte G; + + /// + /// Gets or sets the associated red component. + /// + public byte R; + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Abgr32P(byte r, byte g, byte b) + : this(r, g, b, byte.MaxValue) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Abgr32P(byte r, byte g, byte b, byte a) + { + this.A = a; + this.B = b; + this.G = g; + this.R = r; + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Abgr32P(float r, float g, float b) + : this(new Vector4(r, g, b, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Abgr32P(float r, float g, float b, float a) + : this(new Vector4(r, g, b, a)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Abgr32P(Vector3 vector) + : this(new Vector4(vector, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Abgr32P(Vector4 vector) + : this() => this = FromScaledVector4(vector); + + /// + /// Initializes a new instance of the struct from a packed associated value. + /// + /// The packed associated value. + public Abgr32P(uint packed) + : this() => this.PackedValue = packed; + + /// + public uint PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(Abgr32P left, Abgr32P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(Abgr32P left, Abgr32P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() + => Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); + + /// + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + // Divide the stored byte magnitudes before normalization so unassociation cannot move an exact byte conversion across its rounding midpoint. + return Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToUnassociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 8, 8, 8, 8), + PixelColorType.Alpha | PixelColorType.BGR, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static Abgr32P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32P FromUnassociatedVector4(Vector4 source) => FromUnassociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32P FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32P FromUnassociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4ToAbgr32P(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Abgr32P FromAssociatedScaledVector4(Vector4 source) + { + // Rescale associated RGB when alpha rounds to a different byte so the stored channels remain associated with the alpha actually written. + return Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToAbgr32P(source); + } + + /// + public static Abgr32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Abgr32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is Abgr32P other && this.Equals(other); + + /// + public readonly bool Equals(Abgr32P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => $"Abgr32P({this.R}, {this.G}, {this.B}, {this.A})"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Abgr32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + // Each converted component occupies one 32-bit lane. Reinterpreting those lanes as bytes exposes their low bytes at offsets 0, 4, 8, and 12. + Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); + return new Abgr32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs index b74f5db71..90ae88263 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -175,7 +175,7 @@ public partial struct Argb32 : IPixel, IPackedVector /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public static PixelTypeInfo GetPixelTypeInfo() @@ -187,6 +187,47 @@ public partial struct Argb32 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Argb32 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs new file mode 100644 index 000000000..7c866c8fe --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32P.cs @@ -0,0 +1,273 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing alpha and associated red, green, and blue components as 8-bit unsigned normalized values. +/// Components are stored in alpha, red, green, and blue order from least to most significant byte. +/// +/// +/// The native component, packed, and vector representations use associated alpha. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Argb32P : IPixel, IPackedVector +{ + private static readonly Vector4 Half = new(0.5F); + private static readonly Vector4 MaxBytes = new(byte.MaxValue); + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Gets or sets the associated red component. + /// + public byte R; + + /// + /// Gets or sets the associated green component. + /// + public byte G; + + /// + /// Gets or sets the associated blue component. + /// + public byte B; + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Argb32P(byte r, byte g, byte b) + : this(r, g, b, byte.MaxValue) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Argb32P(byte r, byte g, byte b, byte a) + { + this.A = a; + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Argb32P(float r, float g, float b) + : this(new Vector4(r, g, b, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Argb32P(float r, float g, float b, float a) + : this(new Vector4(r, g, b, a)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Argb32P(Vector3 vector) + : this(new Vector4(vector, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Argb32P(Vector4 vector) + : this() => this = FromScaledVector4(vector); + + /// + /// Initializes a new instance of the struct from a packed associated value. + /// + /// The packed associated value. + public Argb32P(uint packed) + : this() => this.PackedValue = packed; + + /// + public uint PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(Argb32P left, Argb32P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(Argb32P left, Argb32P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() + => Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); + + /// + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + // Divide the stored byte magnitudes before normalization so unassociation cannot move an exact byte conversion across its rounding midpoint. + return Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToUnassociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 8, 8, 8, 8), + PixelColorType.Alpha | PixelColorType.RGB, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static Argb32P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32P FromUnassociatedVector4(Vector4 source) => FromUnassociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32P FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32P FromUnassociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4ToArgb32P(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Argb32P FromAssociatedScaledVector4(Vector4 source) + { + // Rescale associated RGB when alpha rounds to a different byte so the stored channels remain associated with the alpha actually written. + return Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToArgb32P(source); + } + + /// + public static Argb32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Argb32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is Argb32P other && this.Equals(other); + + /// + public readonly bool Equals(Argb32P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => $"Argb32P({this.R}, {this.G}, {this.B}, {this.A})"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Argb32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + // Each converted component occupies one 32-bit lane. Reinterpreting those lanes as bytes exposes their low bytes at offsets 0, 4, 8, and 12. + Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); + return new Argb32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs index 944cdf7bf..c64047518 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -97,6 +97,48 @@ public partial struct Bgr24 : IPixel /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr24 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr24 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr24 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr24 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Bgr24 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs index 87055bf22..338c55fe3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -7,14 +7,12 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. +/// Packed pixel type containing three unsigned normalized values. /// The x and z components use 5 bits, and the y component uses 6 bits. -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 1, 1] in vector form. -/// /// /// -/// Initializes a new instance of the struct. +/// and scaled vector conversions return color components in [0, 1] with an implicit +/// alpha of 1. The storage layout matches DXGI_FORMAT_B5G6R5_UNORM. /// /// /// The vector containing the components for the packed value. @@ -75,6 +73,48 @@ public partial struct Bgr565(Vector3 vector) : IPixel, IPackedVector public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr565 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr565 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr565 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgr565 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs index 903d9dc8c..e8405dd2b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -124,7 +124,7 @@ public partial struct Bgra32 : IPixel, IPackedVector /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public static PixelTypeInfo GetPixelTypeInfo() @@ -136,6 +136,47 @@ public partial struct Bgra32 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Bgra32 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs new file mode 100644 index 000000000..6ce3138f7 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32P.cs @@ -0,0 +1,273 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing associated blue, green, red, and alpha components as 8-bit unsigned normalized values. +/// Components are stored in blue, green, red, and alpha order from least to most significant byte. +/// +/// +/// The native component, packed, and vector representations use associated alpha. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Bgra32P : IPixel, IPackedVector +{ + private static readonly Vector4 Half = new(0.5F); + private static readonly Vector4 MaxBytes = new(byte.MaxValue); + + /// + /// Gets or sets the associated blue component. + /// + public byte B; + + /// + /// Gets or sets the associated green component. + /// + public byte G; + + /// + /// Gets or sets the associated red component. + /// + public byte R; + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Bgra32P(byte r, byte g, byte b) + : this(r, g, b, byte.MaxValue) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Bgra32P(byte r, byte g, byte b, byte a) + { + this.B = b; + this.G = g; + this.R = r; + this.A = a; + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Bgra32P(float r, float g, float b) + : this(new Vector4(r, g, b, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Bgra32P(float r, float g, float b, float a) + : this(new Vector4(r, g, b, a)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Bgra32P(Vector3 vector) + : this(new Vector4(vector, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Bgra32P(Vector4 vector) + : this() => this = FromScaledVector4(vector); + + /// + /// Initializes a new instance of the struct from a packed associated value. + /// + /// The packed associated value. + public Bgra32P(uint packed) + : this() => this.PackedValue = packed; + + /// + public uint PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(Bgra32P left, Bgra32P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(Bgra32P left, Bgra32P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() + => Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); + + /// + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + // Divide the stored byte magnitudes before normalization so unassociation cannot move an exact byte conversion across its rounding midpoint. + return Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToUnassociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 8, 8, 8, 8), + PixelColorType.BGR | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static Bgra32P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32P FromUnassociatedVector4(Vector4 source) => FromUnassociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32P FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32P FromUnassociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4ToBgra32P(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra32P FromAssociatedScaledVector4(Vector4 source) + { + // Rescale associated RGB when alpha rounds to a different byte so the stored channels remain associated with the alpha actually written. + return Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToBgra32P(source); + } + + /// + public static Bgra32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Bgra32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is Bgra32P other && this.Equals(other); + + /// + public readonly bool Equals(Bgra32P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => $"Bgra32P({this.R}, {this.G}, {this.B}, {this.A})"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Bgra32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + // Each converted component occupies one 32-bit lane. Reinterpreting those lanes as bytes exposes their low bytes at offsets 0, 4, 8, and 12. + Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); + return new Bgra32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs index 55971210c..17711835a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -7,11 +7,12 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four unsigned normalized values using 4 bits per component. /// +/// +/// and scaled vector conversions return all components in [0, 1]. The storage layout matches +/// DXGI_FORMAT_B4G4R4A4_UNORM. +/// public partial struct Bgra4444 : IPixel, IPackedVector { /// @@ -88,6 +89,47 @@ public partial struct Bgra4444 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra4444 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra4444 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra4444 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra4444 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Bgra4444 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs index 4c94dea5f..22a2c1f99 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -7,12 +7,13 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing unsigned normalized values ranging from 0 to 1. -/// The x , y and z components use 5 bits, and the w component uses 1 bit. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four unsigned normalized values. +/// The x, y, and z components use 5 bits, and the w component uses 1 bit. /// +/// +/// and scaled vector conversions return all components in [0, 1]. The storage layout matches +/// DXGI_FORMAT_B5G5R5A1_UNORM. +/// public partial struct Bgra5551 : IPixel, IPackedVector { /// @@ -86,6 +87,47 @@ public partial struct Bgra5551 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra5551 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra5551 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra5551 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Bgra5551 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Bgra5551 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs index 680a7ee0b..c0372ab97 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -8,11 +8,12 @@ using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing four 8-bit unsigned integer values, ranging from 0 to 255. -/// -/// Ranges from [0, 0, 0, 0] to [255, 255, 255, 255] in vector form. -/// +/// Packed pixel type containing four 8-bit unsigned integer values. /// +/// +/// returns components in [0, 255]. Scaled vector conversions map that range to +/// [0, 1]. The storage layout matches DXGI_FORMAT_R8G8B8A8_UINT. +/// public partial struct Byte4 : IPixel, IPackedVector { private static readonly Vector4 MaxBytes = Vector128.Create(255f).AsVector4(); @@ -88,6 +89,58 @@ public partial struct Byte4 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() + { + Vector4 vector = this.ToAssociatedScaledVector4(); + + // Native components use [0, 255], so association occurs in scaled [0, 1] space before mapping the result back. + return vector * MaxBytes; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Byte4 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Byte4 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Byte4 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Byte4 FromAssociatedVector4(Vector4 source) + { + // Map every native component, including alpha, to scaled [0, 1] space before unassociating. + source /= MaxBytes; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Byte4 FromScaledVector4(Vector4 source) => FromVector4(source * 255f); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs index 00deadb12..cbb0cf086 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -7,11 +7,13 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing a single 16 bit floating point value. -/// -/// Ranges from [-1, 0, 0, 1] to [1, 0, 0, 1] in vector form. -/// +/// Packed pixel type containing a single IEEE 754 binary16 floating-point value. /// +/// +/// returns the stored IEEE 754 binary16 value directly. Scaled vector conversions normalize +/// the finite range [-65504, 65504] to [0, 1]. The packed representation is binary-compatible with +/// DXGI_FORMAT_R16_FLOAT. +/// public partial struct HalfSingle : IPixel, IPackedVector { /// @@ -53,9 +55,8 @@ public partial struct HalfSingle : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToScaledVector4() { - float single = this.ToSingle() + 1F; - single /= 2F; - return new Vector4(single, 0, 0, 1F); + float scaled = HalfTypeHelper.ToScaled(this.ToSingle()); + return new Vector4(scaled, 0, 0, 1F); } /// @@ -72,16 +73,53 @@ public partial struct HalfSingle : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); - /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfSingle FromScaledVector4(Vector4 source) + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromAssociatedScaledVector4(Vector4 source) { - float scaled = source.X; - scaled *= 2F; - scaled--; - return new HalfSingle { PackedValue = HalfTypeHelper.Pack(scaled) }; + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromAssociatedVector4(Vector4 source) + { + // This format has no native alpha component: X uses the binary16 finite range, while W remains the source opacity. + source.X = HalfTypeHelper.ToScaled(source.X); + return FromAssociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfSingle FromScaledVector4(Vector4 source) + => new() { PackedValue = HalfTypeHelper.Pack(HalfTypeHelper.FromScaled(source.X)) }; + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfSingle FromVector4(Vector4 source) => new() { PackedValue = HalfTypeHelper.Pack(source.X) }; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs index 03d4dee89..ad6c7aefa 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -7,11 +7,13 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing two 16-bit floating-point values. -/// -/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. -/// +/// Packed pixel type containing two IEEE 754 binary16 floating-point values. /// +/// +/// and return the stored IEEE 754 binary16 values directly. Scaled +/// vector conversions normalize the finite range [-65504, 65504] to [0, 1]. The packed representation is +/// binary-compatible with DXGI_FORMAT_R16G16_FLOAT. +/// public partial struct HalfVector2 : IPixel, IPackedVector { /// @@ -60,9 +62,7 @@ public partial struct HalfVector2 : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToScaledVector4() { - Vector2 scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2F; + Vector2 scaled = HalfTypeHelper.ToScaled(this.ToVector2()); return new Vector4(scaled, 0F, 1F); } @@ -84,13 +84,56 @@ public partial struct HalfVector2 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector2 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector2 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector2 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector2 FromAssociatedVector4(Vector4 source) + { + // This format has no native alpha component: XY use the binary16 finite range, while W remains the source opacity. + Vector2 scaled = HalfTypeHelper.ToScaled(new Vector2(source.X, source.Y)); + source.X = scaled.X; + source.Y = scaled.Y; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfVector2 FromScaledVector4(Vector4 source) { - Vector2 scaled = new Vector2(source.X, source.Y) * 2F; - scaled -= Vector2.One; - return new HalfVector2 { PackedValue = Pack(scaled.X, scaled.Y) }; + Vector2 native = HalfTypeHelper.FromScaled(new Vector2(source.X, source.Y)); + return new HalfVector2 { PackedValue = Pack(native.X, native.Y) }; } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index d0b57d788..42531937c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -7,11 +7,13 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing four 16-bit floating-point values. -/// -/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four IEEE 754 binary16 floating-point values. /// +/// +/// returns the stored IEEE 754 binary16 values directly. Scaled vector conversions normalize +/// the finite range [-65504, 65504] to [0, 1]. The packed representation is binary-compatible with +/// DXGI_FORMAT_R16G16B16A16_FLOAT. +/// public partial struct HalfVector4 : IPixel, IPackedVector { /// @@ -63,13 +65,7 @@ public partial struct HalfVector4 : IPixel, IPackedVector /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToScaledVector4() - { - Vector4 scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2f; - return scaled; - } + public readonly Vector4 ToScaledVector4() => HalfTypeHelper.ToScaled(this.ToVector4()); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -89,15 +85,62 @@ public partial struct HalfVector4 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); - /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static HalfVector4 FromScaledVector4(Vector4 source) + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() { - source *= 2f; - source -= Vector4.One; - return FromVector4(source); + Vector4 vector = this.ToAssociatedScaledVector4(); + + // Association is defined in scaled color space, so map the associated result back to the native binary16 range. + return HalfTypeHelper.FromScaled(vector); } + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4 FromAssociatedVector4(Vector4 source) + { + // Restore scaled opacity before unassociating the color channels. + source = HalfTypeHelper.ToScaled(source); + return FromAssociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4 FromScaledVector4(Vector4 source) => FromVector4(HalfTypeHelper.FromScaled(source)); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static HalfVector4 FromVector4(Vector4 source) => new() { PackedValue = Pack(source) }; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs new file mode 100644 index 000000000..50fc27255 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4P.cs @@ -0,0 +1,270 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four associated 16-bit floating-point values. +/// +/// +/// returns the stored associated IEEE 754 binary16 values directly. Scaled vector conversions +/// normalize the finite range [-65504, 65504] to [0, 1] while preserving associated alpha. The packed +/// representation is binary-compatible with DXGI_FORMAT_R16G16B16A16_FLOAT. +/// +public partial struct HalfVector4P : IPixel, IPackedVector +{ + /// + /// Initializes a new instance of the struct. + /// + /// The associated x-component. + /// The associated y-component. + /// The associated z-component. + /// The alpha component. + public HalfVector4P(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the associated component values. + public HalfVector4P(Vector4 vector) => this.PackedValue = Pack(vector); + + /// + public ulong PackedValue { get; set; } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(HalfVector4P left, HalfVector4P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(HalfVector4P left, HalfVector4P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() + { + return Rgba32.FromScaledVector4(this.ToUnassociatedScaledVector4()); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => HalfTypeHelper.ToScaled(this.ToVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.UnPremultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() => new( + HalfTypeHelper.Unpack((ushort)this.PackedValue), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x10)), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x20)), + HalfTypeHelper.Unpack((ushort)(this.PackedValue >> 0x30))); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() + { + Vector4 vector = this.ToUnassociatedScaledVector4(); + return HalfTypeHelper.FromScaled(vector); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 16, 16, 16, 16), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + public static HalfVector4P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static HalfVector4P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4P FromUnassociatedVector4(Vector4 source) + { + source = HalfTypeHelper.ToScaled(source); + return FromUnassociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4P FromAssociatedVector4(Vector4 source) + { + source = HalfTypeHelper.ToScaled(source); + return FromAssociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4P FromUnassociatedScaledVector4(Vector4 source) => PackAssociatedScaledVector4(Associate(source)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HalfVector4P FromAssociatedScaledVector4(Vector4 source) => PackAssociatedScaledVector4(Reassociate(source)); + + /// + public static HalfVector4P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static HalfVector4P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is HalfVector4P other && this.Equals(other); + + /// + public readonly bool Equals(HalfVector4P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"HalfVector4P({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + /// + /// Converts an unassociated scaled vector to the associated representation of a half-precision destination. + /// + /// The unassociated scaled vector. + /// The associated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Associate(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + + // RGB must use the scaled alpha that the binary16 representation can reproduce. + source.W = QuantizeScaledAlpha(source.W); + Numerics.Premultiply(ref source); + return source; + } + + /// + /// Reassociates a scaled vector with the alpha value the destination stores. + /// + /// The associated scaled vector. + /// The reassociated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Reassociate(Vector4 source) + { + float alpha = source.W; + + if (alpha <= 0) + { + return Vector4.Zero; + } + + float storedAlpha = QuantizeScaledAlpha(alpha); + + // Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization. + source *= storedAlpha / alpha; + source.W = storedAlpha; + Numerics.ClampRgbToAlpha(ref source); + return source; + } + + /// + /// Packs an associated scaled vector into the native binary16 representation. + /// + /// The associated scaled vector. + /// The packed pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static HalfVector4P PackAssociatedScaledVector4(Vector4 source) + { + source = HalfTypeHelper.FromScaled(source); + return new HalfVector4P { PackedValue = Pack(source) }; + } + + /// + /// Quantizes scaled alpha through the native binary16 representation. + /// + /// The scaled alpha value. + /// The scaled value represented by the stored binary16 component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float QuantizeScaledAlpha(float alpha) + { + float nativeAlpha = HalfTypeHelper.FromScaled(Numerics.Clamp(alpha, 0F, 1F)); + return HalfTypeHelper.ToScaled(HalfTypeHelper.Unpack(HalfTypeHelper.Pack(nativeAlpha))); + } + + /// + /// Packs native half-precision components into a 64-bit value. + /// + /// The native component values. + /// The packed value. + private static ulong Pack(Vector4 vector) + { + ulong x = HalfTypeHelper.Pack(vector.X); + ulong y = (ulong)HalfTypeHelper.Pack(vector.Y) << 0x10; + ulong z = (ulong)HalfTypeHelper.Pack(vector.Z) << 0x20; + ulong w = (ulong)HalfTypeHelper.Pack(vector.W) << 0x30; + return x | y | z | w; + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs index c5893f770..1ab8e9650 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs @@ -77,6 +77,48 @@ public partial struct L16 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L16 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L16 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L16 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L16 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static L16 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs index 008eed459..9908e0306 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs @@ -79,6 +79,48 @@ public partial struct L8 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L8 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L8 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L8 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static L8 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static L8 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs index 6c3fa7829..3a15f24d5 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs @@ -106,6 +106,47 @@ public partial struct La16 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La16 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La16 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La16 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La16 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static La16 FromScaledVector4(Vector4 source) => Pack(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs index c99f6fe60..ae3190d96 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs @@ -103,6 +103,47 @@ public partial struct La32 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La32 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static La32 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static La32 FromScaledVector4(Vector4 source) => Pack(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index da2ab2440..6039bd9d7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -7,15 +7,20 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing two 8-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. -/// +/// Packed pixel type containing two 8-bit signed normalized values. /// +/// +/// and return components in the native signed-normalized range +/// [-1, 1]. Scaled vector conversions return components in [0, 1]. +/// The packed two's-complement codes -128 and -127 both represent -1, +/// matching DXGI_FORMAT_R8G8_SNORM. +/// public partial struct NormalizedByte2 : IPixel, IPackedVector { private const float MaxPos = 127f; + private const float ScaledMagnitude = MaxPos * 2F; private static readonly Vector2 Half = new(MaxPos); + private static readonly Vector2 Minimum = new(-MaxPos); private static readonly Vector2 MinusOne = new(-1f); /// @@ -67,9 +72,14 @@ public partial struct NormalizedByte2 : IPixel, IPackedVector> 8)); + + // SNORM reserves both minimum two's-complement codes for -1. Clamp before offsetting so raw -128 cannot escape the scaled range. + scaled = Vector2.Max(scaled, Minimum); + scaled += Half; + scaled /= ScaledMagnitude; return new Vector4(scaled, 0f, 1f); } @@ -87,6 +97,49 @@ public partial struct NormalizedByte2 : IPixel, IPackedVector public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte2 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte2 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte2 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte2 FromAssociatedVector4(Vector4 source) + { + // Only the stored color components use the native [-1, 1] encoding; W remains the normalized source alpha. + source.X = (source.X + 1F) / 2F; + source.Y = (source.Y + 1F) / 2F; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NormalizedByte2 FromScaledVector4(Vector4 source) @@ -158,9 +211,15 @@ public partial struct NormalizedByte2 : IPixel, IPackedVector /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector2 ToVector2() => new( - (sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos); + public readonly Vector2 ToVector2() + { + Vector2 vector = new( + (sbyte)(this.PackedValue & 0xFF), + (sbyte)(this.PackedValue >> 8)); + + // DirectX SNORM maps both -128 and -127 to -1. + return Vector2.Max(vector, Minimum) / MaxPos; + } /// public override readonly bool Equals(object? obj) => obj is NormalizedByte2 other && this.Equals(other); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs index 1fb386725..2f91718cf 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -8,15 +8,20 @@ using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing four 8-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four 8-bit signed normalized values. /// +/// +/// returns components in the native signed-normalized range [-1, 1]. +/// Scaled vector conversions return components in [0, 1]. +/// The packed two's-complement codes -128 and -127 both represent -1, +/// matching DXGI_FORMAT_R8G8B8A8_SNORM. +/// public partial struct NormalizedByte4 : IPixel, IPackedVector { private const float MaxPos = 127f; + private const float ScaledMagnitude = MaxPos * 2F; private static readonly Vector4 Half = Vector128.Create(MaxPos).AsVector4(); + private static readonly Vector4 Minimum = -Half; private static readonly Vector4 MinusOne = Vector128.Create(-1f).AsVector4(); /// @@ -70,19 +75,30 @@ public partial struct NormalizedByte4 : IPixel, IPackedVector> 0) & 0xFF), + (sbyte)((this.PackedValue >> 8) & 0xFF), + (sbyte)((this.PackedValue >> 16) & 0xFF), + (sbyte)((this.PackedValue >> 24) & 0xFF)); + + // SNORM reserves both minimum two's-complement codes for -1. Clamp before offsetting so raw -128 cannot escape the scaled range. + return (Vector4.Max(scaled, Minimum) + Half) / ScaledMagnitude; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new( - (sbyte)((this.PackedValue >> 0) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 8) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 16) & 0xFF) / MaxPos, - (sbyte)((this.PackedValue >> 24) & 0xFF) / MaxPos); + public readonly Vector4 ToVector4() + { + Vector4 vector = new( + (sbyte)((this.PackedValue >> 0) & 0xFF), + (sbyte)((this.PackedValue >> 8) & 0xFF), + (sbyte)((this.PackedValue >> 16) & 0xFF), + (sbyte)((this.PackedValue >> 24) & 0xFF)); + + // DirectX SNORM maps both -128 and -127 to -1. + return Vector4.Max(vector, Minimum) / MaxPos; + } /// public static PixelTypeInfo GetPixelTypeInfo() @@ -94,6 +110,61 @@ public partial struct NormalizedByte4 : IPixel, IPackedVector public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() + { + Vector4 vector = this.ToAssociatedScaledVector4(); + + // Native components use an affine [-1, 1] encoding, so direct multiplication would use the wrong zero point. + vector *= 2F; + vector -= Vector4.One; + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4 FromAssociatedVector4(Vector4 source) + { + // Map the affine native encoding to logical [0, 1] space before unassociating. + source += Vector4.One; + source /= 2F; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NormalizedByte4 FromScaledVector4(Vector4 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs new file mode 100644 index 000000000..e70dc548c --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4P.cs @@ -0,0 +1,318 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four associated 8-bit signed normalized values ranging from -1 to 1. +/// +/// +/// returns associated components in the native signed-normalized range [-1, 1]. +/// Scaled vector conversions return associated components in [0, 1]. +/// The packed two's-complement codes -128 and -127 both represent -1, +/// matching DXGI_FORMAT_R8G8B8A8_SNORM. +/// +public partial struct NormalizedByte4P : IPixel, IPackedVector +{ + private const float MaxPos = 127F; + private const float ScaledMagnitude = MaxPos * 2F; + private static readonly Vector4 Half = Vector128.Create(MaxPos).AsVector4(); + private static readonly Vector4 Minimum = -Half; + private static readonly Vector4 MinusOne = Vector128.Create(-1F).AsVector4(); + + /// + /// Initializes a new instance of the struct. + /// + /// The associated x-component. + /// The associated y-component. + /// The associated z-component. + /// The alpha component. + public NormalizedByte4P(float x, float y, float z, float w) + : this(new Vector4(x, y, z, w)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the associated component values. + public NormalizedByte4P(Vector4 vector) => this.PackedValue = Pack(vector); + + /// + public uint PackedValue { get; set; } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(NormalizedByte4P left, NormalizedByte4P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(NormalizedByte4P left, NormalizedByte4P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(ToUnassociatedScaledVector4(this)); + + /// + public readonly Vector4 ToScaledVector4() + { + // Offset the exact signed components before division. Mapping an already normalized value through (value + 1) / 2 loses precision near -1 through cancellation. + Vector4 scaled = new( + (sbyte)((this.PackedValue >> 0) & 0xFF), + (sbyte)((this.PackedValue >> 8) & 0xFF), + (sbyte)((this.PackedValue >> 16) & 0xFF), + (sbyte)((this.PackedValue >> 24) & 0xFF)); + + // SNORM reserves both minimum two's-complement codes for -1. Clamp before offsetting so raw -128 cannot escape the scaled range. + return (Vector4.Max(scaled, Minimum) + Half) / ScaledMagnitude; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => ToUnassociatedScaledVector4(this); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() + { + Vector4 vector = new( + (sbyte)((this.PackedValue >> 0) & 0xFF), + (sbyte)((this.PackedValue >> 8) & 0xFF), + (sbyte)((this.PackedValue >> 16) & 0xFF), + (sbyte)((this.PackedValue >> 24) & 0xFF)); + + // DirectX SNORM maps both -128 and -127 to -1. + return Vector4.Max(vector, Minimum) / MaxPos; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() + { + // Association is defined in the common scaled domain. Signed-native W is an affine encoding of alpha, so it cannot be used as a divisor directly. + Vector4 vector = this.ToUnassociatedScaledVector4(); + vector *= 2F; + vector -= Vector4.One; + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 8, 8, 8, 8), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + public static NormalizedByte4P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static NormalizedByte4P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4P FromUnassociatedVector4(Vector4 source) + { + // Convert the signed-native range to the common scaled domain before associating because native W is not opacity. + source += Vector4.One; + source /= 2F; + return FromUnassociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4P FromAssociatedVector4(Vector4 source) + { + // Reassociation must also operate in scaled space so RGB follows the quantized scaled alpha rather than the signed-native W component. + source += Vector4.One; + source /= 2F; + return FromAssociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4P FromUnassociatedScaledVector4(Vector4 source) => PackAssociatedScaledVector4(Associate(source)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedByte4P FromAssociatedScaledVector4(Vector4 source) => PackAssociatedScaledVector4(Reassociate(source)); + + /// + /// Packs an associated scaled vector into signed-normalized storage. + /// + /// The associated scaled vector. + /// The packed pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static NormalizedByte4P PackAssociatedScaledVector4(Vector4 source) + { + // Signed-normalized storage uses the native [-1, 1] range even though association is defined in scaled opacity space. + source *= 2F; + source -= Vector4.One; + return new NormalizedByte4P { PackedValue = Pack(source) }; + } + + /// + public static NormalizedByte4P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static NormalizedByte4P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is NormalizedByte4P other && this.Equals(other); + + /// + public readonly bool Equals(NormalizedByte4P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() + { + Vector4 vector = this.ToVector4(); + return FormattableString.Invariant($"NormalizedByte4P({vector.X:#0.##}, {vector.Y:#0.##}, {vector.Z:#0.##}, {vector.W:#0.##})"); + } + + /// + /// Converts an unassociated scaled vector to the associated representation of a signed-normalized-byte destination. + /// + /// The unassociated scaled vector. + /// The associated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Associate(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + + // Reproduce the signed-normalized packer's alpha quantization, then associate RGB with the exact alpha that will be stored. + float nativeAlpha = Numerics.Clamp((source.W * 2F) - 1F, -1F, 1F); + float storedAlpha = MathF.Round(nativeAlpha * MaxPos); + source.W = (storedAlpha + MaxPos) / ScaledMagnitude; + Numerics.Premultiply(ref source); + return source; + } + + /// + /// Converts the stored associated components to an unassociated scaled vector. + /// + /// The associated pixel. + /// The unassociated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 ToUnassociatedScaledVector4(NormalizedByte4P source) + { + // Offset signed storage into exact nonnegative byte magnitudes before division so the quotient retains the destination's byte-rounding midpoint. + Vector4 vector = new( + (sbyte)(source.PackedValue >> 0), + (sbyte)(source.PackedValue >> 8), + (sbyte)(source.PackedValue >> 16), + (sbyte)(source.PackedValue >> 24)); + + // Clamp the duplicate SNORM minimum encoding before converting it to the nonnegative associated domain. + vector = Vector4.Max(vector, Minimum) + Half; + + if (vector.W == 0F) + { + // Numerics.UnPremultiply preserves RGB when alpha is zero. Normalize the stored components because they already are the unassociated value in this case. + return vector / ScaledMagnitude; + } + + Numerics.UnPremultiply(ref vector); + vector.W /= ScaledMagnitude; + return vector; + } + + /// + /// Reassociates a scaled vector with the alpha value the destination stores. + /// + /// The associated scaled vector. + /// The reassociated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Reassociate(Vector4 source) + { + float alpha = source.W; + + if (alpha <= 0) + { + return Vector4.Zero; + } + + float nativeAlpha = Numerics.Clamp((alpha * 2F) - 1F, -1F, 1F); + float storedAlpha = (MathF.Round(nativeAlpha * MaxPos) + MaxPos) / ScaledMagnitude; + + // Associated RGB scales by the same ratio as alpha. Applying that ratio directly avoids the extra division and multiplication of an unpremultiply/premultiply round trip and preserves exact midpoints when alpha needs no quantization. + source *= storedAlpha / alpha; + source.W = storedAlpha; + Numerics.ClampRgbToAlpha(ref source); + return source; + } + + /// + /// Packs native signed normalized components into a 32-bit value. + /// + /// The native component values. + /// The packed value. + private static uint Pack(Vector4 vector) + { + vector = Numerics.Clamp(vector, MinusOne, Vector4.One) * Half; + + uint byte4 = ((uint)Convert.ToInt16(MathF.Round(vector.X)) & 0xFF) << 0; + uint byte3 = ((uint)Convert.ToInt16(MathF.Round(vector.Y)) & 0xFF) << 8; + uint byte2 = ((uint)Convert.ToInt16(MathF.Round(vector.Z)) & 0xFF) << 16; + uint byte1 = ((uint)Convert.ToInt16(MathF.Round(vector.W)) & 0xFF) << 24; + + return byte4 | byte3 | byte2 | byte1; + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index 0942c3a76..3da0f7f0b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -7,15 +7,19 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing two 16-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, 0, 1] to [1, 1, 0, 1] in vector form. -/// +/// Packed pixel type containing two 16-bit signed normalized values. /// +/// +/// and return components in the native signed-normalized range +/// [-1, 1]. Scaled vector conversions return components in [0, 1]. +/// The packed two's-complement codes -32768 and -32767 both represent -1, +/// matching DXGI_FORMAT_R16G16_SNORM. +/// public partial struct NormalizedShort2 : IPixel, IPackedVector { // Largest two byte positive number 0xFFFF >> 1; private const float MaxPos = 0x7FFF; + private const float ScaledMagnitude = MaxPos * 2F; private static readonly Vector2 Max = new(MaxPos); private static readonly Vector2 Min = Vector2.Negate(Max); @@ -69,9 +73,14 @@ public partial struct NormalizedShort2 : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToScaledVector4() { - Vector2 scaled = this.ToVector2(); - scaled += Vector2.One; - scaled /= 2f; + Vector2 scaled = new( + (short)(this.PackedValue & 0xFFFF), + (short)(this.PackedValue >> 0x10)); + + // SNORM reserves both minimum two's-complement codes for -1. Clamp before offsetting so raw -32768 cannot escape the scaled range. + scaled = Vector2.Max(scaled, Min); + scaled += Max; + scaled /= ScaledMagnitude; return new Vector4(scaled, 0f, 1f); } @@ -89,6 +98,49 @@ public partial struct NormalizedShort2 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort2 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort2 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort2 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort2 FromAssociatedVector4(Vector4 source) + { + // Only the stored color components use the native [-1, 1] encoding; W remains the normalized source alpha. + source.X = (source.X + 1F) / 2F; + source.Y = (source.Y + 1F) / 2F; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NormalizedShort2 FromScaledVector4(Vector4 source) @@ -160,9 +212,15 @@ public partial struct NormalizedShort2 : IPixel, IPackedVector /// /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector2 ToVector2() => new( - (short)(this.PackedValue & 0xFFFF) / MaxPos, - (short)(this.PackedValue >> 0x10) / MaxPos); + public readonly Vector2 ToVector2() + { + Vector2 vector = new( + (short)(this.PackedValue & 0xFFFF), + (short)(this.PackedValue >> 0x10)); + + // DirectX SNORM maps both -32768 and -32767 to -1. + return Vector2.Max(vector, Min) / MaxPos; + } /// public override readonly bool Equals(object? obj) => obj is NormalizedShort2 other && this.Equals(other); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs index 2b33fec27..7acac0b7d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -8,15 +8,19 @@ using System.Runtime.Intrinsics; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing four 16-bit signed normalized values, ranging from −1 to 1. -/// -/// Ranges from [-1, -1, -1, -1] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four 16-bit signed normalized values. /// +/// +/// returns components in the native signed-normalized range [-1, 1]. +/// Scaled vector conversions return components in [0, 1]. +/// The packed two's-complement codes -32768 and -32767 both represent -1, +/// matching DXGI_FORMAT_R16G16B16A16_SNORM. +/// public partial struct NormalizedShort4 : IPixel, IPackedVector { // Largest two byte positive number 0xFFFF >> 1; private const float MaxPos = 0x7FFF; + private const float ScaledMagnitude = MaxPos * 2F; private static readonly Vector4 Max = Vector128.Create(MaxPos).AsVector4(); private static readonly Vector4 Min = Vector4.Negate(Max); @@ -71,19 +75,30 @@ public partial struct NormalizedShort4 : IPixel, IPackedVector [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Vector4 ToScaledVector4() { - Vector4 scaled = this.ToVector4(); - scaled += Vector4.One; - scaled /= 2f; - return scaled; + // Offset the exact signed components before division. Mapping an already normalized value through (value + 1) / 2 loses precision near -1 through cancellation. + Vector4 scaled = new( + (short)((this.PackedValue >> 0x00) & 0xFFFF), + (short)((this.PackedValue >> 0x10) & 0xFFFF), + (short)((this.PackedValue >> 0x20) & 0xFFFF), + (short)((this.PackedValue >> 0x30) & 0xFFFF)); + + // SNORM reserves both minimum two's-complement codes for -1. Clamp before offsetting so raw -32768 cannot escape the scaled range. + return (Vector4.Max(scaled, Min) + Max) / ScaledMagnitude; } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new( - (short)((this.PackedValue >> 0x00) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x10) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x20) & 0xFFFF) / MaxPos, - (short)((this.PackedValue >> 0x30) & 0xFFFF) / MaxPos); + public readonly Vector4 ToVector4() + { + Vector4 vector = new( + (short)((this.PackedValue >> 0x00) & 0xFFFF), + (short)((this.PackedValue >> 0x10) & 0xFFFF), + (short)((this.PackedValue >> 0x20) & 0xFFFF), + (short)((this.PackedValue >> 0x30) & 0xFFFF)); + + // DirectX SNORM maps both -32768 and -32767 to -1. + return Vector4.Max(vector, Min) / MaxPos; + } /// public static PixelTypeInfo GetPixelTypeInfo() @@ -95,6 +110,61 @@ public partial struct NormalizedShort4 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() + { + Vector4 vector = this.ToAssociatedScaledVector4(); + + // Native components use an affine [-1, 1] encoding, so direct multiplication would use the wrong zero point. + vector *= 2F; + vector -= Vector4.One; + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort4 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort4 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort4 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NormalizedShort4 FromAssociatedVector4(Vector4 source) + { + // Map the affine native encoding to logical [0, 1] space before unassociating. + source += Vector4.One; + source /= 2F; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NormalizedShort4 FromScaledVector4(Vector4 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs index da0e6ec60..da6762912 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/A8.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,20 @@ public partial struct A8 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // A8 stores alpha without color components, so association cannot alter any emitted or consumed vector component. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) => this.FromUnassociatedVector4Destructive(configuration, source, destination); + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) => this.FromUnassociatedScaledVector4Destructive(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs new file mode 100644 index 000000000..746fbf85f --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32P.PixelOperations.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Abgr32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Native and scaled vectors have the same range for this format, so the byte-specialized converter is valid for both contracts. + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // The converter rescales RGB when alpha rounds so the channels remain associated with the byte alpha actually stored. + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs new file mode 100644 index 000000000..80dbd3c31 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Argb32P.PixelOperations.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Argb32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Native and scaled vectors have the same range for this format, so the byte-specialized converter is valid for both contracts. + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // The converter rescales RGB when alpha rounds so the channels remain associated with the byte alpha actually stored. + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs index 87175fe21..737f6999f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgr565.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct Bgr565 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs new file mode 100644 index 000000000..606990d66 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Bgra32P.PixelOperations.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Bgra32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Native and scaled vectors have the same range for this format, so the byte-specialized converter is valid for both contracts. + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // The converter rescales RGB when alpha rounds so the channels remain associated with the byte alpha actually stored. + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs index c012547c9..7f0919919 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Byte4.PixelOperations.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +15,93 @@ public partial struct Byte4 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // Protected hooks are same-instance extension points. Cross-format reuse must request the representation through public modifier dispatch. + private const PixelConversionModifiers UnassociatedScaledModifiers = PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply; + private const PixelConversionModifiers AssociatedScaledModifiers = PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply; + + private static readonly Vector4 NativeMaximum = new(byte.MaxValue); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Byte4 and Rgba32 have the same packed layout. Reusing the optimized Rgba32 unpacker avoids four scalar byte + // extractions per pixel; the affine transform then restores Byte4's native [0, 255] vector range. + PixelOperations.Instance.ToVector4(configuration, MemoryMarshal.Cast(source), destination, UnassociatedScaledModifiers); + Vector4Converters.MultiplyThenAdd(destination, NativeMaximum, Vector4.Zero); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Association is performed in normalized color space before mapping all four components back to Byte4's native range. + PixelOperations.Instance.ToVector4(configuration, MemoryMarshal.Cast(source), destination, AssociatedScaledModifiers); + Vector4Converters.MultiplyThenAdd(destination, NativeMaximum, Vector4.Zero); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + PixelOperations.Instance.ToVector4(configuration, MemoryMarshal.Cast(source), destination, UnassociatedScaledModifiers); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + PixelOperations.Instance.ToVector4(configuration, MemoryMarshal.Cast(source), destination, AssociatedScaledModifiers); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Vector4Converters.AddThenDivide(source, Vector4.Zero, NativeMaximum); + PixelOperations.Instance.FromVector4Destructive(configuration, source, MemoryMarshal.Cast(destination), UnassociatedScaledModifiers); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Native alpha is normalized with RGB so the Rgba32 bulk path can unassociate in a single, consistent coordinate space. + Vector4Converters.AddThenDivide(source, Vector4.Zero, NativeMaximum); + PixelOperations.Instance.FromVector4Destructive(configuration, source, MemoryMarshal.Cast(destination), AssociatedScaledModifiers); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + PixelOperations.Instance.FromVector4Destructive(configuration, source, MemoryMarshal.Cast(destination), UnassociatedScaledModifiers); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + PixelOperations.Instance.FromVector4Destructive(configuration, source, MemoryMarshal.Cast(destination), AssociatedScaledModifiers); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs index a5e4e3048..bcfad0117 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs @@ -38,6 +38,7 @@ public partial struct Abgr32 source.CopyTo(destination.Slice(0, source.Length)); } + /// public override void FromVector4Destructive( Configuration configuration, @@ -45,7 +46,20 @@ public partial struct Abgr32 Span destination, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); + + destination = destination[..sourceVectors.Length]; + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + + Span destinationBytes = MemoryMarshal.Cast(destination); + + // The SIMD saturating conversion produces RGBA byte order. Reusing the destination + // buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by + // the generic Rgba-compatible path for non-Rgba32 formats. + SimdUtils.NormalizedFloatToByteSaturate( + MemoryMarshal.Cast(sourceVectors), + destinationBytes); + PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs index 30fed8d1c..3ef93de9f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs @@ -38,6 +38,7 @@ public partial struct Argb32 source.CopyTo(destination.Slice(0, source.Length)); } + /// public override void FromVector4Destructive( Configuration configuration, @@ -45,7 +46,20 @@ public partial struct Argb32 Span destination, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); + + destination = destination[..sourceVectors.Length]; + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + + Span destinationBytes = MemoryMarshal.Cast(destination); + + // The SIMD saturating conversion produces RGBA byte order. Reusing the destination + // buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by + // the generic Rgba-compatible path for non-Rgba32 formats. + SimdUtils.NormalizedFloatToByteSaturate( + MemoryMarshal.Cast(sourceVectors), + destinationBytes); + PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs index e3ff3a7fe..773b8c1b8 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs @@ -7,7 +7,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; - namespace SixLabors.ImageSharp.PixelFormats; /// @@ -45,7 +44,7 @@ public partial struct Bgr24 Span destination, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs index ae6be4401..6afcb6432 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs @@ -38,6 +38,7 @@ public partial struct Bgra32 source.CopyTo(destination.Slice(0, source.Length)); } + /// public override void FromVector4Destructive( Configuration configuration, @@ -45,7 +46,20 @@ public partial struct Bgra32 Span destination, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); + + destination = destination[..sourceVectors.Length]; + Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + + Span destinationBytes = MemoryMarshal.Cast(destination); + + // The SIMD saturating conversion produces RGBA byte order. Reusing the destination + // buffer and shuffling it in place avoids the row-sized Rgba32 temporary used by + // the generic Rgba-compatible path for non-Rgba32 formats. + SimdUtils.NormalizedFloatToByteSaturate( + MemoryMarshal.Cast(sourceVectors), + destinationBytes); + PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs index fd6465a22..5c8aeea76 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs @@ -7,7 +7,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats.Utils; - namespace SixLabors.ImageSharp.PixelFormats; /// @@ -45,7 +44,7 @@ public partial struct Rgb24 Span destination, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(PixelConversionModifiers.Scale)); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude index ae29223de..abf077d15 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude @@ -147,10 +147,20 @@ using SixLabors.ImageSharp.PixelFormats.Utils; void GenerateRgba32CompatibleVector4ConversionMethods(string pixelType, bool hasAlpha) { - string removeTheseModifiers = "PixelConversionModifiers.Scale"; + string removeFromModifiers = "PixelConversionModifiers.Scale"; + string removeToModifiers = "PixelConversionModifiers.Scale"; + if (!hasAlpha) { - removeTheseModifiers += " | PixelConversionModifiers.Premultiply"; + // Premultiplication cannot change an alpha-less source, but premultiplied input + // must still be unassociated before its alpha channel is discarded. + removeToModifiers += " | PixelConversionModifiers.Premultiply"; + } + + if (hasAlpha) + { + GenerateRgba32Compatible32BitVector4ConversionMethods(pixelType, removeFromModifiers); + return; } #> @@ -161,7 +171,46 @@ using SixLabors.ImageSharp.PixelFormats.Utils; Span<<#=pixelType#>> destination, PixelConversionModifiers modifiers) { - Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(<#=removeTheseModifiers#>)); + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destination, modifiers.Remove(<#=removeFromModifiers#>)); + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan<<#=pixelType#>> source, + Span destination, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, source, destination, modifiers.Remove(<#=removeToModifiers#>)); + } +<#+ + } + + 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); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs index 770f3a1de..07428f484 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfSingle.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct HalfSingle /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs index 160ab9bd0..e167c4eb7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector2.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct HalfVector2 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs index 703138454..9eae7d5cb 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4.PixelOperations.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +15,91 @@ public partial struct HalfVector4 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeToScaledMultiplier = new(HalfTypeHelper.InverseFiniteRange); + private static readonly Vector4 NativeToScaledOffset = new(HalfTypeHelper.ScaledMidpoint); + private static readonly Vector4 ScaledToNativeMultiplier = new(HalfTypeHelper.FiniteRange); + private static readonly Vector4 ScaledToNativeOffset = new(HalfTypeHelper.FiniteMinimum); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // The half-vector layouts are identical, so the shared expansion kernel can process the source without copying it. + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination[..source.Length]); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Association uses normalized opacity, not the native binary16 alpha value. + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination); + Vector4Converters.MultiplyThenAdd(destination, NativeToScaledMultiplier, NativeToScaledOffset); + Numerics.Premultiply(destination); + Vector4Converters.MultiplyThenAdd(destination, ScaledToNativeMultiplier, ScaledToNativeOffset); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination); + Vector4Converters.MultiplyThenAdd(destination, NativeToScaledMultiplier, NativeToScaledOffset); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + this.ToUnassociatedScaledVector4(configuration, source, destination); + Numerics.Premultiply(destination[..source.Length]); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // DirectX half vectors are not normalized formats, so values outside the nominal color range must reach storage unchanged. + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination[..source.Length])); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // Restore normalized opacity before unassociating, then return the result to the native binary16 range. + Vector4Converters.MultiplyThenAdd(source, NativeToScaledMultiplier, NativeToScaledOffset); + Numerics.UnPremultiply(source); + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination[..source.Length])); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination[..source.Length])); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Numerics.UnPremultiply(source); + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination[..source.Length])); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs new file mode 100644 index 000000000..52b2dbf37 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/HalfVector4P.PixelOperations.cs @@ -0,0 +1,375 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct HalfVector4P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + private static readonly Vector4 NativeToScaledMultiplier = new(HalfTypeHelper.InverseFiniteRange); + private static readonly Vector4 NativeToScaledOffset = new(HalfTypeHelper.ScaledMidpoint); + private static readonly Vector4 ScaledToNativeMultiplier = new(HalfTypeHelper.FiniteRange); + private static readonly Vector4 ScaledToNativeOffset = new(HalfTypeHelper.FiniteMinimum); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + this.ToUnassociatedScaledVector4(configuration, source, destination); + Vector4Converters.MultiplyThenAdd(destination[..source.Length], ScaledToNativeMultiplier, ScaledToNativeOffset); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination[..source.Length]); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + this.ToAssociatedScaledVector4(configuration, source, destination); + Numerics.UnPremultiply(destination[..source.Length]); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination); + Vector4Converters.MultiplyThenAdd(destination, NativeToScaledMultiplier, NativeToScaledOffset); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Vector4Converters.MultiplyThenAdd(source, NativeToScaledMultiplier, NativeToScaledOffset); + Associate(source); + PackAssociatedScaled(source, destination[..source.Length]); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Vector4Converters.MultiplyThenAdd(source, NativeToScaledMultiplier, NativeToScaledOffset); + Reassociate(source); + PackAssociatedScaled(source, destination[..source.Length]); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Associate(source); + PackAssociatedScaled(source, destination[..source.Length]); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Reassociate(source); + PackAssociatedScaled(source, destination[..source.Length]); + } + + /// + /// Associates scaled vectors with the alpha values representable by native binary16 storage. + /// + /// The vectors to convert in place. + private static void Associate(Span source) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + int vectorsPerRegister = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) + { + ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Associate(vector); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorsPerRegister = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) + { + ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Associate(vector); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i < source.Length; i++) + { + ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Associate(vector); + } + + return; + } + + for (; i < source.Length; i++) + { + Unsafe.Add(ref sourceBase, (uint)i) = HalfVector4P.Associate(Unsafe.Add(ref sourceBase, (uint)i)); + } + } + + /// + /// Reassociates scaled vectors with the alpha values representable by native binary16 storage. + /// + /// The vectors to convert in place. + private static void Reassociate(Span source) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + int vectorsPerRegister = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) + { + ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Reassociate(vector); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorsPerRegister = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - vectorsPerRegister; i += vectorsPerRegister) + { + ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Reassociate(vector); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i < source.Length; i++) + { + ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + vector = Reassociate(vector); + } + + return; + } + + for (; i < source.Length; i++) + { + Unsafe.Add(ref sourceBase, (uint)i) = HalfVector4P.Reassociate(Unsafe.Add(ref sourceBase, (uint)i)); + } + } + + /// + /// Converts an unassociated scaled vector to associated scaled components. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Associate(Vector128 source) + { + source = ClampUnit(source); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 storedAlpha = QuantizeScaledAlpha(alpha); + Vector128 result = source * storedAlpha; + return Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), storedAlpha, result); + } + + /// + /// Converts unassociated scaled vectors to associated scaled components. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Associate(Vector256 source) + { + source = ClampUnit(source); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 storedAlpha = QuantizeScaledAlpha(alpha); + Vector256 result = source * storedAlpha; + return Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), storedAlpha, result); + } + + /// + /// Converts unassociated scaled vectors to associated scaled components. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Associate(Vector512 source) + { + source = ClampUnit(source); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 storedAlpha = QuantizeScaledAlpha(alpha); + Vector512 result = source * storedAlpha; + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Reassociates an associated scaled vector after alpha quantization. + /// + /// The associated vectors. + /// The reassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Reassociate(Vector128 source) + { + Vector128 zero = Vector128.Zero; + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 storedAlpha = QuantizeScaledAlpha(alpha); + Vector128 result = source * (storedAlpha / alpha); + result = Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), storedAlpha, result); + result = Vector128.Min(Vector128.Max(result, zero), storedAlpha); + return Vector128.ConditionalSelect(Vector128.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates associated scaled vectors after alpha quantization. + /// + /// The associated vectors. + /// The reassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Reassociate(Vector256 source) + { + Vector256 zero = Vector256.Zero; + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 storedAlpha = QuantizeScaledAlpha(alpha); + Vector256 result = source * (storedAlpha / alpha); + result = Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), storedAlpha, result); + result = Vector256.Min(Vector256.Max(result, zero), storedAlpha); + return Vector256.ConditionalSelect(Vector256.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates associated scaled vectors after alpha quantization. + /// + /// The associated vectors. + /// The reassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Reassociate(Vector512 source) + { + Vector512 zero = Vector512.Zero; + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 storedAlpha = QuantizeScaledAlpha(alpha); + Vector512 result = source * (storedAlpha / alpha); + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + result = Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector512.Min(Vector512.Max(result, zero), storedAlpha); + return Vector512.ConditionalSelect(Vector512.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Quantizes scaled alpha through the native binary16 representation. + /// + /// The scaled alpha lanes. + /// The scaled alpha values represented by binary16 storage. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 QuantizeScaledAlpha(Vector128 alpha) + { + Vector128 native = (ClampUnit(alpha) * Vector128.Create(HalfTypeHelper.FiniteRange)) + Vector128.Create(HalfTypeHelper.FiniteMinimum); + return (HalfTypeHelper.RoundToHalf(native) * Vector128.Create(HalfTypeHelper.InverseFiniteRange)) + Vector128.Create(HalfTypeHelper.ScaledMidpoint); + } + + /// + /// Quantizes scaled alpha through the native binary16 representation. + /// + /// The scaled alpha lanes. + /// The scaled alpha values represented by binary16 storage. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 QuantizeScaledAlpha(Vector256 alpha) + { + Vector256 native = (ClampUnit(alpha) * Vector256.Create(HalfTypeHelper.FiniteRange)) + Vector256.Create(HalfTypeHelper.FiniteMinimum); + return (HalfTypeHelper.RoundToHalf(native) * Vector256.Create(HalfTypeHelper.InverseFiniteRange)) + Vector256.Create(HalfTypeHelper.ScaledMidpoint); + } + + /// + /// Quantizes scaled alpha through the native binary16 representation. + /// + /// The scaled alpha lanes. + /// The scaled alpha values represented by binary16 storage. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 QuantizeScaledAlpha(Vector512 alpha) + { + Vector512 native = (ClampUnit(alpha) * Vector512.Create(HalfTypeHelper.FiniteRange)) + Vector512.Create(HalfTypeHelper.FiniteMinimum); + return (HalfTypeHelper.RoundToHalf(native) * Vector512.Create(HalfTypeHelper.InverseFiniteRange)) + Vector512.Create(HalfTypeHelper.ScaledMidpoint); + } + + /// + /// Clamps vectors to the scaled color range while preserving NaN lanes. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ClampUnit(Vector128 source) + { + Vector128 clamped = Vector128.Min(Vector128.Max(source, Vector128.Zero), Vector128.One); + return Vector128.ConditionalSelect(Vector128.Equals(source, source), clamped, source); + } + + /// + /// Clamps vectors to the scaled color range while preserving NaN lanes. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ClampUnit(Vector256 source) + { + Vector256 clamped = Vector256.Min(Vector256.Max(source, Vector256.Zero), Vector256.One); + return Vector256.ConditionalSelect(Vector256.Equals(source, source), clamped, source); + } + + /// + /// Clamps vectors to the scaled color range while preserving NaN lanes. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ClampUnit(Vector512 source) + { + Vector512 clamped = Vector512.Min(Vector512.Max(source, Vector512.Zero), Vector512.One); + return Vector512.ConditionalSelect(Vector512.Equals(source, source), clamped, source); + } + + /// + /// Maps associated scaled vectors to native components and packs them as binary16 values. + /// + /// The associated scaled vectors. + /// The destination pixels. + private static void PackAssociatedScaled(Span source, Span destination) + { + Vector4Converters.MultiplyThenAdd(source, ScaledToNativeMultiplier, ScaledToNativeOffset); + RgbaHalfP.PixelOperations.PackUnclamped(source, MemoryMarshal.Cast(destination)); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs index c9714c217..b79d2a5b2 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L16.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct L16 /// /// Provides optimized overrides for bulk operations. /// - internal partial class PixelOperations : PixelOperations; + internal partial class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs index e7b463fe0..fb7945fdf 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/L8.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct L8 /// /// Provides optimized overrides for bulk operations. /// - internal partial class PixelOperations : PixelOperations; + internal partial class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs index 36f4a0bf5..5568fc742 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte2.PixelOperations.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +14,25 @@ public partial struct NormalizedByte2 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeOffset = new(1F, 1F, 0F, 0F); + private static readonly Vector4 NativeDivisor = new(2F, 2F, 1F, 1F); + + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Only X and Y use affine native coordinates. Preserve W as normalized alpha for the scaled unassociation step. + Vector4Converters.AddThenDivide(source, NativeOffset, NativeDivisor); + this.FromAssociatedScaledVector4Destructive(configuration, source, destination); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs index e67321e4f..d467b802e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4.PixelOperations.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +15,89 @@ public partial struct NormalizedByte4 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeScale = new(2F); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + NormalizedByte4P.PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], false); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // The packed P and non-P layouts are identical, so the shared SIMD unpacker supplies normalized values. Association + // happens in [0, 1] before the result is mapped back to the signed-native [-1, 1] coordinate system. + NormalizedByte4P.PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, true); + Numerics.Premultiply(destination); + Vector4Converters.MultiplyThenAdd(destination, NativeScale, -Vector4.One); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + NormalizedByte4P.PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], true); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + NormalizedByte4P.PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, true); + Numerics.Premultiply(destination); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + NormalizedByte4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), false); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Convert all native components together so alpha and RGB enter unassociation in the same normalized coordinate system. + Vector4Converters.AddThenDivide(source, Vector4.One, NativeScale); + Numerics.UnPremultiply(source); + NormalizedByte4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), true); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + NormalizedByte4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), true); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Numerics.UnPremultiply(source); + NormalizedByte4P.PixelOperations.Pack(source, MemoryMarshal.Cast(destination), true); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs new file mode 100644 index 000000000..5928cbe52 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedByte4P.PixelOperations.cs @@ -0,0 +1,814 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct NormalizedByte4P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + private const byte RestorePackedPixelOrder = 0b_11_01_10_00; + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToUnassociatedVector4(source, destination[..source.Length], false); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToVector4(source, destination[..source.Length], false); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Associate(source, false); + Pack(source, destination, true); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Reassociate(source, false); + Pack(source, destination, true); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToUnassociatedVector4(source, destination[..source.Length], true); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToVector4(source, destination[..source.Length], true); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Associate(source, true); + Pack(source, destination, true); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Reassociate(source, true); + Pack(source, destination, true); + } + + /// + /// Converts packed signed-normalized pixels to native or scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + /// Whether to produce scaled vectors. + internal static void ToVector4(ReadOnlySpan source, Span destination, bool scaled) + { + ref byte sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + // Each packed component occupies one byte, so the destination Vector4 lane count also defines the source byte stride. + int componentsPerPixel = Vector128.Count; + int i = 0; + + // Portable widening advances one integer width at a time, so assemble the wider vectors from ordered 128-bit halves. + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)).AsSByte(); + Vector128 lowerShorts = Vector128.WidenLower(packed); + Vector128 upperShorts = Vector128.WidenUpper(packed); + Vector256 lowerIntegers = Vector256.Create(Vector128.WidenLower(lowerShorts), Vector128.WidenUpper(lowerShorts)); + Vector256 upperIntegers = Vector256.Create(Vector128.WidenLower(upperShorts), Vector128.WidenUpper(upperShorts)); + Vector512 integers = Vector512.Create(lowerIntegers, upperIntegers); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToVector4(Vector512.ConvertToSingle(integers), scaled); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + ulong packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector128 shorts = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsSByte()); + Vector256 integers = Vector256.Create(Vector128.WidenLower(shorts), Vector128.WidenUpper(shorts)); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToVector4(Vector256.ConvertToSingle(integers), scaled); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i < source.Length; i++) + { + uint packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector128 shorts = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsSByte()); + Vector128 integers = Vector128.WidenLower(shorts); + Unsafe.Add(ref destinationBase, (uint)i) = ToVector4(Vector128.ConvertToSingle(integers), scaled).AsVector4(); + } + + return; + } + + ref NormalizedByte4P pixelBase = ref Unsafe.As(ref sourceBase); + + for (; i < source.Length; i++) + { + NormalizedByte4P pixel = Unsafe.Add(ref pixelBase, (uint)i); + Unsafe.Add(ref destinationBase, (uint)i) = scaled ? pixel.ToScaledVector4() : pixel.ToVector4(); + } + } + + /// + /// Converts signed-normalized storage to native or scaled vectors. + /// + /// The signed storage components. + /// Whether to produce scaled vectors. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ToVector4(Vector512 source, bool scaled) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector512.Max(source, Vector512.Create(-MaxPos)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + return (source + Vector512.Create(MaxPos)) / Vector512.Create(ScaledMagnitude); + } + + return source / Vector512.Create(MaxPos); + } + + /// + /// Converts signed-normalized storage to native or scaled vectors. + /// + /// The signed storage components. + /// Whether to produce scaled vectors. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ToVector4(Vector256 source, bool scaled) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector256.Max(source, Vector256.Create(-MaxPos)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + return (source + Vector256.Create(MaxPos)) / Vector256.Create(ScaledMagnitude); + } + + return source / Vector256.Create(MaxPos); + } + + /// + /// Converts signed-normalized storage to native or scaled vectors. + /// + /// The signed storage components. + /// Whether to produce scaled vectors. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ToVector4(Vector128 source, bool scaled) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector128.Max(source, Vector128.Create(-MaxPos)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + return (source + Vector128.Create(MaxPos)) / Vector128.Create(ScaledMagnitude); + } + + return source / Vector128.Create(MaxPos); + } + + /// + /// Converts packed associated pixels directly to unassociated native or scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + /// Whether to produce scaled vectors. + private static void ToUnassociatedVector4(ReadOnlySpan source, Span destination, bool scaled) + { + ref byte sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + // Each packed component occupies one byte, so the destination Vector4 lane count also defines the source byte stride. + int componentsPerPixel = Vector128.Count; + int i = 0; + + // Portable widening advances one integer width at a time, so assemble the wider vectors from ordered 128-bit halves. + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)).AsSByte(); + Vector128 lowerShorts = Vector128.WidenLower(packed); + Vector128 upperShorts = Vector128.WidenUpper(packed); + Vector256 lowerIntegers = Vector256.Create(Vector128.WidenLower(lowerShorts), Vector128.WidenUpper(lowerShorts)); + Vector256 upperIntegers = Vector256.Create(Vector128.WidenLower(upperShorts), Vector128.WidenUpper(upperShorts)); + Vector512 integers = Vector512.Create(lowerIntegers, upperIntegers); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Vector512.ConvertToSingle(integers), scaled); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + ulong packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector128 shorts = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsSByte()); + Vector256 integers = Vector256.Create(Vector128.WidenLower(shorts), Vector128.WidenUpper(shorts)); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Vector256.ConvertToSingle(integers), scaled); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i < source.Length; i++) + { + uint packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector128 shorts = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsSByte()); + Vector128 integers = Vector128.WidenLower(shorts); + Unsafe.Add(ref destinationBase, (uint)i) = ToUnassociatedVector4(Vector128.ConvertToSingle(integers), scaled).AsVector4(); + } + + return; + } + + ref NormalizedByte4P pixelBase = ref Unsafe.As(ref sourceBase); + + for (; i < source.Length; i++) + { + NormalizedByte4P pixel = Unsafe.Add(ref pixelBase, (uint)i); + Unsafe.Add(ref destinationBase, (uint)i) = scaled ? pixel.ToUnassociatedScaledVector4() : pixel.ToUnassociatedVector4(); + } + } + + /// + /// Converts four associated signed-byte vectors to unassociated native or scaled vectors. + /// + /// The signed storage components. + /// Whether to produce scaled vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ToUnassociatedVector4(Vector512 source, bool scaled) + { + // Clamp the duplicate SNORM minimum encoding before converting it to the nonnegative associated domain. + source = Vector512.Max(source, Vector512.Create(-MaxPos)); + source += Vector512.Create(MaxPos); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + Vector512 normalizeMask = Vector512.Equals(alpha, Vector512.Zero) | alphaMask; + Vector512 divisor = Vector512.ConditionalSelect(normalizeMask, Vector512.Create(ScaledMagnitude), alpha); + Vector512 result = source / divisor; + + // Unassociation is performed in scaled space; map the result back only after alpha has served as a true opacity value. + return scaled ? result : (result * Vector512.Create(2F)) - Vector512.One; + } + + /// + /// Converts two associated signed-byte vectors to unassociated native or scaled vectors. + /// + /// The signed storage components. + /// Whether to produce scaled vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ToUnassociatedVector4(Vector256 source, bool scaled) + { + // Clamp the duplicate SNORM minimum encoding before converting it to the nonnegative associated domain. + source = Vector256.Max(source, Vector256.Create(-MaxPos)); + source += Vector256.Create(MaxPos); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + Vector256 normalizeMask = Vector256.Equals(alpha, Vector256.Zero) | alphaMask; + Vector256 divisor = Vector256.ConditionalSelect(normalizeMask, Vector256.Create(ScaledMagnitude), alpha); + Vector256 result = source / divisor; + + // Unassociation is performed in scaled space; map the result back only after alpha has served as a true opacity value. + return scaled ? result : (result * Vector256.Create(2F)) - Vector256.One; + } + + /// + /// Converts an associated signed-byte vector to an unassociated native or scaled vector. + /// + /// The signed storage components. + /// Whether to produce a scaled vector. + /// The unassociated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ToUnassociatedVector4(Vector128 source, bool scaled) + { + // Clamp the duplicate SNORM minimum encoding before converting it to the nonnegative associated domain. + source = Vector128.Max(source, Vector128.Create(-MaxPos)); + source += Vector128.Create(MaxPos); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + Vector128 normalizeMask = Vector128.Equals(alpha, Vector128.Zero) | alphaMask; + Vector128 divisor = Vector128.ConditionalSelect(normalizeMask, Vector128.Create(ScaledMagnitude), alpha); + Vector128 result = source / divisor; + + // Unassociation is performed in scaled space; map the result back only after alpha has served as a true opacity value. + return scaled ? result : (result * Vector128.Create(2F)) - Vector128.One; + } + + /// + /// Converts unassociated native or scaled vectors to the destination's associated scaled representation. + /// + /// The vectors to convert in place. + /// Whether the source contains scaled vectors. + private static void Associate(Span source, bool scaled) + { + // Quantize alpha through signed-normalized destination storage before associating RGB. The kernels replace W separately + // so alpha is stored unchanged instead of being multiplied by itself. + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector512 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Associate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Associate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector128.IsHardwareAccelerated) + { + ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Associate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + return; + } + + ref Vector4 tailBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Vector4 vector = Unsafe.Add(ref tailBase, i); + + if (!scaled) + { + // Signed-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + vector += Vector4.One; + vector /= 2F; + } + + // Qualify the containing pixel type because this nested class also declares SIMD helpers named Associate. + Unsafe.Add(ref tailBase, i) = NormalizedByte4P.Associate(vector); + } + } + + /// + /// Converts four unassociated native or scaled vectors to the destination's associated scaled representation. + /// + /// The unassociated vectors. + /// Whether the source contains scaled vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Associate(Vector512 source, bool scaled) + { + Vector512 zero = Vector512.Zero; + Vector512 one = Vector512.One; + + if (!scaled) + { + // Signed-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + source = (source + one) / Vector512.Create(2F); + } + + source = Vector512.Min(Vector512.Max(source, zero), one); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 nativeAlpha = Vector512.Min(Vector512.Max((alpha * Vector512.Create(2F)) - one, -one), one); + Vector512 storedAlpha = Vector512.Round(nativeAlpha * Vector512.Create(MaxPos)); + storedAlpha += Vector512.Create(MaxPos); + storedAlpha /= Vector512.Create(ScaledMagnitude); + Vector512 result = source * storedAlpha; + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts two unassociated native or scaled vectors to the destination's associated scaled representation. + /// + /// The unassociated vectors. + /// Whether the source contains scaled vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Associate(Vector256 source, bool scaled) + { + Vector256 zero = Vector256.Zero; + Vector256 one = Vector256.One; + + if (!scaled) + { + // Signed-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + source = (source + one) / Vector256.Create(2F); + } + + source = Vector256.Min(Vector256.Max(source, zero), one); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 nativeAlpha = Vector256.Min(Vector256.Max((alpha * Vector256.Create(2F)) - one, -one), one); + Vector256 storedAlpha = Vector256.Round(nativeAlpha * Vector256.Create(MaxPos)); + storedAlpha += Vector256.Create(MaxPos); + storedAlpha /= Vector256.Create(ScaledMagnitude); + Vector256 result = source * storedAlpha; + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector256.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts an unassociated native or scaled vector to the destination's associated scaled representation. + /// + /// The unassociated vector. + /// Whether the source contains a scaled vector. + /// The associated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Associate(Vector128 source, bool scaled) + { + Vector128 zero = Vector128.Zero; + Vector128 one = Vector128.One; + + if (!scaled) + { + // Signed-native W is an affine encoding rather than opacity, so association must happen after mapping to scaled space. + source = (source + one) / Vector128.Create(2F); + } + + source = Vector128.Min(Vector128.Max(source, zero), one); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 nativeAlpha = Vector128.Min(Vector128.Max((alpha * Vector128.Create(2F)) - one, -one), one); + Vector128 storedAlpha = Vector128.Round(nativeAlpha * Vector128.Create(MaxPos)); + storedAlpha += Vector128.Create(MaxPos); + storedAlpha /= Vector128.Create(ScaledMagnitude); + Vector128 result = source * storedAlpha; + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + return Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Reassociates native or scaled vectors with the scaled alpha values the destination stores. + /// + /// The vectors to convert in place. + /// Whether the source contains scaled vectors. + private static void Reassociate(Span source, bool scaled) + { + // Preserve straight color across destination-alpha quantization by scaling RGB by storedAlpha / inputAlpha. The kernels + // replace W, clamp RGB to stored alpha, and clear zero-alpha vectors so the stored result remains valid associated color. + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector512 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Reassociate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Reassociate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector128.IsHardwareAccelerated) + { + ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = Reassociate(sourceBase, scaled); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + return; + } + + ref Vector4 tailBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Vector4 vector = Unsafe.Add(ref tailBase, i); + + if (!scaled) + { + // Convert native components together so RGB and alpha enter reassociation in the same scaled coordinate system. + vector += Vector4.One; + vector /= 2F; + } + + // Qualify the containing pixel type because this nested class also declares SIMD helpers named Reassociate. + Unsafe.Add(ref tailBase, i) = NormalizedByte4P.Reassociate(vector); + } + } + + /// + /// Reassociates four native or scaled vectors with the scaled alpha values the destination stores. + /// + /// The associated vectors. + /// Whether the source contains scaled vectors. + /// The reassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Reassociate(Vector512 source, bool scaled) + { + Vector512 zero = Vector512.Zero; + Vector512 one = Vector512.One; + + if (!scaled) + { + // Convert native components together so RGB and alpha enter reassociation in the same scaled coordinate system. + source = (source + one) / Vector512.Create(2F); + } + + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 nativeAlpha = Vector512.Min(Vector512.Max((alpha * Vector512.Create(2F)) - one, -one), one); + Vector512 storedAlpha = Vector512.Round(nativeAlpha * Vector512.Create(MaxPos)); + storedAlpha += Vector512.Create(MaxPos); + storedAlpha /= Vector512.Create(ScaledMagnitude); + Vector512 result = source * (storedAlpha / alpha); + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + result = Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector512.Min(Vector512.Max(result, zero), storedAlpha); + return Vector512.ConditionalSelect(Vector512.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates two native or scaled vectors with the scaled alpha values the destination stores. + /// + /// The associated vectors. + /// Whether the source contains scaled vectors. + /// The reassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Reassociate(Vector256 source, bool scaled) + { + Vector256 zero = Vector256.Zero; + Vector256 one = Vector256.One; + + if (!scaled) + { + // Convert native components together so RGB and alpha enter reassociation in the same scaled coordinate system. + source = (source + one) / Vector256.Create(2F); + } + + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 nativeAlpha = Vector256.Min(Vector256.Max((alpha * Vector256.Create(2F)) - one, -one), one); + Vector256 storedAlpha = Vector256.Round(nativeAlpha * Vector256.Create(MaxPos)); + storedAlpha += Vector256.Create(MaxPos); + storedAlpha /= Vector256.Create(ScaledMagnitude); + Vector256 result = source * (storedAlpha / alpha); + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + result = Vector256.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector256.Min(Vector256.Max(result, zero), storedAlpha); + return Vector256.ConditionalSelect(Vector256.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates a native or scaled vector with the scaled alpha value the destination stores. + /// + /// The associated vector. + /// Whether the source contains a scaled vector. + /// The reassociated vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Reassociate(Vector128 source, bool scaled) + { + Vector128 zero = Vector128.Zero; + Vector128 one = Vector128.One; + + if (!scaled) + { + // Convert native components together so RGB and alpha enter reassociation in the same scaled coordinate system. + source = (source + one) / Vector128.Create(2F); + } + + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 nativeAlpha = Vector128.Min(Vector128.Max((alpha * Vector128.Create(2F)) - one, -one), one); + Vector128 storedAlpha = Vector128.Round(nativeAlpha * Vector128.Create(MaxPos)); + storedAlpha += Vector128.Create(MaxPos); + storedAlpha /= Vector128.Create(ScaledMagnitude); + Vector128 result = source * (storedAlpha / alpha); + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + result = Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector128.Min(Vector128.Max(result, zero), storedAlpha); + return Vector128.ConditionalSelect(Vector128.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Packs native or scaled vectors into signed-normalized storage. + /// + /// The source vectors. + /// The destination pixels. + /// Whether the source contains scaled vectors. + internal static void Pack(Span source, Span destination, bool scaled) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + ref NormalizedByte4P destinationBase = ref MemoryMarshal.GetReference(destination); + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector512 vector = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + Vector512 integers = ConvertToPackedInt32(vector, scaled); + Vector256 shorts = Vector256_.PackSignedSaturate(integers.GetLower(), integers.GetUpper()); + Vector128 packed = Vector128.NarrowWithSaturation(shorts.GetLower(), shorts.GetUpper()); + + if (Avx2.IsSupported) + { + // AVX2 narrows each 128-bit lane independently, placing the middle two pixels out of order. + packed = Vector128_.ShuffleNative(packed.AsSingle(), RestorePackedPixelOrder).AsSByte(); + } + + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = packed; + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + + for (; i <= source.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector256 vector = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + Vector256 integers = ConvertToPackedInt32(vector, scaled); + Vector128 shorts = Vector128.NarrowWithSaturation(integers.GetLower(), integers.GetUpper()); + Vector128 packed = Vector128.NarrowWithSaturation(shorts, shorts); + Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)) = packed.AsUInt64().GetElement(0); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i < source.Length; i++) + { + Vector128 vector = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)i)); + Vector128 integers = ConvertToPackedInt32(vector, scaled); + Vector128 shorts = Vector128.NarrowWithSaturation(integers, integers); + Vector128 packed = Vector128.NarrowWithSaturation(shorts, shorts); + Unsafe.Add(ref destinationBase, (uint)i).PackedValue = packed.AsUInt32().GetElement(0); + } + + return; + } + + for (; i < source.Length; i++) + { + Vector4 vector = Unsafe.Add(ref sourceBase, (uint)i); + Unsafe.Add(ref destinationBase, (uint)i) = scaled ? FromScaledVector4(vector) : FromVector4(vector); + } + } + + /// + /// Converts four vectors to signed integers using the destination's packing contract. + /// + /// The source vectors. + /// Whether the source contains scaled vectors. + /// The packed integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ConvertToPackedInt32(Vector512 source, bool scaled) + { + Vector512 one = Vector512.One; + + if (scaled) + { + source *= Vector512.Create(2F); + source -= one; + } + + source = Vector512.Min(Vector512.Max(source, -one), one) * Vector512.Create(MaxPos); + return Vector512_.ConvertToInt32RoundToEven(source); + } + + /// + /// Converts two vectors to signed integers using the destination's packing contract. + /// + /// The source vectors. + /// Whether the source contains scaled vectors. + /// The packed integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToPackedInt32(Vector256 source, bool scaled) + { + Vector256 one = Vector256.One; + + if (scaled) + { + source *= Vector256.Create(2F); + source -= one; + } + + source = Vector256.Min(Vector256.Max(source, -one), one) * Vector256.Create(MaxPos); + return Vector256_.ConvertToInt32RoundToEven(source); + } + + /// + /// Converts a vector to signed integers using the destination's packing contract. + /// + /// The source vector. + /// Whether the source contains a scaled vector. + /// The packed integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ConvertToPackedInt32(Vector128 source, bool scaled) + { + Vector128 one = Vector128.One; + + if (scaled) + { + source *= Vector128.Create(2F); + source -= one; + } + + source = Vector128.Min(Vector128.Max(source, -one), one) * Vector128.Create(MaxPos); + return Vector128_.ConvertToInt32RoundToEven(source); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs index 99636cb37..f35f2a2da 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort2.PixelOperations.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +14,25 @@ public partial struct NormalizedShort2 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeOffset = new(1F, 1F, 0F, 0F); + private static readonly Vector4 NativeDivisor = new(2F, 2F, 1F, 1F); + + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Only X and Y use affine native coordinates. Preserve W as normalized alpha for the scaled unassociation step. + Vector4Converters.AddThenDivide(source, NativeOffset, NativeDivisor); + this.FromAssociatedScaledVector4Destructive(configuration, source, destination); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs index ad6aa8d8a..8a60d68b4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/NormalizedShort4.PixelOperations.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +15,89 @@ public partial struct NormalizedShort4 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeScale = new(2F); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], true, false); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Association is defined in scaled color space, not around the signed-native zero point. The shared signed-short SIMD + // kernel handles unpacking before the result is mapped back to the format's native [-1, 1] range. + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, true, true); + Numerics.Premultiply(destination); + Vector4Converters.MultiplyThenAdd(destination, NativeScale, -Vector4.One); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], true, true); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, true, true); + Numerics.Premultiply(destination); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), true, false); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Convert native RGB and alpha together before unassociation so the subsequent scaled pack retains the scalar contract. + Vector4Converters.AddThenDivide(source, Vector4.One, NativeScale); + Numerics.UnPremultiply(source); + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), true, true); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), true, true); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Numerics.UnPremultiply(source); + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), true, true); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs index 7bca1c781..5be973404 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rg32.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct Rg32 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs index c9ed730c4..31816ceb0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb48.PixelOperations.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +13,14 @@ public partial struct Rgb48 /// /// Provides optimized overrides for bulk operations. /// - internal partial class PixelOperations : PixelOperations; + internal partial class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb96.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb96.PixelOperations.cs new file mode 100644 index 000000000..990fd64bd --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgb96.PixelOperations.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// The alpha component is always fully opaque. +/// +public partial struct Rgb96 +{ + /// + /// Provides optimized overrides for bulk operations. + /// + internal class PixelOperations : PixelOperations + { + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs new file mode 100644 index 000000000..19338f12f --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Rgba32P.PixelOperations.cs @@ -0,0 +1,64 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct Rgba32P +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // Native and scaled vectors have the same range for this format, so the byte-specialized converter is valid for both contracts. + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // The converter rescales RGB when alpha rounds so the channels remain associated with the byte alpha actually stored. + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + // This byte format has the same normalized native and scaled ranges, so the unmodified converter satisfies both contracts. + Vector4Converters.AssociatedRgbaCompatible.ToAssociatedVector4(source, destination); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4(source, destination); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4(source, destination); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalf.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalf.PixelOperations.cs new file mode 100644 index 000000000..f3c01e082 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalf.PixelOperations.cs @@ -0,0 +1,72 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct RgbaHalf +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : PixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // RgbaHalf and RgbaHalfP have the same four-half layout. Sharing the binary16 expansion kernel keeps this path + // vectorized without changing the unassociated meaning of the source components. + RgbaHalfP.PixelOperations.Unpack(MemoryMarshal.Cast(source), destination[..source.Length]); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // Association is fused with half expansion so the conversion reads and writes each span once. + RgbaHalfP.PixelOperations.UnpackAssociated(MemoryMarshal.Cast(source), destination[..source.Length]); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ToAssociatedVector4(configuration, source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // The layouts are identical, so the shared packer can write RgbaHalf storage without an intermediate buffer. + RgbaHalfP.PixelOperations.Pack(source, MemoryMarshal.Cast(destination[..source.Length])); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + // Unassociation is fused with binary16 packing so processors do not pay for another pass over their vector buffer. + RgbaHalfP.PixelOperations.PackFromAssociated(source, MemoryMarshal.Cast(destination[..source.Length])); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + => this.FromUnassociatedVector4Destructive(configuration, source, destination); + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + => this.FromAssociatedVector4Destructive(configuration, source, destination); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalfP.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalfP.PixelOperations.cs new file mode 100644 index 000000000..825ebeec9 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaHalfP.PixelOperations.cs @@ -0,0 +1,795 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Provides optimized overrides for bulk operations. +/// +public partial struct RgbaHalfP +{ + /// + /// Provides optimized bulk operations for . + /// + internal class PixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + UnpackUnassociated(source, destination[..source.Length]); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Unpack(source, destination[..source.Length]); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => this.ToAssociatedVector4(configuration, source, destination); + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + PackUnassociated(source, destination[..source.Length]); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + PackAssociated(source, destination[..source.Length]); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + => this.FromUnassociatedVector4Destructive(configuration, source, destination); + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + => this.FromAssociatedVector4Destructive(configuration, source, destination); + + /// + /// Expands binary16 components without changing their unit-range representation. + /// + /// The packed source pixels. + /// The destination vectors. + internal static void Unpack(ReadOnlySpan source, Span destination) + { + ref ushort sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref float destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 packed = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector512 lower, Vector512 upper) = HalfTypeHelper.Unpack(packed); + Vector512.StoreUnsafe(lower, ref destinationBase, (nuint)i); + Vector512.StoreUnsafe(upper, ref destinationBase, (nuint)(i + Vector512.Count)); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 packed = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector256 lower, Vector256 upper) = HalfTypeHelper.Unpack(packed); + Vector256.StoreUnsafe(lower, ref destinationBase, (nuint)i); + Vector256.StoreUnsafe(upper, ref destinationBase, (nuint)(i + Vector256.Count)); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector128 lower, Vector128 upper) = HalfTypeHelper.Unpack(packed); + Vector128.StoreUnsafe(lower, ref destinationBase, (nuint)i); + Vector128.StoreUnsafe(upper, ref destinationBase, (nuint)(i + Vector128.Count)); + } + + if (i < componentCount) + { + // Four binary16 components form one pixel, so the only possible remainder is one complete pixel. + ulong remainder = Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref sourceBase, (uint)i))); + Vector128 packed = Vector128.CreateScalarUnsafe(remainder).AsUInt16(); + Vector128.StoreUnsafe(HalfTypeHelper.Unpack(packed).Lower, ref destinationBase, (nuint)i); + } + + return; + } + + ref RgbaHalfP pixelBase = ref Unsafe.As(ref sourceBase); + ref Vector4 vectorBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Unsafe.Add(ref vectorBase, (uint)pixelIndex) = Unsafe.Add(ref pixelBase, (uint)pixelIndex).ToVector4(); + } + } + + /// + /// Expands unassociated binary16 components and associates RGB in the same pass. + /// + /// The packed source pixels. + /// The destination vectors. + internal static void UnpackAssociated(ReadOnlySpan source, Span destination) + { + ref ushort sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref float destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 packed = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector512 lower, Vector512 upper) = HalfTypeHelper.Unpack(packed); + Vector512.StoreUnsafe(Associate(lower), ref destinationBase, (nuint)i); + Vector512.StoreUnsafe(Associate(upper), ref destinationBase, (nuint)(i + Vector512.Count)); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 packed = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector256 lower, Vector256 upper) = HalfTypeHelper.Unpack(packed); + Vector256.StoreUnsafe(Associate(lower), ref destinationBase, (nuint)i); + Vector256.StoreUnsafe(Associate(upper), ref destinationBase, (nuint)(i + Vector256.Count)); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector128 lower, Vector128 upper) = HalfTypeHelper.Unpack(packed); + Vector128.StoreUnsafe(Associate(lower), ref destinationBase, (nuint)i); + Vector128.StoreUnsafe(Associate(upper), ref destinationBase, (nuint)(i + Vector128.Count)); + } + + if (i < componentCount) + { + // Four binary16 components form one pixel, so the only possible remainder is one complete pixel. + ulong remainder = Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref sourceBase, (uint)i))); + Vector128 packed = Vector128.CreateScalarUnsafe(remainder).AsUInt16(); + Vector128.StoreUnsafe(Associate(HalfTypeHelper.Unpack(packed).Lower), ref destinationBase, (nuint)i); + } + + return; + } + + ref RgbaHalfP pixelBase = ref Unsafe.As(ref sourceBase); + ref Vector4 vectorBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Vector4 vector = Unsafe.Add(ref pixelBase, (uint)pixelIndex).ToVector4(); + Numerics.Premultiply(ref vector); + Unsafe.Add(ref vectorBase, (uint)pixelIndex) = vector; + } + } + + /// + /// Expands associated binary16 components and unassociates RGB in the same pass. + /// + /// The packed source pixels. + /// The destination vectors. + internal static void UnpackUnassociated(ReadOnlySpan source, Span destination) + { + ref ushort sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref float destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 packed = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector512 lower, Vector512 upper) = HalfTypeHelper.Unpack(packed); + Vector512.StoreUnsafe(Unassociate(lower), ref destinationBase, (nuint)i); + Vector512.StoreUnsafe(Unassociate(upper), ref destinationBase, (nuint)(i + Vector512.Count)); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 packed = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector256 lower, Vector256 upper) = HalfTypeHelper.Unpack(packed); + Vector256.StoreUnsafe(Unassociate(lower), ref destinationBase, (nuint)i); + Vector256.StoreUnsafe(Unassociate(upper), ref destinationBase, (nuint)(i + Vector256.Count)); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + (Vector128 lower, Vector128 upper) = HalfTypeHelper.Unpack(packed); + Vector128.StoreUnsafe(Unassociate(lower), ref destinationBase, (nuint)i); + Vector128.StoreUnsafe(Unassociate(upper), ref destinationBase, (nuint)(i + Vector128.Count)); + } + + if (i < componentCount) + { + // Four binary16 components form one pixel, so the only possible remainder is one complete pixel. + ulong remainder = Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref sourceBase, (uint)i))); + Vector128 packed = Vector128.CreateScalarUnsafe(remainder).AsUInt16(); + Vector128.StoreUnsafe(Unassociate(HalfTypeHelper.Unpack(packed).Lower), ref destinationBase, (nuint)i); + } + + return; + } + + ref RgbaHalfP pixelBase = ref Unsafe.As(ref sourceBase); + ref Vector4 vectorBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Unsafe.Add(ref vectorBase, (uint)pixelIndex) = Unsafe.Add(ref pixelBase, (uint)pixelIndex).ToUnassociatedVector4(); + } + } + + /// + /// Packs unassociated unit-range vectors directly into binary16 storage. + /// + /// The source vectors. + /// The destination pixels. + internal static void Pack(Span source, Span destination) + { + ref float sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ushort destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = ClampUnit(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector512 upper = ClampUnit(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count))); + Vector512.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = ClampUnit(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector256 upper = ClampUnit(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count))); + Vector256.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = ClampUnit(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 upper = ClampUnit(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count))); + Vector128.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only one complete pixel. + Vector128 vector = ClampUnit(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 packed = HalfTypeHelper.Pack(vector, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + } + + return; + } + + ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); + ref RgbaHalfP pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Vector4 vector = Numerics.Clamp(Unsafe.Add(ref vectorBase, (uint)pixelIndex), Vector4.Zero, Vector4.One); + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = new RgbaHalfP(vector.X, vector.Y, vector.Z, vector.W); + } + } + + /// + /// Packs vectors directly into IEEE 754 binary16 storage without applying unit-range color constraints. + /// + /// The source vectors. + /// The destination pixels. + internal static void PackUnclamped(Span source, Span destination) + { + ref float sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ushort destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = Vector512.LoadUnsafe(ref sourceBase, (nuint)i); + Vector512 upper = Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count)); + Vector512.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = Vector256.LoadUnsafe(ref sourceBase, (nuint)i); + Vector256 upper = Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count)); + Vector256.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + Vector128 upper = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count)); + Vector128.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only one complete pixel. + Vector128 vector = Vector128.LoadUnsafe(ref sourceBase, (nuint)i); + Vector128 packed = HalfTypeHelper.Pack(vector, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + } + + return; + } + + ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); + ref RgbaHalfP pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Vector4 vector = Unsafe.Add(ref vectorBase, (uint)pixelIndex); + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = new RgbaHalfP(vector.X, vector.Y, vector.Z, vector.W); + } + } + + /// + /// Unassociates vectors and packs unassociated unit-range binary16 storage in the same pass. + /// + /// The associated source vectors. + /// The destination pixels. + internal static void PackFromAssociated(Span source, Span destination) + { + ref float sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ushort destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = ClampUnit(Unassociate(Vector512.LoadUnsafe(ref sourceBase, (nuint)i))); + Vector512 upper = ClampUnit(Unassociate(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count)))); + Vector512.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = ClampUnit(Unassociate(Vector256.LoadUnsafe(ref sourceBase, (nuint)i))); + Vector256 upper = ClampUnit(Unassociate(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count)))); + Vector256.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = ClampUnit(Unassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i))); + Vector128 upper = ClampUnit(Unassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count)))); + Vector128.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only one complete pixel. + Vector128 vector = ClampUnit(Unassociate(Vector128.LoadUnsafe(ref sourceBase, (nuint)i))); + Vector128 packed = HalfTypeHelper.Pack(vector, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + } + + return; + } + + ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); + ref RgbaHalfP pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Vector4 vector = Unsafe.Add(ref vectorBase, (uint)pixelIndex); + Numerics.UnPremultiply(ref vector); + vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One); + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = new RgbaHalfP(vector.X, vector.Y, vector.Z, vector.W); + } + } + + /// + /// Associates unassociated vectors with their stored binary16 alpha and packs them in one pass. + /// + /// The unassociated source vectors. + /// The destination pixels. + internal static void PackUnassociated(Span source, Span destination) + { + ref float sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ushort destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + // Alpha is rounded to the exact binary16 value that will be stored before RGB is associated with it. + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = AssociateForStorage(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector512 upper = AssociateForStorage(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count))); + Vector512.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = AssociateForStorage(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector256 upper = AssociateForStorage(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count))); + Vector256.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = AssociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 upper = AssociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count))); + Vector128.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only one complete pixel. + Vector128 vector = AssociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 packed = HalfTypeHelper.Pack(vector, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + } + + return; + } + + ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); + ref RgbaHalfP pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = RgbaHalfP.FromUnassociatedVector4(Unsafe.Add(ref vectorBase, (uint)pixelIndex)); + } + } + + /// + /// Reassociates vectors with their stored binary16 alpha and packs them in one pass. + /// + /// The associated source vectors. + /// The destination pixels. + internal static void PackAssociated(Span source, Span destination) + { + ref float sourceBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref ushort destinationBase = ref Unsafe.As(ref MemoryMarshal.GetReference(destination)); + int componentCount = source.Length * Vector128.Count; + int i = 0; + + // Scaling RGB by storedAlpha / inputAlpha preserves straight color when binary16 rounds the alpha channel. + if (Vector512.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector512.Count; i += Vector512.Count) + { + Vector512 lower = ReassociateForStorage(Vector512.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector512 upper = ReassociateForStorage(Vector512.LoadUnsafe(ref sourceBase, (nuint)(i + Vector512.Count))); + Vector512.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector256.Count; i += Vector256.Count) + { + Vector256 lower = ReassociateForStorage(Vector256.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector256 upper = ReassociateForStorage(Vector256.LoadUnsafe(ref sourceBase, (nuint)(i + Vector256.Count))); + Vector256.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; i <= componentCount - Vector128.Count; i += Vector128.Count) + { + Vector128 lower = ReassociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 upper = ReassociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)(i + Vector128.Count))); + Vector128.StoreUnsafe(HalfTypeHelper.Pack(lower, upper), ref destinationBase, (nuint)i); + } + + if (i < componentCount) + { + // Duplicate the final vector to use the two-input narrowing primitive, then store only one complete pixel. + Vector128 vector = ReassociateForStorage(Vector128.LoadUnsafe(ref sourceBase, (nuint)i)); + Vector128 packed = HalfTypeHelper.Pack(vector, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref Unsafe.Add(ref destinationBase, (uint)i)), packed.AsUInt64().GetElement(0)); + } + + return; + } + + ref Vector4 vectorBase = ref Unsafe.As(ref sourceBase); + ref RgbaHalfP pixelBase = ref Unsafe.As(ref destinationBase); + + for (int pixelIndex = 0; pixelIndex < source.Length; pixelIndex++) + { + Unsafe.Add(ref pixelBase, (uint)pixelIndex) = RgbaHalfP.FromAssociatedVector4(Unsafe.Add(ref vectorBase, (uint)pixelIndex)); + } + } + + /// + /// Associates RGB with alpha while preserving each alpha lane. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Associate(Vector128 source) + { + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 result = source * alpha; + return Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), alpha, result); + } + + /// + /// Associates RGB with alpha while preserving each alpha lane. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Associate(Vector256 source) + { + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 result = source * alpha; + return Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), alpha, result); + } + + /// + /// Associates RGB with alpha while preserving each alpha lane. + /// + /// The unassociated vectors. + /// The associated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Associate(Vector512 source) + { + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 result = source * alpha; + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector512.ConditionalSelect(alphaMask, alpha, result); + } + + /// + /// Unassociates RGB while preserving each alpha lane. + /// + /// The associated vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Unassociate(Vector128 source) + { + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + return Numerics.UnPremultiply(source, alpha); + } + + /// + /// Unassociates RGB while preserving each alpha lane. + /// + /// The associated vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Unassociate(Vector256 source) + { + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + return Numerics.UnPremultiply(source, alpha); + } + + /// + /// Unassociates RGB while preserving each alpha lane. + /// + /// The associated vectors. + /// The unassociated vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 Unassociate(Vector512 source) + { + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + return Numerics.UnPremultiply(source, alpha); + } + + /// + /// Clamps vectors to the unit range represented by the pixel format. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ClampUnit(Vector128 source) + { + Vector128 clamped = Vector128.Min(Vector128.Max(source, Vector128.Zero), Vector128.One); + + // Ordered comparison is false for NaN, restoring the source lane to match the scalar clamp contract. + return Vector128.ConditionalSelect(Vector128.Equals(source, source), clamped, source); + } + + /// + /// Clamps vectors to the unit range represented by the pixel format. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ClampUnit(Vector256 source) + { + Vector256 clamped = Vector256.Min(Vector256.Max(source, Vector256.Zero), Vector256.One); + + // Ordered comparison is false for NaN, restoring the source lane to match the scalar clamp contract. + return Vector256.ConditionalSelect(Vector256.Equals(source, source), clamped, source); + } + + /// + /// Clamps vectors to the unit range represented by the pixel format. + /// + /// The vectors to clamp. + /// The clamped vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ClampUnit(Vector512 source) + { + Vector512 clamped = Vector512.Min(Vector512.Max(source, Vector512.Zero), Vector512.One); + + // Ordered comparison is false for NaN, restoring the source lane to match the scalar clamp contract. + return Vector512.ConditionalSelect(Vector512.Equals(source, source), clamped, source); + } + + /// + /// Associates unassociated vectors with the alpha value binary16 storage can reproduce. + /// + /// The unassociated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 AssociateForStorage(Vector128 source) + { + source = ClampUnit(source); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 storedAlpha = HalfTypeHelper.RoundToHalf(alpha); + Vector128 result = source * storedAlpha; + return Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), storedAlpha, result); + } + + /// + /// Associates unassociated vectors with the alpha value binary16 storage can reproduce. + /// + /// The unassociated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 AssociateForStorage(Vector256 source) + { + source = ClampUnit(source); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 storedAlpha = HalfTypeHelper.RoundToHalf(alpha); + Vector256 result = source * storedAlpha; + return Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), storedAlpha, result); + } + + /// + /// Associates unassociated vectors with the alpha value binary16 storage can reproduce. + /// + /// The unassociated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 AssociateForStorage(Vector512 source) + { + source = ClampUnit(source); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 storedAlpha = HalfTypeHelper.RoundToHalf(alpha); + Vector512 result = source * storedAlpha; + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Reassociates vectors with the alpha value binary16 storage can reproduce. + /// + /// The associated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ReassociateForStorage(Vector128 source) + { + Vector128 zero = Vector128.Zero; + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 clampedAlpha = ClampUnit(alpha); + Vector128 storedAlpha = HalfTypeHelper.RoundToHalf(clampedAlpha); + Vector128 result = source * (storedAlpha / alpha); + result = Vector128.ConditionalSelect(Vector128.Create(0, 0, 0, -1).AsSingle(), storedAlpha, result); + result = Vector128.Min(Vector128.Max(result, zero), storedAlpha); + return Vector128.ConditionalSelect(Vector128.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates vectors with the alpha value binary16 storage can reproduce. + /// + /// The associated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ReassociateForStorage(Vector256 source) + { + Vector256 zero = Vector256.Zero; + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 clampedAlpha = ClampUnit(alpha); + Vector256 storedAlpha = HalfTypeHelper.RoundToHalf(clampedAlpha); + Vector256 result = source * (storedAlpha / alpha); + result = Vector256.ConditionalSelect(Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(), storedAlpha, result); + result = Vector256.Min(Vector256.Max(result, zero), storedAlpha); + return Vector256.ConditionalSelect(Vector256.LessThanOrEqual(alpha, zero), zero, result); + } + + /// + /// Reassociates vectors with the alpha value binary16 storage can reproduce. + /// + /// The associated vectors. + /// The associated vectors ready for packing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ReassociateForStorage(Vector512 source) + { + Vector512 zero = Vector512.Zero; + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 clampedAlpha = ClampUnit(alpha); + Vector512 storedAlpha = HalfTypeHelper.RoundToHalf(clampedAlpha); + Vector512 result = source * (storedAlpha / alpha); + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + result = Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector512.Min(Vector512.Max(result, zero), storedAlpha); + return Vector512.ConditionalSelect(Vector512.LessThanOrEqual(alpha, zero), zero, result); + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs index 5f9b51af9..d4b868871 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/RgbaVector.PixelOperations.cs @@ -23,9 +23,16 @@ public partial struct RgbaVector ReadOnlySpan sourcePixels, Span destinationPixels) { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + destinationPixels = destinationPixels[..sourcePixels.Length]; Span destinationVectors = MemoryMarshal.Cast(destinationPixels); - PixelOperations.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale); + // Cross-format conversion uses public dispatch so associated source pixels are unassociated before entering RgbaVector storage. + PixelOperations.Instance.ToVector4(configuration, sourcePixels, destinationVectors, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + // RgbaVector.FromScaledVector4 clamps scaled input, so the optimized bulk path must preserve that behavior after unassociating. + Numerics.Clamp(MemoryMarshal.Cast(destinationVectors), 0F, 1F); } /// @@ -38,7 +45,11 @@ public partial struct RgbaVector Guard.DestinationShouldNotBeTooShort(sourceVectors, destinationPixels, nameof(destinationPixels)); Vector4Converters.ApplyBackwardConversionModifiers(sourceVectors, modifiers); - MemoryMarshal.Cast(sourceVectors).CopyTo(destinationPixels); + + // RgbaVector.FromVector4 and FromScaledVector4 both clamp to the representable [0, 1] range. Preserve that scalar + // contract before the zero-copy representation cast so bulk processor output cannot retain HDR or negative values. + Numerics.Clamp(MemoryMarshal.Cast(sourceVectors), 0F, 1F); + MemoryMarshal.Cast(sourceVectors).CopyTo(destinationPixels[..sourceVectors.Length]); } /// @@ -50,6 +61,7 @@ public partial struct RgbaVector { Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationVectors, nameof(destinationVectors)); + destinationVectors = destinationVectors[..sourcePixels.Length]; MemoryMarshal.Cast(sourcePixels).CopyTo(destinationVectors); Vector4Converters.ApplyForwardConversionModifiers(destinationVectors, modifiers); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs index d8225f18a..4d6b066ad 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short2.PixelOperations.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +14,26 @@ public partial struct Short2 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeOffset = new(-MinNeg, -MinNeg, 0F, 0F); + private static readonly Vector4 NativeRange = new(Range, Range, 1F, 1F); + + // Alpha is implicitly one, so both outward representations already contain associated color components. + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedVector4(configuration, source, destination); + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.ToUnassociatedScaledVector4(configuration, source, destination); + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + // X and Y use signed-native coordinates while incoming W remains normalized alpha. Map only the stored components before + // the scaled bulk path unassociates RGB; transforming W would change the opacity that must be removed. + Vector4Converters.AddThenDivide(source, NativeOffset, NativeRange); + this.FromAssociatedScaledVector4Destructive(configuration, source, destination); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs index 3d7043b0c..83c3ce053 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Short4.PixelOperations.cs @@ -1,6 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + namespace SixLabors.ImageSharp.PixelFormats; /// @@ -11,5 +15,90 @@ public partial struct Short4 /// /// Provides optimized overrides for bulk operations. /// - internal class PixelOperations : PixelOperations; + internal class PixelOperations : PixelOperations + { + private static readonly Vector4 NativeOffset = new(-MinNeg); + private static readonly Vector4 NativeMagnitude = new(Range); + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], false, false); + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Signed integer components use an affine native range. Expand to scaled vectors, associate there, then restore the + // native zero point so RGB and alpha remain in the same coordinate system. + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, false, true); + Numerics.Premultiply(destination); + Vector4Converters.MultiplyThenAdd(destination, NativeMagnitude, -NativeOffset); + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination[..source.Length], false, true); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.ToVector4(MemoryMarshal.Cast(source), destination, false, true); + Numerics.Premultiply(destination); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), false, false); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Normalize all four components before unassociating, then use the scaled packing path to preserve Short4's affine mapping. + Vector4Converters.AddThenDivide(source, NativeOffset, NativeMagnitude); + Numerics.UnPremultiply(source); + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), false, true); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), false, true); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + Numerics.UnPremultiply(source); + SignedShort4PixelOperations.FromVector4(source, MemoryMarshal.Cast(destination), false, true); + } + } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index e7c97269e..a88823069 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -7,11 +7,12 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing two 16-bit unsigned normalized values ranging from 0 to 1. -/// -/// Ranges from [0, 0, 0, 1] to [1, 1, 0, 1] in vector form. -/// +/// Packed pixel type containing two 16-bit unsigned normalized values. /// +/// +/// , , and scaled vector conversions return x and y in [0, 1], z +/// as 0, and implicit alpha as 1. The packed storage layout matches DXGI_FORMAT_R16G16_UNORM. +/// public partial struct Rg32 : IPixel, IPackedVector { private static readonly Vector2 Max = new(ushort.MaxValue); @@ -84,6 +85,48 @@ public partial struct Rg32 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rg32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rg32 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rg32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rg32 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rg32 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs index 190407296..b8c2d2098 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -108,6 +108,48 @@ public partial struct Rgb24 : IPixel /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb24 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb24 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb24 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb24 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgb24 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs index ed6f57abb..0a240b369 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -91,6 +91,48 @@ public partial struct Rgb48 : IPixel /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb48 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb48 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb48 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb48 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgb48 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs new file mode 100644 index 000000000..08cc3afa1 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb96.cs @@ -0,0 +1,245 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Pixel type containing three 32-bit unsigned normalized values ranging from 0 to 4294967295. +/// The color components are stored in red, green, blue. +/// +/// Ranges from [0, 0, 0] to [1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Rgb96 : IPixel, IEquatable +{ + private const float InvMax = 1.0f / uint.MaxValue; + + // Use double here because at this magnitude a float cannot represent all 32-bit + // integer values exactly. A float only has 24 bits of precision, so around + // uint.MaxValue it can only represent multiples of 256 and will round + // 4294967295 up to 4294967296. Double has 53 bits of precision and can + // represent all uint values exactly, avoiding precision loss before scaling. + private const double Max = uint.MaxValue; + + /// + /// Gets the red component. + /// + public uint R; + + /// + /// Gets the green component. + /// + public uint G; + + /// + /// Gets the blue component. + /// + public uint B; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgb96(uint r, uint g, uint b) + { + this.R = r; + this.G = g; + this.B = b; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgb96 left, Rgb96 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgb96 left, Rgb96 right) => !left.Equals(right); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + this.R * InvMax, + this.G * InvMax, + this.B * InvMax, + 1.0f); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromAssociatedVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromScaledVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromVector4(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + return new Rgb96( + (uint)Math.Round(source.X * Max), + (uint)Math.Round(source.Y * Max), + (uint)Math.Round(source.Z * Max)); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromAbgr32(Abgr32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromArgb32(Argb32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromBgr24(Bgr24 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromBgra32(Bgra32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromL8(L8 source) + { + uint rgb = ColorNumerics.From8BitTo32Bit(source.PackedValue); + return new Rgb96(rgb, rgb, rgb); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromL16(L16 source) + { + uint rgb = ColorNumerics.From16BitTo32Bit(source.PackedValue); + return new Rgb96(rgb, rgb, rgb); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromLa16(La16 source) + { + uint rgb = ColorNumerics.From8BitTo32Bit((byte)source.PackedValue); + return new Rgb96(rgb, rgb, rgb); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromLa32(La32 source) + { + uint rgb = ColorNumerics.From16BitTo32Bit(source.L); + return new Rgb96(rgb, rgb, rgb); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromRgb24(Rgb24 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromRgba32(Rgba32 source) => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromRgb48(Rgb48 source) => new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgb96 FromRgba64(Rgba64 source) => new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B)); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PixelTypeInfo GetPixelTypeInfo() => PixelTypeInfo.Create( + PixelComponentInfo.Create(3, 32, 32, 32), + PixelColorType.RGB, + PixelAlphaRepresentation.None); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Rgba32 ToRgba32() => Rgba32.FromRgb96(this); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B); + + /// + public override readonly string ToString() => FormattableString.Invariant($"Rgb96({this.R}, {this.G}, {this.B})"); + + /// + public override readonly bool Equals(object? obj) => obj is Rgb96 rgb && rgb.R == this.R && rgb.G == this.G && rgb.B == this.B; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgb96 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B); +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs index cdee22964..340e24777 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -7,12 +7,13 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed vector type containing 4 unsigned normalized values ranging from 0 to 1. +/// Packed pixel type containing four unsigned normalized values. /// The x, y and z components use 10 bits, and the w component uses 2 bits. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// /// +/// +/// and scaled vector conversions return all components in [0, 1]. The storage layout matches +/// DXGI_FORMAT_R10G10B10A2_UNORM. +/// public partial struct Rgba1010102 : IPixel, IPackedVector { private static readonly Vector4 Multiplier = new(1023F, 1023F, 1023F, 3F); @@ -86,6 +87,47 @@ public partial struct Rgba1010102 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba1010102 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba1010102 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba1010102 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba1010102 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgba1010102 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs new file mode 100644 index 000000000..064b71af2 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba128.cs @@ -0,0 +1,241 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Pixel type containing four 32-bit unsigned normalized values ranging from 0 to 4294967295. +/// The color components are stored in red, green, blue and alpha. +/// +/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. +/// +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Rgba128 : IPixel, IEquatable +{ + private const float InvMax = 1.0f / uint.MaxValue; + + // Use double here because at this magnitude a float cannot represent all 32-bit + // integer values exactly. A float only has 24 bits of precision, so around + // uint.MaxValue it can only represent multiples of 256 and will round + // 4294967295 up to 4294967296. Double has 53 bits of precision and can + // represent all uint values exactly, avoiding precision loss before scaling. + private const double Max = uint.MaxValue; + + /// + /// Gets the red component. + /// + public uint R; + + /// + /// Gets the green component. + /// + public uint G; + + /// + /// Gets the blue component. + /// + public uint B; + + /// + /// Gets the alpha channel. + /// + public uint A; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba128(uint r, uint g, uint b, uint a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + /// The on the right side of the operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rgba128 left, Rgba128 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rgba128 left, Rgba128 right) => !left.Equals(right); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new( + this.R * InvMax, + this.G * InvMax, + this.B * InvMax, + this.A * InvMax); + + /// + public static PixelOperations CreatePixelOperations() => new(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba128 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba128 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba128 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba128 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static Rgba128 FromScaledVector4(Vector4 source) => FromVector4(source); + + /// + public static Rgba128 FromVector4(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + return new Rgba128( + (uint)Math.Round(source.X * Max), + (uint)Math.Round(source.Y * Max), + (uint)Math.Round(source.Z * Max), + (uint)Math.Round(source.W * Max)); + } + + /// + public static Rgba128 FromAbgr32(Abgr32 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); + + /// + public static Rgba128 FromArgb32(Argb32 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); + + /// + public static Rgba128 FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba128 FromBgr24(Bgr24 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), uint.MaxValue); + + /// + public static Rgba128 FromBgra32(Bgra32 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); + + /// + public static Rgba128 FromL8(L8 source) + { + uint rgb = ColorNumerics.From8BitTo32Bit(source.PackedValue); + return new Rgba128(rgb, rgb, rgb, rgb); + } + + /// + public static Rgba128 FromL16(L16 source) + { + uint rgb = ColorNumerics.From16BitTo32Bit(source.PackedValue); + return new Rgba128(rgb, rgb, rgb, rgb); + } + + /// + public static Rgba128 FromLa16(La16 source) + { + uint rgb = ColorNumerics.From8BitTo32Bit((byte)source.PackedValue); + return new Rgba128(rgb, rgb, rgb, rgb); + } + + /// + public static Rgba128 FromLa32(La32 source) + { + uint rgb = ColorNumerics.From16BitTo32Bit(source.L); + return new Rgba128(rgb, rgb, rgb, rgb); + } + + /// + public static Rgba128 FromRgb24(Rgb24 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), uint.MaxValue); + + /// + public static Rgba128 FromRgba32(Rgba32 source) + => new(ColorNumerics.From8BitTo32Bit(source.R), ColorNumerics.From8BitTo32Bit(source.G), ColorNumerics.From8BitTo32Bit(source.B), ColorNumerics.From8BitTo32Bit(source.A)); + + /// + public static Rgba128 FromRgb48(Rgb48 source) + => new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B), uint.MaxValue); + + /// + public static Rgba128 FromRgba64(Rgba64 source) + => new(ColorNumerics.From16BitTo32Bit(source.R), ColorNumerics.From16BitTo32Bit(source.G), ColorNumerics.From16BitTo32Bit(source.B), ColorNumerics.From16BitTo32Bit(source.A)); + + /// + public static PixelTypeInfo GetPixelTypeInfo() => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 32, 32, 32, 32), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Unassociated); + + /// + public readonly Rgba32 ToRgba32() => Rgba32.FromRgba128(this); + + /// + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override readonly int GetHashCode() => HashCode.Combine(this.R, this.G, this.B, this.A); + + /// + public override readonly string ToString() => FormattableString.Invariant($"Rgba128({this.R}, {this.G}, {this.B}, {this.A})"); + + /// + public override readonly bool Equals(object? obj) => obj is Rgba128 rgb && rgb.R == this.R && rgb.G == this.G && rgb.B == this.B && rgb.A == this.A; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool Equals(Rgba128 other) => this.R.Equals(other.R) && this.G.Equals(other.G) && this.B.Equals(other.B) && this.A.Equals(other.A); +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 199754c69..689d31793 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -220,7 +220,7 @@ public partial struct Rgba32 : IPixel, IPackedVector /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; /// public static PixelTypeInfo GetPixelTypeInfo() @@ -232,6 +232,47 @@ public partial struct Rgba32 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgba32 FromScaledVector4(Vector4 source) => FromVector4(source); @@ -314,6 +355,36 @@ public partial struct Rgba32 : IPixel, IPackedVector A = ColorNumerics.From16BitTo8Bit(source.A) }; + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + /// The pixel value as Rgba32. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromRgb96(Rgb96 source) + => new() + { + R = ColorNumerics.From32BitTo8Bit(source.R), + G = ColorNumerics.From32BitTo8Bit(source.G), + B = ColorNumerics.From32BitTo8Bit(source.B), + A = byte.MaxValue + }; + + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + /// The pixel value as Rgba32. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32 FromRgba128(Rgba128 source) + => new() + { + R = ColorNumerics.From32BitTo8Bit(source.R), + G = ColorNumerics.From32BitTo8Bit(source.G), + B = ColorNumerics.From32BitTo8Bit(source.B), + A = ColorNumerics.From32BitTo8Bit(source.A), + }; + /// /// Converts the value of this instance to a hexadecimal string. /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs new file mode 100644 index 000000000..c7bb17ce8 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32P.cs @@ -0,0 +1,273 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing associated red, green, blue, and alpha components as 8-bit unsigned normalized values. +/// Components are stored in red, green, blue, and alpha order from least to most significant byte. +/// +/// +/// The native component, packed, and vector representations use associated alpha. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct Rgba32P : IPixel, IPackedVector +{ + private static readonly Vector4 Half = new(0.5F); + private static readonly Vector4 MaxBytes = new(byte.MaxValue); + + /// + /// Gets or sets the associated red component. + /// + public byte R; + + /// + /// Gets or sets the associated green component. + /// + public byte G; + + /// + /// Gets or sets the associated blue component. + /// + public byte B; + + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Rgba32P(byte r, byte g, byte b) + : this(r, g, b, byte.MaxValue) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Rgba32P(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public Rgba32P(float r, float g, float b) + : this(new Vector4(r, g, b, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public Rgba32P(float r, float g, float b, float a) + : this(new Vector4(r, g, b, a)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Rgba32P(Vector3 vector) + : this(new Vector4(vector, 1F)) + { + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public Rgba32P(Vector4 vector) + : this() => this = FromScaledVector4(vector); + + /// + /// Initializes a new instance of the struct from a packed associated value. + /// + /// The packed associated value. + public Rgba32P(uint packed) + : this() => this.PackedValue = packed; + + /// + public uint PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(Rgba32P left, Rgba32P right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(Rgba32P left, Rgba32P right) => !left.Equals(right); + + /// + public readonly Rgba32 ToRgba32() + => Rgba32.FromScaledVector4(Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A)); + + /// + public readonly Vector4 ToScaledVector4() => new Vector4(this.R, this.G, this.B, this.A) / byte.MaxValue; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + // Divide the stored byte magnitudes before normalization so unassociation cannot move an exact byte conversion across its rounding midpoint. + return Vector4Converters.AssociatedRgbaCompatible.ToUnassociatedVector4(this.R, this.G, this.B, this.A); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + public readonly Vector4 ToVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToUnassociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 8, 8, 8, 8), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32P FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static Rgba32P FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32P FromUnassociatedVector4(Vector4 source) => FromUnassociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32P FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32P FromUnassociatedScaledVector4(Vector4 source) + => Vector4Converters.AssociatedRgbaCompatible.FromUnassociatedVector4ToRgba32P(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba32P FromAssociatedScaledVector4(Vector4 source) + { + // Rescale associated RGB when alpha rounds to a different byte so the stored channels remain associated with the alpha actually written. + return Vector4Converters.AssociatedRgbaCompatible.FromAssociatedVector4ToRgba32P(source); + } + + /// + public static Rgba32P FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static Rgba32P FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is Rgba32P other && this.Equals(other); + + /// + public readonly bool Equals(Rgba32P other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => $"Rgba32P({this.R}, {this.G}, {this.B}, {this.A})"; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Rgba32P Pack(Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + // Each converted component occupies one 32-bit lane. Reinterpreting those lanes as bytes exposes their low bytes at offsets 0, 4, 8, and 12. + Vector128 result = Vector128.ConvertToInt32(vector.AsVector128()).AsByte(); + return new Rgba32P(result.GetElement(0), result.GetElement(4), result.GetElement(8), result.GetElement(12)); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index c73daaf86..300f2357f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -8,11 +8,12 @@ using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.PixelFormats; /// -/// Packed pixel type containing four 16-bit unsigned normalized values ranging from 0 to 65535. -/// -/// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. -/// +/// Packed pixel type containing four 16-bit unsigned normalized values. /// +/// +/// Component fields expose storage values in [0, 65535]. and scaled vector conversions +/// return them in [0, 1]. The storage layout matches DXGI_FORMAT_R16G16B16A16_UNORM. +/// [StructLayout(LayoutKind.Sequential)] public partial struct Rgba64 : IPixel, IPackedVector { @@ -212,6 +213,47 @@ public partial struct Rgba64 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba64 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba64 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba64 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rgba64 FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rgba64 FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalf.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalf.cs new file mode 100644 index 000000000..573bfca55 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalf.cs @@ -0,0 +1,227 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four 16-bit floating-point values typically ranging from 0 to 1. +/// The color components are stored in red, green, blue, and alpha order. +/// +/// +/// and scaled vector conversions return the same component values in the nominal color range +/// [0, 1]. The packed representation is binary-compatible with DXGI_FORMAT_R16G16B16A16_FLOAT. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct RgbaHalf : IPixel, IPackedVector +{ + /// + /// Gets or sets the red component. + /// + public Half R; + + /// + /// Gets or sets the green component. + /// + public Half G; + + /// + /// Gets or sets the blue component. + /// + public Half B; + + /// + /// Gets or sets the alpha component. + /// + public Half A; + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + public RgbaHalf(float r, float g, float b) + : this(r, g, b, 1F) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + public RgbaHalf(float r, float g, float b, float a) + { + this.R = (Half)r; + this.G = (Half)g; + this.B = (Half)b; + this.A = (Half)a; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The vector containing the component values. + public RgbaHalf(Vector4 vector) + : this(vector.X, vector.Y, vector.Z, vector.W) + { + } + + /// + public ulong PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(RgbaHalf left, RgbaHalf right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(RgbaHalf left, RgbaHalf right) => !left.Equals(right); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new((float)this.R, (float)this.G, (float)this.B, (float)this.A); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 16, 16, 16, 16), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Unassociated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromScaledVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalf FromVector4(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + return new RgbaHalf(source); + } + + /// + public static RgbaHalf FromAbgr32(Abgr32 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromArgb32(Argb32 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromBgra5551(Bgra5551 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromBgr24(Bgr24 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromBgra32(Bgra32 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromL8(L8 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromL16(L16 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromLa16(La16 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromLa32(La32 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromRgb24(Rgb24 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromRgba32(Rgba32 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromRgb48(Rgb48 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalf FromRgba64(Rgba64 source) => FromScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is RgbaHalf other && this.Equals(other); + + /// + public readonly bool Equals(RgbaHalf other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => FormattableString.Invariant($"RgbaHalf({(float)this.R:#0.##}, {(float)this.G:#0.##}, {(float)this.B:#0.##}, {(float)this.A:#0.##})"); +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalfP.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalfP.cs new file mode 100644 index 000000000..75fe4907c --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaHalfP.cs @@ -0,0 +1,242 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats; + +/// +/// Packed pixel type containing four associated 16-bit floating-point values typically ranging from 0 to 1. +/// The color components are stored in red, green, blue, and alpha order. +/// +/// +/// and scaled vector conversions return the same associated component values in the nominal +/// color range [0, 1]. The packed representation is binary-compatible with +/// DXGI_FORMAT_R16G16B16A16_FLOAT. +/// +[StructLayout(LayoutKind.Sequential)] +public partial struct RgbaHalfP : IPixel, IPackedVector +{ + /// + /// Gets or sets the associated red component. + /// + public Half R; + + /// + /// Gets or sets the associated green component. + /// + public Half G; + + /// + /// Gets or sets the associated blue component. + /// + public Half B; + + /// + /// Gets or sets the alpha component. + /// + public Half A; + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + public RgbaHalfP(float r, float g, float b) + : this(r, g, b, 1F) + { + } + + /// + /// Initializes a new instance of the struct from associated components. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + public RgbaHalfP(float r, float g, float b, float a) + { + this.R = (Half)r; + this.G = (Half)g; + this.B = (Half)b; + this.A = (Half)a; + } + + /// + /// Initializes a new instance of the struct from an associated vector. + /// + /// The associated vector. + public RgbaHalfP(Vector4 vector) + : this() => this = FromAssociatedScaledVector4(vector); + + /// + public ulong PackedValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => Unsafe.As(ref Unsafe.AsRef(in this)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => Unsafe.As(ref this) = value; + } + + /// + /// Compares two values for equality. + /// + /// The left value. + /// The right value. + /// when the values are equal. + public static bool operator ==(RgbaHalfP left, RgbaHalfP right) => left.Equals(right); + + /// + /// Compares two values for inequality. + /// + /// The left value. + /// The right value. + /// when the values are not equal. + public static bool operator !=(RgbaHalfP left, RgbaHalfP right) => !left.Equals(right); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Rgba32 ToRgba32() => Rgba32.FromScaledVector4(this.ToUnassociatedScaledVector4()); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => new((float)this.R, (float)this.G, (float)this.B, (float)this.A); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.UnPremultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToUnassociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 16, 16, 16, 16), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + /// + public static PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromScaledVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromVector4(Vector4 source) => FromAssociatedVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromUnassociatedScaledVector4(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + + // RGB must be associated with the alpha value that binary16 storage can reproduce, not the higher-precision input alpha. + source.W = HalfTypeHelper.Unpack(HalfTypeHelper.Pack(source.W)); + Numerics.Premultiply(ref source); + return new RgbaHalfP(source.X, source.Y, source.Z, source.W); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromAssociatedScaledVector4(Vector4 source) + { + float alpha = source.W; + + if (alpha <= 0F) + { + return default; + } + + float storedAlpha = HalfTypeHelper.Unpack(HalfTypeHelper.Pack(Numerics.Clamp(alpha, 0F, 1F))); + + // Preserve the represented straight color when binary16 rounds alpha, then restore the associated RGB <= alpha invariant. + source *= storedAlpha / alpha; + source.W = storedAlpha; + Numerics.ClampRgbToAlpha(ref source); + return new RgbaHalfP(source.X, source.Y, source.Z, source.W); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromUnassociatedVector4(Vector4 source) => FromUnassociatedScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaHalfP FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + + /// + public static RgbaHalfP FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public static RgbaHalfP FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + /// + public override readonly bool Equals(object? obj) => obj is RgbaHalfP other && this.Equals(other); + + /// + public readonly bool Equals(RgbaHalfP other) => this.PackedValue.Equals(other.PackedValue); + + /// + public override readonly int GetHashCode() => this.PackedValue.GetHashCode(); + + /// + public override readonly string ToString() => FormattableString.Invariant($"RgbaHalfP({(float)this.R:#0.##}, {(float)this.G:#0.##}, {(float)this.B:#0.##}, {(float)this.A:#0.##})"); +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs index 3f9cab32f..b1704d526 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -106,6 +106,47 @@ public partial struct RgbaVector : IPixel /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaVector FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaVector FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaVector FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaVector FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RgbaVector FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs index 39c853a5e..d6ab2ac1a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -8,10 +8,12 @@ namespace SixLabors.ImageSharp.PixelFormats; /// /// Packed pixel type containing two 16-bit signed integer values. -/// -/// Ranges from [-32767, -32767, 0, 1] to [32767, 32767, 0, 1] in vector form. -/// /// +/// +/// and return stored components in [-32768, 32767]. +/// Scaled vector conversions map the full stored range to [0, 1]. The packed storage layout matches +/// DXGI_FORMAT_R16G16_SINT. +/// public partial struct Short2 : IPixel, IPackedVector { // Largest two byte positive number 0xFFFF >> 1; @@ -20,6 +22,9 @@ public partial struct Short2 : IPixel, IPackedVector // Two's complement private const float MinNeg = ~(int)MaxPos; + // Scaled conversions cover every signed 16-bit code, including the asymmetric minimum value. + private const float Range = MaxPos - MinNeg; + private static readonly Vector2 Max = new(MaxPos); private static readonly Vector2 Min = new(MinNeg); @@ -73,8 +78,8 @@ public partial struct Short2 : IPixel, IPackedVector public readonly Vector4 ToScaledVector4() { Vector2 scaled = this.ToVector2(); - scaled += new Vector2(32767f); - scaled /= 65534F; + scaled -= Min; + scaled /= Range; return new Vector4(scaled, 0f, 1f); } @@ -92,12 +97,55 @@ public partial struct Short2 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short2 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short2 FromAssociatedScaledVector4(Vector4 source) + { + // The destination has implicit alpha one, but associated input must be restored before its alpha is discarded. + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short2 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short2 FromAssociatedVector4(Vector4 source) + { + // Only the stored color components use the signed native encoding; W remains the scaled source alpha. + source.X = (source.X - MinNeg) / Range; + source.Y = (source.Y - MinNeg) / Range; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Short2 FromScaledVector4(Vector4 source) { - Vector2 scaled = new Vector2(source.X, source.Y) * 65534F; - scaled -= new Vector2(32767F); + Vector2 scaled = new Vector2(source.X, source.Y) * Range; + scaled += Min; return new Short2 { PackedValue = Pack(scaled) }; } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs index b6cece2be..fc3d3367d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -8,10 +8,11 @@ namespace SixLabors.ImageSharp.PixelFormats; /// /// Packed pixel type containing four 16-bit signed integer values. -/// -/// Ranges from [-37267, -37267, -37267, -37267] to [37267, 37267, 37267, 37267] in vector form. -/// /// +/// +/// returns stored components in [-32768, 32767]. Scaled vector conversions map the full +/// stored range to [0, 1]. The packed storage layout matches DXGI_FORMAT_R16G16B16A16_SINT. +/// public partial struct Short4 : IPixel, IPackedVector { // Largest two byte positive number 0xFFFF >> 1; @@ -20,6 +21,9 @@ public partial struct Short4 : IPixel, IPackedVector // Two's complement private const float MinNeg = ~(int)MaxPos; + // Scaled conversions cover every signed 16-bit code, including the asymmetric minimum value. + private const float Range = MaxPos - MinNeg; + private static readonly Vector4 Max = new(MaxPos); private static readonly Vector4 Min = new(MinNeg); @@ -75,8 +79,8 @@ public partial struct Short4 : IPixel, IPackedVector public readonly Vector4 ToScaledVector4() { Vector4 scaled = this.ToVector4(); - scaled += new Vector4(32767f); - scaled /= 65534f; + scaled -= Min; + scaled /= Range; return scaled; } @@ -99,12 +103,67 @@ public partial struct Short4 : IPixel, IPackedVector /// public static PixelOperations CreatePixelOperations() => new PixelOperations(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() + { + Vector4 vector = this.ToAssociatedScaledVector4(); + + // Native components use the full asymmetric signed range, so scaled zero must map to -32768 rather than -32767. + vector *= Range; + vector += Min; + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short4 FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short4 FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short4 FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Short4 FromAssociatedVector4(Vector4 source) + { + // Map the full signed native encoding to scaled [0, 1] space before unassociating. + source -= Min; + source /= Range; + return FromAssociatedScaledVector4(source); + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Short4 FromScaledVector4(Vector4 source) { - source *= 65534F; - source -= new Vector4(32767F); + source *= Range; + source += Min; return FromVector4(source); } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs index 712d43f76..854b515dc 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs @@ -57,7 +57,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Argb32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Argb32.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -123,7 +123,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Abgr32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Abgr32.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -189,7 +189,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Bgr24.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Bgr24.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -255,7 +255,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Bgra32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Bgra32.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -321,7 +321,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = L8.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = L8.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -387,7 +387,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = L16.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = L16.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -453,7 +453,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = La16.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = La16.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -519,7 +519,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = La32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = La32.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -585,7 +585,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Rgb24.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Rgb24.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -651,7 +651,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Rgba32.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Rgba32.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -717,7 +717,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Rgb48.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Rgb48.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -783,7 +783,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Rgba64.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Rgba64.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } @@ -849,7 +849,7 @@ public partial class PixelOperations for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = Bgra5551.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = Bgra5551.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt index b2697cb4e..769f3b44b 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt @@ -69,7 +69,7 @@ for (nuint i = 0; i < (uint)source.Length; i++) { - Unsafe.Add(ref destinationBase, i) = <#=pixelType#>.FromScaledVector4(Unsafe.Add(ref sourceBase, i).ToScaledVector4()); + Unsafe.Add(ref destinationBase, i) = <#=pixelType#>.FromUnassociatedScaledVector4(Unsafe.Add(ref sourceBase, i).ToUnassociatedScaledVector4()); } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs index bf1391990..b17c90b92 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -34,182 +34,314 @@ public partial class PixelOperations case PixelAlphaCompositionMode.Clear: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyClear.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddClear.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractClear.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenClear.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenClear.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenClear.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayClear.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightClear.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyClear; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddClear; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractClear; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenClear; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenClear; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenClear; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayClear; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightClear; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeClear; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnClear; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightClear; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceClear; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionClear; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueClear; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationClear; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorClear; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminosityClear; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalClear.Instance; + default: return DefaultPixelBlenders.NormalClear; } case PixelAlphaCompositionMode.Xor: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyXor.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddXor.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractXor.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenXor.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenXor.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenXor.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayXor.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightXor.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyXor; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddXor; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractXor; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenXor; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenXor; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenXor; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayXor; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightXor; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeXor; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnXor; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightXor; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceXor; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionXor; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueXor; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationXor; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorXor; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminosityXor; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalXor.Instance; + default: return DefaultPixelBlenders.NormalXor; } case PixelAlphaCompositionMode.Src: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrc.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrc.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrc.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrc.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrc.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrc.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrc.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrc.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrc; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrc; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrc; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrc; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrc; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrc; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrc; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrc; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeSrc; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnSrc; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightSrc; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceSrc; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionSrc; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueSrc; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationSrc; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorSrc; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminositySrc; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrc.Instance; + default: return DefaultPixelBlenders.NormalSrc; } case PixelAlphaCompositionMode.SrcAtop: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcAtop.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcAtop.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcAtop.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcAtop.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcAtop.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcAtop.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcAtop.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcAtop.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcAtop; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcAtop; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcAtop; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcAtop; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcAtop; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcAtop; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcAtop; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcAtop; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeSrcAtop; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnSrcAtop; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightSrcAtop; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceSrcAtop; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionSrcAtop; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueSrcAtop; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationSrcAtop; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorSrcAtop; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminositySrcAtop; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcAtop.Instance; + default: return DefaultPixelBlenders.NormalSrcAtop; } case PixelAlphaCompositionMode.SrcIn: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcIn.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcIn.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcIn.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcIn.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcIn.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcIn.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcIn.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcIn.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcIn; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcIn; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcIn; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcIn; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcIn; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcIn; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcIn; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcIn; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeSrcIn; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnSrcIn; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightSrcIn; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceSrcIn; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionSrcIn; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueSrcIn; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationSrcIn; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorSrcIn; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminositySrcIn; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcIn.Instance; + default: return DefaultPixelBlenders.NormalSrcIn; } case PixelAlphaCompositionMode.SrcOut: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOut.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOut.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOut.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOut.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOut.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOut.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOut.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOut.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOut; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOut; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOut; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOut; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOut; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOut; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOut; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOut; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeSrcOut; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnSrcOut; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightSrcOut; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceSrcOut; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionSrcOut; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueSrcOut; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationSrcOut; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorSrcOut; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminositySrcOut; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcOut.Instance; + default: return DefaultPixelBlenders.NormalSrcOut; } case PixelAlphaCompositionMode.Dest: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDest.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDest.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDest.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDest.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDest.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDest.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDest.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDest.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDest; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDest; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDest; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDest; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDest; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDest; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDest; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDest; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeDest; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnDest; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightDest; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceDest; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionDest; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueDest; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationDest; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorDest; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminosityDest; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDest.Instance; + default: return DefaultPixelBlenders.NormalDest; } case PixelAlphaCompositionMode.DestAtop: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestAtop.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestAtop.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestAtop.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestAtop.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestAtop.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestAtop.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestAtop.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestAtop.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestAtop; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestAtop; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestAtop; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestAtop; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestAtop; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestAtop; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestAtop; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestAtop; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeDestAtop; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnDestAtop; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightDestAtop; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceDestAtop; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionDestAtop; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueDestAtop; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationDestAtop; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorDestAtop; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminosityDestAtop; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestAtop.Instance; + default: return DefaultPixelBlenders.NormalDestAtop; } case PixelAlphaCompositionMode.DestIn: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestIn.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestIn.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestIn.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestIn.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestIn.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestIn.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestIn.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestIn.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestIn; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestIn; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestIn; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestIn; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestIn; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestIn; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestIn; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestIn; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeDestIn; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnDestIn; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightDestIn; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceDestIn; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionDestIn; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueDestIn; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationDestIn; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorDestIn; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminosityDestIn; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestIn.Instance; + default: return DefaultPixelBlenders.NormalDestIn; } case PixelAlphaCompositionMode.DestOut: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOut.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOut.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOut.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOut.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOut.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOut.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOut.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOut.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOut; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOut; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOut; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOut; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOut; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOut; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOut; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOut; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeDestOut; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnDestOut; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightDestOut; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceDestOut; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionDestOut; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueDestOut; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationDestOut; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorDestOut; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminosityDestOut; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestOut.Instance; + default: return DefaultPixelBlenders.NormalDestOut; } case PixelAlphaCompositionMode.DestOver: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOver.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOver.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOver.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOver.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOver.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOver.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOver.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOver.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyDestOver; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddDestOver; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractDestOver; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenDestOver; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenDestOver; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenDestOver; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayDestOver; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightDestOver; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeDestOver; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnDestOver; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightDestOver; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceDestOver; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionDestOver; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueDestOver; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationDestOver; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorDestOver; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminosityDestOver; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalDestOver.Instance; + default: return DefaultPixelBlenders.NormalDestOver; + } + + case PixelAlphaCompositionMode.Plus: + switch (colorMode) + { + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplyPlus; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddPlus; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractPlus; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenPlus; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenPlus; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenPlus; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlayPlus; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightPlus; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgePlus; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnPlus; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightPlus; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferencePlus; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionPlus; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HuePlus; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationPlus; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorPlus; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminosityPlus; + case PixelColorBlendingMode.Normal: + default: return DefaultPixelBlenders.NormalPlus; } case PixelAlphaCompositionMode.SrcOver: default: switch (colorMode) { - case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOver.Instance; - case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOver.Instance; - case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOver.Instance; - case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOver.Instance; - case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOver.Instance; - case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOver.Instance; - case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOver.Instance; - case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOver.Instance; + case PixelColorBlendingMode.Multiply: return DefaultPixelBlenders.MultiplySrcOver; + case PixelColorBlendingMode.Add: return DefaultPixelBlenders.AddSrcOver; + case PixelColorBlendingMode.Subtract: return DefaultPixelBlenders.SubtractSrcOver; + case PixelColorBlendingMode.Screen: return DefaultPixelBlenders.ScreenSrcOver; + case PixelColorBlendingMode.Darken: return DefaultPixelBlenders.DarkenSrcOver; + case PixelColorBlendingMode.Lighten: return DefaultPixelBlenders.LightenSrcOver; + case PixelColorBlendingMode.Overlay: return DefaultPixelBlenders.OverlaySrcOver; + case PixelColorBlendingMode.HardLight: return DefaultPixelBlenders.HardLightSrcOver; + case PixelColorBlendingMode.ColorDodge: return DefaultPixelBlenders.ColorDodgeSrcOver; + case PixelColorBlendingMode.ColorBurn: return DefaultPixelBlenders.ColorBurnSrcOver; + case PixelColorBlendingMode.SoftLight: return DefaultPixelBlenders.SoftLightSrcOver; + case PixelColorBlendingMode.Difference: return DefaultPixelBlenders.DifferenceSrcOver; + case PixelColorBlendingMode.Exclusion: return DefaultPixelBlenders.ExclusionSrcOver; + case PixelColorBlendingMode.Hue: return DefaultPixelBlenders.HueSrcOver; + case PixelColorBlendingMode.Saturation: return DefaultPixelBlenders.SaturationSrcOver; + case PixelColorBlendingMode.Color: return DefaultPixelBlenders.ColorSrcOver; + case PixelColorBlendingMode.Luminosity: return DefaultPixelBlenders.LuminositySrcOver; case PixelColorBlendingMode.Normal: - default: return DefaultPixelBlenders.NormalSrcOver.Instance; + default: return DefaultPixelBlenders.NormalSrcOver; } } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index ea970a718..63ece4188 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -5,14 +5,19 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.ColorProfiles.Companding; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.PixelFormats; /// -/// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations -/// for pixel buffers of type . +/// Provides bulk conversion operations for pixel buffers of type . /// +/// +/// The default conversion behavior is suitable for pixel formats that store unassociated alpha. +/// Pixel formats that store associated alpha should derive their operations from +/// . +/// /// The pixel format. public partial class PixelOperations where TPixel : unmanaged, IPixel @@ -26,6 +31,90 @@ public partial class PixelOperations public static PixelOperations Instance => LazyInstance.Value; #pragma warning restore CA1000 // Do not declare static members on generic types + /// + /// Converts pixels to unassociated vectors in the pixel format's native numeric range. + /// + /// The configuration. + /// The source pixels. + /// The destination vectors. + protected virtual void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Utils.Vector4Converters.Default.ToVector4(source, destination, PixelConversionModifiers.UnPremultiply); + + /// + /// Converts pixels to associated vectors in the pixel format's native numeric range. + /// + /// The configuration. + /// The source pixels. + /// The destination vectors. + protected virtual void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + => Utils.Vector4Converters.Default.ToVector4(source, destination, PixelConversionModifiers.Premultiply); + + /// + /// Converts pixels to the unassociated scaled vector representation used by color operations. + /// + /// The configuration. + /// The source pixels. + /// The destination vectors. + protected virtual void ToUnassociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + => Utils.Vector4Converters.Default.ToVector4(source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + /// + /// Converts pixels to associated scaled vectors. + /// + /// The configuration. + /// The source pixels. + /// The destination vectors. + protected virtual void ToAssociatedScaledVector4( + Configuration configuration, + ReadOnlySpan source, + Span destination) + => Utils.Vector4Converters.Default.ToVector4(source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + + /// + /// Converts unassociated vectors in the pixel format's native numeric range to pixels. + /// + /// The configuration. + /// The source vectors. Implementations may modify this buffer during conversion. + /// The destination pixels. + protected virtual void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + => Utils.Vector4Converters.Default.FromVector4(source, destination, PixelConversionModifiers.UnPremultiply); + + /// + /// Converts associated vectors in the pixel format's native numeric range to pixels. + /// + /// The configuration. + /// The source vectors. Implementations may modify this buffer during conversion. + /// The destination pixels. + protected virtual void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + => Utils.Vector4Converters.Default.FromVector4(source, destination, PixelConversionModifiers.Premultiply); + + /// + /// Converts unassociated scaled vectors to pixels. + /// + /// The configuration. + /// The source vectors. Implementations may modify this buffer during conversion. + /// The destination pixels. + protected virtual void FromUnassociatedScaledVector4Destructive( + Configuration configuration, + Span source, + Span destination) + => Utils.Vector4Converters.Default.FromVector4(source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + /// + /// Converts associated scaled vectors to pixels. + /// + /// The configuration. + /// The source vectors. Implementations may modify this buffer during conversion. + /// The destination pixels. + protected virtual void FromAssociatedScaledVector4Destructive( + Configuration configuration, + Span source, + Span destination) + => Utils.Vector4Converters.Default.FromVector4(source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + /// /// Gets the pixel type info for the given . /// @@ -33,17 +122,16 @@ public partial class PixelOperations public PixelTypeInfo GetPixelTypeInfo() => TPixel.GetPixelTypeInfo(); /// - /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. - /// The method is DESTRUCTIVE altering the contents of . + /// Converts vectors to pixels using the numeric range, alpha representation, and companding specified by . + /// The contents of are not preserved. /// /// - /// The destructive behavior is a design choice for performance reasons. - /// In a typical use case the contents of are abandoned after the conversion. + /// Callers must not rely on the contents of after this method returns. /// - /// A to configure internal operations - /// The to the source vectors. - /// The to the destination colors. - /// The to apply during the conversion + /// The configuration. + /// The source vectors. Their contents may be modified during conversion. + /// The destination pixels. + /// The representation of the source vectors and the transformations applied before storing the pixels. public virtual void FromVector4Destructive( Configuration configuration, Span sourceVectors, @@ -51,20 +139,63 @@ public partial class PixelOperations PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourceVectors, destination, nameof(destination)); - Utils.Vector4Converters.Default.FromVector4(sourceVectors, destination, modifiers); + bool associated = modifiers.IsDefined(PixelConversionModifiers.Premultiply); + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); + + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + // Transfer functions operate on straight color components, so restore straight RGB before compressing associated input. + if (associated) + { + Numerics.UnPremultiply(sourceVectors); + } + + SRgbCompanding.Compress(sourceVectors); + + if (scaled) + { + this.FromUnassociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedVector4Destructive(configuration, sourceVectors, destination); + } + + return; + } + + if (scaled) + { + if (associated) + { + this.FromAssociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedScaledVector4Destructive(configuration, sourceVectors, destination); + } + } + else if (associated) + { + this.FromAssociatedVector4Destructive(configuration, sourceVectors, destination); + } + else + { + this.FromUnassociatedVector4Destructive(configuration, sourceVectors, destination); + } } /// /// Bulk version of converting 'sourceVectors.Length' pixels into 'destinationColors'. - /// The method is DESTRUCTIVE altering the contents of . + /// The contents of are not preserved. /// /// - /// The destructive behavior is a design choice for performance reasons. - /// In a typical use case the contents of are abandoned after the conversion. + /// Callers must not rely on the contents of after this method returns. /// /// A to configure internal operations - /// The to the source vectors. + /// The source vectors. Their contents may be modified during conversion. /// The to the destination colors. public void FromVector4Destructive( Configuration configuration, @@ -73,12 +204,12 @@ public partial class PixelOperations => this.FromVector4Destructive(configuration, sourceVectors, destination, PixelConversionModifiers.None); /// - /// Bulk version of converting 'sourceColors.Length' pixels into 'destinationVectors'. + /// Converts pixels to vectors using the numeric range, alpha representation, and companding specified by . /// - /// A to configure internal operations - /// The to the source colors. - /// The to the destination vectors. - /// The to apply during the conversion + /// The configuration. + /// The source pixels. + /// The destination vectors. + /// The requested vector representation and the transformations applied after reading the pixels. public virtual void ToVector4( Configuration configuration, ReadOnlySpan source, @@ -86,8 +217,53 @@ public partial class PixelOperations PixelConversionModifiers modifiers) { Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationVectors, nameof(destinationVectors)); + + bool associated = modifiers.IsDefined(PixelConversionModifiers.Premultiply); + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); - Utils.Vector4Converters.Default.ToVector4(source, destinationVectors, modifiers); + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) + { + // Expand straight RGB first because a transfer function applied to associated components would make color depend on alpha. + if (scaled) + { + this.ToUnassociatedScaledVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedVector4(configuration, source, destinationVectors); + } + + Span converted = destinationVectors[..source.Length]; + SRgbCompanding.Expand(converted); + + if (associated) + { + Numerics.Premultiply(converted); + } + + return; + } + + if (scaled) + { + if (associated) + { + this.ToAssociatedScaledVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedScaledVector4(configuration, source, destinationVectors); + } + } + else if (associated) + { + this.ToAssociatedVector4(configuration, source, destinationVectors); + } + else + { + this.ToUnassociatedVector4(configuration, source, destinationVectors); + } } /// @@ -103,10 +279,10 @@ public partial class PixelOperations => this.ToVector4(configuration, source, destinationVectors, PixelConversionModifiers.None); /// - /// Bulk operation that copies the to in - /// format. + /// Bulk operation that converts pixels from format to + /// destination pixels. /// - /// The destination pixel type. + /// The source pixel type. /// A to configure internal operations. /// The to the source pixels. /// The to the destination pixels. @@ -121,13 +297,14 @@ public partial class PixelOperations using IMemoryOwner tempVectors = configuration.MemoryAllocator.Allocate(sliceLength); Span vectorSpan = tempVectors.GetSpan(); + for (int i = 0; i < numberOfSlices; i++) { int start = i * sliceLength; ReadOnlySpan s = source.Slice(start, sliceLength); Span d = destination.Slice(start, sliceLength); - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); - this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } int endOfCompleteSlices = numberOfSlices * sliceLength; @@ -137,8 +314,8 @@ public partial class PixelOperations ReadOnlySpan s = source[endOfCompleteSlices..]; Span d = destination[endOfCompleteSlices..]; vectorSpan = vectorSpan[..remainder]; - PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale); - this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, s, vectorSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + this.FromVector4Destructive(configuration, vectorSpan, d, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } @@ -159,6 +336,13 @@ public partial class PixelOperations Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + if (typeof(TPixel) == typeof(TDestinationPixel)) + { + // An identical destination stores the same representation, including associated alpha, so copying preserves every packed component without a lossy representation round trip. + MemoryMarshal.Cast(source).CopyTo(destination); + return; + } + PixelOperations.Instance.From(configuration, source, destination); } @@ -228,7 +412,7 @@ public partial class PixelOperations } [MethodImpl(InliningOptions.ShortMethod)] - internal static void GuardUnpackIntoRgbPlanes(Span redChannel, Span greenChannel, Span blueChannel, ReadOnlySpan source) + internal static void GuardUnpackIntoRgbPlanes(ReadOnlySpan redChannel, Span greenChannel, Span blueChannel, ReadOnlySpan source) { Guard.IsTrue(greenChannel.Length == redChannel.Length, nameof(greenChannel), "Channels must be of same size!"); Guard.IsTrue(blueChannel.Length == redChannel.Length, nameof(blueChannel), "Channels must be of same size!"); diff --git a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs index 50a68906e..c25cc301c 100644 --- a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs +++ b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs @@ -33,7 +33,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -44,7 +44,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -55,7 +55,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -66,7 +66,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, default); + => SimdUtils.Shuffle4Slice3(source, dest); /// /// Converts a representing a collection of @@ -77,7 +77,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Shuffle4Slice3(source, dest); } /// @@ -96,7 +96,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -107,7 +107,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -118,7 +118,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -129,7 +129,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0321)); + => SimdUtils.Shuffle4Slice3(source, dest); /// /// Converts a representing a collection of @@ -140,7 +140,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0123)); + => SimdUtils.Shuffle4Slice3(source, dest); } /// @@ -159,7 +159,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -170,7 +170,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -181,7 +181,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -192,7 +192,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Shuffle4Slice3(source, dest); /// /// Converts a representing a collection of @@ -203,7 +203,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, default); + => SimdUtils.Shuffle4Slice3(source, dest); } /// @@ -222,7 +222,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -233,7 +233,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -244,7 +244,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4(source, dest, default); + => SimdUtils.Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -255,7 +255,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0123)); + => SimdUtils.Shuffle4Slice3(source, dest); /// /// Converts a representing a collection of @@ -266,7 +266,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(SimdUtils.Shuffle.MMShuffle0321)); + => SimdUtils.Shuffle4Slice3(source, dest); } /// @@ -285,7 +285,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, default); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -296,7 +296,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle2103)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -307,7 +307,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -318,7 +318,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle0123)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -329,7 +329,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgr24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Shuffle3(source, dest); } /// @@ -348,7 +348,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToArgb32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle0123)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -359,7 +359,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -370,7 +370,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToBgra32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, default); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -381,7 +381,7 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToAbgr32(ReadOnlySpan source, Span dest) - => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(SimdUtils.Shuffle.MMShuffle2103)); + => SimdUtils.Pad3Shuffle4(source, dest); /// /// Converts a representing a collection of @@ -392,6 +392,6 @@ internal static class PixelConverter /// The destination span of bytes. [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgb24(ReadOnlySpan source, Span dest) - => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(SimdUtils.Shuffle.MMShuffle3012)); + => SimdUtils.Shuffle3(source, dest); } } diff --git a/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs b/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs new file mode 100644 index 000000000..22d1ba4ac --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/SignedShort4PixelOperations.cs @@ -0,0 +1,415 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.PixelFormats.Utils; + +/// +/// Provides shared SIMD conversion for pixel layouts containing four signed 16-bit components. +/// +internal static class SignedShort4PixelOperations +{ + private const byte RestorePackedPixelOrder = 0b_11_01_10_00; + private const float ShortMaximum = short.MaxValue; + private const float SignedNormalizedMagnitude = ShortMaximum * 2F; + private const float SignedIntegerMinimum = short.MinValue; + private const float SignedIntegerRange = ushort.MaxValue; + + /// + /// Expands signed 16-bit components to native or scaled vectors. + /// + /// The source components. Each consecutive group of four components represents one pixel. + /// The destination vectors. + /// Whether native components use signed-normalized values rather than integer values. + /// Whether to map native components to the scaled range. + internal static void ToVector4(ReadOnlySpan source, Span destination, bool normalized, bool scaled) + { + ref short sourceBase = ref MemoryMarshal.GetReference(source); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + int index = 0; + + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; index <= destination.Length - pixelsPerVector; index += pixelsPerVector) + { + // Packed shorts occupy half the expanded width, so one 256-bit load feeds a complete 512-bit conversion. + Vector256 packed = Vector256.LoadUnsafe(ref sourceBase, (nuint)(index * Vector128.Count)); + Vector512 integers = Vector512.Create(Vector256.WidenLower(packed), Vector256.WidenUpper(packed)); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)index)) = ConvertToVector4(Vector512.ConvertToSingle(integers), normalized, scaled); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + + for (; index <= destination.Length - pixelsPerVector; index += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(index * Vector128.Count)); + Vector256 integers = Vector256.Create(Vector128.WidenLower(packed), Vector128.WidenUpper(packed)); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)index)) = ConvertToVector4(Vector256.ConvertToSingle(integers), normalized, scaled); + } + } + + if (Vector128.IsHardwareAccelerated) + { + ref byte sourceBytes = ref Unsafe.As(ref sourceBase); + + for (; index < destination.Length; index++) + { + ulong packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBytes, (uint)(index * sizeof(ulong)))); + Vector128 integers = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsInt16()); + Unsafe.Add(ref destinationBase, (uint)index) = ConvertToVector4(Vector128.ConvertToSingle(integers), normalized, scaled).AsVector4(); + } + + return; + } + + // The fallback preserves the format implementations' operation order on platforms without vector acceleration. + for (; index < destination.Length; index++) + { + int componentIndex = index * Vector128.Count; + Vector4 vector = new(source[componentIndex], source[componentIndex + 1], source[componentIndex + 2], source[componentIndex + 3]); + + if (normalized) + { + // Both minimum two's-complement SNORM encodings represent -1. + vector = Vector4.Max(vector, new Vector4(-ShortMaximum)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + vector += new Vector4(ShortMaximum); + vector /= SignedNormalizedMagnitude; + } + else + { + vector /= ShortMaximum; + } + } + else if (scaled) + { + // SINT uses every two's-complement code, unlike SNORM where both minimum encodings represent -1. + vector -= new Vector4(SignedIntegerMinimum); + vector /= SignedIntegerRange; + } + + Unsafe.Add(ref destinationBase, (uint)index) = vector; + } + } + + /// + /// Packs native or scaled vectors into signed 16-bit components. + /// + /// The source vectors. + /// The destination components. Each consecutive group of four components represents one pixel. + /// Whether native components use signed-normalized values rather than integer values. + /// Whether the source vectors use the scaled range. + internal static void FromVector4(Span source, Span destination, bool normalized, bool scaled) + { + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + ref short destinationBase = ref MemoryMarshal.GetReference(destination); + int index = 0; + + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + + for (; index <= source.Length - pixelsPerVector; index += pixelsPerVector) + { + Vector512 vectors = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)index)); + Vector512 integers = ConvertToInt32(vectors, normalized, scaled); + + // AVX2 packing operates independently in each 128-bit lane, producing pixels 0, 2, 1, 3. Restore the + // source order with a native lane permutation; the portable PackSignedSaturate fallback is already ordered. + Vector256 packed = Vector256_.PackSignedSaturate(integers.GetLower(), integers.GetUpper()); + + if (Avx2.IsSupported) + { + packed = Avx2.Permute4x64(packed.AsInt64(), RestorePackedPixelOrder).AsInt16(); + } + + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)(index * Vector128.Count))) = packed; + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + + for (; index <= source.Length - pixelsPerVector; index += pixelsPerVector) + { + Vector256 vectors = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)index)); + Vector256 integers = ConvertToInt32(vectors, normalized, scaled); + Vector128 packed = Vector128.NarrowWithSaturation(integers.GetLower(), integers.GetUpper()); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)(index * Vector128.Count))) = packed; + } + } + + if (Vector128.IsHardwareAccelerated) + { + ref byte destinationBytes = ref Unsafe.As(ref destinationBase); + + for (; index < source.Length; index++) + { + Vector128 vector = Unsafe.As>(ref Unsafe.Add(ref sourceBase, (uint)index)); + Vector128 integers = ConvertToInt32(vector, normalized, scaled); + Vector128 packed = Vector128.NarrowWithSaturation(integers, integers); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref destinationBytes, (uint)(index * sizeof(ulong))), packed.AsUInt64().GetElement(0)); + } + + return; + } + + for (; index < source.Length; index++) + { + Vector4 vector = Unsafe.Add(ref sourceBase, (uint)index); + + if (normalized) + { + if (scaled) + { + vector *= 2F; + vector -= Vector4.One; + } + + vector *= ShortMaximum; + vector = Numerics.Clamp(vector, new Vector4(-ShortMaximum), new Vector4(ShortMaximum)); + } + else + { + if (scaled) + { + vector *= SignedIntegerRange; + vector += new Vector4(SignedIntegerMinimum); + } + + vector = Numerics.Clamp(vector, new Vector4(short.MinValue), new Vector4(short.MaxValue)); + } + + int componentIndex = index * Vector128.Count; + destination[componentIndex] = (short)MathF.Round(vector.X); + destination[componentIndex + 1] = (short)MathF.Round(vector.Y); + destination[componentIndex + 2] = (short)MathF.Round(vector.Z); + destination[componentIndex + 3] = (short)MathF.Round(vector.W); + } + } + + /// + /// Converts signed integer components to native or scaled vectors while preserving the scalar conversion order. + /// + /// The signed integer components. + /// Whether native components use signed-normalized values. + /// Whether to map native components to the scaled range. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ConvertToVector4(Vector512 source, bool normalized, bool scaled) + { + if (normalized) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector512.Max(source, Vector512.Create(-ShortMaximum)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + source += Vector512.Create(ShortMaximum); + source /= Vector512.Create(SignedNormalizedMagnitude); + } + else + { + source /= Vector512.Create(ShortMaximum); + } + } + else if (scaled) + { + source -= Vector512.Create(SignedIntegerMinimum); + source /= Vector512.Create(SignedIntegerRange); + } + + return source; + } + + /// + /// Converts signed integer components to native or scaled vectors while preserving the scalar conversion order. + /// + /// The signed integer components. + /// Whether native components use signed-normalized values. + /// Whether to map native components to the scaled range. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToVector4(Vector256 source, bool normalized, bool scaled) + { + if (normalized) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector256.Max(source, Vector256.Create(-ShortMaximum)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + source += Vector256.Create(ShortMaximum); + source /= Vector256.Create(SignedNormalizedMagnitude); + } + else + { + source /= Vector256.Create(ShortMaximum); + } + } + else if (scaled) + { + source -= Vector256.Create(SignedIntegerMinimum); + source /= Vector256.Create(SignedIntegerRange); + } + + return source; + } + + /// + /// Converts signed integer components to native or scaled vectors while preserving the scalar conversion order. + /// + /// The signed integer components. + /// Whether native components use signed-normalized values. + /// Whether to map native components to the scaled range. + /// The converted vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ConvertToVector4(Vector128 source, bool normalized, bool scaled) + { + if (normalized) + { + // Both minimum two's-complement SNORM encodings represent -1. + source = Vector128.Max(source, Vector128.Create(-ShortMaximum)); + + if (scaled) + { + // Offset exact integer components before division to avoid cancellation near the signed-normalized lower bound. + source += Vector128.Create(ShortMaximum); + source /= Vector128.Create(SignedNormalizedMagnitude); + } + else + { + source /= Vector128.Create(ShortMaximum); + } + } + else if (scaled) + { + source -= Vector128.Create(SignedIntegerMinimum); + source /= Vector128.Create(SignedIntegerRange); + } + + return source; + } + + /// + /// Converts vectors to signed integer components using the format's packing contract. + /// + /// The source vectors. + /// Whether native components use signed-normalized values. + /// Whether the source vectors use the scaled range. + /// The converted integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ConvertToInt32(Vector512 source, bool normalized, bool scaled) + { + if (normalized) + { + if (scaled) + { + source *= Vector512.Create(2F); + source -= Vector512.One; + } + + source *= Vector512.Create(ShortMaximum); + source = Vector512.Min(Vector512.Max(source, Vector512.Create(-ShortMaximum)), Vector512.Create(ShortMaximum)); + } + else + { + if (scaled) + { + source *= Vector512.Create(SignedIntegerRange); + source += Vector512.Create(SignedIntegerMinimum); + } + + source = Vector512.Min(Vector512.Max(source, Vector512.Create((float)short.MinValue)), Vector512.Create((float)short.MaxValue)); + } + + return Vector512_.ConvertToInt32RoundToEven(source); + } + + /// + /// Converts vectors to signed integer components using the format's packing contract. + /// + /// The source vectors. + /// Whether native components use signed-normalized values. + /// Whether the source vectors use the scaled range. + /// The converted integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ConvertToInt32(Vector256 source, bool normalized, bool scaled) + { + if (normalized) + { + if (scaled) + { + source *= Vector256.Create(2F); + source -= Vector256.One; + } + + source *= Vector256.Create(ShortMaximum); + source = Vector256.Min(Vector256.Max(source, Vector256.Create(-ShortMaximum)), Vector256.Create(ShortMaximum)); + } + else + { + if (scaled) + { + source *= Vector256.Create(SignedIntegerRange); + source += Vector256.Create(SignedIntegerMinimum); + } + + source = Vector256.Min(Vector256.Max(source, Vector256.Create((float)short.MinValue)), Vector256.Create((float)short.MaxValue)); + } + + return Vector256_.ConvertToInt32RoundToEven(source); + } + + /// + /// Converts vectors to signed integer components using the format's packing contract. + /// + /// The source vectors. + /// Whether native components use signed-normalized values. + /// Whether the source vectors use the scaled range. + /// The converted integer components. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ConvertToInt32(Vector128 source, bool normalized, bool scaled) + { + if (normalized) + { + if (scaled) + { + source *= Vector128.Create(2F); + source -= Vector128.One; + } + + source *= Vector128.Create(ShortMaximum); + source = Vector128.Min(Vector128.Max(source, Vector128.Create(-ShortMaximum)), Vector128.Create(ShortMaximum)); + } + else + { + if (scaled) + { + source *= Vector128.Create(SignedIntegerRange); + source += Vector128.Create(SignedIntegerMinimum); + } + + source = Vector128.Min(Vector128.Max(source, Vector128.Create((float)short.MinValue)), Vector128.Create((float)short.MaxValue)); + } + + return Vector128_.ConvertToInt32RoundToEven(source); + } +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs new file mode 100644 index 000000000..637071421 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Affine.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats.Utils; + +internal static partial class Vector4Converters +{ + /// + /// Multiplies each vector component and then adds the corresponding offset component. + /// + /// The vectors to transform in place. + /// The component-wise multiplier. + /// The component-wise offset applied after multiplication. + public static void MultiplyThenAdd(Span vectors, Vector4 multiplier, Vector4 offset) + => Apply(vectors, new MultiplyThenAddOperator(multiplier, offset)); + + /// + /// Adds the corresponding offset component and then divides each vector component by its divisor. + /// + /// The vectors to transform in place. + /// The component-wise offset applied before division. + /// The component-wise divisor. + public static void AddThenDivide(Span vectors, Vector4 offset, Vector4 divisor) + => Apply(vectors, new AddThenDivideOperator(offset, divisor)); + + /// + /// Applies a stateful component transform to a vector buffer in place. + /// + /// The transform selected for this closed traversal. + /// The vectors to transform. + /// The transform and its component-wise state. + // Closing and inlining the traversal lets the JIT devirtualize every Invoke call, + // specialize the active hardware-width branches, and discard unused operator state. + [MethodImpl(InliningOptions.AlwaysInline)] + private static void Apply(Span vectors, TOperator transform) + where TOperator : struct, IStatefulVector4Operator + { + ref Vector4 vectorBase = ref MemoryMarshal.GetReference(vectors); + int index = 0; + + // A Vector4 is one complete pixel. Descending register widths therefore consume groups + // of four, two, and one pixels without splitting a pixel across traversal boundaries. + if (Vector512.IsHardwareAccelerated) + { + int vectorsPerRegister = Vector512.Count / Vector128.Count; + int oneRegisterFromEnd = vectors.Length - vectorsPerRegister; + + for (; index <= oneRegisterFromEnd; index += vectorsPerRegister) + { + ref Vector512 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); + + vector = transform.Invoke(vector); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int vectorsPerRegister = Vector256.Count / Vector128.Count; + int oneRegisterFromEnd = vectors.Length - vectorsPerRegister; + + for (; index <= oneRegisterFromEnd; index += vectorsPerRegister) + { + ref Vector256 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); + + vector = transform.Invoke(vector); + } + } + + if (Vector128.IsHardwareAccelerated) + { + // Vector128 and Vector4 have the same four-lane layout, so this stage + // consumes every remaining complete pixel and leaves no scalar remainder. + for (; index < vectors.Length; index++) + { + ref Vector128 vector = ref Unsafe.As>(ref Unsafe.Add(ref vectorBase, (uint)index)); + + vector = transform.Invoke(vector); + } + + return; + } + + // Vector4 retains the same component order and expression ordering when hardware + // intrinsics are unavailable, preserving the format-specific conversion contract. + for (; index < vectors.Length; index++) + { + ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); + vector = transform.Invoke(vector); + } + } +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs new file mode 100644 index 000000000..96d81617b --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AffineOperators.cs @@ -0,0 +1,147 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.PixelFormats.Utils; + +internal static partial class Vector4Converters +{ + /// + /// Defines a stateful component transform for each register width used by the shared traversal. + /// + private interface IStatefulVector4Operator + { + /// + /// Transforms one pixel represented by four components. + /// + /// The source components. + /// The transformed components. + public Vector4 Invoke(Vector4 source); + + /// + /// Transforms one pixel represented by the four single-precision lanes in a 128-bit register. + /// + /// The source components. + /// The transformed components. + public Vector128 Invoke(Vector128 source); + + /// + /// Transforms two pixels represented by the eight single-precision lanes in a 256-bit register. + /// + /// The source components. + /// The transformed components. + public Vector256 Invoke(Vector256 source); + + /// + /// Transforms four pixels represented by the sixteen single-precision lanes in a 512-bit register. + /// + /// The source components. + /// The transformed components. + public Vector512 Invoke(Vector512 source); + } + + /// + /// Carries the component state for a multiply-then-add transform. + /// + private readonly struct MultiplyThenAddOperator : IStatefulVector4Operator + { + private readonly Vector512 multiplier; + private readonly Vector512 offset; + + /// + /// Initializes a new instance of the struct. + /// + /// The component-wise multiplier. + /// The component-wise offset applied after multiplication. + public MultiplyThenAddOperator(Vector4 multiplier, Vector4 offset) + { + Vector128 multiplier128 = multiplier.AsVector128(); + Vector128 offset128 = offset.AsVector128(); + Vector256 multiplier256 = Vector256.Create(multiplier128, multiplier128); + Vector256 offset256 = Vector256.Create(offset128, offset128); + + // Expanding the invariant state once prevents the width-specific Invoke methods + // from rebuilding identical lane groups for every vector processed by the loop. + this.multiplier = Vector512.Create(multiplier256, multiplier256); + this.offset = Vector512.Create(offset256, offset256); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 Invoke(Vector4 source) + { + Vector128 result = (source.AsVector128() * this.multiplier.GetLower().GetLower()) + this.offset.GetLower().GetLower(); + + return result.AsVector4(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector128 Invoke(Vector128 source) + => (source * this.multiplier.GetLower().GetLower()) + this.offset.GetLower().GetLower(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector256 Invoke(Vector256 source) + => (source * this.multiplier.GetLower()) + this.offset.GetLower(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector512 Invoke(Vector512 source) + => (source * this.multiplier) + this.offset; + } + + /// + /// Carries the component state for an add-then-divide transform. + /// + private readonly struct AddThenDivideOperator : IStatefulVector4Operator + { + private readonly Vector512 offset; + private readonly Vector512 divisor; + + /// + /// Initializes a new instance of the struct. + /// + /// The component-wise offset applied before division. + /// The component-wise divisor. + public AddThenDivideOperator(Vector4 offset, Vector4 divisor) + { + Vector128 offset128 = offset.AsVector128(); + Vector128 divisor128 = divisor.AsVector128(); + Vector256 offset256 = Vector256.Create(offset128, offset128); + Vector256 divisor256 = Vector256.Create(divisor128, divisor128); + + // All register widths consume prefixes of this repeated four-pixel state, + // so one construction serves the wide loop and every narrower remainder. + this.offset = Vector512.Create(offset256, offset256); + this.divisor = Vector512.Create(divisor256, divisor256); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 Invoke(Vector4 source) + { + Vector128 result = (source.AsVector128() + this.offset.GetLower().GetLower()) / this.divisor.GetLower().GetLower(); + + return result.AsVector4(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector128 Invoke(Vector128 source) + => (source + this.offset.GetLower().GetLower()) / this.divisor.GetLower().GetLower(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector256 Invoke(Vector256 source) + => (source + this.offset.GetLower()) / this.divisor.GetLower(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector512 Invoke(Vector512 source) + => (source + this.offset) / this.divisor; + } +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs new file mode 100644 index 000000000..b47779df8 --- /dev/null +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.AssociatedRgbaCompatible.cs @@ -0,0 +1,892 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.PixelFormats.Utils; + +/// +/// Contains . +/// +internal static partial class Vector4Converters +{ + /// + /// Provides efficient batched conversion for four-byte pixel types that store premultiplied alpha. + /// + public static class AssociatedRgbaCompatible + { + /// + /// Converts associated RGBA byte components to an unassociated scaled vector. + /// + /// The associated red component. + /// The associated green component. + /// The associated blue component. + /// The alpha component. + /// The unassociated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Vector4 ToUnassociatedVector4(byte red, byte green, byte blue, byte alpha) + { + // Divide the original byte magnitudes before normalization so a floating-point intermediate cannot move an exact byte conversion across its rounding midpoint. + Vector4 vector = new(red, green, blue, alpha); + float colorDivisor = alpha == 0 ? byte.MaxValue : alpha; + return vector / new Vector4(colorDivisor, colorDivisor, colorDivisor, byte.MaxValue); + } + + /// + /// Converts packed RGBA byte magnitudes to unassociated scaled vectors. + /// + /// The packed RGBA bytes. + /// The destination vectors. + private static void ToUnassociatedVector4(ReadOnlySpan source, Span destination) + { + ref byte sourceBase = ref MemoryMarshal.GetReference(source); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + // Each packed component occupies one byte, so the destination Vector4 lane count also defines the source byte stride. + int componentsPerPixel = Vector128.Count; + int i = 0; + + // Portable widening advances one integer width at a time, so assemble the wider vectors from ordered 128-bit halves. + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / componentsPerPixel; + + for (; i <= destination.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)); + Vector128 lowerShorts = Vector128.WidenLower(packed); + Vector128 upperShorts = Vector128.WidenUpper(packed); + Vector256 lowerIntegers = Vector256.Create(Vector128.WidenLower(lowerShorts), Vector128.WidenUpper(lowerShorts)); + Vector256 upperIntegers = Vector256.Create(Vector128.WidenLower(upperShorts), Vector128.WidenUpper(upperShorts)); + Vector512 integers = Vector512.Create(lowerIntegers, upperIntegers).AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Vector512.ConvertToSingle(integers)); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / componentsPerPixel; + + for (; i <= destination.Length - pixelsPerVector; i += pixelsPerVector) + { + ulong packed = Unsafe.ReadUnaligned(ref Unsafe.Add(ref sourceBase, (uint)(i * componentsPerPixel))); + Vector128 shorts = Vector128.WidenLower(Vector128.CreateScalarUnsafe(packed).AsByte()); + Vector256 integers = Vector256.Create(Vector128.WidenLower(shorts), Vector128.WidenUpper(shorts)).AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref destinationBase, (uint)i)) = ToUnassociatedVector4(Vector256.ConvertToSingle(integers)); + } + } + + if (Vector128.IsHardwareAccelerated) + { + int pixelsPerVector = Vector128.Count / componentsPerPixel; + + for (; i <= destination.Length - pixelsPerVector; i += pixelsPerVector) + { + Vector128 packed = Vector128.LoadUnsafe(ref sourceBase, (nuint)(i * componentsPerPixel)); + (Vector128 shorts0, Vector128 shorts1) = Vector128.Widen(packed); + (Vector128 integers0, Vector128 integers1) = Vector128.Widen(shorts0); + (Vector128 integers2, Vector128 integers3) = Vector128.Widen(shorts1); + + Unsafe.Add(ref destinationBase, (uint)i) = ToUnassociatedVector4(Vector128.ConvertToSingle(integers0.AsInt32())).AsVector4(); + Unsafe.Add(ref destinationBase, (uint)i + 1u) = ToUnassociatedVector4(Vector128.ConvertToSingle(integers1.AsInt32())).AsVector4(); + Unsafe.Add(ref destinationBase, (uint)i + 2u) = ToUnassociatedVector4(Vector128.ConvertToSingle(integers2.AsInt32())).AsVector4(); + Unsafe.Add(ref destinationBase, (uint)i + 3u) = ToUnassociatedVector4(Vector128.ConvertToSingle(integers3.AsInt32())).AsVector4(); + } + } + + for (; i < destination.Length; i++) + { + int offset = i * componentsPerPixel; + + Unsafe.Add(ref destinationBase, (uint)i) = ToUnassociatedVector4( + Unsafe.Add(ref sourceBase, (uint)offset), + Unsafe.Add(ref sourceBase, (uint)offset + 1u), + Unsafe.Add(ref sourceBase, (uint)offset + 2u), + Unsafe.Add(ref sourceBase, (uint)offset + 3u)); + } + } + + /// + /// Converts four associated RGBA byte-magnitude vectors to unassociated scaled vectors. + /// + /// The associated byte-magnitude vectors. + /// The unassociated scaled vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ToUnassociatedVector4(Vector512 source) + { + Vector512 zero = Vector512.Zero; + Vector512 byteMax = Vector512.Create((float)byte.MaxValue); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + + // Zero-alpha storage has no alpha divisor, while W always uses the byte normalization divisor. + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + Vector512 normalizeMask = Vector512.Equals(alpha, zero) | alphaMask; + Vector512 divisor = Vector512.ConditionalSelect(normalizeMask, byteMax, alpha); + return source / divisor; + } + + /// + /// Converts two associated RGBA byte-magnitude vectors to unassociated scaled vectors. + /// + /// The associated byte-magnitude vectors. + /// The unassociated scaled vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ToUnassociatedVector4(Vector256 source) + { + Vector256 zero = Vector256.Zero; + Vector256 byteMax = Vector256.Create((float)byte.MaxValue); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + + // Zero-alpha storage has no alpha divisor, while W always uses the byte normalization divisor. + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + Vector256 normalizeMask = Vector256.Equals(alpha, zero) | alphaMask; + Vector256 divisor = Vector256.ConditionalSelect(normalizeMask, byteMax, alpha); + return source / divisor; + } + + /// + /// Converts an associated RGBA byte-magnitude vector to an unassociated scaled vector. + /// + /// The associated byte-magnitude vector. + /// The unassociated scaled vector. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ToUnassociatedVector4(Vector128 source) + { + Vector128 zero = Vector128.Zero; + Vector128 byteMax = Vector128.Create((float)byte.MaxValue); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + + // Zero-alpha storage has no alpha divisor, while W always uses the byte normalization divisor. + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + Vector128 normalizeMask = Vector128.Equals(alpha, zero) | alphaMask; + Vector128 divisor = Vector128.ConditionalSelect(normalizeMask, byteMax, alpha); + return source / divisor; + } + + /// + /// Converts unassociated scaled vectors to associated byte magnitudes. + /// + /// The vectors to convert in place. + private static void AssociateToByte(Span source) + { + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + ref Vector512 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector512 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = AssociateToByte(sourceBase); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = AssociateToByte(sourceBase); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector128.IsHardwareAccelerated) + { + ref Vector128 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)source.Length); + + while (Unsafe.IsAddressLessThan(ref sourceBase, ref sourceEnd)) + { + sourceBase = AssociateToByte(sourceBase); + sourceBase = ref Unsafe.Add(ref sourceBase, 1); + } + + return; + } + + ref Vector4 tailBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Unsafe.Add(ref tailBase, i) = AssociateToByte(Unsafe.Add(ref tailBase, i)); + } + } + + /// + /// Converts four unassociated scaled vectors to associated byte magnitudes. + /// + /// The unassociated scaled vectors. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 AssociateToByte(Vector512 source) + { + Vector512 zero = Vector512.Zero; + Vector512 one = Vector512.One; + Vector512 byteMax = Vector512.Create((float)byte.MaxValue); + source = Vector512.Min(Vector512.Max(source, zero), one); + Vector512 alpha = Vector512_.ShuffleNative(source, 0b_11_11_11_11); + Vector512 storedAlpha = Vector512.Floor((alpha * byteMax) + Vector512.Create(.5F)); + Vector512 result = source * storedAlpha; + + // RGB is associated using the quantized alpha byte, while W stores that byte directly. + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts two unassociated scaled vectors to associated byte magnitudes. + /// + /// The unassociated scaled vectors. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 AssociateToByte(Vector256 source) + { + Vector256 zero = Vector256.Zero; + Vector256 one = Vector256.One; + Vector256 byteMax = Vector256.Create((float)byte.MaxValue); + source = Vector256.Min(Vector256.Max(source, zero), one); + Vector256 alpha = Vector256_.ShuffleNative(source, 0b_11_11_11_11); + Vector256 storedAlpha = Vector256.Floor((alpha * byteMax) + Vector256.Create(.5F)); + Vector256 result = source * storedAlpha; + + // RGB is associated using the quantized alpha byte, while W stores that byte directly. + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + return Vector256.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts an unassociated scaled vector to associated byte magnitudes. + /// + /// The unassociated scaled vector. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 AssociateToByte(Vector128 source) + { + Vector128 zero = Vector128.Zero; + Vector128 one = Vector128.One; + Vector128 byteMax = Vector128.Create((float)byte.MaxValue); + source = Vector128.Min(Vector128.Max(source, zero), one); + Vector128 alpha = Vector128_.ShuffleNative(source, 0b_11_11_11_11); + Vector128 storedAlpha = Vector128.Floor((alpha * byteMax) + Vector128.Create(.5F)); + Vector128 result = source * storedAlpha; + + // RGB is associated using the quantized alpha byte, while W stores that byte directly. + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + return Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + } + + /// + /// Converts an unassociated scaled vector to associated byte magnitudes. + /// + /// The unassociated scaled vector. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 AssociateToByte(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + float storedAlpha = (byte)Numerics.Clamp((source.W * byte.MaxValue) + 0.5F, 0, byte.MaxValue); + + source *= storedAlpha; + source.W = storedAlpha; + return source; + } + + /// + /// Converts an unassociated scaled vector to an pixel. + /// + /// The unassociated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Rgba32P FromUnassociatedVector4ToRgba32P(Vector4 source) + { + source = AssociateToByte(source); + return new Rgba32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an unassociated scaled vector to a pixel. + /// + /// The unassociated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Bgra32P FromUnassociatedVector4ToBgra32P(Vector4 source) + { + source = AssociateToByte(source); + return new Bgra32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an unassociated scaled vector to an pixel. + /// + /// The unassociated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Argb32P FromUnassociatedVector4ToArgb32P(Vector4 source) + { + source = AssociateToByte(source); + return new Argb32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an unassociated scaled vector to an pixel. + /// + /// The unassociated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Abgr32P FromUnassociatedVector4ToAbgr32P(Vector4 source) + { + source = AssociateToByte(source); + return new Abgr32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an associated scaled vector to associated byte magnitudes using the alpha byte the destination stores. + /// + /// The associated scaled vector. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 ReassociateToByte(Vector4 source) + { + float alpha = source.W; + + if (alpha <= 0) + { + return Vector4.Zero; + } + + float byteAlpha = alpha * byte.MaxValue; + float storedAlpha = (byte)Numerics.Clamp(byteAlpha + 0.5F, 0, byte.MaxValue); + + if (byteAlpha == storedAlpha) + { + // Quantization leaves alpha unchanged, so RGB is already associated correctly. Avoiding division preserves exact byte midpoints produced by scaling stored components. + source *= byte.MaxValue; + } + else + { + // Recover straight RGB before multiplying by the stored alpha byte. Keeping the association in byte magnitude preserves exact half-byte rounding boundaries. + Numerics.UnPremultiply(ref source); + source *= storedAlpha; + } + + source.W = storedAlpha; + Numerics.ClampRgbToAlpha(ref source); + return source; + } + + /// + /// Converts associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores. + /// + /// The vectors to convert in place. + private static void ReassociateToByte(Span source) + { + if (Vector512.IsHardwareAccelerated) + { + int pixelsPerVector = Vector512.Count / Vector128.Count; + ref Vector512 vectorSourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector512 sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) + { + vectorSourceBase = ReassociateToByte(vectorSourceBase); + vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector256.IsHardwareAccelerated) + { + int pixelsPerVector = Vector256.Count / Vector128.Count; + ref Vector256 vectorSourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector256 sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length / (uint)pixelsPerVector); + + while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) + { + vectorSourceBase = ReassociateToByte(vectorSourceBase); + vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); + } + + // SIMD widths are powers of two, so masking finds the start of the unprocessed remainder without division. + source = source[(source.Length & ~(pixelsPerVector - 1))..]; + } + + if (Vector128.IsHardwareAccelerated) + { + ref Vector128 vectorSourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); + ref Vector128 sourceEnd = ref Unsafe.Add(ref vectorSourceBase, (uint)source.Length); + + while (Unsafe.IsAddressLessThan(ref vectorSourceBase, ref sourceEnd)) + { + vectorSourceBase = ReassociateToByte(vectorSourceBase); + vectorSourceBase = ref Unsafe.Add(ref vectorSourceBase, 1); + } + + return; + } + + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(source); + + for (nuint i = 0; i < (uint)source.Length; i++) + { + Unsafe.Add(ref sourceBase, i) = ReassociateToByte(Unsafe.Add(ref sourceBase, i)); + } + } + + /// + /// Converts four associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores. + /// + /// The associated scaled vectors. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector512 ReassociateToByte(Vector512 source) + { + Vector512 zero = Vector512.Zero; + Vector512 byteMax = Vector512.Create((float)byte.MaxValue); + Vector512 alpha = Vector512.Max(Vector512_.ShuffleNative(source, 0b_11_11_11_11), zero); + Vector512 byteAlpha = alpha * byteMax; + Vector512 storedAlpha = Vector512.Floor(Vector512.Min(Vector512.Max(byteAlpha + Vector512.Create(.5F), zero), byteMax)); + Vector512 result = (source / alpha) * storedAlpha; + + // Exact byte alpha values need no reassociation. Multiplying by 255 directly preserves RGB values that already lie on byte midpoints. + result = Vector512.ConditionalSelect(Vector512.Equals(byteAlpha, storedAlpha), source * byteMax, result); + Vector512 alphaMask = Vector512.Create(0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + result = Vector512.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector512.Min(Vector512.Max(result, zero), storedAlpha); + return Vector512.ConditionalSelect(Vector512.Equals(alpha, zero), zero, result); + } + + /// + /// Converts two associated scaled vectors to associated byte magnitudes using the alpha bytes the destination stores. + /// + /// The associated scaled vectors. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 ReassociateToByte(Vector256 source) + { + Vector256 zero = Vector256.Zero; + Vector256 byteMax = Vector256.Create((float)byte.MaxValue); + Vector256 alpha = Vector256.Max(Vector256_.ShuffleNative(source, 0b_11_11_11_11), zero); + Vector256 byteAlpha = alpha * byteMax; + Vector256 storedAlpha = Vector256.Floor(Vector256.Min(Vector256.Max(byteAlpha + Vector256.Create(.5F), zero), byteMax)); + Vector256 result = (source / alpha) * storedAlpha; + + // Exact byte alpha values need no reassociation. Multiplying by 255 directly preserves RGB values that already lie on byte midpoints. + result = Vector256.ConditionalSelect(Vector256.Equals(byteAlpha, storedAlpha), source * byteMax, result); + Vector256 alphaMask = Vector256.Create(0, 0, 0, -1, 0, 0, 0, -1).AsSingle(); + result = Vector256.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector256.Min(Vector256.Max(result, zero), storedAlpha); + return Vector256.ConditionalSelect(Vector256.Equals(alpha, zero), zero, result); + } + + /// + /// Converts an associated scaled vector to associated byte magnitudes using the alpha byte the destination stores. + /// + /// The associated scaled vector. + /// The associated byte magnitudes. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 ReassociateToByte(Vector128 source) + { + Vector128 zero = Vector128.Zero; + Vector128 byteMax = Vector128.Create((float)byte.MaxValue); + Vector128 alpha = Vector128.Max(Vector128_.ShuffleNative(source, 0b_11_11_11_11), zero); + Vector128 byteAlpha = alpha * byteMax; + Vector128 storedAlpha = Vector128.Floor(Vector128.Min(Vector128.Max(byteAlpha + Vector128.Create(.5F), zero), byteMax)); + Vector128 result = (source / alpha) * storedAlpha; + + // Exact byte alpha values need no reassociation. Multiplying by 255 directly preserves RGB values that already lie on byte midpoints. + result = Vector128.ConditionalSelect(Vector128.Equals(byteAlpha, storedAlpha), source * byteMax, result); + Vector128 alphaMask = Vector128.Create(0, 0, 0, -1).AsSingle(); + result = Vector128.ConditionalSelect(alphaMask, storedAlpha, result); + result = Vector128.Min(Vector128.Max(result, zero), storedAlpha); + return Vector128.ConditionalSelect(Vector128.Equals(alpha, zero), zero, result); + } + + /// + /// Converts an associated scaled vector to an pixel. + /// + /// The associated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Rgba32P FromAssociatedVector4ToRgba32P(Vector4 source) + { + source = ReassociateToByte(source); + return new Rgba32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an associated scaled vector to a pixel. + /// + /// The associated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Bgra32P FromAssociatedVector4ToBgra32P(Vector4 source) + { + source = ReassociateToByte(source); + return new Bgra32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an associated scaled vector to an pixel. + /// + /// The associated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Argb32P FromAssociatedVector4ToArgb32P(Vector4 source) + { + source = ReassociateToByte(source); + return new Argb32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts an associated scaled vector to an pixel. + /// + /// The associated scaled vector. + /// The associated pixel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Abgr32P FromAssociatedVector4ToAbgr32P(Vector4 source) + { + source = ReassociateToByte(source); + return new Abgr32P(ConvertToByte(source.X), ConvertToByte(source.Y), ConvertToByte(source.Z), ConvertToByte(source.W)); + } + + /// + /// Converts associated RGBA pixels to associated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(source), MemoryMarshal.Cast(destination)); + } + + /// + /// Converts premultiplied RGBA pixels to unassociated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + internal static void ToUnassociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + ToUnassociatedVector4(MemoryMarshal.Cast(source), destination[..source.Length]); + } + + /// + /// Converts unassociated scaled vectors to premultiplied RGBA pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromUnassociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + AssociateToByte(source); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), MemoryMarshal.Cast(destination)); + } + + /// + /// Converts associated scaled vectors to premultiplied RGBA pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromAssociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ReassociateToByte(source); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), MemoryMarshal.Cast(destination)); + } + + /// + /// Converts associated BGRA pixels to associated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // ByteToNormalizedFloat preserves component order, so the packed BGRA bytes must first be shuffled to RGBA. + // Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation. + // The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromBgra32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(temporary), MemoryMarshal.Cast(destination[..lastIndex])); + destination[lastIndex] = source[lastIndex].ToVector4(); + } + + /// + /// Converts premultiplied BGRA pixels to unassociated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + internal static void ToUnassociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // Stage all but the final pixel as RGBA bytes in the unwritten end of the expanding Vector4 destination. + // The final expanded vector overlaps that staging region, so convert it directly after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromBgra32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + ToUnassociatedVector4(MemoryMarshal.Cast(temporary), destination[..lastIndex]); + destination[lastIndex] = ToUnassociatedVector4(source[lastIndex].R, source[lastIndex].G, source[lastIndex].B, source[lastIndex].A); + } + + /// + /// Converts unassociated scaled vectors to premultiplied BGRA pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromUnassociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + AssociateToByte(source); + + // FloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated scaled vectors to premultiplied BGRA pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromAssociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ReassociateToByte(source); + + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToBgra32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated ARGB pixels to associated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // ByteToNormalizedFloat preserves component order, so the packed ARGB bytes must first be shuffled to RGBA. + // Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation. + // The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromArgb32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(temporary), MemoryMarshal.Cast(destination[..lastIndex])); + destination[lastIndex] = source[lastIndex].ToVector4(); + } + + /// + /// Converts premultiplied ARGB pixels to unassociated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + internal static void ToUnassociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // Stage all but the final pixel as RGBA bytes in the unwritten end of the expanding Vector4 destination. + // The final expanded vector overlaps that staging region, so convert it directly after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromArgb32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + ToUnassociatedVector4(MemoryMarshal.Cast(temporary), destination[..lastIndex]); + destination[lastIndex] = ToUnassociatedVector4(source[lastIndex].R, source[lastIndex].G, source[lastIndex].B, source[lastIndex].A); + } + + /// + /// Converts unassociated scaled vectors to premultiplied ARGB pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromUnassociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + AssociateToByte(source); + + // FloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated scaled vectors to premultiplied ARGB pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromAssociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ReassociateToByte(source); + + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToArgb32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated ABGR pixels to associated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ToAssociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // ByteToNormalizedFloat preserves component order, so the packed ABGR bytes must first be shuffled to RGBA. + // Reuse the unwritten end of the larger Vector4 destination as staging to avoid a temporary allocation. + // The final vector overlaps that staging, so its source pixel is converted after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromAbgr32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + SimdUtils.ByteToNormalizedFloat(MemoryMarshal.Cast(temporary), MemoryMarshal.Cast(destination[..lastIndex])); + destination[lastIndex] = source[lastIndex].ToVector4(); + } + + /// + /// Converts premultiplied ABGR pixels to unassociated scaled vectors. + /// + /// The source pixels. + /// The destination vectors. + internal static void ToUnassociatedVector4(ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + if (source.IsEmpty) + { + return; + } + + // Stage all but the final pixel as RGBA bytes in the unwritten end of the expanding Vector4 destination. + // The final expanded vector overlaps that staging region, so convert it directly after the staged bytes are consumed. + int lastIndex = source.Length - 1; + Span temporary = MemoryMarshal.Cast(destination).Slice((3 * source.Length) + 1, lastIndex); + PixelConverter.FromAbgr32.ToRgba32(MemoryMarshal.Cast(source[..lastIndex]), MemoryMarshal.Cast(temporary)); + ToUnassociatedVector4(MemoryMarshal.Cast(temporary), destination[..lastIndex]); + destination[lastIndex] = ToUnassociatedVector4(source[lastIndex].R, source[lastIndex].G, source[lastIndex].B, source[lastIndex].A); + } + + /// + /// Converts unassociated scaled vectors to premultiplied ABGR pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromUnassociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + AssociateToByte(source); + + // FloatToByteSaturate emits RGBA bytes, which can be shuffled in place to avoid a temporary pixel buffer. + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes); + } + + /// + /// Converts associated scaled vectors to premultiplied ABGR pixels. + /// + /// The source vectors, modified in place during conversion. + /// The destination pixels. + internal static void FromAssociatedVector4(Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + ReassociateToByte(source); + + Span destinationBytes = MemoryMarshal.Cast(destination); + SimdUtils.FloatToByteSaturate(MemoryMarshal.Cast(source), destinationBytes); + PixelConverter.FromRgba32.ToAbgr32(destinationBytes, destinationBytes); + } + + /// + /// Converts a floating-point byte magnitude to a byte. + /// + /// The byte magnitude. + /// The rounded and clamped byte. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte ConvertToByte(float value) => (byte)Numerics.Clamp(value + .5F, 0, byte.MaxValue); + } +} diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs index d7cac1530..05799961d 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.Default.cs @@ -50,15 +50,43 @@ internal static partial class Vector4Converters PixelConversionModifiers modifiers) where TPixel : unmanaged, IPixel { - ApplyBackwardConversionModifiers(source, modifiers); + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); - if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) { - UnsafeFromScaledVector4Core(source, destination); + // Transfer functions consume straight color, so preserve the established modifier order before selecting a scalar storage conversion. + ApplyBackwardConversionModifiers(source, modifiers); + + if (scaled) + { + UnsafeFromUnassociatedScaledVector4Core(source, destination); + } + else + { + UnsafeFromUnassociatedVector4Core(source, destination); + } + + return; + } + + if (scaled) + { + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + UnsafeFromAssociatedScaledVector4Core(source, destination); + } + else + { + UnsafeFromUnassociatedScaledVector4Core(source, destination); + } + } + else if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + UnsafeFromAssociatedVector4Core(source, destination); } else { - UnsafeFromVector4Core(source, destination); + UnsafeFromUnassociatedVector4Core(source, destination); } } @@ -69,20 +97,82 @@ internal static partial class Vector4Converters PixelConversionModifiers modifiers) where TPixel : unmanaged, IPixel { - if (modifiers.IsDefined(PixelConversionModifiers.Scale)) + // Conversion length is defined by the source. Limiting the destination here also keeps modifiers from + // changing spare capacity supplied by callers of either the checked or unchecked conversion path. + destination = destination[..source.Length]; + + bool scaled = modifiers.IsDefined(PixelConversionModifiers.Scale); + + if (modifiers.IsDefined(PixelConversionModifiers.SRgbCompand)) { - UnsafeToScaledVector4Core(source, destination); + if (scaled) + { + UnsafeToUnassociatedScaledVector4Core(source, destination); + } + else + { + UnsafeToUnassociatedVector4Core(source, destination); + } + + // Transfer functions consume straight color; association is applied only after expansion. + ApplyForwardConversionModifiers(destination, modifiers); + return; + } + + if (scaled) + { + if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + UnsafeToAssociatedScaledVector4Core(source, destination); + } + else + { + UnsafeToUnassociatedScaledVector4Core(source, destination); + } + } + else if (modifiers.IsDefined(PixelConversionModifiers.Premultiply)) + { + UnsafeToAssociatedVector4Core(source, destination); } else { - UnsafeToVector4Core(source, destination); + UnsafeToUnassociatedVector4Core(source, destination); } + } - ApplyForwardConversionModifiers(destination, modifiers); + /// + /// Converts unassociated native-range vectors to pixels without checking span lengths. + /// + /// The destination pixel format. + /// The source vectors. + /// The destination pixels. Its length must be at least length. + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeFromUnassociatedVector4Core( + ReadOnlySpan source, + Span destination) + where TPixel : unmanaged, IPixel + { + ref Vector4 sourceStart = ref MemoryMarshal.GetReference(source); + ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); + ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + { + destinationBase = TPixel.FromUnassociatedVector4(sourceStart); + + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + } } + /// + /// Converts associated native-range vectors to pixels without checking span lengths. + /// + /// The destination pixel format. + /// The source vectors. + /// The destination pixels. Its length must be at least length. [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeFromVector4Core( + private static void UnsafeFromAssociatedVector4Core( ReadOnlySpan source, Span destination) where TPixel : unmanaged, IPixel @@ -93,15 +183,46 @@ internal static partial class Vector4Converters while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) { - destinationBase = TPixel.FromVector4(sourceStart); + destinationBase = TPixel.FromAssociatedVector4(sourceStart); + + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + } + } + + /// + /// Converts pixels to unassociated native-range vectors without checking span lengths. + /// + /// The source pixel format. + /// The source pixels. + /// The destination vectors. Its length must be at least length. + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeToUnassociatedVector4Core( + ReadOnlySpan source, + Span destination) + where TPixel : unmanaged, IPixel + { + ref TPixel sourceStart = ref MemoryMarshal.GetReference(source); + ref TPixel sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + { + destinationBase = sourceStart.ToUnassociatedVector4(); sourceStart = ref Unsafe.Add(ref sourceStart, 1); destinationBase = ref Unsafe.Add(ref destinationBase, 1); } } + /// + /// Converts pixels to associated native-range vectors without checking span lengths. + /// + /// The source pixel format. + /// The source pixels. + /// The destination vectors. Its length must be at least length. [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeToVector4Core( + private static void UnsafeToAssociatedVector4Core( ReadOnlySpan source, Span destination) where TPixel : unmanaged, IPixel @@ -112,15 +233,21 @@ internal static partial class Vector4Converters while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) { - destinationBase = sourceStart.ToVector4(); + destinationBase = sourceStart.ToAssociatedVector4(); sourceStart = ref Unsafe.Add(ref sourceStart, 1); destinationBase = ref Unsafe.Add(ref destinationBase, 1); } } + /// + /// Converts unassociated scaled vectors to pixels without checking span lengths. + /// + /// The destination pixel format. + /// The source vectors. + /// The destination pixels. Its length must be at least length. [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeFromScaledVector4Core( + private static void UnsafeFromUnassociatedScaledVector4Core( ReadOnlySpan source, Span destination) where TPixel : unmanaged, IPixel @@ -131,15 +258,71 @@ internal static partial class Vector4Converters while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) { - destinationBase = TPixel.FromScaledVector4(sourceStart); + destinationBase = TPixel.FromUnassociatedScaledVector4(sourceStart); + + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + } + } + + /// + /// Converts associated scaled vectors to pixels without checking span lengths. + /// + /// The destination pixel format. + /// The source vectors. + /// The destination pixels. Its length must be at least length. + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeFromAssociatedScaledVector4Core( + ReadOnlySpan source, + Span destination) + where TPixel : unmanaged, IPixel + { + ref Vector4 sourceStart = ref MemoryMarshal.GetReference(source); + ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); + ref TPixel destinationBase = ref MemoryMarshal.GetReference(destination); + + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + { + destinationBase = TPixel.FromAssociatedScaledVector4(sourceStart); + + sourceStart = ref Unsafe.Add(ref sourceStart, 1); + destinationBase = ref Unsafe.Add(ref destinationBase, 1); + } + } + + /// + /// Converts pixels to unassociated scaled vectors without checking span lengths. + /// + /// The source pixel format. + /// The source pixels. + /// The destination vectors. Its length must be at least length. + [MethodImpl(InliningOptions.ShortMethod)] + private static void UnsafeToUnassociatedScaledVector4Core( + ReadOnlySpan source, + Span destination) + where TPixel : unmanaged, IPixel + { + ref TPixel sourceStart = ref MemoryMarshal.GetReference(source); + ref TPixel sourceEnd = ref Unsafe.Add(ref sourceStart, (uint)source.Length); + ref Vector4 destinationBase = ref MemoryMarshal.GetReference(destination); + + while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) + { + destinationBase = sourceStart.ToUnassociatedScaledVector4(); sourceStart = ref Unsafe.Add(ref sourceStart, 1); destinationBase = ref Unsafe.Add(ref destinationBase, 1); } } + /// + /// Converts pixels to associated scaled vectors without checking span lengths. + /// + /// The source pixel format. + /// The source pixels. + /// The destination vectors. Its length must be at least length. [MethodImpl(InliningOptions.ShortMethod)] - private static void UnsafeToScaledVector4Core( + private static void UnsafeToAssociatedScaledVector4Core( ReadOnlySpan source, Span destination) where TPixel : unmanaged, IPixel @@ -150,7 +333,7 @@ internal static partial class Vector4Converters while (Unsafe.IsAddressLessThan(ref sourceStart, ref sourceEnd)) { - destinationBase = sourceStart.ToScaledVector4(); + destinationBase = sourceStart.ToAssociatedScaledVector4(); sourceStart = ref Unsafe.Add(ref sourceStart, 1); destinationBase = ref Unsafe.Add(ref destinationBase, 1); diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs index 9e649f3c0..886732eb9 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.RgbaCompatible.cs @@ -15,10 +15,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils; internal static partial class Vector4Converters { /// - /// Provides efficient implementations for batched to/from conversion. - /// which is applicable for -compatible pixel types where - /// returns the same scaled result as . - /// The method is works by internally converting to a therefore it's not applicable for that type! + /// Provides efficient implementations for batched conversion between RGBA-compatible pixel types and values. /// public static class RgbaCompatible { @@ -51,6 +48,7 @@ internal static partial class Vector4Converters Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); int count = source.Length; + destination = destination[..count]; // Not worth for small buffers: if (count < Vector4ConversionThreshold) @@ -60,14 +58,14 @@ internal static partial class Vector4Converters return; } - // Using the last quarter of 'destination' as a temporary buffer to avoid allocation: + // ToVector4 expands each pixel into a 16-byte vector. Reuse the unwritten destination tail as RGBA staging + // so pixelOperations can reorder the source without allocating a temporary row. int countWithoutLastItem = count - 1; ReadOnlySpan reducedSource = source[..countWithoutLastItem]; Span lastQuarterOfDestination = MemoryMarshal.Cast(destination).Slice((3 * count) + 1, countWithoutLastItem); pixelOperations.ToRgba32(configuration, reducedSource, lastQuarterOfDestination); - // 'destination' and 'lastQuarterOfDestination' are overlapping buffers, - // but we are always reading/writing at different positions: + // Staging overlaps the final output vector, which remains unwritten until the staged bytes are consumed. SimdUtils.ByteToNormalizedFloat( MemoryMarshal.Cast(lastQuarterOfDestination), MemoryMarshal.Cast(destination[..countWithoutLastItem])); @@ -102,6 +100,10 @@ internal static partial class Vector4Converters int count = source.Length; + // Source defines the conversion length. Do not expose spare destination capacity + // to downstream shuffles whose scalar tail is destination-length driven. + destination = destination[..count]; + // Not worth for small buffers: if (count < Vector4ConversionThreshold) { diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs index 0a0b5660d..d701b0ab5 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils; internal static partial class Vector4Converters { /// - /// Apply modifiers used requested by ToVector4() conversion. + /// Applies the modifiers requested by ToVector4() conversion. /// /// The span of vectors. /// The modifier rule. @@ -29,7 +29,7 @@ internal static partial class Vector4Converters } /// - /// Apply modifiers used requested by FromVector4() conversion. + /// Applies the modifiers requested by FromVector4() conversion. /// /// The span of vectors. /// The modifier rule. diff --git a/src/ImageSharp/Primitives/Point.cs b/src/ImageSharp/Primitives/Point.cs index 8f817fe3c..8e9cadcbe 100644 --- a/src/ImageSharp/Primitives/Point.cs +++ b/src/ImageSharp/Primitives/Point.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp; @@ -69,7 +70,7 @@ public struct Point : IEquatable /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); + public readonly bool IsEmpty => this.Equals(Empty); /// /// Creates a with the coordinates of the specified . @@ -250,14 +251,26 @@ public struct Point : IEquatable /// The transformation matrix used. /// The transformed . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point Transform(Point point, Matrix3x2 matrix) => Round(Vector2.Transform(new Vector2(point.X, point.Y), matrix)); + public static PointF Transform(Point point, Matrix3x2 matrix) + => Vector2.Transform(new Vector2(point.X, point.Y), matrix); + + /// + /// Transforms a point by a specified 4x4 matrix, applying a projective transform + /// flattened into 2D space. + /// + /// The point to transform. + /// The transformation matrix used. + /// The transformed . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Transform(Point point, Matrix4x4 matrix) + => TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix); /// /// Deconstructs this point into two integers. /// /// The out value for X. /// The out value for Y. - public void Deconstruct(out int x, out int y) + public readonly void Deconstruct(out int x, out int y) { x = this.X; y = this.Y; @@ -304,17 +317,17 @@ public struct Point : IEquatable public static Point ShiftLeft(Point point, int bitCount) => new(unchecked(point.X << bitCount), unchecked(point.Y << bitCount)); /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y); + public override readonly int GetHashCode() => HashCode.Combine(this.X, this.Y); /// - public override string ToString() => $"Point [ X={this.X}, Y={this.Y} ]"; + public override readonly string ToString() => $"Point [ X={this.X}, Y={this.Y} ]"; /// - public override bool Equals(object? obj) => obj is Point other && this.Equals(other); + public override readonly bool Equals(object? obj) => obj is Point other && this.Equals(other); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Point other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); + public readonly bool Equals(Point other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff)); diff --git a/src/ImageSharp/Primitives/PointF.cs b/src/ImageSharp/Primitives/PointF.cs index de363e2bd..7979c0af0 100644 --- a/src/ImageSharp/Primitives/PointF.cs +++ b/src/ImageSharp/Primitives/PointF.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp; @@ -58,7 +59,7 @@ public struct PointF : IEquatable /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); + public readonly bool IsEmpty => this.Equals(Empty); /// /// Creates a with the coordinates of the specified . @@ -246,12 +247,23 @@ public struct PointF : IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PointF Transform(PointF point, Matrix3x2 matrix) => Vector2.Transform(point, matrix); + /// + /// Transforms a point by a specified 4x4 matrix, applying a projective transform + /// flattened into 2D space. + /// + /// The point to transform. + /// The transformation matrix used. + /// The transformed . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Transform(PointF point, Matrix4x4 matrix) + => TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix); + /// /// Deconstructs this point into two floats. /// /// The out value for X. /// The out value for Y. - public void Deconstruct(out float x, out float y) + public readonly void Deconstruct(out float x, out float y) { x = this.X; y = this.Y; @@ -277,15 +289,15 @@ public struct PointF : IEquatable public void Offset(PointF point) => this.Offset(point.X, point.Y); /// - public override int GetHashCode() => HashCode.Combine(this.X, this.Y); + public override readonly int GetHashCode() => HashCode.Combine(this.X, this.Y); /// - public override string ToString() => $"PointF [ X={this.X}, Y={this.Y} ]"; + public override readonly string ToString() => $"PointF [ X={this.X}, Y={this.Y} ]"; /// - public override bool Equals(object? obj) => obj is PointF pointF && this.Equals(pointF); + public override readonly bool Equals(object? obj) => obj is PointF pointF && this.Equals(pointF); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); + public readonly bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); } diff --git a/src/ImageSharp/Primitives/Rectangle.cs b/src/ImageSharp/Primitives/Rectangle.cs index e2ae5071e..26a94837f 100644 --- a/src/ImageSharp/Primitives/Rectangle.cs +++ b/src/ImageSharp/Primitives/Rectangle.cs @@ -260,11 +260,17 @@ public struct Rectangle : IEquatable /// The transformation matrix. /// A transformed rectangle. public static RectangleF Transform(Rectangle rectangle, Matrix3x2 matrix) - { - PointF bottomRight = Point.Transform(new Point(rectangle.Right, rectangle.Bottom), matrix); - PointF topLeft = Point.Transform(rectangle.Location, matrix); - return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); - } + => RectangleF.Transform(rectangle, matrix); + + /// + /// Transforms a rectangle by the given 4x4 matrix, applying a projective transform + /// flattened into 2D space. + /// + /// The source rectangle. + /// The transformation matrix. + /// A transformed rectangle. + public static RectangleF Transform(Rectangle rectangle, Matrix4x4 matrix) + => RectangleF.Transform(rectangle, matrix); /// /// Converts a to a by performing a truncate operation on all the coordinates. diff --git a/src/ImageSharp/Primitives/RectangleF.cs b/src/ImageSharp/Primitives/RectangleF.cs index 68add77d0..3f41c2d07 100644 --- a/src/ImageSharp/Primitives/RectangleF.cs +++ b/src/ImageSharp/Primitives/RectangleF.cs @@ -236,9 +236,39 @@ public struct RectangleF : IEquatable /// A transformed . public static RectangleF Transform(RectangleF rectangle, Matrix3x2 matrix) { + PointF topLeft = PointF.Transform(rectangle.Location, matrix); + PointF topRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Top), matrix); + PointF bottomLeft = PointF.Transform(new PointF(rectangle.Left, rectangle.Bottom), matrix); PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); + + float left = MathF.Min(MathF.Min(topLeft.X, topRight.X), MathF.Min(bottomLeft.X, bottomRight.X)); + float top = MathF.Min(MathF.Min(topLeft.Y, topRight.Y), MathF.Min(bottomLeft.Y, bottomRight.Y)); + float right = MathF.Max(MathF.Max(topLeft.X, topRight.X), MathF.Max(bottomLeft.X, bottomRight.X)); + float bottom = MathF.Max(MathF.Max(topLeft.Y, topRight.Y), MathF.Max(bottomLeft.Y, bottomRight.Y)); + + return FromLTRB(left, top, right, bottom); + } + + /// + /// Transforms a rectangle by the given 4x4 matrix, applying a projective transform + /// flattened into 2D space. + /// + /// The source rectangle. + /// The transformation matrix. + /// A transformed . + public static RectangleF Transform(RectangleF rectangle, Matrix4x4 matrix) + { PointF topLeft = PointF.Transform(rectangle.Location, matrix); - return new RectangleF(topLeft, new SizeF(bottomRight - topLeft)); + PointF topRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Top), matrix); + PointF bottomLeft = PointF.Transform(new PointF(rectangle.Left, rectangle.Bottom), matrix); + PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix); + + float left = MathF.Min(MathF.Min(topLeft.X, topRight.X), MathF.Min(bottomLeft.X, bottomRight.X)); + float top = MathF.Min(MathF.Min(topLeft.Y, topRight.Y), MathF.Min(bottomLeft.Y, bottomRight.Y)); + float right = MathF.Max(MathF.Max(topLeft.X, topRight.X), MathF.Max(bottomLeft.X, bottomRight.X)); + float bottom = MathF.Max(MathF.Max(topLeft.Y, topRight.Y), MathF.Max(bottomLeft.Y, bottomRight.Y)); + + return FromLTRB(left, top, right, bottom); } /// diff --git a/src/ImageSharp/Primitives/SizeF.cs b/src/ImageSharp/Primitives/SizeF.cs index 81c749875..108ea1eed 100644 --- a/src/ImageSharp/Primitives/SizeF.cs +++ b/src/ImageSharp/Primitives/SizeF.cs @@ -67,7 +67,7 @@ public struct SizeF : IEquatable /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); + public readonly bool IsEmpty => this.Equals(Empty); /// /// Creates a with the coordinates of the specified . @@ -201,24 +201,24 @@ public struct SizeF : IEquatable /// /// The out value for the width. /// The out value for the height. - public void Deconstruct(out float width, out float height) + public readonly void Deconstruct(out float width, out float height) { width = this.Width; height = this.Height; } /// - public override int GetHashCode() => HashCode.Combine(this.Width, this.Height); + public override readonly int GetHashCode() => HashCode.Combine(this.Width, this.Height); /// - public override string ToString() => $"SizeF [ Width={this.Width}, Height={this.Height} ]"; + public override readonly string ToString() => $"SizeF [ Width={this.Width}, Height={this.Height} ]"; /// - public override bool Equals(object? obj) => obj is SizeF && this.Equals((SizeF)obj); + public override readonly bool Equals(object? obj) => obj is SizeF sizeF && this.Equals(sizeF); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); + public readonly bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); /// /// Multiplies by a producing . diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index 6d1e8aaa5..84d0d2ea1 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -17,24 +17,9 @@ public class AffineTransformBuilder /// Initializes a new instance of the class. /// public AffineTransformBuilder() - : this(TransformSpace.Pixel) { } - /// - /// Initializes a new instance of the class. - /// - /// - /// The to use when applying the affine transform. - /// - public AffineTransformBuilder(TransformSpace transformSpace) - => this.TransformSpace = transformSpace; - - /// - /// Gets the to use when applying the affine transform. - /// - public TransformSpace TransformSpace { get; } - /// /// Prepends a rotation matrix using the given rotation angle in degrees /// and the image center point as rotation center. @@ -52,7 +37,7 @@ public class AffineTransformBuilder /// The . public AffineTransformBuilder PrependRotationRadians(float radians) => this.Prepend( - size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace)); + size => TransformUtilities.CreateRotationTransformMatrixRadians(radians, size)); /// /// Prepends a rotation matrix using the given rotation in degrees at the given origin. @@ -88,7 +73,7 @@ public class AffineTransformBuilder /// The amount of rotation, in radians. /// The . public AffineTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace)); + => this.Append(size => TransformUtilities.CreateRotationTransformMatrixRadians(radians, size)); /// /// Appends a rotation matrix using the given rotation in degrees at the given origin. @@ -172,7 +157,7 @@ public class AffineTransformBuilder /// The Y angle, in radians. /// The . public AffineTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace)); + => this.Prepend(size => TransformUtilities.CreateSkewTransformMatrixRadians(radiansX, radiansY, size)); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -210,7 +195,7 @@ public class AffineTransformBuilder /// The Y angle, in radians. /// The . public AffineTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace)); + => this.Append(size => TransformUtilities.CreateSkewTransformMatrixRadians(radiansX, radiansY, size)); /// /// Appends a skew matrix using the given angles in degrees at the given origin. @@ -344,15 +329,39 @@ public class AffineTransformBuilder /// for linear transforms. /// /// The . - public Size GetTransformedSize(Rectangle sourceRectangle) + public SizeF GetTransformedSize(Rectangle sourceRectangle) { Matrix3x2 matrix = this.BuildMatrix(sourceRectangle); - return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace); + return GetTransformedSize(sourceRectangle, matrix); + } + + /// + /// Returns the size of a rectangle large enough to contain the transformed source rectangle. + /// + /// The rectangle in the source image. + /// The transformation matrix. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// + /// The . + internal static SizeF GetTransformedSize(Rectangle sourceRectangle, Matrix3x2 matrix) + => TransformUtilities.GetRawTransformedSize(matrix, sourceRectangle.Size); + + /// + /// Clears all accumulated transform matrices, resetting the builder to its initial state. + /// + /// The . + public AffineTransformBuilder Clear() + { + this.transformMatrixFactories.Clear(); + return this; } private static void CheckDegenerate(Matrix3x2 matrix) { - if (TransformUtils.IsDegenerate(matrix)) + if (TransformUtilities.IsDegenerate(matrix)) { throw new DegenerateTransformException("Matrix is degenerate. Check input values."); } diff --git a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs index 25e504831..79fe68c20 100644 --- a/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Drawing/DrawImageExtensions.cs @@ -22,9 +22,27 @@ public static class DrawImageExtensions this IImageProcessingContext source, Image foreground, float opacity) + => DrawImage(source, foreground, opacity, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + float opacity, + int foregroundRepeatCount) { GraphicsOptions options = source.GetGraphicsOptions(); - return DrawImage(source, foreground, options.ColorBlendingMode, options.AlphaCompositionMode, opacity); + return DrawImage(source, foreground, options.ColorBlendingMode, options.AlphaCompositionMode, opacity, foregroundRepeatCount); } /// @@ -40,9 +58,29 @@ public static class DrawImageExtensions Image foreground, Rectangle foregroundRectangle, float opacity) + => DrawImage(source, foreground, foregroundRectangle, opacity, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The rectangle structure that specifies the portion of the image to draw. + /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Rectangle foregroundRectangle, + float opacity, + int foregroundRepeatCount) { GraphicsOptions options = source.GetGraphicsOptions(); - return DrawImage(source, foreground, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity); + return DrawImage(source, foreground, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity, foregroundRepeatCount); } /// @@ -58,7 +96,27 @@ public static class DrawImageExtensions Image foreground, PixelColorBlendingMode colorBlending, float opacity) - => DrawImage(source, foreground, Point.Empty, colorBlending, opacity); + => DrawImage(source, foreground, colorBlending, opacity, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The color blending mode. + /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + PixelColorBlendingMode colorBlending, + float opacity, + int foregroundRepeatCount) + => DrawImage(source, foreground, Point.Empty, colorBlending, opacity, foregroundRepeatCount); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -75,7 +133,29 @@ public static class DrawImageExtensions Rectangle foregroundRectangle, PixelColorBlendingMode colorBlending, float opacity) - => DrawImage(source, foreground, foregroundRectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity); + => DrawImage(source, foreground, foregroundRectangle, colorBlending, opacity, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The rectangle structure that specifies the portion of the image to draw. + /// The color blending mode. + /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Rectangle foregroundRectangle, + PixelColorBlendingMode colorBlending, + float opacity, + int foregroundRepeatCount) + => DrawImage(source, foreground, Point.Empty, foregroundRectangle, colorBlending, opacity, foregroundRepeatCount); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -92,7 +172,29 @@ public static class DrawImageExtensions PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity) - => DrawImage(source, foreground, Point.Empty, colorBlending, alphaComposition, opacity); + => DrawImage(source, foreground, colorBlending, alphaComposition, opacity, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The color blending mode. + /// The alpha composition mode. + /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + PixelColorBlendingMode colorBlending, + PixelAlphaCompositionMode alphaComposition, + float opacity, + int foregroundRepeatCount) + => DrawImage(source, foreground, Point.Empty, colorBlending, alphaComposition, opacity, foregroundRepeatCount); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -111,7 +213,31 @@ public static class DrawImageExtensions PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity) - => DrawImage(source, foreground, Point.Empty, foregroundRectangle, colorBlending, alphaComposition, opacity); + => DrawImage(source, foreground, foregroundRectangle, colorBlending, alphaComposition, opacity, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The rectangle structure that specifies the portion of the image to draw. + /// The color blending mode. + /// The alpha composition mode. + /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Rectangle foregroundRectangle, + PixelColorBlendingMode colorBlending, + PixelAlphaCompositionMode alphaComposition, + float opacity, + int foregroundRepeatCount) + => DrawImage(source, foreground, Point.Empty, foregroundRectangle, colorBlending, alphaComposition, opacity, foregroundRepeatCount); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -124,7 +250,25 @@ public static class DrawImageExtensions this IImageProcessingContext source, Image foreground, GraphicsOptions options) - => DrawImage(source, foreground, Point.Empty, options); + => DrawImage(source, foreground, options, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The options, including the blending type and blending amount. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + GraphicsOptions options, + int foregroundRepeatCount) + => DrawImage(source, foreground, Point.Empty, options, foregroundRepeatCount); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -139,7 +283,27 @@ public static class DrawImageExtensions Image foreground, Rectangle foregroundRectangle, GraphicsOptions options) - => DrawImage(source, foreground, Point.Empty, foregroundRectangle, options); + => DrawImage(source, foreground, foregroundRectangle, options, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The rectangle structure that specifies the portion of the image to draw. + /// The options, including the blending type and blending amount. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Rectangle foregroundRectangle, + GraphicsOptions options, + int foregroundRepeatCount) + => DrawImage(source, foreground, Point.Empty, foregroundRectangle, options, foregroundRepeatCount); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -154,9 +318,29 @@ public static class DrawImageExtensions Image foreground, Point backgroundLocation, float opacity) + => DrawImage(source, foreground, backgroundLocation, opacity, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Point backgroundLocation, + float opacity, + int foregroundRepeatCount) { GraphicsOptions options = source.GetGraphicsOptions(); - return DrawImage(source, foreground, backgroundLocation, options.ColorBlendingMode, options.AlphaCompositionMode, opacity); + return DrawImage(source, foreground, backgroundLocation, options.ColorBlendingMode, options.AlphaCompositionMode, opacity, foregroundRepeatCount); } /// @@ -174,9 +358,31 @@ public static class DrawImageExtensions Point backgroundLocation, Rectangle foregroundRectangle, float opacity) + => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, opacity, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The rectangle structure that specifies the portion of the image to draw. + /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Point backgroundLocation, + Rectangle foregroundRectangle, + float opacity, + int foregroundRepeatCount) { GraphicsOptions options = source.GetGraphicsOptions(); - return DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity); + return DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, opacity, foregroundRepeatCount); } /// @@ -194,7 +400,29 @@ public static class DrawImageExtensions Point backgroundLocation, PixelColorBlendingMode colorBlending, float opacity) - => DrawImage(source, foreground, backgroundLocation, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity); + => DrawImage(source, foreground, backgroundLocation, colorBlending, opacity, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The color blending to apply. + /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Point backgroundLocation, + PixelColorBlendingMode colorBlending, + float opacity, + int foregroundRepeatCount) + => DrawImage(source, foreground, backgroundLocation, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity, foregroundRepeatCount); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -213,7 +441,31 @@ public static class DrawImageExtensions Rectangle foregroundRectangle, PixelColorBlendingMode colorBlending, float opacity) - => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity); + => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, colorBlending, opacity, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The rectangle structure that specifies the portion of the image to draw. + /// The color blending to apply. + /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Point backgroundLocation, + Rectangle foregroundRectangle, + PixelColorBlendingMode colorBlending, + float opacity, + int foregroundRepeatCount) + => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, colorBlending, source.GetGraphicsOptions().AlphaCompositionMode, opacity, foregroundRepeatCount); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -228,7 +480,27 @@ public static class DrawImageExtensions Image foreground, Point backgroundLocation, GraphicsOptions options) - => DrawImage(source, foreground, backgroundLocation, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage); + => DrawImage(source, foreground, backgroundLocation, options, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The options containing the blend mode and opacity. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Point backgroundLocation, + GraphicsOptions options, + int foregroundRepeatCount) + => DrawImage(source, foreground, backgroundLocation, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage, foregroundRepeatCount); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -245,7 +517,48 @@ public static class DrawImageExtensions Point backgroundLocation, Rectangle foregroundRectangle, GraphicsOptions options) - => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage); + => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options, 0); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The rectangle structure that specifies the portion of the image to draw. + /// The options containing the blend mode and opacity. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Point backgroundLocation, + Rectangle foregroundRectangle, + GraphicsOptions options, + int foregroundRepeatCount) + => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage, foregroundRepeatCount); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The color blending to apply. + /// The alpha composition mode. + /// The opacity of the image to draw. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Point backgroundLocation, + PixelColorBlendingMode colorBlending, + PixelAlphaCompositionMode alphaComposition, + float opacity) + => DrawImage(source, foreground, backgroundLocation, colorBlending, alphaComposition, opacity, 0); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -256,6 +569,10 @@ public static class DrawImageExtensions /// The color blending to apply. /// The alpha composition mode. /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// /// The . public static IImageProcessingContext DrawImage( this IImageProcessingContext source, @@ -263,8 +580,30 @@ public static class DrawImageExtensions Point backgroundLocation, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, + float opacity, + int foregroundRepeatCount) + => source.ApplyProcessor(new DrawImageProcessor(foreground, backgroundLocation, foreground.Bounds, colorBlending, alphaComposition, opacity, foregroundRepeatCount)); + + /// + /// Draws the given image together with the currently processing image by blending their pixels. + /// + /// The current image processing context. + /// The image to draw on the currently processing image. + /// The location on the currently processing image at which to draw. + /// The rectangle structure that specifies the portion of the image to draw. + /// The color blending to apply. + /// The alpha composition mode. + /// The opacity of the image to draw. Must be between 0 and 1. + /// The . + public static IImageProcessingContext DrawImage( + this IImageProcessingContext source, + Image foreground, + Point backgroundLocation, + Rectangle foregroundRectangle, + PixelColorBlendingMode colorBlending, + PixelAlphaCompositionMode alphaComposition, float opacity) - => source.ApplyProcessor(new DrawImageProcessor(foreground, backgroundLocation, foreground.Bounds, colorBlending, alphaComposition, opacity)); + => DrawImage(source, foreground, backgroundLocation, foregroundRectangle, colorBlending, alphaComposition, opacity, 0); /// /// Draws the given image together with the currently processing image by blending their pixels. @@ -276,6 +615,10 @@ public static class DrawImageExtensions /// The color blending to apply. /// The alpha composition mode. /// The opacity of the image to draw. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this operation across successive frames. + /// A value of 0 means loop indefinitely. + /// /// The . public static IImageProcessingContext DrawImage( this IImageProcessingContext source, @@ -284,8 +627,9 @@ public static class DrawImageExtensions Rectangle foregroundRectangle, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, - float opacity) => + float opacity, + int foregroundRepeatCount) => source.ApplyProcessor( - new DrawImageProcessor(foreground, backgroundLocation, foregroundRectangle, colorBlending, alphaComposition, opacity), + new DrawImageProcessor(foreground, backgroundLocation, foregroundRectangle, colorBlending, alphaComposition, opacity, foregroundRepeatCount), foregroundRectangle); } diff --git a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs index bf6d2221f..b0f5cb7d6 100644 --- a/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Quantization/QuantizeExtensions.cs @@ -12,12 +12,12 @@ namespace SixLabors.ImageSharp.Processing; public static class QuantizeExtensions { /// - /// Applies quantization to the image using the . + /// Applies quantization to the image using the . /// /// The current image processing context. /// The . public static IImageProcessingContext Quantize(this IImageProcessingContext source) => - Quantize(source, KnownQuantizers.Octree); + Quantize(source, KnownQuantizers.Hexadecatree); /// /// Applies quantization to the image. @@ -29,7 +29,7 @@ public static class QuantizeExtensions source.ApplyProcessor(new QuantizeProcessor(quantizer)); /// - /// Applies quantization to the image using the . + /// Applies quantization to the image using the . /// /// The current image processing context. /// @@ -37,7 +37,7 @@ public static class QuantizeExtensions /// /// The . public static IImageProcessingContext Quantize(this IImageProcessingContext source, Rectangle rectangle) => - Quantize(source, KnownQuantizers.Octree, rectangle); + Quantize(source, KnownQuantizers.Hexadecatree, rectangle); /// /// Applies quantization to the image. diff --git a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs index 60f90b10f..dd200013a 100644 --- a/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Transforms/TransformExtensions.cs @@ -7,8 +7,8 @@ using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Processing; /// -/// Defines extensions that allow the application of composable transform operations on an -/// using Mutate/Clone. +/// Defines extensions that allow the application of composable transform operations +/// on an using Mutate/Clone. /// public static class TransformExtensions { @@ -51,7 +51,7 @@ public static class TransformExtensions IResampler sampler) { Matrix3x2 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = builder.GetTransformedSize(sourceRectangle); + Size targetDimensions = TransformUtilities.GetTransformedCanvasSize(transform, sourceRectangle.Size); return source.Transform(sourceRectangle, transform, targetDimensions, sampler); } @@ -113,7 +113,7 @@ public static class TransformExtensions IResampler sampler) { Matrix4x4 transform = builder.BuildMatrix(sourceRectangle); - Size targetDimensions = builder.GetTransformedSize(sourceRectangle); + Size targetDimensions = TransformUtilities.GetTransformedCanvasSize(transform, sourceRectangle.Size); return source.Transform(sourceRectangle, transform, targetDimensions, sampler); } diff --git a/src/ImageSharp/Processing/KnownQuantizers.cs b/src/ImageSharp/Processing/KnownQuantizers.cs index 6fb3c72e8..b63ba597d 100644 --- a/src/ImageSharp/Processing/KnownQuantizers.cs +++ b/src/ImageSharp/Processing/KnownQuantizers.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -6,14 +6,14 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Processing; /// -/// Contains reusable static instances of known quantizing algorithms +/// Contains reusable static instances of known quantizing algorithms. /// public static class KnownQuantizers { /// - /// Gets the adaptive Octree quantizer. Fast with good quality. + /// Gets the adaptive hexadecatree quantizer. Fast with good quality. /// - public static IQuantizer Octree { get; } = new OctreeQuantizer(); + public static IQuantizer Hexadecatree { get; } = new HexadecatreeQuantizer(); /// /// Gets the Xiaolin Wu's Color Quantizer which generates high quality output. diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index a96fa1993..7023a3cd9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -5,6 +5,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ColorProfiles.Companding; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; @@ -112,7 +113,7 @@ internal class BokehBlurProcessor : ImageProcessor } else { - ApplyInverseGammaExposureRowOperation operation = new(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, 1 / this.gamma); + ApplyInverseGammaExposureRowOperation operation = new(sourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, this.gamma); ParallelRowIterator.IterateRows( this.Configuration, sourceRectangle, @@ -237,9 +238,10 @@ internal class BokehBlurProcessor : ImageProcessor Span targetBuffer = this.targetValues.DangerousGetRowSpan(y); targetBuffer.Clear(); - // Execute the bulk pixel format conversion for the current row + // Gamma exposure temporarily stores premultiplied numeric intermediates in TPixel without changing their + // representation. Read the native scaled values back directly to avoid an associate/unassociate round trip. Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, span); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, span, PixelConversionModifiers.Scale); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(span); ref ComplexVector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); @@ -304,18 +306,14 @@ internal class BokehBlurProcessor : ImageProcessor public void Invoke(int y, Span span) { Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); - ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref baseRef, (uint)x); - v.X = MathF.Pow(v.X, this.gamma); - v.Y = MathF.Pow(v.Y, this.gamma); - v.Z = MathF.Pow(v.Z, this.gamma); - } + // Input is premultiplied [0,1] so the LUT is safe here. + GammaCompanding.Expand(span[..this.bounds.Width], this.gamma); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + // The first convolution pass consumes these values as raw premultiplied numerics. Preserve them in the + // native TPixel representation instead of quantizing through an alpha-association conversion. + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan, PixelConversionModifiers.Scale); } } @@ -350,11 +348,13 @@ internal class BokehBlurProcessor : ImageProcessor { Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Premultiply); + PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); Numerics.CubePowOnXYZ(span); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan); + // The first convolution pass consumes these values as raw premultiplied numerics. Preserve them in the + // native TPixel representation instead of quantizing through an alpha-association conversion. + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan, PixelConversionModifiers.Scale); } } @@ -367,7 +367,7 @@ internal class BokehBlurProcessor : ImageProcessor private readonly Buffer2D targetPixels; private readonly Buffer2D sourceValues; private readonly Configuration configuration; - private readonly float inverseGamma; + private readonly float gamma; [MethodImpl(InliningOptions.ShortMethod)] public ApplyInverseGammaExposureRowOperation( @@ -375,36 +375,26 @@ internal class BokehBlurProcessor : ImageProcessor Buffer2D targetPixels, Buffer2D sourceValues, Configuration configuration, - float inverseGamma) + float gamma) { this.bounds = bounds; this.targetPixels = targetPixels; this.sourceValues = sourceValues; this.configuration = configuration; - this.inverseGamma = inverseGamma; + this.gamma = gamma; } /// [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Vector4 low = Vector4.Zero; - Vector4 high = new(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y)[this.bounds.X..]; - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); + Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - for (int x = 0; x < this.bounds.Width; x++) - { - ref Vector4 v = ref Unsafe.Add(ref sourceRef, (uint)x); - Vector4 clamp = Numerics.Clamp(v, low, high); - v.X = MathF.Pow(clamp.X, this.inverseGamma); - v.Y = MathF.Pow(clamp.Y, this.inverseGamma); - v.Z = MathF.Pow(clamp.Z, this.inverseGamma); - } + Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, 1F); + GammaCompanding.Compress(sourceRowSpan, this.gamma); - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan[..this.bounds.Width], targetPixelSpan, PixelConversionModifiers.Premultiply); + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan, targetPixelSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } @@ -433,17 +423,16 @@ internal class BokehBlurProcessor : ImageProcessor /// [MethodImpl(InliningOptions.ShortMethod)] - public unsafe void Invoke(int y) + public void Invoke(int y) { Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); - Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, float.PositiveInfinity); + Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, 1F); Numerics.CubeRootOnXYZ(sourceRowSpan); Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan[..this.bounds.Width], targetPixelSpan, PixelConversionModifiers.Premultiply); + PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan, targetPixelSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs index 02e06db49..4ed6934a2 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor{TPixel}.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; using System.Numerics; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -79,10 +79,16 @@ internal class Convolution2DProcessor : ImageProcessor this.Configuration, this.PreserveAlpha); - ParallelRowIterator.IterateRows, Vector4>( - this.Configuration, - interest, - in operation); + // Convolution is memory-bandwidth-bound with low arithmetic intensity. + // Parallelization degrades performance due to cache line contention from + // overlapping source row reads. See #3111. + using IMemoryOwner buffer = allocator.Allocate(operation.GetRequiredBufferLength(interest)); + Span span = buffer.Memory.Span; + + for (int y = interest.Top; y < interest.Bottom; y++) + { + operation.Invoke(y, span); + } } Buffer2D.SwapOrCopyContent(source.PixelBuffer, targetPixels); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs index bccf19174..409480871 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs @@ -78,6 +78,8 @@ internal readonly struct Convolution2DRowOperation : IRowOperation : IRowOperation.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -115,7 +117,7 @@ internal readonly struct Convolution2DRowOperation : IRowOperation.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (nuint x = 0; x < (uint)sourceRow.Length; x++) { @@ -128,7 +130,7 @@ internal readonly struct Convolution2DRowOperation : IRowOperation targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -144,6 +146,8 @@ internal readonly struct Convolution2DRowOperation : IRowOperation : IRowOperation sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); - Numerics.Premultiply(sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); for (uint x = 0; x < (uint)sourceBuffer.Length; x++) @@ -186,11 +189,12 @@ internal readonly struct Convolution2DRowOperation : IRowOperation targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 1bbbdb350..1e94812fd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -199,12 +199,14 @@ internal class Convolution2PassProcessor : ImageProcessor Span sourceBuffer = span[..this.bounds.Width]; Span targetBuffer = span[this.bounds.Width..]; + // Alpha is preserved separately, so filter straight color and restore the source alpha after applying the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); // Get the precalculated source sample row for this kernel row and copy to our buffer. Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); @@ -233,9 +235,6 @@ internal class Convolution2PassProcessor : ImageProcessor } // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - targetStart = ref MemoryMarshal.GetReference(targetBuffer); while (Unsafe.IsAddressLessThan(ref targetStart, ref targetEnd)) @@ -247,7 +246,7 @@ internal class Convolution2PassProcessor : ImageProcessor } Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -261,14 +260,14 @@ internal class Convolution2PassProcessor : ImageProcessor Span sourceBuffer = span[..this.bounds.Width]; Span targetBuffer = span[this.bounds.Width..]; + // Alpha participates in this convolution, so filter associated color to keep RGB weighted by coverage throughout the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); // Get the precalculated source sample row for this kernel row and copy to our buffer. Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - Numerics.Premultiply(sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 targetStart = ref MemoryMarshal.GetReference(targetBuffer); @@ -296,10 +295,8 @@ internal class Convolution2PassProcessor : ImageProcessor sampleColumnBase = ref Unsafe.Add(ref sampleColumnBase, (uint)kernelSize); } - Numerics.UnPremultiply(targetBuffer); - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } @@ -367,6 +364,8 @@ internal class Convolution2PassProcessor : ImageProcessor ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (uint)((y - this.bounds.Y) * kernelSize)); + // Alpha is preserved separately, so filter straight color and restore the source alpha after applying the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); @@ -380,7 +379,7 @@ internal class Convolution2PassProcessor : ImageProcessor // Get the precalculated source sample row for this kernel row and copy to our buffer. sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)sourceBuffer.Length); @@ -401,7 +400,7 @@ internal class Convolution2PassProcessor : ImageProcessor // Now we need to copy the original alpha values from the source row. sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); { ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)sourceBuffer.Length); @@ -416,7 +415,7 @@ internal class Convolution2PassProcessor : ImageProcessor } Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -432,6 +431,8 @@ internal class Convolution2PassProcessor : ImageProcessor ref int sampleRowBase = ref Unsafe.Add(ref MemoryMarshal.GetReference(this.map.GetRowOffsetSpan()), (uint)((y - this.bounds.Y) * kernelSize)); + // Alpha participates in this convolution, so filter associated color to keep RGB weighted by coverage throughout the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); @@ -445,9 +446,7 @@ internal class Convolution2PassProcessor : ImageProcessor // Get the precalculated source sample row for this kernel row and copy to our buffer. sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); - - Numerics.Premultiply(sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, (uint)sourceBuffer.Length); @@ -466,10 +465,8 @@ internal class Convolution2PassProcessor : ImageProcessor sampleRowBase = ref Unsafe.Add(ref sampleRowBase, 1); } - Numerics.UnPremultiply(targetBuffer); - Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs index 4aefa0dae..df76fd081 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessorHelpers.cs @@ -2,6 +2,8 @@ // Licensed under the Six Labors Split License. using System.Diagnostics.CodeAnalysis; +using System.Numerics.Tensors; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp.Processing.Processors.Convolution; @@ -36,11 +38,8 @@ internal static class ConvolutionProcessorHelpers kernel[i] = gx; } - // Normalize kernel so that the sum of all weights equals 1 - for (int i = 0; i < size; i++) - { - kernel[i] /= sum; - } + // Divide every weight by the accumulated Gaussian sum so the kernel has unit response. + TensorPrimitives.Divide(kernel, sum, kernel); return kernel; } @@ -68,25 +67,15 @@ internal static class ConvolutionProcessorHelpers // Invert the kernel for sharpening. int midpointRounded = (int)midpoint; - for (int i = 0; i < size; i++) - { - if (i == midpointRounded) - { - // Calculate central value - kernel[i] = (2F * sum) - kernel[i]; - } - else - { - // invert value - kernel[i] = -kernel[i]; - } - } + float midpointValue = kernel[midpointRounded]; + TensorPrimitives.Negate(kernel, kernel); - // Normalize kernel so that the sum of all weights equals 1 - for (int i = 0; i < size; i++) - { - kernel[i] /= sum; - } + // The sharpening kernel negates every Gaussian weight except its center. Restore that original + // center while adding twice the Gaussian sum so the complete kernel retains unit response. + kernel[midpointRounded] = (2F * sum) - midpointValue; + + // Sharpening changes signs but preserves the Gaussian sum, so the same divisor produces unit response. + TensorPrimitives.Divide(kernel, sum, kernel); return kernel; } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index feaaf30ce..e599324f9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -161,6 +161,8 @@ internal class ConvolutionProcessor : ImageProcessor if (this.preserveAlpha) { + // Alpha is preserved separately, so filter straight color and restore the source alpha after applying the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); @@ -171,7 +173,7 @@ internal class ConvolutionProcessor : ImageProcessor // Get the precalculated source sample row for this kernel row and copy to our buffer. int offsetY = Unsafe.Add(ref sampleRowBase, kY); sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -191,16 +193,20 @@ internal class ConvolutionProcessor : ImageProcessor // Now we need to copy the original alpha values from the source row. sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (nuint x = 0; x < (uint)sourceRow.Length; x++) { ref Vector4 target = ref Unsafe.Add(ref targetBase, x); target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; } + + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } else { + // Alpha participates in this convolution, so filter associated color to keep RGB weighted by coverage throughout the kernel. + // Clear the target buffer for each row run. targetBuffer.Clear(); ref Vector4 targetBase = ref MemoryMarshal.GetReference(targetBuffer); @@ -210,9 +216,8 @@ internal class ConvolutionProcessor : ImageProcessor // Get the precalculated source sample row for this kernel row and copy to our buffer. int offsetY = Unsafe.Add(ref sampleRowBase, kY); Span sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); - Numerics.Premultiply(sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); for (uint x = 0; x < (uint)sourceBuffer.Length; x++) @@ -229,10 +234,8 @@ internal class ConvolutionProcessor : ImageProcessor } } - Numerics.UnPremultiply(targetBuffer); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } - - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index eae748166..5b62b9b8a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -122,10 +122,12 @@ internal class EdgeDetectorCompassProcessor : ImageProcessor for (nuint x = this.minX; x < this.maxX; x++) { - // Grab the max components of the two pixels + // Compass responses are logical color magnitudes. Comparing associated components would make the edge strength + // depend on the storage alpha representation instead of the filtered colors. ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, x); ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, x); - currentTargetPixel = TPixel.FromVector4(Vector4.Max(currentPassPixel.ToVector4(), currentTargetPixel.ToVector4())); + currentTargetPixel = TPixel.FromUnassociatedScaledVector4( + Vector4.Max(currentPassPixel.ToUnassociatedScaledVector4(), currentTargetPixel.ToUnassociatedScaledVector4())); } } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs index 7653aeaa9..2e4292890 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs @@ -104,7 +104,15 @@ internal sealed class KernelSamplingMap : IDisposable { int affectedSize = (kernelSize >> 1) * kernelSize; ref int spanBase = ref MemoryMarshal.GetReference(span); - if (affectedSize > 0) + if (affectedSize >= span.Length && span.Length > 0) + { + // The bounds are no larger than the kernel radius: every offset can overshoot the + // borders by a full sampling extent or more, which the single-pass head and tail + // corrections below cannot fold back into range (and for bounds strictly smaller + // than the radius they cannot even slice the span). Fold each offset exactly. + CorrectBorderExact(span, min, max, borderMode); + } + else if (affectedSize > 0) { switch (borderMode) { @@ -180,4 +188,58 @@ internal sealed class KernelSamplingMap : IDisposable } } } + + private static void CorrectBorderExact(Span span, int min, int max, BorderWrappingMode borderMode) + { + int extent = max - min + 1; + switch (borderMode) + { + case BorderWrappingMode.Repeat: + Numerics.Clamp(span, min, max); + break; + + case BorderWrappingMode.Mirror: + // Reflection about the half-sample border: ..., min+1, min | min, min+1, ... + int mirrorPeriod = 2 * extent; + for (int i = 0; i < span.Length; i++) + { + int offset = Modulo(span[i] - min, mirrorPeriod); + span[i] = min + (offset < extent ? offset : mirrorPeriod - 1 - offset); + } + + break; + + case BorderWrappingMode.Bounce: + // Reflection about the border sample itself: ..., min+2, min+1 | min, min+1, ... + if (extent == 1) + { + span.Fill(min); + break; + } + + int bouncePeriod = (2 * extent) - 2; + for (int i = 0; i < span.Length; i++) + { + int offset = Modulo(span[i] - min, bouncePeriod); + span[i] = min + (offset < extent ? offset : bouncePeriod - offset); + } + + break; + + case BorderWrappingMode.Wrap: + for (int i = 0; i < span.Length; i++) + { + span[i] = min + Modulo(span[i] - min, extent); + } + + break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Modulo(int value, int period) + { + int remainder = value % period; + return remainder < 0 ? remainder + period : remainder; + } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs index ca2cabab5..97ddd3be3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/MedianRowOperation{TPixel}.cs @@ -78,7 +78,7 @@ internal readonly struct MedianRowOperation : IRowOperation int currentYIndex = Unsafe.Add(ref sampleRowBase, (uint)i); Span sourceRow = this.sourcePixels.DangerousGetRowSpan(currentYIndex).Slice(boundsX, boundsWidth); Span sourceVectorRow = sourceVectorBuffer.Slice(i * boundsWidth, boundsWidth); - PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceVectorRow); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceVectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } if (this.preserveAlpha) @@ -130,7 +130,7 @@ internal readonly struct MedianRowOperation : IRowOperation } Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan); + PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs index 4b3dd7fe7..1593dc788 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs @@ -3,8 +3,10 @@ using System.Collections.Concurrent; using System.Numerics; +using System.Numerics.Tensors; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp.Processing.Processors.Convolution.Parameters; @@ -209,13 +211,11 @@ internal static class BokehBlurKernelDataProvider for (int i = 0; i < kernelsSpan.Length; i++) { ref Complex64[] kernelsRef = ref Unsafe.Add(ref baseKernelsRef, (uint)i); - int length = kernelsRef.Length; - ref Complex64 valueRef = ref MemoryMarshal.GetArrayDataReference(kernelsRef); - for (int j = 0; j < length; j++) - { - Unsafe.Add(ref valueRef, (uint)j) *= scalar; - } + // Complex64 stores each value as adjacent real and imaginary floats. Multiplying that flattened + // float span scales both parts independently, which is exactly complex multiplication by a real scalar. + Span values = MemoryMarshal.Cast(kernelsRef.AsSpan()); + TensorPrimitives.Multiply(values, scalar, values); } } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 3c0ebb707..36cc5564a 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -168,8 +168,8 @@ public readonly partial struct ErrorDither : IDither, IEquatable, I return transformed; } - // Calculate the error - Vector4 error = (source.ToVector4() - transformed.ToVector4()) * scale; + // Palette error is a logical color difference, so it must be diffused independently of the pixel storage representation. + Vector4 error = (source.ToUnassociatedScaledVector4() - transformed.ToUnassociatedScaledVector4()) * scale; int offset = this.offset; DenseMatrix matrix = this.matrix; @@ -200,10 +200,19 @@ public readonly partial struct ErrorDither : IDither, IEquatable, I } ref TPixel pixel = ref rowSpan[targetX]; - Vector4 result = pixel.ToVector4(); + Vector4 result = pixel.ToUnassociatedScaledVector4(); + + // Do not diffuse error into fully transparent pixels. They carry no visible color + // (a decoder shows whatever is behind them), so perturbing them is meaningless and, + // for indexed transparency, nudges them off the exact transparent color so they are + // matched to the nearest opaque palette entry instead of being kept transparent. + if (result.W <= 0) + { + continue; + } result += error * coefficient; - pixel = TPixel.FromVector4(result); + pixel = TPixel.FromUnassociatedScaledVector4(result); } } diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index b0622cac5..cec4874f6 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -182,6 +182,16 @@ public readonly partial struct OrderedDither : IDither, IEquatable { Rgba32 rgba = source.ToRgba32(); + + // Leave fully transparent pixels untouched. They carry no visible color (a decoder shows + // whatever is behind them), so perturbing them is meaningless and, for indexed transparency, + // nudges them off the exact transparent color so they are matched to the nearest opaque + // palette entry instead of being kept transparent. + if (rgba.A == 0) + { + return source; + } + Unsafe.SkipInit(out Rgba32 attempt); float factor = spread * this.thresholdMatrix[y % this.modulusY, x % this.modulusX] * scale; diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs index 6ecf16fc6..675d1a311 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor.cs @@ -19,13 +19,15 @@ public class DrawImageProcessor : IImageProcessor /// The blending mode to use when drawing the image. /// The Alpha blending mode to use when drawing the image. /// The opacity of the image to blend. + /// The number of times the foreground frames are allowed to loop. 0 means infinitely. public DrawImageProcessor( Image foreground, Point backgroundLocation, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, - float opacity) - : this(foreground, backgroundLocation, foreground.Bounds, colorBlendingMode, alphaCompositionMode, opacity) + float opacity, + int foregroundRepeatCount) + : this(foreground, backgroundLocation, foreground.Bounds, colorBlendingMode, alphaCompositionMode, opacity, foregroundRepeatCount) { } @@ -38,13 +40,15 @@ public class DrawImageProcessor : IImageProcessor /// The blending mode to use when drawing the image. /// The Alpha blending mode to use when drawing the image. /// The opacity of the image to blend. + /// The number of times the foreground frames are allowed to loop. 0 means infinitely. public DrawImageProcessor( Image foreground, Point backgroundLocation, Rectangle foregroundRectangle, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, - float opacity) + float opacity, + int foregroundRepeatCount) { this.ForeGround = foreground; this.BackgroundLocation = backgroundLocation; @@ -52,6 +56,7 @@ public class DrawImageProcessor : IImageProcessor this.ColorBlendingMode = colorBlendingMode; this.AlphaCompositionMode = alphaCompositionMode; this.Opacity = opacity; + this.ForegroundRepeatCount = foregroundRepeatCount; } /// @@ -84,6 +89,11 @@ public class DrawImageProcessor : IImageProcessor /// public float Opacity { get; } + /// + /// Gets the number of times the foreground frames are allowed to loop. 0 means infinitely. + /// + public int ForegroundRepeatCount { get; } + /// public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) where TPixelBg : unmanaged, IPixel @@ -122,6 +132,7 @@ public class DrawImageProcessor : IImageProcessor this.definition.ForegroundRectangle, this.definition.ColorBlendingMode, this.definition.AlphaCompositionMode, - this.definition.Opacity); + this.definition.Opacity, + this.definition.ForegroundRepeatCount); } } diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index d2a99ce92..f2faee4f9 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; @@ -17,6 +18,12 @@ internal class DrawImageProcessor : ImageProcessor where TPixelBg : unmanaged, IPixel where TPixelFg : unmanaged, IPixel { + /// + /// Counts how many times has been called for this processor instance. + /// Used to select the current foreground frame. + /// + private int foregroundFrameCounter; + /// /// Initializes a new instance of the class. /// @@ -28,6 +35,10 @@ internal class DrawImageProcessor : ImageProcessor /// The blending mode to use when drawing the image. /// The alpha blending mode to use when drawing the image. /// The opacity of the image to blend. Must be between 0 and 1. + /// + /// The number of times the foreground frames are allowed to loop while applying this processor across successive frames. + /// A value of 0 means loop indefinitely. + /// public DrawImageProcessor( Configuration configuration, Image foregroundImage, @@ -36,9 +47,11 @@ internal class DrawImageProcessor : ImageProcessor Rectangle foregroundRectangle, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, - float opacity) + float opacity, + int foregroundRepeatCount) : base(configuration, backgroundImage, backgroundImage.Bounds) { + Guard.MustBeGreaterThanOrEqualTo(foregroundRepeatCount, 0, nameof(foregroundRepeatCount)); Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); this.ForegroundImage = foregroundImage; @@ -46,6 +59,7 @@ internal class DrawImageProcessor : ImageProcessor this.Opacity = opacity; this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); this.BackgroundLocation = backgroundLocation; + this.ForegroundRepeatCount = foregroundRepeatCount; } /// @@ -73,6 +87,12 @@ internal class DrawImageProcessor : ImageProcessor /// public Point BackgroundLocation { get; } + /// + /// Gets the number of times the foreground frames are allowed to loop while applying this processor across + /// successive frames. A value of 0 means loop indefinitely. + /// + public int ForegroundRepeatCount { get; } + /// protected override void OnFrameApply(ImageFrame source) { @@ -114,27 +134,35 @@ internal class DrawImageProcessor : ImageProcessor // Sanitize the dimensions so that we don't try and sample outside the image. Rectangle backgroundRectangle = Rectangle.Intersect(new Rectangle(left, top, width, height), this.SourceRectangle); Configuration configuration = this.Configuration; + int currentFrameIndex = this.foregroundFrameCounter % this.ForegroundImage.Frames.Count; - DrawImageProcessor.RowOperation operation = + RowOperation operation = new( configuration, source.PixelBuffer, - this.ForegroundImage.Frames.RootFrame.PixelBuffer, + this.ForegroundImage.Frames[currentFrameIndex].PixelBuffer, backgroundRectangle, foregroundRectangle, this.Blender, this.Opacity); - ParallelRowIterator.IterateRows( + ParallelRowIterator.IterateRows( configuration, new Rectangle(0, 0, foregroundRectangle.Width, foregroundRectangle.Height), in operation); + + // The repeat count only affects how the foreground frame advances across successive background frames. + // When exhausted, the selected foreground frame stops advancing. + if (this.ForegroundRepeatCount is 0 || this.foregroundFrameCounter / this.ForegroundImage.Frames.Count < this.ForegroundRepeatCount) + { + this.foregroundFrameCounter++; + } } /// /// A implementing the draw logic for . /// - private readonly struct RowOperation : IRowOperation + private readonly struct RowOperation : IRowOperation { private readonly Buffer2D background; private readonly Buffer2D foreground; @@ -163,13 +191,20 @@ internal class DrawImageProcessor : ImageProcessor this.opacity = opacity; } + /// + public int GetRequiredBufferLength(Rectangle bounds) + + // By using a dedicated vector span we can avoid per-row pool allocations in PixelBlender.Blend + // We need 3 Vector4 values per pixel to store the background, foreground, and result pixels for blending. + => 3 * bounds.Width; + /// [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) + public void Invoke(int y, Span span) { Span background = this.background.DangerousGetRowSpan(y + this.backgroundRectangle.Top).Slice(this.backgroundRectangle.Left, this.backgroundRectangle.Width); Span foreground = this.foreground.DangerousGetRowSpan(y + this.foregroundRectangle.Top).Slice(this.foregroundRectangle.Left, this.foregroundRectangle.Width); - this.blender.Blend(this.configuration, background, background, foreground, this.opacity); + this.blender.Blend(this.configuration, background, background, foreground, this.opacity, span); } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index f811bae0f..ddb6d1545 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -119,13 +119,15 @@ internal class OilPaintingProcessor : ImageProcessor Span redBinSpan = binsSpan[this.levels..]; Span blueBinSpan = redBinSpan[this.levels..]; Span greenBinSpan = blueBinSpan[this.levels..]; + PixelOperations pixelOperations = PixelOperations.Instance; for (int y = rows.Min; y < rows.Max; y++) { Span sourceRowPixelSpan = this.source.DangerousGetRowSpan(y); Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span, PixelConversionModifiers.Scale); + // Intensity bins and channel averages describe logical colors, so they must not depend on the pixel storage association. + pixelOperations.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (int x = this.bounds.X; x < this.bounds.Right; x++) { @@ -149,7 +151,7 @@ internal class OilPaintingProcessor : ImageProcessor int offsetX = x + fxr; offsetX = Numerics.Clamp(offsetX, 0, maxX); - Vector4 vector = sourceOffsetRow[offsetX].ToScaledVector4(); + Vector4 vector = sourceOffsetRow[offsetX].ToUnassociatedScaledVector4(); float sourceRed = vector.X; float sourceBlue = vector.Z; @@ -180,7 +182,7 @@ internal class OilPaintingProcessor : ImageProcessor Span targetRowAreaPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan, PixelConversionModifiers.Scale); + pixelOperations.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index 37286086c..8b287b727 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -74,11 +74,14 @@ internal class FilterProcessor : ImageProcessor public void Invoke(int y, Span span) { Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); + PixelOperations pixelOperations = PixelOperations.Instance; + + // Color matrices transform logical color components, so associated storage must be unassociated before applying the matrix. + pixelOperations.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ColorNumerics.Transform(span, ref Unsafe.AsRef(in this.matrix)); - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); + pixelOperations.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs index 41560d120..bffe6cf63 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs @@ -56,7 +56,10 @@ internal sealed class OpaqueProcessor : ImageProcessor public void Invoke(int y, Span span) { Span targetRowSpan = this.target.DangerousGetRowSpan(y)[this.bounds.X..]; - PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Scale); + PixelOperations pixelOperations = PixelOperations.Instance; + + // Opacity changes alpha without changing logical color, so associated storage must be unassociated before replacing alpha. + pixelOperations.ToVector4(this.configuration, targetRowSpan[..span.Length], span, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); for (int x = 0; x < this.bounds.Width; x++) @@ -65,7 +68,7 @@ internal sealed class OpaqueProcessor : ImageProcessor v.W = 1F; } - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, targetRowSpan, PixelConversionModifiers.Scale); + pixelOperations.FromVector4Destructive(this.configuration, span, targetRowSpan[..span.Length], PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index 3d54836c5..b18a52bec 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -144,8 +144,11 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz for (int dx = xStart; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, GetLuminance(pixel, luminanceLevels)); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + Vector4 vector = pixel.ToUnassociatedScaledVector4(); + int luminance = ColorNumerics.GetBT709Luminance(vector, luminanceLevels); + float luminanceEqualized = cdfData.RemapGreyValue(cdfX, cdfY, luminance); + vector.X = vector.Y = vector.Z = luminanceEqualized; + pixel = TPixel.FromUnassociatedScaledVector4(vector); } } } @@ -190,8 +193,10 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz for (int dx = xStart; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + Vector4 vector = pixel.ToUnassociatedScaledVector4(); + float luminanceEqualized = InterpolateBetweenTwoTiles(vector, cdfData, cdfX, cdfY, cdfX, cdfY + 1, tileY, tileHeight, luminanceLevels); + vector.X = vector.Y = vector.Z = luminanceEqualized; + pixel = TPixel.FromUnassociatedScaledVector4(vector); } tileY++; @@ -242,8 +247,10 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz for (int dx = x; dx < xLimit; dx++) { ref TPixel pixel = ref rowSpan[dx]; - float luminanceEqualized = InterpolateBetweenTwoTiles(pixel, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + Vector4 vector = pixel.ToUnassociatedScaledVector4(); + float luminanceEqualized = InterpolateBetweenTwoTiles(vector, cdfData, cdfX, cdfY, cdfX + 1, cdfY, tileX, tileWidth, luminanceLevels); + vector.X = vector.Y = vector.Z = luminanceEqualized; + pixel = TPixel.FromUnassociatedScaledVector4(vector); tileX++; } } @@ -256,7 +263,7 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz /// /// Bilinear interpolation between four adjacent tiles. /// - /// The pixel to remap the grey value from. + /// The unassociated scaled vector to remap the grey value from. /// The pre-computed lookup tables to remap the grey values for each tiles. /// The number of tiles in the x-direction. /// The number of tiles in the y-direction. @@ -273,7 +280,7 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz /// A re-mapped grey value. [MethodImpl(InliningOptions.ShortMethod)] private static float InterpolateBetweenFourTiles( - TPixel sourcePixel, + Vector4 sourceVector, CdfTileData cdfData, int tileCountX, int tileCountY, @@ -285,7 +292,7 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz int tileHeight, int luminanceLevels) { - int luminance = GetLuminance(sourcePixel, luminanceLevels); + int luminance = ColorNumerics.GetBT709Luminance(sourceVector, luminanceLevels); float tx = tileX / (float)(tileWidth - 1); float ty = tileY / (float)(tileHeight - 1); @@ -304,7 +311,7 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz /// /// Linear interpolation between two tiles. /// - /// The pixel to remap the grey value from. + /// The unassociated scaled vector to remap the grey value from. /// The CDF lookup map. /// X position inside the first tile. /// Y position inside the first tile. @@ -319,7 +326,7 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz /// A re-mapped grey value. [MethodImpl(InliningOptions.ShortMethod)] private static float InterpolateBetweenTwoTiles( - TPixel sourcePixel, + Vector4 sourceVector, CdfTileData cdfData, int tileX1, int tileY1, @@ -329,7 +336,7 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz int tileWidth, int luminanceLevels) { - int luminance = GetLuminance(sourcePixel, luminanceLevels); + int luminance = ColorNumerics.GetBT709Luminance(sourceVector, luminanceLevels); float tx = tilePos / (float)(tileWidth - 1); float cdfLuminance1 = cdfData.RemapGreyValue(tileX1, tileY1, luminance); @@ -419,8 +426,9 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz for (int dx = x; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; + Vector4 vector = pixel.ToUnassociatedScaledVector4(); float luminanceEqualized = InterpolateBetweenFourTiles( - pixel, + vector, this.cdfData, this.tileCount, this.tileCount, @@ -432,7 +440,8 @@ internal class AdaptiveHistogramEqualizationProcessor : HistogramEqualiz this.tileHeight, this.luminanceLevels); - pixel = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, pixel.ToVector4().W)); + vector.X = vector.Y = vector.Z = luminanceEqualized; + pixel = TPixel.FromUnassociatedScaledVector4(vector); tileX++; } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs index 8ca4a86a2..624820d2f 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -194,7 +194,7 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : His int idx = 0; for (int dx = x; dx < x + tileWidth; dx++) { - rowPixels[idx] = source[Numerics.Abs(dx), y].ToVector4(); + rowPixels[idx] = source[Numerics.Abs(dx), y].ToUnassociatedScaledVector4(); idx++; } @@ -209,11 +209,11 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : His if (dx >= source.Width) { int diff = dx - source.Width; - rowPixels[idx] = source[dx - diff - 1, y].ToVector4(); + rowPixels[idx] = source[dx - diff - 1, y].ToUnassociatedScaledVector4(); } else { - rowPixels[idx] = source[dx, y].ToVector4(); + rowPixels[idx] = source[dx, y].ToUnassociatedScaledVector4(); } idx++; @@ -242,7 +242,7 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : His int y, int tileWidth, Configuration configuration) - => PixelOperations.Instance.ToVector4(configuration, source.DangerousGetRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); + => PixelOperations.Instance.ToVector4(configuration, source.DangerousGetRowSpan(y).Slice(start: x, length: tileWidth), rowPixels, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); /// /// Adds a column of grey values to the histogram. @@ -256,7 +256,7 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : His { for (nuint idx = 0; idx < (uint)length; idx++) { - int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + int luminance = ColorNumerics.GetBT709Luminance(Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); Unsafe.Add(ref histogramBase, (uint)luminance)++; } } @@ -273,7 +273,7 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : His { for (nuint idx = 0; idx < (uint)length; idx++) { - int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); + int luminance = ColorNumerics.GetBT709Luminance(Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); Unsafe.Add(ref histogramBase, (uint)luminance)--; } } @@ -381,9 +381,11 @@ internal class AdaptiveHistogramEqualizationSlidingWindowProcessor : His float numberOfPixelsMinusCdfMin = this.swInfos.PixelInTile - cdfMin; // Map the current pixel to the new equalized value. - int luminance = GetLuminance(this.source[x, y], this.processor.LuminanceLevels); + Vector4 vector = this.source[x, y].ToUnassociatedScaledVector4(); + int luminance = ColorNumerics.GetBT709Luminance(vector, this.processor.LuminanceLevels); float luminanceEqualized = Unsafe.Add(ref cdfBase, (uint)luminance) / numberOfPixelsMinusCdfMin; - this.targetPixels[x, y] = TPixel.FromVector4(new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, this.source[x, y].ToVector4().W)); + vector.X = vector.Y = vector.Z = luminanceEqualized; + this.targetPixels[x, y] = TPixel.FromUnassociatedScaledVector4(vector); // Remove top most row from the histogram, mirroring rows which exceeds the borders. if (this.useFastPath) diff --git a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs index e830cc55f..9acca6d40 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs @@ -142,18 +142,19 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor pixelRow = sourceAccess.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + PixelOperations operations = PixelOperations.Instance; + operations.ToVector4(this.configuration, pixelRow, vectorBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (int x = 0; x < this.bounds.Width; x++) { Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); + int luminance = ColorNumerics.GetBT709Luminance(vector, levels); float scaledLuminance = Unsafe.Add(ref cdfBase, (uint)luminance) / noOfPixelsMinusCdfMin; float scalingFactor = scaledLuminance * levels / luminance; Unsafe.Add(ref vectorRef, (uint)x) = new Vector4(scalingFactor * vector.X, scalingFactor * vector.Y, scalingFactor * vector.Z, vector.W); } - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); + operations.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } @@ -201,12 +202,17 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor pixelRow = sourceAccess.GetRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + Span pixelRow = sourceAccess.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + PixelOperations operations = PixelOperations.Instance; + operations.ToVector4(this.configuration, pixelRow, vectorBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (int x = 0; x < this.bounds.Width; x++) { - Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x) * levelsMinusOne; + Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); + float alpha = vector.W; + + // Alpha is not a histogram component and must not be scaled into the CDF index range. + vector *= levelsMinusOne; uint originalX = (uint)MathF.Round(vector.X); float scaledX = Unsafe.Add(ref cdfBase, originalX) / noOfPixelsMinusCdfMin; @@ -214,10 +220,10 @@ internal class AutoLevelProcessor : HistogramEqualizationProcessor.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); + operations.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 3ab8f7431..3c07d05c6 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -124,18 +124,19 @@ internal class GlobalHistogramEqualizationProcessor : HistogramEqualizat int levels = this.luminanceLevels; float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; - Span pixelRow = this.source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + Span pixelRow = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + PixelOperations operations = PixelOperations.Instance; + operations.ToVector4(this.configuration, pixelRow, vectorBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (int x = 0; x < this.bounds.Width; x++) { Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); + int luminance = ColorNumerics.GetBT709Luminance(vector, levels); float luminanceEqualized = Unsafe.Add(ref cdfBase, (uint)luminance) / noOfPixelsMinusCdfMin; Unsafe.Add(ref vectorRef, (uint)x) = new Vector4(luminanceEqualized, luminanceEqualized, luminanceEqualized, vector.W); } - PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow); + operations.FromVector4Destructive(this.configuration, vectorBuffer, pixelRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs index 3584f2954..c7e40b420 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GrayscaleLevelsRowOperation{TPixel}.cs @@ -51,13 +51,13 @@ internal readonly struct GrayscaleLevelsRowOperation : IRowOperation pixelRow = this.source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer); + Span pixelRow = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorBuffer, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); for (int x = 0; x < this.bounds.Width; x++) { Vector4 vector = Unsafe.Add(ref vectorRef, (uint)x); - int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); + int luminance = ColorNumerics.GetBT709Luminance(vector, levels); Interlocked.Increment(ref Unsafe.Add(ref histogramBase, (uint)luminance)); } } diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs index 6ac36f3ce..77b741e88 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs @@ -2,8 +2,10 @@ // Licensed under the Six Labors Split License. using System.Numerics; +using System.Numerics.Tensors; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Normalization; @@ -115,10 +117,7 @@ internal abstract class HistogramEqualizationProcessor : ImageProcessor< int addToEachBin = sumOverClip > 0 ? (int)MathF.Floor(sumOverClip / this.luminanceLevelsFloat) : 0; if (addToEachBin > 0) { - for (nuint i = 0; i < (uint)histogram.Length; i++) - { - Unsafe.Add(ref histogramBase, i) += addToEachBin; - } + TensorPrimitives.Add(histogram, addToEachBin, histogram); } int residual = sumOverClip - (addToEachBin * this.LuminanceLevels); @@ -142,7 +141,8 @@ internal abstract class HistogramEqualizationProcessor : ImageProcessor< public static int GetLuminance(TPixel sourcePixel, int luminanceLevels) { // TODO: We need a bulk per span equivalent. - Vector4 vector = sourcePixel.ToVector4(); - return ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); + // Histogram bins describe logical color, so storage alpha association must not scale luminance. + Vector4 vector = sourcePixel.ToUnassociatedScaledVector4(); + return ColorNumerics.GetBT709Luminance(vector, luminanceLevels); } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs b/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs index 26fd7d5d7..c520d7c54 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/ColorMatchingMode.cs @@ -15,14 +15,8 @@ public enum ColorMatchingMode Coarse, /// - /// Enables an exact color match cache for the first 512 unique colors encountered, - /// falling back to coarse matching thereafter. - /// - Hybrid, - - /// - /// Performs exact color matching without any caching optimizations. - /// This is the slowest but most accurate matching strategy. + /// Performs exact color matching using a bounded exact-match cache with eviction. + /// This preserves exact color matching while accelerating repeated colors. /// Exact } diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs index 5b0c7252c..e2e7206e0 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel,TCache}.cs @@ -3,6 +3,8 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -71,32 +73,107 @@ internal sealed class EuclideanPixelMap : PixelMap [MethodImpl(InliningOptions.ColdPath)] private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match) { - // Loop through the palette and find the nearest match. + ReadOnlySpan rgbaPalette = this.rgbaPalette; + ref Rgba32 rgbaPaletteRef = ref MemoryMarshal.GetReference(rgbaPalette); int index = 0; - float leastDistance = float.MaxValue; - for (int i = 0; i < this.rgbaPalette.Length; i++) + int leastDistance = int.MaxValue; + int i = 0; + + if (Vector128.IsHardwareAccelerated && rgbaPalette.Length >= 4) { - Rgba32 candidate = this.rgbaPalette[i]; - if (candidate.PackedValue == rgba.PackedValue) - { - index = i; - break; - } + // Duplicate the query color so one 128-bit register can be subtracted from + // two packed RGBA candidates at a time after widening. + Vector128 pixel = Vector128.Create( + rgba.R, + rgba.G, + rgba.B, + rgba.A, + rgba.R, + rgba.G, + rgba.B, + rgba.A); - float distance = DistanceSquared(rgba, candidate); - if (distance == 0) + int vectorizedLength = rgbaPalette.Length & ~0x03; + + for (; i < vectorizedLength; i += 4) { - index = i; - break; + // Load four packed Rgba32 values (16 bytes) and widen them into two vectors: + // [c0.r, c0.g, c0.b, c0.a, c1.r, ...] and [c2.r, c2.g, c2.b, c2.a, c3.r, ...]. + Vector128 packed = Vector128.LoadUnsafe(ref Unsafe.As(ref Unsafe.Add(ref rgbaPaletteRef, i))); + Vector128 lowerDiff = Vector128.WidenLower(packed).AsInt16() - pixel; + Vector128 upperDiff = Vector128.WidenUpper(packed).AsInt16() - pixel; + + // MultiplyAddAdjacent collapses channel squares into RG + BA partial sums, + // so each pair of int lanes still corresponds to one candidate color. + Vector128 lowerPairs = Vector128_.MultiplyAddAdjacent(lowerDiff, lowerDiff); + Vector128 upperPairs = Vector128_.MultiplyAddAdjacent(upperDiff, upperDiff); + + // Sum the two partials for candidates i and i + 1. + ref int lowerRef = ref Unsafe.As, int>(ref lowerPairs); + int distance = lowerRef + Unsafe.Add(ref lowerRef, 1); + if (distance < leastDistance) + { + index = i; + leastDistance = distance; + if (distance == 0) + { + goto Found; + } + } + + distance = Unsafe.Add(ref lowerRef, 2) + Unsafe.Add(ref lowerRef, 3); + if (distance < leastDistance) + { + index = i + 1; + leastDistance = distance; + if (distance == 0) + { + goto Found; + } + } + + // Sum the two partials for candidates i + 2 and i + 3. + ref int upperRef = ref Unsafe.As, int>(ref upperPairs); + distance = upperRef + Unsafe.Add(ref upperRef, 1); + if (distance < leastDistance) + { + index = i + 2; + leastDistance = distance; + if (distance == 0) + { + goto Found; + } + } + + distance = Unsafe.Add(ref upperRef, 2) + Unsafe.Add(ref upperRef, 3); + if (distance < leastDistance) + { + index = i + 3; + leastDistance = distance; + if (distance == 0) + { + goto Found; + } + } } + } + for (; i < rgbaPalette.Length; i++) + { + int distance = DistanceSquared(rgba, Unsafe.Add(ref rgbaPaletteRef, i)); if (distance < leastDistance) { index = i; leastDistance = distance; + if (distance == 0) + { + goto Found; + } } } + Found: + // Now I have the index, pop it into the cache for next time _ = this.cache.TryAdd(rgba, (short)index); match = Unsafe.Add(ref paletteRef, (uint)index); @@ -111,12 +188,12 @@ internal sealed class EuclideanPixelMap : PixelMap /// The second point. /// The distance squared. [MethodImpl(InliningOptions.ShortMethod)] - private static float DistanceSquared(Rgba32 a, Rgba32 b) + private static int DistanceSquared(Rgba32 a, Rgba32 b) { - float deltaR = a.R - b.R; - float deltaG = a.G - b.G; - float deltaB = a.B - b.B; - float deltaA = a.A - b.A; + int deltaR = a.R - b.R; + int deltaG = a.G - b.G; + int deltaB = a.B - b.B; + int deltaA = a.A - b.A; return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA); } @@ -177,8 +254,7 @@ internal static class PixelMapFactory ColorMatchingMode colorMatchingMode) where TPixel : unmanaged, IPixel => colorMatchingMode switch { - ColorMatchingMode.Hybrid => new EuclideanPixelMap(configuration, palette), - ColorMatchingMode.Exact => new EuclideanPixelMap(configuration, palette), + ColorMatchingMode.Exact => new EuclideanPixelMap(configuration, palette), _ => new EuclideanPixelMap(configuration, palette), }; } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer.cs similarity index 51% rename from src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs rename to src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer.cs index 0a1032bf0..6b2f5a013 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer.cs @@ -6,25 +6,29 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization; /// -/// Allows the quantization of images pixels using Octrees. -/// +/// Quantizes images by grouping colors in an adaptive 16-way tree and reducing those groups into a palette. /// -public class OctreeQuantizer : IQuantizer +/// +/// Each level routes colors using one bit of RGB and, when useful, one bit of alpha. Fully opaque mid-tone colors +/// use RGB-only routing so more branch resolution is spent on visible color detail, while transparent, dark, and +/// light colors use alpha-aware routing so opacity changes can form their own palette buckets. +/// +public class HexadecatreeQuantizer : IQuantizer { /// - /// Initializes a new instance of the class + /// Initializes a new instance of the class /// using the default . /// - public OctreeQuantizer() + public HexadecatreeQuantizer() : this(new QuantizerOptions()) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The quantizer options defining quantization rules. - public OctreeQuantizer(QuantizerOptions options) + /// The quantizer options that control palette size, dithering, and transparency behavior. + public HexadecatreeQuantizer(QuantizerOptions options) { Guard.NotNull(options, nameof(options)); this.Options = options; @@ -41,5 +45,5 @@ public class OctreeQuantizer : IQuantizer /// public IQuantizer CreatePixelSpecificQuantizer(Configuration configuration, QuantizerOptions options) where TPixel : unmanaged, IPixel - => new OctreeQuantizer(configuration, options); + => new HexadecatreeQuantizer(configuration, options); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs similarity index 52% rename from src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs rename to src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs index 07596b68a..d669f885a 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/HexadecatreeQuantizer{TPixel}.cs @@ -12,19 +12,28 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Quantization; /// -/// Encapsulates methods to calculate the color palette if an image using an Octree pattern. -/// +/// Quantizes an image by building an adaptive 16-way color tree and reducing it to the requested palette size. /// +/// +/// +/// Each level routes colors using one bit of RGB and, when useful, one bit of alpha, giving the tree up to 16 children +/// per node and letting transparency participate directly in palette construction. +/// +/// +/// Fully opaque mid-tone colors use RGB-only routing so more branch resolution is spent on visible color detail. +/// Transparent, dark, and light colors use alpha-aware routing so opacity changes can form distinct palette buckets. +/// +/// /// The pixel format. #pragma warning disable CA1001 // Types that own disposable fields should be disposable // See https://github.com/dotnet/roslyn-analyzers/issues/6151 -public struct OctreeQuantizer : IQuantizer +public struct HexadecatreeQuantizer : IQuantizer #pragma warning restore CA1001 // Types that own disposable fields should be disposable where TPixel : unmanaged, IPixel { private readonly int maxColors; private readonly int bitDepth; - private readonly Octree octree; + private readonly Hexadecatree tree; private readonly IMemoryOwner paletteOwner; private ReadOnlyMemory palette; private PixelMap? pixelMap; @@ -32,19 +41,19 @@ public struct OctreeQuantizer : IQuantizer private bool isDisposed; /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct. /// - /// The configuration which allows altering default behavior or extending the library. - /// The quantizer options defining quantization rules. + /// The configuration that provides memory allocation and pixel conversion services. + /// The quantizer options that control palette size, dithering, and transparency behavior. [MethodImpl(InliningOptions.ShortMethod)] - public OctreeQuantizer(Configuration configuration, QuantizerOptions options) + public HexadecatreeQuantizer(Configuration configuration, QuantizerOptions options) { this.Configuration = configuration; this.Options = options; this.maxColors = this.Options.MaxColors; this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8); - this.octree = new Octree(configuration, this.bitDepth, this.maxColors, this.Options.TransparencyThreshold); + this.tree = new Hexadecatree(configuration, this.bitDepth, this.maxColors, this.Options.TransparencyThreshold); this.paletteOwner = configuration.MemoryAllocator.Allocate(this.maxColors, AllocationOptions.Clean); this.pixelMap = default; this.palette = default; @@ -76,23 +85,28 @@ public struct OctreeQuantizer : IQuantizer /// public readonly void AddPaletteColors(in Buffer2DRegion pixelRegion) { - PixelRowDelegate pixelRowDelegate = new(this.octree); - QuantizerUtilities.AddPaletteColors, TPixel, Rgba32, PixelRowDelegate>( + PixelRowDelegate pixelRowDelegate = new(this.tree); + QuantizerUtilities.AddPaletteColors, TPixel, Rgba32, PixelRowDelegate>( ref Unsafe.AsRef(in this), in pixelRegion, in pixelRowDelegate); } + /// + /// Materializes the final palette from the accumulated tree and prepares the dither lookup map when needed. + /// private void ResolvePalette() { short paletteIndex = 0; Span paletteSpan = this.paletteOwner.GetSpan(); - this.octree.Palettize(paletteSpan, ref paletteIndex); + this.tree.Palettize(paletteSpan, ref paletteIndex); ReadOnlyMemory result = this.paletteOwner.Memory[..paletteSpan.Length]; if (this.isDithering) { + // Dithered colors often no longer land on a color that was seen during palette construction, + // so the quantization pass switches to nearest-palette matching once the palette is finalized. this.pixelMap = PixelMapFactory.Create(this.Configuration, result, this.Options.ColorMatchingMode); } @@ -108,17 +122,15 @@ public struct OctreeQuantizer : IQuantizer [MethodImpl(InliningOptions.ShortMethod)] public readonly byte GetQuantizedColor(TPixel color, out TPixel match) { - // Due to the addition of new colors by dithering that are not part of the original histogram, - // the octree nodes might not match the correct color. - // In this case, we must use the pixel map to get the closest color. if (this.isDithering) { + // Dithering introduces adjusted colors that were never inserted into the tree, so tree lookup + // is only reliable for the non-dithered path. return (byte)this.pixelMap!.GetClosestColor(color, out match); } ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span); - - int index = this.octree.GetPaletteIndex(color); + int index = this.tree.GetPaletteIndex(color); match = Unsafe.Add(ref paletteRef, (nuint)index); return (byte)index; } @@ -132,34 +144,43 @@ public struct OctreeQuantizer : IQuantizer this.paletteOwner.Dispose(); this.pixelMap?.Dispose(); this.pixelMap = null; - this.octree.Dispose(); + this.tree.Dispose(); } } + /// + /// Forwards source rows into the tree without creating an intermediate buffer. + /// private readonly struct PixelRowDelegate : IQuantizingPixelRowDelegate { - private readonly Octree octree; + private readonly Hexadecatree tree; - public PixelRowDelegate(Octree octree) => this.octree = octree; + /// + /// Initializes a new instance of the struct. + /// + /// The destination tree that should accumulate each visited row. + public PixelRowDelegate(Hexadecatree tree) => this.tree = tree; - public void Invoke(ReadOnlySpan row, int rowIndex) => this.octree.AddColors(row); + /// + public void Invoke(ReadOnlySpan row, int rowIndex) => this.tree.AddColors(row); } /// - /// A hexadecatree-based color quantization structure used for fast color distance lookups and palette generation. - /// This tree maintains a fixed pool of nodes (capacity 4096) where each node can have up to 16 children, stores - /// color accumulation data, and supports dynamic node allocation and reduction. It offers near-constant-time insertions - /// and lookups while consuming roughly 240 KB for the node pool. + /// Stores the adaptive 16-way partition tree used to accumulate colors and emit palette entries. /// - internal sealed class Octree : IDisposable + /// + /// The tree uses a fixed node arena for predictable allocation behavior, keeps per-level reducible node lists so + /// deeper buckets can be merged until the palette fits, and caches the previously inserted leaf so repeated colors + /// can be accumulated cheaply. + /// + internal sealed class Hexadecatree : IDisposable { - // The memory allocator. - private readonly MemoryAllocator allocator; - // Pooled buffer for OctreeNodes. - private readonly IMemoryOwner nodesOwner; + private readonly IMemoryOwner nodesOwner; - // Reducible nodes: one per level; we use an integer index; -1 means “no node.” + // One reducible-node head per level. + // Each entry stores a node index, or -1 when that level currently + // has no reducible nodes. private readonly short[] reducibleNodes; // Maximum number of allowable colors. @@ -168,8 +189,8 @@ public struct OctreeQuantizer : IQuantizer // Maximum significant bits. private readonly int maxColorBits; - // The threshold for transparent colors. - private readonly int transparencyThreshold255; + // Keep the fractional byte-domain threshold so values such as .5 remain distinct from alpha 127. + private readonly float transparencyThreshold255; // Instead of a reference to the root, we store the index of the root node. // Index 0 is reserved for the root. @@ -186,13 +207,13 @@ public struct OctreeQuantizer : IQuantizer private readonly Stack freeIndices = new(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The configuration which allows altering default behavior or extending the library. - /// The maximum number of significant bits in the image. - /// The maximum number of colors to allow in the palette. - /// The threshold for transparent colors. - public Octree( + /// The configuration that provides the backing memory allocator. + /// The number of levels to descend before forcing leaves. + /// The maximum number of palette entries the reduced tree may retain. + /// The alpha threshold below which generated palette entries become fully transparent. + public Hexadecatree( Configuration configuration, int maxColorBits, int maxColors, @@ -200,15 +221,14 @@ public struct OctreeQuantizer : IQuantizer { this.maxColorBits = maxColorBits; this.maxColors = maxColors; - this.transparencyThreshold255 = (int)(transparencyThreshold * 255F); + this.transparencyThreshold255 = transparencyThreshold * 255F; this.Leaves = 0; this.previousNode = -1; this.previousColor = default; // Allocate a conservative buffer for nodes. const int capacity = 4096; - this.allocator = configuration.MemoryAllocator; - this.nodesOwner = this.allocator.Allocate(capacity, AllocationOptions.Clean); + this.nodesOwner = configuration.MemoryAllocator.Allocate(capacity, AllocationOptions.Clean); // Create the reducible nodes array (one per level 0 .. maxColorBits-1). this.reducibleNodes = new short[this.maxColorBits]; @@ -216,24 +236,24 @@ public struct OctreeQuantizer : IQuantizer // Reserve index 0 for the root. this.rootIndex = 0; - ref OctreeNode root = ref this.Nodes[this.rootIndex]; + ref Node root = ref this.Nodes[this.rootIndex]; root.Initialize(0, this.maxColorBits, this, this.rootIndex); } /// - /// Gets or sets the number of leaves in the tree. + /// Gets or sets the number of leaf nodes currently representing palette buckets. /// public int Leaves { get; set; } /// - /// Gets the full collection of nodes as a span. + /// Gets the underlying node arena. /// - internal Span Nodes => this.nodesOwner.Memory.Span; + internal Span Nodes => this.nodesOwner.Memory.Span; /// - /// Adds a span of colors to the octree. + /// Adds a row of colors to the tree. /// - /// A span of color values to be added. + /// The colors to accumulate. public void AddColors(ReadOnlySpan row) { for (int x = 0; x < row.Length; x++) @@ -243,12 +263,13 @@ public struct OctreeQuantizer : IQuantizer } /// - /// Add a color to the Octree. + /// Adds a single color sample to the tree. /// - /// The color to add. + /// The color to accumulate. private void AddColor(Rgba32 color) { - // Ensure that the tree is not already full. + // Once the node arena is full and there are no recycled slots available, keep collapsing + // reducible leaves until the tree is small enough to make forward progress again. if (this.nextNode >= this.Nodes.Length && this.freeIndices.Count == 0) { while (this.Leaves > this.maxColors) @@ -257,32 +278,32 @@ public struct OctreeQuantizer : IQuantizer } } - // If the color is the same as the previous color, increment the node. - // Otherwise, add a new node. + // Scanlines often contain long runs of the same color. Caching the previous leaf lets those + // repeats skip the tree walk and just bump the accumulated sums in place. if (this.previousColor.Equals(color)) { if (this.previousNode == -1) { this.previousColor = color; - OctreeNode.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); + Node.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); } else { - OctreeNode.Increment(this.previousNode, color, this); + Node.Increment(this.previousNode, color, this); } } else { this.previousColor = color; - OctreeNode.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); + Node.AddColor(this.rootIndex, color, this.maxColorBits, 0, this); } } /// - /// Construct the palette from the octree. + /// Reduces the tree to the requested palette size and emits the final palette entries. /// - /// The palette to construct. - /// The current palette index. + /// The destination palette span. + /// The running palette index. public void Palettize(Span palette, ref short paletteIndex) { while (this.Leaves > this.maxColors) @@ -294,48 +315,45 @@ public struct OctreeQuantizer : IQuantizer } /// - /// Get the palette index for the passed color. + /// Gets the palette index selected by the tree for the supplied color. /// - /// The color to get the palette index for. - /// The . + /// The color to resolve. + /// The palette index represented by the best matching leaf in the reduced tree. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetPaletteIndex(TPixel color) => this.Nodes[this.rootIndex].GetPaletteIndex(color.ToRgba32(), 0, this); /// - /// Track the previous node and color. + /// Records the most recently touched leaf so repeated colors can bypass another descent. /// - /// The node index. + /// The leaf node index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrackPrevious(int nodeIndex) => this.previousNode = nodeIndex; /// - /// Reduce the depth of the tree. + /// Collapses the deepest currently reducible node into a single leaf. /// private void Reduce() { - // Find the deepest level containing at least one reducible node int index = this.maxColorBits - 1; while ((index > 0) && (this.reducibleNodes[index] == -1)) { index--; } - // Reduce the node most recently added to the list at level 'index' - ref OctreeNode node = ref this.Nodes[this.reducibleNodes[index]]; + ref Node node = ref this.Nodes[this.reducibleNodes[index]]; this.reducibleNodes[index] = node.NextReducibleIndex; - - // Decrement the leaf count after reducing the node node.Reduce(this); - // And just in case I've reduced the last color to be added, and the next color to - // be added is the same, invalidate the previousNode... + // If the last inserted leaf was merged away, the next repeated color must walk the tree again. this.previousNode = -1; } - // Allocate a new OctreeNode from the pooled buffer. - // First check the freeIndices stack. + /// + /// Allocates a node index from the free list or from the unused tail of the arena. + /// + /// The allocated node index, or -1 if no node can be allocated. internal short AllocateNode() { if (this.freeIndices.Count > 0) @@ -354,9 +372,9 @@ public struct OctreeQuantizer : IQuantizer } /// - /// Free a node index, making it available for re-allocation. + /// Returns a node index to the free list. /// - /// The index to free. + /// The node index to recycle. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void FreeNode(short index) { @@ -367,32 +385,37 @@ public struct OctreeQuantizer : IQuantizer /// public void Dispose() => this.nodesOwner.Dispose(); + /// + /// Represents one node in the hexadecatree node arena. + /// [StructLayout(LayoutKind.Sequential)] - internal unsafe struct OctreeNode + internal struct Node { public bool Leaf; - public int PixelCount; - public int Red; - public int Green; - public int Blue; - public int Alpha; + public long PixelCount; + public long Red; + public long Green; + public long Blue; + public long Alpha; public short PaletteIndex; public short NextReducibleIndex; private InlineArray16 children; + /// + /// Gets the 16 child slots for this node. + /// [UnscopedRef] public Span Children => this.children; /// - /// Initialize the . + /// Initializes a node either as a leaf or as a reducible interior node. /// - /// The level of the node. - /// The number of significant color bits in the image. - /// The parent octree. - /// The index of the node. - public void Initialize(int level, int colorBits, Octree octree, short index) + /// The depth of the node being initialized. + /// The maximum tree depth. + /// The owning tree. + /// The node index in the arena. + public void Initialize(int level, int colorBits, Hexadecatree tree, short index) { - // Construct the new node. this.Leaf = level == colorBits; this.Red = 0; this.Green = 0; @@ -401,76 +424,73 @@ public struct OctreeQuantizer : IQuantizer this.PixelCount = 0; this.PaletteIndex = 0; this.NextReducibleIndex = -1; - - // Always clear the Children array. this.Children.Fill(-1); if (this.Leaf) { - octree.Leaves++; + tree.Leaves++; } else { - // Add this node to the reducible nodes list for its level. - this.NextReducibleIndex = octree.reducibleNodes[level]; - octree.reducibleNodes[level] = index; + // Track reducible nodes per level so palette reduction can always collapse the deepest + // buckets first without scanning the entire arena. + this.NextReducibleIndex = tree.reducibleNodes[level]; + tree.reducibleNodes[level] = index; } } /// - /// Add a color to the Octree. + /// Descends the tree for the supplied color, allocating nodes as needed until a leaf is reached. /// - /// The node index. - /// The color to add. - /// The number of significant color bits in the image. - /// The level of the node. - /// The parent octree. - public static void AddColor(int nodeIndex, Rgba32 color, int colorBits, int level, Octree octree) + /// The current node index. + /// The color being accumulated. + /// The maximum tree depth. + /// The current depth. + /// The owning tree. + public static void AddColor(int nodeIndex, Rgba32 color, int colorBits, int level, Hexadecatree tree) { - ref OctreeNode node = ref octree.Nodes[nodeIndex]; + ref Node node = ref tree.Nodes[nodeIndex]; if (node.Leaf) { - Increment(nodeIndex, color, octree); - octree.TrackPrevious(nodeIndex); + Increment(nodeIndex, color, tree); + tree.TrackPrevious(nodeIndex); + return; } - else - { - int index = GetColorIndex(color, level); - short childIndex; - Span children = node.Children; - childIndex = children[index]; + int index = GetColorIndex(color, level); + Span children = node.Children; + short childIndex = children[index]; + if (childIndex == -1) + { + childIndex = tree.AllocateNode(); if (childIndex == -1) { - childIndex = octree.AllocateNode(); - - if (childIndex == -1) - { - // No room in the tree, so increment the count and return. - Increment(nodeIndex, color, octree); - octree.TrackPrevious(nodeIndex); - return; - } - - ref OctreeNode child = ref octree.Nodes[childIndex]; - child.Initialize(level + 1, colorBits, octree, childIndex); - children[index] = childIndex; + // If the arena is exhausted and no node can be reclaimed yet, fall back to + // accumulating into the current node instead of failing the insert outright. + Increment(nodeIndex, color, tree); + tree.TrackPrevious(nodeIndex); + return; } - AddColor(childIndex, color, colorBits, level + 1, octree); + ref Node child = ref tree.Nodes[childIndex]; + child.Initialize(level + 1, colorBits, tree, childIndex); + children[index] = childIndex; } + + // Keep descending until we reach the leaf bucket that should accumulate this sample. + AddColor(childIndex, color, colorBits, level + 1, tree); } /// - /// Increment the color components of this node. + /// Adds the supplied color sample to an existing node's running sums. /// - /// The node index. - /// The color to increment by. - /// The parent octree. - public static void Increment(int nodeIndex, Rgba32 color, Octree octree) + /// The node index to update. + /// The color sample being accumulated. + /// The owning tree. + public static void Increment(int nodeIndex, Rgba32 color, Hexadecatree tree) { - ref OctreeNode node = ref octree.Nodes[nodeIndex]; + ref Node node = ref tree.Nodes[nodeIndex]; node.PixelCount++; node.Red += color.R; node.Green += color.G; @@ -479,10 +499,10 @@ public struct OctreeQuantizer : IQuantizer } /// - /// Reduce this node by ensuring its children are all reduced (i.e. leaves) and then merging their data. + /// Merges all child nodes into this node and turns it into a leaf. /// - /// The parent octree. - public void Reduce(Octree octree) + /// The owning tree. + public void Reduce(Hexadecatree tree) { // If already a leaf, do nothing. if (this.Leaf) @@ -490,27 +510,30 @@ public struct OctreeQuantizer : IQuantizer return; } - // Now merge the (presumably reduced) children. - int pixelCount = 0; - int sumRed = 0, sumGreen = 0, sumBlue = 0, sumAlpha = 0; + // Allocation fallback can accumulate samples on an interior node. Seed the merge + // with this node's own sums so reduction preserves those samples with its children. + long pixelCount = this.PixelCount; + long sumRed = this.Red; + long sumGreen = this.Green; + long sumBlue = this.Blue; + long sumAlpha = this.Alpha; Span children = this.Children; + for (int i = 0; i < children.Length; i++) { short childIndex = children[i]; if (childIndex != -1) { - ref OctreeNode child = ref octree.Nodes[childIndex]; - int pixels = child.PixelCount; - + ref Node child = ref tree.Nodes[childIndex]; + long pixels = child.PixelCount; sumRed += child.Red; sumGreen += child.Green; sumBlue += child.Blue; sumAlpha += child.Alpha; pixelCount += pixels; - // Free the child immediately. children[i] = -1; - octree.FreeNode(childIndex); + tree.FreeNode(childIndex); } } @@ -529,16 +552,16 @@ public struct OctreeQuantizer : IQuantizer } this.Leaf = true; - octree.Leaves++; + tree.Leaves++; } /// - /// Traverse the tree to construct the palette. + /// Traverses the reduced tree and emits one palette color per leaf. /// - /// The parent octree. - /// The palette to construct. - /// The current palette index. - public void ConstructPalette(Octree octree, Span palette, ref short paletteIndex) + /// The owning tree. + /// The destination palette span. + /// The running palette index. + public void ConstructPalette(Hexadecatree tree, Span palette, ref short paletteIndex) { if (this.Leaf) { @@ -549,13 +572,12 @@ public struct OctreeQuantizer : IQuantizer Vector4.Zero, new Vector4(255)); - if (vector.W < octree.transparencyThreshold255) + if (vector.W < tree.transparencyThreshold255) { vector = Vector4.Zero; } palette[paletteIndex] = TPixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, (byte)vector.W)); - this.PaletteIndex = paletteIndex++; } else @@ -566,19 +588,20 @@ public struct OctreeQuantizer : IQuantizer int childIndex = children[i]; if (childIndex != -1) { - octree.Nodes[childIndex].ConstructPalette(octree, palette, ref paletteIndex); + tree.Nodes[childIndex].ConstructPalette(tree, palette, ref paletteIndex); } } } } /// - /// Get the palette index for the passed color. + /// Resolves the palette index represented by this node for the supplied color. /// - /// The color to get the palette index for. - /// The level of the node. - /// The parent octree. - public int GetPaletteIndex(Rgba32 color, int level, Octree octree) + /// The color to resolve. + /// The current tree depth. + /// The owning tree. + /// The palette index for the best reachable leaf, or -1 if no leaf can be reached. + public int GetPaletteIndex(Rgba32 color, int level, Hexadecatree tree) { if (this.Leaf) { @@ -590,15 +613,16 @@ public struct OctreeQuantizer : IQuantizer int childIndex = children[colorIndex]; if (childIndex != -1) { - return octree.Nodes[childIndex].GetPaletteIndex(color, level + 1, octree); + return tree.Nodes[childIndex].GetPaletteIndex(color, level + 1, tree); } + // After reductions the exact branch can disappear, so fall back to the first reachable descendant leaf. for (int i = 0; i < children.Length; i++) { childIndex = children[i]; if (childIndex != -1) { - int childPaletteIndex = octree.Nodes[childIndex].GetPaletteIndex(color, level + 1, octree); + int childPaletteIndex = tree.Nodes[childIndex].GetPaletteIndex(color, level + 1, tree); if (childPaletteIndex != -1) { return childPaletteIndex; @@ -610,37 +634,35 @@ public struct OctreeQuantizer : IQuantizer } /// - /// Gets the color index at the given level. + /// Computes the child slot for a color at the supplied tree level. /// - /// The color to get the index for. - /// The level to get the index at. + /// The color being routed. + /// The tree depth whose bit plane should be sampled. + /// The child slot index for the color at the supplied level. + /// + /// For fully opaque mid-tone colors the tree ignores alpha and routes on RGB only, preserving more branch + /// resolution for visible color detail. For transparent, dark, and light colors it includes alpha as the + /// most significant routing bit so opacity changes can form their own branches. + /// public static int GetColorIndex(Rgba32 color, int level) { - // Determine how many bits to shift based on the current tree level. - // At level 0, shift = 7; as level increases, the shift decreases. + // Sample one bit plane per level, starting at the most significant bit and moving downward. int shift = 7 - level; byte mask = (byte)(1 << shift); - // Compute the luminance of the RGB components using the BT.709 standard. - // This gives a measure of brightness for the color. + // Use BT.709 luminance as a cheap brightness estimate for deciding whether alpha carries + // useful information at this level for fully opaque colors. int luminance = ColorNumerics.Get8BitBT709Luminance(color.R, color.G, color.B); - // Define thresholds for determining when to include the alpha bit in the index. - // The thresholds are scaled according to the current level. - // 128 is the midpoint of the 8-bit range (0–255), so shifting it right by 'level' - // produces a threshold that scales with the color cube subdivision. + // Scale the brightness thresholds with depth so deeper levels become stricter about when + // to spend a branch bit on alpha instead of RGB detail. int darkThreshold = 128 >> level; - - // The light threshold is set symmetrically: 255 minus the scaled midpoint. int lightThreshold = 255 - (128 >> level); - // If the pixel is fully opaque and its brightness falls between the dark and light thresholds, - // ignore the alpha channel to maximize RGB resolution. - // Otherwise (if the pixel is dark, light, or semi-transparent), include the alpha bit - // to preserve any gradient that may be present. if (color.A == 255 && luminance > darkThreshold && luminance < lightThreshold) { - // Extract one bit each from R, G, and B channels and combine them into a 3-bit index. + // Fully opaque mid-tone colors route on RGB only, which preserves more visible color + // resolution because alpha would contribute no extra separation here. int rBits = ((color.R & mask) >> shift) << 2; int gBits = ((color.G & mask) >> shift) << 1; int bBits = (color.B & mask) >> shift; @@ -648,7 +670,8 @@ public struct OctreeQuantizer : IQuantizer } else { - // Extract one bit from each channel including alpha (alpha becomes the most significant bit). + // Transparent, dark, and light colors include alpha as the high routing bit so opacity + // changes can form distinct buckets alongside RGB differences. int aBits = ((color.A & mask) >> shift) << 3; int rBits = ((color.R & mask) >> shift) << 2; int gBits = ((color.G & mask) >> shift) << 1; diff --git a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs index 32d95137b..76598e004 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/IColorIndexCache.cs @@ -56,147 +56,6 @@ internal interface IColorIndexCache : IColorIndexCache public static abstract T Create(MemoryAllocator allocator); } -/// -/// A hybrid color distance cache that combines a small, fixed-capacity exact-match dictionary -/// (ExactCache, ~4–5 KB for up to 512 entries) with a coarse lookup table (CoarseCache) for 5,5,5,6 precision. -/// -/// -/// ExactCache provides O(1) lookup for common cases using a simple 256-entry hash-based dictionary, while CoarseCache -/// quantizes RGB channels to 5 bits (yielding 32^3 buckets) and alpha to 6 bits, storing up to 4 alpha entries per bucket -/// (a design chosen based on probability theory to capture most real-world variations) for a total memory footprint of -/// roughly 576 KB. Lookups and insertions are performed in constant time, making the overall design both fast and memory-predictable. -/// -internal unsafe struct HybridCache : IColorIndexCache -{ - private CoarseCache coarseCache; - private AccurateCache accurateCache; - - public HybridCache(MemoryAllocator allocator) - { - this.accurateCache = AccurateCache.Create(allocator); - this.coarseCache = CoarseCache.Create(allocator); - } - - /// - public static HybridCache Create(MemoryAllocator allocator) => new(allocator); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public bool TryAdd(Rgba32 color, short index) - { - if (this.accurateCache.TryAdd(color, index)) - { - return true; - } - - return this.coarseCache.TryAdd(color, index); - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool TryGetValue(Rgba32 color, out short value) - { - if (this.accurateCache.TryGetValue(color, out value)) - { - return true; - } - - return this.coarseCache.TryGetValue(color, out value); - } - - /// - public readonly void Clear() - { - this.accurateCache.Clear(); - this.coarseCache.Clear(); - } - - /// - public void Dispose() - { - this.accurateCache.Dispose(); - this.coarseCache.Dispose(); - } -} - -/// -/// A coarse cache for color distance lookups that uses a fixed-size lookup table. -/// -/// -/// This cache uses a fixed lookup table with 2,097,152 bins, each storing a 2-byte value, -/// resulting in a memory usage of approximately 4 MB. Lookups and insertions are -/// performed in constant time (O(1)) via direct table indexing. This design is optimized for -/// speed while maintaining a predictable, fixed memory footprint. -/// -internal unsafe struct CoarseCache : IColorIndexCache -{ - private const int IndexRBits = 5; - private const int IndexGBits = 5; - private const int IndexBBits = 5; - private const int IndexABits = 6; - private const int IndexRCount = 1 << IndexRBits; // 32 bins for red - private const int IndexGCount = 1 << IndexGBits; // 32 bins for green - private const int IndexBCount = 1 << IndexBBits; // 32 bins for blue - private const int IndexACount = 1 << IndexABits; // 64 bins for alpha - private const int TotalBins = IndexRCount * IndexGCount * IndexBCount * IndexACount; // 2,097,152 bins - - private readonly IMemoryOwner binsOwner; - private readonly short* binsPointer; - private MemoryHandle binsHandle; - - private CoarseCache(MemoryAllocator allocator) - { - this.binsOwner = allocator.Allocate(TotalBins); - this.binsOwner.GetSpan().Fill(-1); - this.binsHandle = this.binsOwner.Memory.Pin(); - this.binsPointer = (short*)this.binsHandle.Pointer; - } - - /// - public static CoarseCache Create(MemoryAllocator allocator) => new(allocator); - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool TryAdd(Rgba32 color, short value) - { - this.binsPointer[GetCoarseIndex(color)] = value; - return true; - } - - /// - [MethodImpl(InliningOptions.ShortMethod)] - public readonly bool TryGetValue(Rgba32 color, out short value) - { - value = this.binsPointer[GetCoarseIndex(color)]; - return value > -1; // Coarse match found - } - - [MethodImpl(InliningOptions.ShortMethod)] - private static int GetCoarseIndex(Rgba32 color) - { - int rIndex = color.R >> (8 - IndexRBits); - int gIndex = color.G >> (8 - IndexGBits); - int bIndex = color.B >> (8 - IndexBBits); - int aIndex = color.A >> (8 - IndexABits); - - return (aIndex * IndexRCount * IndexGCount * IndexBCount) + - (rIndex * IndexGCount * IndexBCount) + - (gIndex * IndexBCount) + - bIndex; - } - - /// - public readonly void Clear() - => this.binsOwner.GetSpan().Fill(-1); - - /// - public void Dispose() - { - this.binsHandle.Dispose(); - this.binsOwner.Dispose(); - } -} - /// /// /// CoarseCache is a fast, low-memory lookup structure for caching palette indices associated with RGBA values, @@ -225,7 +84,7 @@ internal unsafe struct CoarseCache : IColorIndexCache /// making it ideal for applications such as color distance caching in images with a limited palette (up to 256 entries). /// /// -internal unsafe struct CoarseCacheLite : IColorIndexCache +internal unsafe struct CoarseCache : IColorIndexCache { // Use 5 bits per channel for R, G, and B: 32 levels each. // Total buckets = 32^3 = 32768. @@ -236,7 +95,7 @@ internal unsafe struct CoarseCacheLite : IColorIndexCache private readonly AlphaBucket* buckets; private MemoryHandle bucketHandle; - private CoarseCacheLite(MemoryAllocator allocator) + private CoarseCache(MemoryAllocator allocator) { this.bucketsOwner = allocator.Allocate(BucketCount, AllocationOptions.Clean); this.bucketHandle = this.bucketsOwner.Memory.Pin(); @@ -244,7 +103,7 @@ internal unsafe struct CoarseCacheLite : IColorIndexCache } /// - public static CoarseCacheLite Create(MemoryAllocator allocator) => new(allocator); + public static CoarseCache Create(MemoryAllocator allocator) => new(allocator); /// public readonly bool TryAdd(Rgba32 color, short paletteIndex) @@ -289,14 +148,11 @@ internal unsafe struct CoarseCacheLite : IColorIndexCache } [MethodImpl(InliningOptions.ShortMethod)] - private static byte QuantizeAlpha(byte a) - - // Quantize to 6 bits: shift right by (8 - 6) = 2 bits. - => (byte)(a >> 2); + private static byte QuantizeAlpha(byte a) => (byte)(a >> 2); public struct AlphaEntry { - // Store the alpha value quantized to 6 bits (0..63) + // Store the alpha value quantized to 6 bits (0..63). public byte QuantizedAlpha; public short PaletteIndex; } @@ -312,7 +168,7 @@ internal unsafe struct CoarseCacheLite : IColorIndexCache // 2. However, in practice (based on probability theory and typical image data), // the number of unique alpha values that actually occur for a given quantized RGB // bucket is usually very small. If you randomly sample 8 values out of 64, - // the probability that these 4 samples are all unique is high if the distribution + // the probability that these samples are all unique is high if the distribution // of alpha values is skewed or if only a few alpha values are used. // // 3. Statistically, for many real-world images, most RGB buckets will have only a couple @@ -377,51 +233,49 @@ internal unsafe struct CoarseCacheLite : IColorIndexCache } /// -/// A fixed-capacity dictionary with exactly 512 entries mapping a key -/// to a value. +/// A fixed-size exact-match cache that stores packed RGBA keys with 4-way set associativity. /// /// -/// The dictionary is implemented using a fixed array of 512 buckets and an entries array -/// of the same size. The bucket for a key is computed as (key & 0x1FF), and collisions are -/// resolved through a linked chain stored in the field. +/// The cache holds 512 total entries split across 128 sets. Entries are evicted within a set +/// using round-robin replacement, but cached values are returned only when the full packed RGBA +/// key matches, preserving exact quantization results with predictable memory usage. /// The overall memory usage is approximately 4–5 KB. Both lookup and insertion operations are, -/// on average, O(1) since the bucket is determined via a simple bitmask and collision chains are -/// typically very short; in the worst-case, the number of iterations is bounded by 256. +/// on average, O(1) since each lookup probes at most four candidate entries within the selected set. /// This guarantees highly efficient and predictable performance for small, fixed-size color palettes. /// internal unsafe struct AccurateCache : IColorIndexCache { - // Buckets array: each bucket holds the index (0-based) into the entries array - // of the first entry in the chain, or -1 if empty. - private readonly IMemoryOwner bucketsOwner; - private MemoryHandle bucketsHandle; - private short* buckets; + public const int Capacity = 512; + private const int Ways = 4; + private const int SetCount = Capacity / Ways; + private const int SetMask = SetCount - 1; - // Entries array: stores up to 256 entries. - private readonly IMemoryOwner entriesOwner; - private MemoryHandle entriesHandle; - private Entry* entries; + private readonly IMemoryOwner keysOwner; + private MemoryHandle keysHandle; + private uint* keys; - public const int Capacity = 512; + private readonly IMemoryOwner valuesOwner; + private MemoryHandle valuesHandle; + private ushort* values; + + private readonly IMemoryOwner nextVictimOwner; + private MemoryHandle nextVictimHandle; + private byte* nextVictim; private AccurateCache(MemoryAllocator allocator) { - this.Count = 0; - - // Allocate exactly 512 indexes for buckets. - this.bucketsOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); - Span bucketSpan = this.bucketsOwner.GetSpan(); - bucketSpan.Fill(-1); - this.bucketsHandle = this.bucketsOwner.Memory.Pin(); - this.buckets = (short*)this.bucketsHandle.Pointer; - - // Allocate exactly 512 entries. - this.entriesOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); - this.entriesHandle = this.entriesOwner.Memory.Pin(); - this.entries = (Entry*)this.entriesHandle.Pointer; - } + this.keysOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); + this.keysHandle = this.keysOwner.Memory.Pin(); + this.keys = (uint*)this.keysHandle.Pointer; - public int Count { get; private set; } + this.valuesOwner = allocator.Allocate(Capacity, AllocationOptions.Clean); + this.valuesHandle = this.valuesOwner.Memory.Pin(); + this.values = (ushort*)this.valuesHandle.Pointer; + + this.nextVictimOwner = allocator.Allocate(SetCount, AllocationOptions.Clean); + this.nextVictimHandle = this.nextVictimOwner.Memory.Pin(); + this.nextVictim = (byte*)this.nextVictimHandle.Pointer; + } /// public static AccurateCache Create(MemoryAllocator allocator) => new(allocator); @@ -430,140 +284,113 @@ internal unsafe struct AccurateCache : IColorIndexCache [MethodImpl(InliningOptions.ShortMethod)] public bool TryAdd(Rgba32 color, short value) { - if (this.Count == Capacity) - { - return false; // Dictionary is full. - } - uint key = color.PackedValue; + int set = GetSetIndex(key); + int start = set * Ways; + int empty = -1; + + uint* keys = this.keys; + ushort* values = this.values; + ushort storedValue = (ushort)(value + 1); - // The key is a 32-bit unsigned integer representing an RGBA color, where the bytes are laid out as R|G|B|A - // (with R in the most significant byte and A in the least significant). - // To compute the bucket index: - // 1. (key >> 16) extracts the top 16 bits, effectively giving us the R and G channels. - // 2. (key >> 8) shifts the key right by 8 bits, bringing R, G, and B into the lower 24 bits (dropping A). - // 3. XORing these two values with the original key mixes bits from all four channels (R, G, B, and A), - // which helps to counteract situations where one or more channels have a limited range. - // 4. Finally, we apply a bitmask of 0x1FF to keep only the lowest 9 bits, ensuring the result is between 0 and 511, - // which corresponds to our fixed bucket count of 512. - int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); - int i = this.buckets[bucket]; - - // Traverse the collision chain. - Entry* entries = this.entries; - while (i != -1) + for (int i = start; i < start + Ways; i++) { - Entry e = entries[i]; - if (e.Key == key) + ushort candidate = values[i]; + if (candidate == 0) { - // Key already exists; do not overwrite. - return false; + empty = i; + continue; } - i = e.Next; + if (keys[i] == key) + { + values[i] = storedValue; + return true; + } } - short index = (short)this.Count; - this.Count++; + int slot = empty >= 0 ? empty : start + this.nextVictim[set]; + keys[slot] = key; + values[slot] = storedValue; - // Insert the new entry: - entries[index].Key = key; - entries[index].Value = value; + if (empty < 0) + { + this.nextVictim[set] = (byte)((this.nextVictim[set] + 1) & (Ways - 1)); + } - // Link this new entry into the bucket chain. - entries[index].Next = this.buckets[bucket]; - this.buckets[bucket] = index; return true; } /// [MethodImpl(InliningOptions.ShortMethod)] - public bool TryGetValue(Rgba32 color, out short value) + public readonly bool TryGetValue(Rgba32 color, out short value) { uint key = color.PackedValue; - int bucket = (int)(((key >> 16) ^ (key >> 8) ^ key) & 0x1FF); - int i = this.buckets[bucket]; + int start = GetSetIndex(key) * Ways; - // If the bucket is empty, return immediately. - if (i == -1) - { - value = -1; - return false; - } + uint* keys = this.keys; + ushort* values = this.values; - // Traverse the chain. - Entry* entries = this.entries; - do + for (int i = start; i < start + Ways; i++) { - Entry e = entries[i]; - if (e.Key == key) + ushort candidate = values[i]; + if (candidate != 0 && keys[i] == key) { - value = e.Value; + value = (short)(candidate - 1); return true; } - - i = e.Next; } - while (i != -1); value = -1; return false; } /// - /// Clears the dictionary. + /// Clears the cache. /// - public void Clear() + public readonly void Clear() { - Span bucketSpan = this.bucketsOwner.GetSpan(); - bucketSpan.Fill(-1); - this.Count = 0; + this.valuesOwner.GetSpan().Clear(); + this.nextVictimOwner.GetSpan().Clear(); } public void Dispose() { - this.bucketsHandle.Dispose(); - this.bucketsOwner.Dispose(); - this.entriesHandle.Dispose(); - this.entriesOwner.Dispose(); - this.buckets = null; - this.entries = null; + this.keysHandle.Dispose(); + this.keysOwner.Dispose(); + this.valuesHandle.Dispose(); + this.valuesOwner.Dispose(); + this.nextVictimHandle.Dispose(); + this.nextVictimOwner.Dispose(); + this.keys = null; + this.values = null; + this.nextVictim = null; } - private struct Entry - { - public uint Key; // The key (packed RGBA) - public short Value; // The value; -1 means unused. - public short Next; // Index of the next entry in the chain, or -1 if none. - } -} - -/// -/// Represents a cache that does not store any values. -/// It allows adding colors, but always returns false when trying to retrieve them. -/// -internal readonly struct NullCache : IColorIndexCache -{ - /// - public static NullCache Create(MemoryAllocator allocator) => default; - - /// - public bool TryAdd(Rgba32 color, short value) => true; - - /// - public bool TryGetValue(Rgba32 color, out short value) - { - value = -1; - return false; - } - - /// - public void Clear() - { - } - - /// - public void Dispose() - { - } + /// + /// Maps a packed RGBA key to one of the cache sets used by . + /// + /// The packed key. + /// The zero-based set index for the key. + /// + /// + /// The cache is 4-way set-associative, so this hash only needs to choose one of + /// sets before probing up to four candidate entries. + /// + /// + /// is laid out as R | (G << 8) | (B << 16) | (A << 24). + /// The XOR-fold mixes neighboring bytes into the low bits, and the final mask selects the + /// set. With the current 128-set layout that makes the selected set effectively depend on + /// the low 7 bits of R ^ G ^ B. Alpha still participates in the later exact key + /// comparison, but not in set selection. + /// + /// + /// Collisions are expected and acceptable here. Correctness comes from the full packed-key + /// comparison during probing; this hash only aims to spread keys cheaply enough that each + /// access touches at most one 4-entry set. + /// + /// + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetSetIndex(uint key) + => (int)(((key >> 16) ^ (key >> 8) ^ key) & SetMask); } diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index e121aff90..1e3453294 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -39,7 +39,7 @@ public static class QuantizerUtilities /// Returns true if transparent pixels can be replaced; otherwise, false. public static bool ShouldReplacePixelsByAlphaThreshold(float threshold) where TPixel : unmanaged, IPixel - => threshold > 0 && TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; + => threshold > 0 && TPixel.GetPixelTypeInfo().AlphaRepresentation != PixelAlphaRepresentation.None; /// /// Replaces pixels in a span with fully transparent pixels based on an alpha threshold. @@ -295,11 +295,11 @@ public static class QuantizerUtilities for (int y = 0; y < source.Height; y++) { Span sourceRow = source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ReplacePixelsByAlphaThreshold(vectorRow, threshold); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); rowDelegate.Invoke(delegateRow, y); } } @@ -308,11 +308,11 @@ public static class QuantizerUtilities for (int y = 0; y < source.Height; y++) { Span sourceRow = source.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); EncodingUtilities.ReplaceTransparentPixels(vectorRow); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, delegateRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); rowDelegate.Invoke(delegateRow, y); } } @@ -363,11 +363,11 @@ public static class QuantizerUtilities for (int y = 0; y < region.Height; y++) { Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ReplacePixelsByAlphaThreshold(vectorRow, threshold); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); for (int x = 0; x < destinationRow.Length; x++) @@ -381,11 +381,11 @@ public static class QuantizerUtilities for (int y = 0; y < region.Height; y++) { Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); EncodingUtilities.ReplaceTransparentPixels(vectorRow); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, quantizingRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y); for (int x = 0; x < destinationRow.Length; x++) @@ -420,11 +420,11 @@ public static class QuantizerUtilities for (int y = 0; y < region.Height; y++) { Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); ReplacePixelsByAlphaThreshold(vectorRow, threshold); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } else @@ -432,11 +432,11 @@ public static class QuantizerUtilities for (int y = 0; y < region.Height; y++) { Span sourceRow = region.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(configuration, sourceRow, vectorRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); EncodingUtilities.ReplaceTransparentPixels(vectorRow); - PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale); + PixelOperations.Instance.FromVector4Destructive(configuration, vectorRow, sourceRow, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index 03d6ac0da..b4b95adc1 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -144,7 +144,8 @@ internal struct WuQuantizer : IQuantizer normalized = Vector4.Zero; } - paletteSpan[k] = TPixel.FromScaledVector4(normalized); + // Histogram moments are accumulated from straight-alpha Rgba32 values, so the normalized palette color is unassociated. + paletteSpan[k] = TPixel.FromUnassociatedScaledVector4(normalized); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index 96c350c6c..dd992df8c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -53,22 +53,20 @@ internal class CropProcessor : TransformProcessor && this.SourceRectangle == this.cropRectangle) { // the cloned will be blank here copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + source.PixelBuffer.CopyTo(destination.PixelBuffer); return; } Rectangle bounds = this.cropRectangle; - // Copying is cheap, we should process more pixels per task: - ParallelExecutionSettings parallelSettings = - ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - + // Copying is too cheap to benefit from parallelization; + // the overhead exceeds the work per task. See #3111. RowOperation operation = new(bounds, source.PixelBuffer, destination.PixelBuffer); - ParallelRowIterator.IterateRows( - bounds, - in parallelSettings, - in operation); + for (int y = bounds.Top; y < bounds.Bottom; y++) + { + operation.Invoke(y); + } } /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs index 8a76ca42f..b9973eea3 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs @@ -96,13 +96,16 @@ internal class EntropyCropProcessor : ImageProcessor int height = bitmap.Height; Point topLeft = default; Point bottomRight = default; + + // BinaryThresholdProcessor produces opaque black and white, so association no longer affects the components. Scaled vectors still normalize pixel formats whose native vectors use another range. Func, int, int, float, bool> delegateFunc = channel switch { - RgbaComponent.R => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon, - RgbaComponent.G => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon, - RgbaComponent.B => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon, - _ => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon, + RgbaComponent.R => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToUnassociatedScaledVector4().X - b) > Constants.Epsilon, + RgbaComponent.G => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToUnassociatedScaledVector4().Y - b) > Constants.Epsilon, + RgbaComponent.B => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToUnassociatedScaledVector4().Z - b) > Constants.Epsilon, + _ => (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToUnassociatedScaledVector4().W - b) > Constants.Epsilon, }; + int GetMinY(ImageFrame pixels) { for (int y = 0; y < height; y++) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs index 30c279e59..afd1b70b7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor.cs @@ -21,7 +21,7 @@ public class AffineTransformProcessor : CloningImageProcessor Guard.NotNull(sampler, nameof(sampler)); Guard.MustBeValueType(sampler); - if (TransformUtils.IsDegenerate(matrix)) + if (TransformUtilities.IsDegenerate(matrix)) { throw new DegenerateTransformException("Matrix is degenerate. Check input values."); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 59f5773cf..b4848c2d6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -35,7 +35,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR { this.destinationSize = definition.DestinationSize; this.transformMatrix = definition.TransformMatrix; - this.transformMatrix4x4 = new(this.transformMatrix); + this.transformMatrix4x4 = new Matrix4x4(this.transformMatrix); this.resampler = definition.Sampler; } @@ -77,7 +77,9 @@ internal class AffineTransformProcessor : TransformProcessor, IR return; } - // Convert from screen to world space. + // All matrices are defined in normalized coordinate space so we need to convert to pixel space. + // After normalization we need to invert the matrix for correct sampling. + matrix = TransformUtilities.NormalizeToPixel(matrix); Matrix3x2.Invert(matrix, out matrix); if (sampler is NearestNeighborResampler) @@ -197,15 +199,14 @@ internal class AffineTransformProcessor : TransformProcessor, IR int maxY = this.bounds.Bottom - 1; int minX = this.bounds.X; int maxX = this.bounds.Right - 1; + PixelOperations pixelOperations = PixelOperations.Instance; for (int y = rows.Min; y < rows.Max; y++) { Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4( - this.configuration, - destinationRowSpan, - span, - PixelConversionModifiers.Scale); + + // The temporary row is reused, so clear values for out-of-bounds samples that skip the resampling loop below. + span.Clear(); for (int x = 0; x < span.Length; x++) { @@ -223,6 +224,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR continue; } + // Resampling interpolates color with coverage, so use associated vectors to prevent transparent RGB from bleeding into visible edges. Vector4 sum = Vector4.Zero; for (int yK = top; yK <= bottom; yK++) { @@ -233,8 +235,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceRowSpan[xK].ToScaledVector4(); - Numerics.Premultiply(ref current); + Vector4 current = sourceRowSpan[xK].ToAssociatedScaledVector4(); sum += current * xWeight * yWeight; } } @@ -242,12 +243,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR span[x] = sum; } - Numerics.UnPremultiply(span); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - destinationRowSpan, - PixelConversionModifiers.Scale); + pixelOperations.FromVector4Destructive(this.configuration, span, destinationRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs index 9e9507b73..1c457e84c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor.cs @@ -21,7 +21,7 @@ public sealed class ProjectiveTransformProcessor : CloningImageProcessor Guard.NotNull(sampler, nameof(sampler)); Guard.MustBeValueType(sampler); - if (TransformUtils.IsDegenerate(matrix)) + if (TransformUtilities.IsDegenerate(matrix)) { throw new DegenerateTransformException("Matrix is degenerate. Check input values."); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index 1c30fd114..90ca8743c 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -75,7 +75,9 @@ internal class ProjectiveTransformProcessor : TransformProcessor return; } - // Convert from screen to world space. + // All matrices are defined in normalized coordinate space so we need to convert to pixel space. + // After normalization we need to invert the matrix for correct sampling. + matrix = TransformUtilities.NormalizeToPixel(matrix); Matrix4x4.Invert(matrix, out matrix); if (sampler is NearestNeighborResampler) @@ -135,7 +137,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor for (int x = 0; x < destinationRowSpan.Length; x++) { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, this.matrix); int px = (int)MathF.Round(point.X); int py = (int)MathF.Round(point.Y); @@ -195,19 +197,18 @@ internal class ProjectiveTransformProcessor : TransformProcessor int maxY = this.bounds.Bottom - 1; int minX = this.bounds.X; int maxX = this.bounds.Right - 1; + PixelOperations pixelOperations = PixelOperations.Instance; for (int y = rows.Min; y < rows.Max; y++) { Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - PixelOperations.Instance.ToVector4( - this.configuration, - destinationRowSpan, - span, - PixelConversionModifiers.Scale); + + // The temporary row is reused, so clear values for out-of-bounds samples that skip the resampling loop below. + span.Clear(); for (int x = 0; x < span.Length; x++) { - Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, matrix); + Vector2 point = TransformUtilities.ProjectiveTransform2D(x, y, matrix); float pY = point.Y; float pX = point.X; @@ -221,6 +222,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor continue; } + // Resampling interpolates color with coverage, so use associated vectors to prevent transparent RGB from bleeding into visible edges. Vector4 sum = Vector4.Zero; for (int yK = top; yK <= bottom; yK++) { @@ -231,8 +233,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceRowSpan[xK].ToScaledVector4(); - Numerics.Premultiply(ref current); + Vector4 current = sourceRowSpan[xK].ToAssociatedScaledVector4(); sum += current * xWeight * yWeight; } } @@ -240,12 +241,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor span[x] = sum; } - Numerics.UnPremultiply(span); - PixelOperations.Instance.FromVector4Destructive( - this.configuration, - span, - destinationRowSpan, - PixelConversionModifiers.Scale); + pixelOperations.FromVector4Destructive(this.configuration, span, destinationRowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs index 0af2b268a..ea0f1c113 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor.cs @@ -28,14 +28,14 @@ public sealed class RotateProcessor : AffineTransformProcessor /// The source image size public RotateProcessor(float degrees, IResampler sampler, Size sourceSize) : this( - TransformUtils.CreateRotationTransformMatrixDegrees(degrees, sourceSize, TransformSpace.Pixel), + TransformUtilities.CreateRotationTransformMatrixDegrees(degrees, sourceSize), sampler, sourceSize) => this.Degrees = degrees; // Helper constructor private RotateProcessor(Matrix3x2 rotationMatrix, IResampler sampler, Size sourceSize) - : base(rotationMatrix, sampler, TransformUtils.GetTransformedSize(rotationMatrix, sourceSize, TransformSpace.Pixel)) + : base(rotationMatrix, sampler, TransformUtilities.GetTransformedCanvasSize(rotationMatrix, sourceSize)) { } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs index 8d3ded09f..e9d0ecf57 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -97,7 +97,7 @@ internal class RotateProcessor : AffineTransformProcessor if (MathF.Abs(degrees) < Constants.Epsilon) { // The destination will be blank here so copy all the pixel data over - source.GetPixelMemoryGroup().CopyTo(destination.GetPixelMemoryGroup()); + source.PixelBuffer.CopyTo(destination.PixelBuffer); return true; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs index 0bbc8e0f6..9c23a9532 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/SkewProcessor.cs @@ -30,7 +30,7 @@ public sealed class SkewProcessor : AffineTransformProcessor /// The source image size public SkewProcessor(float degreesX, float degreesY, IResampler sampler, Size sourceSize) : this( - TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, sourceSize, TransformSpace.Pixel), + TransformUtilities.CreateSkewTransformMatrixDegrees(degreesX, degreesY, sourceSize), sampler, sourceSize) { @@ -40,7 +40,7 @@ public sealed class SkewProcessor : AffineTransformProcessor // Helper constructor: private SkewProcessor(Matrix3x2 skewMatrix, IResampler sampler, Size sourceSize) - : base(skewMatrix, sampler, TransformUtils.GetTransformedSize(skewMatrix, sourceSize, TransformSpace.Pixel)) + : base(skewMatrix, sampler, TransformUtilities.GetTransformedCanvasSize(skewMatrix, sourceSize)) { } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs index 41228e411..9a044756a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/CubicResampler.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms; /// Cubic filters contain a collection of different filters of varying B-Spline and /// Cardinal values. With these two values you can generate any smoothly fitting /// (continuious first derivative) piece-wise cubic filter. -/// +/// /// /// public readonly struct CubicResampler : IResampler @@ -39,23 +39,19 @@ public readonly struct CubicResampler : IResampler /// /// The function implements the Robidoux algorithm. - /// + /// /// public static readonly CubicResampler Robidoux = new(2, .37821575509399867F, .31089212245300067F); /// /// The function implements the Robidoux Sharp algorithm. - /// + /// /// public static readonly CubicResampler RobidouxSharp = new(2, .2620145123990142F, .3689927438004929F); /// /// The function implements the spline algorithm. - /// - /// - /// - /// The function implements the Robidoux Sharp algorithm. - /// + /// /// public static readonly CubicResampler Spline = new(2, 1, 0); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs index de0fe3f80..b866dbb01 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resamplers/WelchResampler.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms; /// /// The function implements the welch algorithm. -/// +/// /// public readonly struct WelchResampler : IResampler { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 26cf60034..e30640ad1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -5,7 +5,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; namespace SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -14,11 +14,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms; /// internal readonly unsafe struct ResizeKernel { + /// + /// The buffer with the convolution factors. + /// Note that when FMA is supported, this is of size 4x that reported in . + /// private readonly float* bufferPtr; /// /// Initializes a new instance of the struct. /// + /// The starting index for the destination row. + /// The pointer to the buffer with the convolution factors. + /// The length of the kernel. [MethodImpl(InliningOptions.ShortMethod)] internal ResizeKernel(int startIndex, float* bufferPtr, int length) { @@ -27,6 +34,15 @@ internal readonly unsafe struct ResizeKernel this.Length = length; } + /// + /// Gets a value indicating whether vectorization is supported. + /// + public static bool IsHardwareAccelerated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Vector256.IsHardwareAccelerated; + } + /// /// Gets the start index for the destination row. /// @@ -53,7 +69,15 @@ internal readonly unsafe struct ResizeKernel public Span Values { [MethodImpl(InliningOptions.ShortMethod)] - get => new(this.bufferPtr, this.Length); + get + { + if (Vector256.IsHardwareAccelerated) + { + return new Span(this.bufferPtr, this.Length * 4); + } + + return new Span(this.bufferPtr, this.Length); + } } /// @@ -68,73 +92,45 @@ internal readonly unsafe struct ResizeKernel [MethodImpl(InliningOptions.ShortMethod)] public Vector4 ConvolveCore(ref Vector4 rowStartRef) { - if (Avx2.IsSupported && Fma.IsSupported) + if (IsHardwareAccelerated) { float* bufferStart = this.bufferPtr; - float* bufferEnd = bufferStart + (this.Length & ~3); + ref Vector4 rowEndRef = ref Unsafe.Add(ref rowStartRef, this.Length & ~3); Vector256 result256_0 = Vector256.Zero; Vector256 result256_1 = Vector256.Zero; - ReadOnlySpan maskBytes = - [ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0, - 1, 0, 0, 0, 1, 0, 0, 0 - ]; - Vector256 mask = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(maskBytes)); - while (bufferStart < bufferEnd) + while (Unsafe.IsAddressLessThan(ref rowStartRef, ref rowEndRef)) { - // It is important to use a single expression here so that the JIT will correctly use vfmadd231ps - // for the FMA operation, and execute it directly on the target register and reading directly from - // memory for the first parameter. This skips initializing a SIMD register, and an extra copy. - // The code below should compile in the following assembly on .NET 5 x64: - // - // vmovsd xmm2, [rax] ; load *(double*)bufferStart into xmm2 as [ab, _] - // vpermps ymm2, ymm1, ymm2 ; permute as a float YMM register to [a, a, a, a, b, b, b, b] - // vfmadd231ps ymm0, ymm2, [r8] ; result256_0 = FMA(pixels, factors) + result256_0 - // - // For tracking the codegen issue with FMA, see: https://github.com/dotnet/runtime/issues/12212. - // Additionally, we're also unrolling two computations per each loop iterations to leverage the - // fact that most CPUs have two ports to schedule multiply operations for FMA instructions. - result256_0 = Fma.MultiplyAdd( - Unsafe.As>(ref rowStartRef), - Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), - result256_0); - - result256_1 = Fma.MultiplyAdd( - Unsafe.As>(ref Unsafe.Add(ref rowStartRef, 2)), - Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)(bufferStart + 2)).AsSingle(), mask), - result256_1); - - bufferStart += 4; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 4); + Vector256 pixels256_0 = Unsafe.As>(ref rowStartRef); + Vector256 pixels256_1 = Unsafe.As>(ref Unsafe.Add(ref rowStartRef, (nuint)2)); + + result256_0 = Vector256.MultiplyAddEstimate(Vector256.Load(bufferStart), pixels256_0, result256_0); + result256_1 = Vector256.MultiplyAddEstimate(Vector256.Load(bufferStart + 8), pixels256_1, result256_1); + + bufferStart += 16; + rowStartRef = ref Unsafe.Add(ref rowStartRef, (nuint)4); } - result256_0 = Avx.Add(result256_0, result256_1); + result256_0 += result256_1; if ((this.Length & 3) >= 2) { - result256_0 = Fma.MultiplyAdd( - Unsafe.As>(ref rowStartRef), - Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), - result256_0); + Vector256 pixels256_0 = Unsafe.As>(ref rowStartRef); + result256_0 = Vector256.MultiplyAddEstimate(Vector256.Load(bufferStart), pixels256_0, result256_0); - bufferStart += 2; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 2); + bufferStart += 8; + rowStartRef = ref Unsafe.Add(ref rowStartRef, (nuint)2); } - Vector128 result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper()); + Vector128 result128 = result256_0.GetLower() + result256_0.GetUpper(); if ((this.Length & 1) != 0) { - result128 = Fma.MultiplyAdd( - Unsafe.As>(ref rowStartRef), - Vector128.Create(*bufferStart), - result128); + Vector128 pixels128 = Unsafe.As>(ref rowStartRef); + result128 = Vector128.MultiplyAddEstimate(Vector128.Load(bufferStart), pixels128, result128); } - return *(Vector4*)&result128; + return result128.AsVector4(); } else { @@ -149,7 +145,7 @@ internal readonly unsafe struct ResizeKernel result += rowStartRef * *bufferStart; bufferStart++; - rowStartRef = ref Unsafe.Add(ref rowStartRef, 1); + rowStartRef = ref Unsafe.Add(ref rowStartRef, (nuint)1); } return result; @@ -160,17 +156,32 @@ internal readonly unsafe struct ResizeKernel /// Copy the contents of altering /// to the value . /// + /// The new value for . [MethodImpl(InliningOptions.ShortMethod)] internal ResizeKernel AlterLeftValue(int left) => new(left, this.bufferPtr, this.Length); - internal void Fill(Span values) + internal void FillOrCopyAndExpand(Span values) { DebugGuard.IsTrue(values.Length == this.Length, nameof(values), "ResizeKernel.Fill: values.Length != this.Length!"); - for (int i = 0; i < this.Length; i++) + if (IsHardwareAccelerated) + { + Vector4* bufferStart = (Vector4*)this.bufferPtr; + ref float valuesStart = ref MemoryMarshal.GetReference(values); + ref float valuesEnd = ref Unsafe.Add(ref valuesStart, values.Length); + + while (Unsafe.IsAddressLessThan(ref valuesStart, ref valuesEnd)) + { + *bufferStart = new Vector4(valuesStart); + + bufferStart++; + valuesStart = ref Unsafe.Add(ref valuesStart, (nuint)1); + } + } + else { - this.Values[i] = (float)values[i]; + values.CopyTo(this.Values); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs index ee1ada43a..d606738ab 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.PeriodicKernelMap.cs @@ -16,6 +16,8 @@ internal partial class ResizeKernelMap private readonly int cornerInterval; + private readonly int sourcePeriod; + public PeriodicKernelMap( MemoryAllocator memoryAllocator, int sourceLength, @@ -24,7 +26,8 @@ internal partial class ResizeKernelMap double scale, int radius, int period, - int cornerInterval) + int cornerInterval, + int sourcePeriod) : base( memoryAllocator, sourceLength, @@ -36,6 +39,7 @@ internal partial class ResizeKernelMap { this.cornerInterval = cornerInterval; this.period = period; + this.sourcePeriod = sourcePeriod; } internal override string Info => base.Info + $"|period:{this.period}|cornerInterval:{this.cornerInterval}"; @@ -54,10 +58,11 @@ internal partial class ResizeKernelMap int bottomStartDest = this.DestinationLength - this.cornerInterval; for (int i = startOfFirstRepeatedMosaic; i < bottomStartDest; i++) { - double center = ((i + .5) * this.ratio) - .5; - int left = (int)TolerantMath.Ceiling(center - this.radius); ResizeKernel kernel = this.kernels[i - this.period]; - this.kernels[i] = kernel.AlterLeftValue(left); + + // Shift the kernel start index by the source-side period so the same weights align to the + // next repeated sampling window in the source image. + this.kernels[i] = kernel.AlterLeftValue(kernel.StartIndex + this.sourcePeriod); } // Build bottom corner data: diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index c1907bb52..b397b9999 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Diagnostics; +using System.Numerics.Tensors; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; @@ -33,7 +34,7 @@ internal partial class ResizeKernelMap : IDisposable private bool isDisposed; // To avoid both GC allocations, and MemoryAllocator ceremony: - private readonly double[] tempValues; + private readonly float[] tempValues; private ResizeKernelMap( MemoryAllocator memoryAllocator, @@ -50,10 +51,12 @@ internal partial class ResizeKernelMap : IDisposable this.sourceLength = sourceLength; this.DestinationLength = destinationLength; this.MaxDiameter = (radius * 2) + 1; - this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, preferContiguosImageBuffers: true, AllocationOptions.Clean); + + int diameter = ResizeKernel.IsHardwareAccelerated ? this.MaxDiameter * 4 : this.MaxDiameter; + this.data = memoryAllocator.Allocate2D(diameter, bufferHeight, preferContiguosImageBuffers: true); this.pinHandle = this.data.DangerousGetSingleMemory().Pin(); this.kernels = new ResizeKernel[destinationLength]; - this.tempValues = new double[this.MaxDiameter]; + this.tempValues = new float[this.MaxDiameter]; } /// @@ -102,6 +105,12 @@ internal partial class ResizeKernelMap : IDisposable [MethodImpl(InliningOptions.ShortMethod)] internal ref ResizeKernel GetKernel(nuint destIdx) => ref this.kernels[(int)destIdx]; + /// + /// Returns a read-only span of over the underlying kernel data. + /// + [MethodImpl(InliningOptions.ShortMethod)] + internal ReadOnlySpan GetKernelSpan() => this.kernels; + /// /// Computes the weights to apply at each pixel when resizing. /// @@ -129,9 +138,13 @@ internal partial class ResizeKernelMap : IDisposable int radius = (int)TolerantMath.Ceiling(scale * sampler.Radius); // 'ratio' is a rational number. - // Multiplying it by destSize/GCD(sourceSize, destSize) will result in a whole number "again". - // This value is determining the length of the periods in repeating kernel map rows. - int period = destinationSize / Numerics.GreatestCommonDivisor(sourceSize, destinationSize); + // Multiplying it by destSize/GCD(sourceSize, destinationSize) yields an integer, so every `period` rows + // the destination-space sampling centers repeat their fractional alignment. `period` is the repeat length + // in destination rows, while `sourcePeriod` is the corresponding integer offset in source pixels that + // must be added to the kernel's left index when we reuse the same weights from a previous period. + int gcd = Numerics.GreatestCommonDivisor(sourceSize, destinationSize); + int period = destinationSize / gcd; + int sourcePeriod = sourceSize / gcd; // the center position at i == 0: double center0 = (ratio - 1) * 0.5; @@ -155,23 +168,24 @@ internal partial class ResizeKernelMap : IDisposable bool hasAtLeast2Periods = 2 * (cornerInterval + period) < destinationSize; ResizeKernelMap result = hasAtLeast2Periods - ? new PeriodicKernelMap( - memoryAllocator, - sourceSize, - destinationSize, - ratio, - scale, - radius, - period, - cornerInterval) - : new ResizeKernelMap( - memoryAllocator, - sourceSize, - destinationSize, - destinationSize, - ratio, - scale, - radius); + ? new PeriodicKernelMap( + memoryAllocator, + sourceSize, + destinationSize, + ratio, + scale, + radius, + period, + cornerInterval, + sourcePeriod) + : new ResizeKernelMap( + memoryAllocator, + sourceSize, + destinationSize, + destinationSize, + ratio, + scale, + radius); result.Initialize(in sampler); @@ -199,6 +213,7 @@ internal partial class ResizeKernelMap : IDisposable where TResampler : struct, IResampler { double center = ((destRowIndex + .5) * this.ratio) - .5; + double scale = this.scale; // Keep inside bounds. int left = (int)TolerantMath.Ceiling(center - this.radius); @@ -214,30 +229,25 @@ internal partial class ResizeKernelMap : IDisposable } ResizeKernel kernel = this.CreateKernel(dataRowIndex, left, right); - - Span kernelValues = this.tempValues.AsSpan(0, kernel.Length); - double sum = 0; + Span kernelValues = this.tempValues.AsSpan(0, kernel.Length); + ref float kernelStart = ref MemoryMarshal.GetReference(kernelValues); + float sum = 0; for (int j = left; j <= right; j++) { - double value = sampler.GetValue((float)((j - center) / this.scale)); + float value = sampler.GetValue((float)((j - center) / scale)); sum += value; - - kernelValues[j - left] = value; + kernelStart = value; + kernelStart = ref Unsafe.Add(ref kernelStart, 1); } // Normalize, best to do it here rather than in the pixel loop later on. if (sum > 0) { - for (int j = 0; j < kernel.Length; j++) - { - // weights[w] = weights[w] / sum: - ref double kRef = ref kernelValues[j]; - kRef /= sum; - } + TensorPrimitives.Divide(kernelValues, sum, kernelValues); } - kernel.Fill(kernelValues); + kernel.FillOrCopyAndExpand(kernelValues); return kernel; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 49439545b..870c38241 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -91,7 +91,7 @@ internal class ResizeProcessor : TransformProcessor, IResampling ImageFrame destinationFrame = destination.Frames[i]; // The cloned will be blank here copy all the pixel data over - sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup()); + sourceFrame.PixelBuffer.CopyTo(destinationFrame.PixelBuffer); } return; @@ -189,16 +189,6 @@ internal class ResizeProcessor : TransformProcessor, IResampling in operation); } - private static PixelConversionModifiers GetModifiers(bool compand, bool premultiplyAlpha) - { - if (premultiplyAlpha) - { - return PixelConversionModifiers.Premultiply.ApplyCompanding(compand); - } - - return PixelConversionModifiers.None.ApplyCompanding(compand); - } - private static void ApplyResizeFrameTransform( Configuration configuration, ImageFrame source, @@ -211,12 +201,9 @@ internal class ResizeProcessor : TransformProcessor, IResampling bool compand, bool premultiplyAlpha) { - PixelAlphaRepresentation? alphaRepresentation = PixelOperations.Instance.GetPixelTypeInfo().AlphaRepresentation; - - // Premultiply only if alpha representation is unknown or Unassociated: - bool needsPremultiplication = alphaRepresentation == null || alphaRepresentation.Value == PixelAlphaRepresentation.Unassociated; - premultiplyAlpha &= needsPremultiplication; - PixelConversionModifiers conversionModifiers = GetModifiers(compand, premultiplyAlpha); + // Resampling uses normalized vectors and interpolates them in the alpha representation selected by the processor options. + PixelConversionModifiers alphaModifier = premultiplyAlpha ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.UnPremultiply; + PixelConversionModifiers conversionModifiers = (PixelConversionModifiers.Scale | alphaModifier).ApplyCompanding(compand); Buffer2DRegion sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index cce27a401..599882fdb 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -59,7 +59,7 @@ internal sealed class ResizeWorker : IDisposable { this.configuration = configuration; this.source = source; - this.sourceRectangle = source.Rectangle; + this.sourceRectangle = source.Bounds; this.conversionModifiers = conversionModifiers; this.horizontalKernelMap = horizontalKernelMap; this.verticalKernelMap = verticalKernelMap; @@ -110,38 +110,65 @@ internal sealed class ResizeWorker : IDisposable { Span tempColSpan = this.tempColumnBuffer.GetSpan(); - // When creating transposedFirstPassBuffer, we made sure it's contiguous: + // When creating transposedFirstPassBuffer, we made sure it's contiguous. Span transposedFirstPassBufferSpan = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); int left = this.targetWorkingRect.Left; - int right = this.targetWorkingRect.Right; int width = this.targetWorkingRect.Width; + nuint widthCount = (uint)width; + + // Normalize destination-space Y to kernel indices using uint arithmetic. + // This relies on the contract that processing addresses are normalized (cropping/padding handled by targetOrigin). + int targetOriginY = this.targetOrigin.Y; + + // Hoist invariant calculations outside the loop. + int currentWindowMax = this.currentWindow.Max; + int currentWindowMin = this.currentWindow.Min; + nuint workerHeight = (uint)this.workerHeight; + nuint workerHeight2 = workerHeight * 2; + + // Ref-walk the kernel table to avoid bounds checks in the tight loop. + ReadOnlySpan vKernels = this.verticalKernelMap.GetKernelSpan(); + ref ResizeKernel vKernelBase = ref MemoryMarshal.GetReference(vKernels); + + ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); + for (int y = rowInterval.Min; y < rowInterval.Max; y++) { - // Ensure offsets are normalized for cropping and padding. - ResizeKernel kernel = this.verticalKernelMap.GetKernel((uint)(y - this.targetOrigin.Y)); + // Normalize destination-space Y to an unsigned kernel index. + uint vIdx = (uint)(y - targetOriginY); + ref ResizeKernel kernel = ref Unsafe.Add(ref vKernelBase, (nint)vIdx); - while (kernel.StartIndex + kernel.Length > this.currentWindow.Max) + // Slide the working window when the kernel would read beyond the current cached region. + int kernelEnd = kernel.StartIndex + kernel.Length; + while (kernelEnd > currentWindowMax) { this.Slide(); + currentWindowMax = this.currentWindow.Max; + currentWindowMin = this.currentWindow.Min; } - ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempColSpan); + int top = kernel.StartIndex - currentWindowMin; + ref Vector4 colRef0 = ref transposedFirstPassBufferSpan[top]; - int top = kernel.StartIndex - this.currentWindow.Min; + // Unroll by 2 and advance column refs via arithmetic to reduce inner-loop overhead. + nuint i = 0; + for (; i + 1 < widthCount; i += 2) + { + ref Vector4 colRef1 = ref Unsafe.Add(ref colRef0, workerHeight); - ref Vector4 fpBase = ref transposedFirstPassBufferSpan[top]; + Unsafe.Add(ref tempRowBase, i) = kernel.ConvolveCore(ref colRef0); + Unsafe.Add(ref tempRowBase, i + 1) = kernel.ConvolveCore(ref colRef1); - for (nuint x = 0; x < (uint)(right - left); x++) - { - ref Vector4 firstPassColumnBase = ref Unsafe.Add(ref fpBase, x * (uint)this.workerHeight); + colRef0 = ref Unsafe.Add(ref colRef0, workerHeight2); + } - // Destination color components - Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); + if (i < widthCount) + { + Unsafe.Add(ref tempRowBase, i) = kernel.ConvolveCore(ref colRef0); } Span targetRowSpan = destination.DangerousGetRowSpan(y).Slice(left, width); - PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); } } @@ -171,28 +198,48 @@ internal sealed class ResizeWorker : IDisposable nuint left = (uint)this.targetWorkingRect.Left; nuint right = (uint)this.targetWorkingRect.Right; + nuint widthCount = right - left; + + // Normalize destination-space X to kernel indices using uint arithmetic. + // This relies on the contract that processing addresses are normalized (cropping/padding handled by targetOrigin). nuint targetOriginX = (uint)this.targetOrigin.X; + + nuint workerHeight = (uint)this.workerHeight; + int currentWindowMin = this.currentWindow.Min; + + // Ref-walk the kernel table to avoid bounds checks in the tight loop. + ReadOnlySpan hKernels = this.horizontalKernelMap.GetKernelSpan(); + ref ResizeKernel hKernelBase = ref MemoryMarshal.GetReference(hKernels); + for (int y = calculationInterval.Min; y < calculationInterval.Max; y++) { Span sourceRow = this.source.DangerousGetRowSpan(y); + PixelOperations.Instance.ToVector4(this.configuration, sourceRow, tempRowSpan, this.conversionModifiers); + + ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - currentWindowMin]; - PixelOperations.Instance.ToVector4( - this.configuration, - sourceRow, - tempRowSpan, - this.conversionModifiers); + // Unroll by 2 to reduce loop and kernel lookup overhead. + nuint x = left; + nuint z = 0; - // optimization for: - // Span firstPassSpan = transposedFirstPassBufferSpan.Slice(y - this.currentWindow.Min); - ref Vector4 firstPassBaseRef = ref transposedFirstPassBufferSpan[y - this.currentWindow.Min]; + for (; z + 1 < widthCount; x += 2, z += 2) + { + nuint hIdx0 = (uint)(x - targetOriginX); + nuint hIdx1 = (uint)((x + 1) - targetOriginX); + + ref ResizeKernel kernel0 = ref Unsafe.Add(ref hKernelBase, (nint)hIdx0); + ref ResizeKernel kernel1 = ref Unsafe.Add(ref hKernelBase, (nint)hIdx1); + + Unsafe.Add(ref firstPassBaseRef, z * workerHeight) = kernel0.Convolve(tempRowSpan); + Unsafe.Add(ref firstPassBaseRef, (z + 1) * workerHeight) = kernel1.Convolve(tempRowSpan); + } - for (nuint x = left, z = 0; x < right; x++, z++) + if (z < widthCount) { - ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - targetOriginX); + nuint hIdx = (uint)(x - targetOriginX); + ref ResizeKernel kernel = ref Unsafe.Add(ref hKernelBase, (nint)hIdx); - // optimization for: - // firstPassSpan[x * this.workerHeight] = kernel.Convolve(tempRowSpan); - Unsafe.Add(ref firstPassBaseRef, z * (uint)this.workerHeight) = kernel.Convolve(tempRowSpan); + Unsafe.Add(ref firstPassBaseRef, z * workerHeight) = kernel.Convolve(tempRowSpan); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs index 3bec82cce..9acf18730 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs @@ -24,7 +24,7 @@ internal class SwizzleProcessor : TransformProcessor // Calculate the transform matrix from the swizzle operation to allow us // to update any metadata that represents pixel coordinates in the source image. this.transformMatrix = new ProjectiveTransformBuilder() - .AppendMatrix(TransformUtils.GetSwizzlerMatrix(swizzler, sourceRectangle)) + .AppendMatrix(TransformUtilities.GetSwizzlerMatrix(swizzler, sourceRectangle)) .BuildMatrix(sourceRectangle); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs similarity index 60% rename from src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs rename to src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs index d25d4f474..b1d7b61ee 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtilities.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms; /// /// Contains utility methods for working with transforms. /// -internal static class TransformUtils +internal static class TransformUtilities { /// /// Returns a value that indicates whether the specified matrix is degenerate @@ -19,7 +19,7 @@ internal static class TransformUtils /// /// The transform matrix. public static bool IsDegenerate(Matrix3x2 matrix) - => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); + => IsNaN(matrix) || matrix.GetDeterminant() == 0F; /// /// Returns a value that indicates whether the specified matrix is degenerate @@ -28,11 +28,7 @@ internal static class TransformUtils /// /// The transform matrix. public static bool IsDegenerate(Matrix4x4 matrix) - => IsNaN(matrix) || IsZero(matrix.GetDeterminant()); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsZero(float a) - => a > -Constants.EpsilonSquared && a < Constants.EpsilonSquared; + => IsNaN(matrix) || matrix.GetDeterminant() == 0F; /// /// Returns a value that indicates whether the specified matrix contains any values @@ -69,90 +65,86 @@ internal static class TransformUtils [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) { - // The w component (v4.W) resulting from the transformation can be less than 0 in certain cases, - // such as when the point is transformed behind the camera in a perspective projection. - // However, in many 2D contexts, negative w values are not meaningful and could cause issues - // like flipped or distorted projections. To avoid this, we take the max of w and epsilon to ensure - // we don't divide by a very small or negative number, effectively treating any negative w as epsilon. + // Transforms the 2D point (x, y) as the homogeneous coordinate (x, y, 0, 1) and + // performs the perspective divide (X/W, Y/W) to project back into Cartesian 2D space. + // + // For affine matrices (M14=0, M24=0, M34=0, M44=1) W is always 1 and the divide + // is a no-op, producing the same result as Vector2.Transform(v, Matrix4x4).AsVector2() + // (the approach used by .NET 10+). + // + // For projective matrices (taper, quad distortion) W varies per point and the divide + // is essential for correct perspective mapping. W <= 0 means the point has crossed the + // vanishing line of the projection; clamping to epsilon avoids division by zero or + // negative values that would flip/mirror the output. const float epsilon = 0.0000001F; Vector4 v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, epsilon); } /// - /// Creates a centered rotation transform matrix using the given rotation in degrees and the source size. + /// Creates a centered rotation transform matrix using the given rotation in degrees and the original source size. /// /// The amount of rotation, in degrees. /// The source image size. - /// The to use when creating the centered matrix. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size, TransformSpace transformSpace) - => CreateRotationTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degrees), size, transformSpace); + public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size) + => CreateRotationTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degrees), size); /// - /// Creates a centered rotation transform matrix using the given rotation in radians and the source size. + /// Creates a centered rotation transform matrix using the given rotation in radians and the original source size. /// /// The amount of rotation, in radians. /// The source image size. - /// The to use when creating the centered matrix. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size, TransformSpace transformSpace) - => CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateRotation(radians, PointF.Empty), size, transformSpace); + public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size) + => CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateRotation(radians, PointF.Empty), size); /// - /// Creates a centered skew transform matrix from the give angles in degrees and the source size. + /// Creates a centered skew transform matrix from the give angles in degrees and the original source size. /// /// The X angle, in degrees. /// The Y angle, in degrees. /// The source image size. - /// The to use when creating the centered matrix. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size, TransformSpace transformSpace) - => CreateSkewTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), size, transformSpace); + public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size) + => CreateSkewTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), size); /// - /// Creates a centered skew transform matrix from the give angles in radians and the source size. + /// Creates a centered skew transform matrix from the give angles in radians and the original source size. /// /// The X angle, in radians. /// The Y angle, in radians. /// The source image size. - /// The to use when creating the centered matrix. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size, TransformSpace transformSpace) - => CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty), size, transformSpace); + public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size) + => CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty), size); /// /// Gets the centered transform matrix based upon the source rectangle. /// /// The transformation matrix. /// The source image size. - /// - /// The to use when creating the centered matrix. - /// /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Matrix3x2 CreateCenteredTransformMatrix(Matrix3x2 matrix, Size size, TransformSpace transformSpace) + public static Matrix3x2 CreateCenteredTransformMatrix(Matrix3x2 matrix, Size size) { - Size transformSize = GetUnboundedTransformedSize(matrix, size, transformSpace); + // 1) Unbounded size. + SizeF ts = GetRawTransformedSize(matrix, size); - // We invert the matrix to handle the transformation from screen to world space. - // This ensures scaling matrices are correct. - Matrix3x2.Invert(matrix, out Matrix3x2 inverted); + // 2) Invert the content transform for screen->world. + Matrix3x2.Invert(matrix, out Matrix3x2 inv); - // The source size is provided using the coordinate space of the source image. - // however the transform should always be applied in the pixel space. - // To account for this we offset by the size - 1 to translate to the pixel space. - float offset = transformSpace == TransformSpace.Pixel ? 1F : 0F; + // 3) Translate target (canvas) so its center is at the origin, + // translate source so its center is at the origin, then undo the content transform. + Matrix3x2 toTarget = Matrix3x2.CreateTranslation(new Vector2(-ts.Width, -ts.Height) * 0.5f); + Matrix3x2 toSource = Matrix3x2.CreateTranslation(new Vector2(size.Width, size.Height) * 0.5f); - Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-(transformSize.Width - offset), -(transformSize.Height - offset)) * .5F); - Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(size.Width - offset, size.Height - offset) * .5F); - - // Translate back to world space. - Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); + // 4) World->screen. + Matrix3x2.Invert(toTarget * inv * toSource, out Matrix3x2 centered); return centered; } @@ -287,7 +279,6 @@ internal static class TransformUtils /// The top-right point of the distorted quad. /// The bottom-right point of the distorted quad. /// The bottom-left point of the distorted quad. - /// The to use when creating the matrix. /// The computed projection matrix for the quad distortion. /// /// This method is based on the algorithm described in the following article: @@ -298,8 +289,7 @@ internal static class TransformUtils PointF topLeft, PointF topRight, PointF bottomRight, - PointF bottomLeft, - TransformSpace transformSpace) + PointF bottomLeft) { PointF p1 = new(rectangle.X, rectangle.Y); PointF p2 = new(rectangle.X + rectangle.Width, rectangle.Y); @@ -345,46 +335,94 @@ internal static class TransformUtils (float)b[2], (float)b[5], 0, 1); #pragma warning restore SA1117 - // Check if the matrix involves only affine transformations by inspecting the relevant components. - // We want to use pixel space for calculations only if the transformation is purely 2D and does not include - // any perspective effects, non-standard scaling, or unusual translations that could distort the image. - if (transformSpace == TransformSpace.Pixel && IsAffineRotationOrSkew(projectionMatrix)) - { - if (projectionMatrix.M41 != 0) - { - projectionMatrix.M41--; - } - - if (projectionMatrix.M42 != 0) - { - projectionMatrix.M42--; - } - } - return projectionMatrix; } /// - /// Returns the size relative to the source for the given transformation matrix. + /// Calculates the size of a destination canvas large enough to contain + /// the fully transformed source content, including any translation offsets. /// /// The transformation matrix. - /// The source size. - /// The to use when calculating the size. - /// The . - public static Size GetTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace) - => GetTransformedSize(matrix, size, transformSpace, true); + /// The original source size. + /// + /// A representing the dimensions of the destination + /// canvas required to fully contain the transformed source, including + /// any positive or negative translation offsets. + /// + /// + /// + /// This method ensures that the transformed content remains fully visible + /// on the destination canvas by expanding its size to include translations + /// in all directions. + /// + /// + /// It behaves identically to calling + /// with + /// preserveCanvas set to . + /// + /// + /// The resulting canvas size represents the total area required to display + /// the transformed image without clipping, not merely the geometric bounds + /// of the transformed source. + /// + /// + public static Size GetTransformedCanvasSize(Matrix3x2 matrix, Size size) + => Size.Ceiling(GetTransformedSize(matrix, size, true)); /// - /// Returns the size relative to the source for the given transformation matrix. + /// Calculates the size of a destination canvas large enough to contain + /// the fully transformed source content, including any translation offsets. /// /// The transformation matrix. - /// The source size. - /// The used when generating the matrix. + /// The original source size. /// - /// The . + /// A representing the dimensions of the destination + /// canvas required to fully contain the transformed source, including + /// any positive or negative translation offsets. /// + /// + /// + /// This method ensures that the transformed content remains fully visible + /// on the destination canvas by expanding its size to include translations + /// in all directions. + /// + /// + /// It behaves identically to calling + /// with + /// preserveCanvas set to . + /// + /// + /// The resulting canvas size represents the total area required to display + /// the transformed image without clipping, not merely the geometric bounds + /// of the transformed source. + /// + /// + public static Size GetTransformedCanvasSize(Matrix4x4 matrix, Size size) + => Size.Ceiling(GetTransformedSize(matrix, size, true)); + + /// + /// Returns the size relative to the source for the given transformation matrix. + /// + /// The transformation matrix. + /// The original source size. + /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size GetTransformedSize(Matrix4x4 matrix, Size size, TransformSpace transformSpace) + public static SizeF GetRawTransformedSize(Matrix4x4 matrix, Size size) + => GetTransformedSize(matrix, size, false); + + /// + /// Returns the size of the transformed source. When is true, + /// the size is expanded to include translation so the full moved content remains visible. + /// + /// The transformation matrix. + /// The original source size. + /// + /// If , expand the size to account for translation (left/up as well as right/down). + /// If , return only the transformed span without translation expansion. + /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static SizeF GetTransformedSize(Matrix4x4 matrix, Size size, bool preserveCanvas) { Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); @@ -393,27 +431,9 @@ internal static class TransformUtils return size; } - // Check if the matrix involves only affine transformations by inspecting the relevant components. - // We want to use pixel space for calculations only if the transformation is purely 2D and does not include - // any perspective effects, non-standard scaling, or unusual translations that could distort the image. - bool usePixelSpace = transformSpace == TransformSpace.Pixel && IsAffineRotationOrSkew(matrix); - - // Define an offset size to translate between pixel space and coordinate space. - // When using pixel space, apply a scaling sensitive offset to translate to discrete pixel coordinates. - // When not using pixel space, use SizeF.Empty as the offset. - - // Compute scaling factors from the matrix - float scaleX = 1F / new Vector2(matrix.M11, matrix.M21).Length(); // sqrt(M11^2 + M21^2) - float scaleY = 1F / new Vector2(matrix.M12, matrix.M22).Length(); // sqrt(M12^2 + M22^2) - - // Apply the offset relative to the scale - SizeF offsetSize = usePixelSpace ? new SizeF(scaleX, scaleY) : SizeF.Empty; - - // Subtract the offset size to translate to the appropriate space (pixel or coordinate). - if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size - offsetSize), matrix, out Rectangle bounds)) + if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size), matrix, out RectangleF bounds)) { - // Add the offset size back to translate the transformed bounds to the correct space. - return Size.Ceiling(ConstrainSize(bounds) + offsetSize); + return preserveCanvas ? GetPreserveCanvasSize(bounds) : bounds.Size; } return size; @@ -438,30 +458,31 @@ internal static class TransformUtils swizzler.Transform(new Point(sourceRectangle.Left, sourceRectangle.Top)), swizzler.Transform(new Point(sourceRectangle.Right, sourceRectangle.Top)), swizzler.Transform(new Point(sourceRectangle.Right, sourceRectangle.Bottom)), - swizzler.Transform(new Point(sourceRectangle.Left, sourceRectangle.Bottom)), - TransformSpace.Pixel); + swizzler.Transform(new Point(sourceRectangle.Left, sourceRectangle.Bottom))); /// /// Returns the size relative to the source for the given transformation matrix. /// /// The transformation matrix. - /// The source size. - /// The to use when calculating the size. + /// The original source size. /// The . - private static Size GetUnboundedTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace) - => GetTransformedSize(matrix, size, transformSpace, false); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF GetRawTransformedSize(Matrix3x2 matrix, Size size) + => GetTransformedSize(matrix, size, false); /// - /// Returns the size relative to the source for the given transformation matrix. + /// Returns the size of the transformed source. When is true, + /// the size is expanded to include translation so the full moved content remains visible. /// /// The transformation matrix. - /// The source size. - /// The to use when calculating the size. - /// Whether to constrain the size to ensure that the dimensions are positive. - /// - /// The . - /// - private static Size GetTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace, bool constrain) + /// The original source size. + /// + /// If , expand the size to account for translation (left/up as well as right/down). + /// If , return only the transformed span without translation expansion. + /// + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static SizeF GetTransformedSize(Matrix3x2 matrix, Size size, bool preserveCanvas) { Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); @@ -470,21 +491,9 @@ internal static class TransformUtils return size; } - // Define an offset size to translate between coordinate space and pixel space. - // Compute scaling factors from the matrix - SizeF offsetSize = SizeF.Empty; - if (transformSpace == TransformSpace.Pixel) + if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size), matrix, out RectangleF bounds)) { - float scaleX = 1F / new Vector2(matrix.M11, matrix.M21).Length(); // sqrt(M11^2 + M21^2) - float scaleY = 1F / new Vector2(matrix.M12, matrix.M22).Length(); // sqrt(M12^2 + M22^2) - offsetSize = new SizeF(scaleX, scaleY); - } - - // Subtract the offset size to translate to the pixel space. - if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size - offsetSize), matrix, out Rectangle bounds)) - { - // Add the offset size back to translate the transformed bounds to the coordinate space. - return Size.Ceiling((constrain ? ConstrainSize(bounds) : bounds.Size) + offsetSize); + return preserveCanvas ? GetPreserveCanvasSize(bounds) : bounds.Size; } return size; @@ -499,7 +508,8 @@ internal static class TransformUtils /// /// if the transformation was successful; otherwise, . /// - private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix3x2 matrix, out Rectangle bounds) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix3x2 matrix, out RectangleF bounds) { if (matrix.IsIdentity || rectangle.Equals(default)) { @@ -526,7 +536,7 @@ internal static class TransformUtils /// if the transformation was successful; otherwise, . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix4x4 matrix, out Rectangle bounds) + internal static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix4x4 matrix, out RectangleF bounds) { if (matrix.IsIdentity || rectangle.Equals(default)) { @@ -543,15 +553,61 @@ internal static class TransformUtils return true; } + /// + /// Calculates the size of a destination canvas large enough to contain the full + /// transformed content of a source rectangle while preserving any translation offsets. + /// + /// + /// The representing the transformed bounds of the source content + /// in destination (output) space. + /// + /// + /// A that describes the canvas dimensions required to fully + /// contain the transformed content while accounting for any positive or negative translation. + /// + /// + /// + /// This method expands the output canvas to ensure that translated content remains visible. + /// + /// + /// If the transformation produces a positive translation, the method extends the canvas + /// on the positive side (right or bottom). + /// If the transformation produces a negative translation (the content moves left or up), + /// the method extends the canvas on the negative side to include that offset. + /// + /// + /// The result is equivalent to taking the union of: + /// + /// + /// The original, untransformed rectangle at the origin [0..Width] × [0..Height]. + /// + /// + /// The translated rectangle defined by . + /// + /// + /// This ensures the entire translated image fits within the resulting canvas, + /// without trimming any portion caused by translation. + /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Size ConstrainSize(Rectangle rectangle) + private static SizeF GetPreserveCanvasSize(RectangleF rectangle) { - // We want to resize the canvas here taking into account any translations. - int height = rectangle.Top < 0 ? rectangle.Bottom : Math.Max(rectangle.Height, rectangle.Bottom); - int width = rectangle.Left < 0 ? rectangle.Right : Math.Max(rectangle.Width, rectangle.Right); - - // If location in either direction is translated to a negative value equal to or exceeding the - // dimensions in either direction we need to reassign the dimension. + // Compute the required height. + // If the top is negative, expand upward by that amount (rectangle.Bottom already includes height). + // Otherwise, take the larger of the transformed height or the bottom offset. + float height = rectangle.Top < 0 + ? rectangle.Bottom + : MathF.Max(rectangle.Height, rectangle.Bottom); + + // Compute the required width. + // If the left is negative, expand leftward by that amount (rectangle.Right already includes width). + // Otherwise, take the larger of the transformed width or the right offset. + float width = rectangle.Left < 0 + ? rectangle.Right + : MathF.Max(rectangle.Width, rectangle.Right); + + // Guard: if translation exceeds or cancels dimensions, + // ensure non-zero positive size using the base rectangle dimensions. if (height <= 0) { height = rectangle.Height; @@ -562,63 +618,63 @@ internal static class TransformUtils width = rectangle.Width; } - return new Size(width, height); + // Return the final size that preserves the full visible region of the transformed content. + return new SizeF(width, height); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Rectangle GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) + private static RectangleF GetBoundingRectangle(Vector2 tl, Vector2 tr, Vector2 bl, Vector2 br) { - // Find the minimum and maximum "corners" based on the given vectors float left = MathF.Min(tl.X, MathF.Min(tr.X, MathF.Min(bl.X, br.X))); float top = MathF.Min(tl.Y, MathF.Min(tr.Y, MathF.Min(bl.Y, br.Y))); float right = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X))); float bottom = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y))); - // Clamp the values to the nearest whole pixel. - return Rectangle.FromLTRB( - (int)Math.Floor(left), - (int)Math.Floor(top), - (int)Math.Ceiling(right), - (int)Math.Ceiling(bottom)); + return RectangleF.FromLTRB(left, top, right, bottom); } - private static bool IsAffineRotationOrSkew(Matrix4x4 matrix) + /// + /// Normalizes an affine 2D matrix so that it operates in pixel space. + /// Applies the row-vector conjugation T(+0.5,+0.5) * M * T(-0.5,-0.5) + /// to align the transform with pixel centers. + /// + /// The affine matrix. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Matrix3x2 NormalizeToPixel(Matrix3x2 matrix) { - const float epsilon = 1e-6f; - - // Check if the matrix is affine (last column should be [0, 0, 0, 1]) - if (Math.Abs(matrix.M14) > epsilon || - Math.Abs(matrix.M24) > epsilon || - Math.Abs(matrix.M34) > epsilon || - Math.Abs(matrix.M44 - 1f) > epsilon) - { - return false; - } - - // Translation component (M41, m42) are allowed, others are not. - if (Math.Abs(matrix.M43) > epsilon) - { - return false; - } + const float dx = 0.5f, dy = 0.5f; - // Extract the linear (rotation and skew) part of the matrix - // Upper-left 3x3 matrix - float m11 = matrix.M11, m12 = matrix.M12, m13 = matrix.M13; - float m21 = matrix.M21, m22 = matrix.M22, m23 = matrix.M23; - float m31 = matrix.M31, m32 = matrix.M32, m33 = matrix.M33; + matrix.M31 += (-dx) + ((dx * matrix.M11) + (dy * matrix.M21)); + matrix.M32 += (-dy) + ((dx * matrix.M12) + (dy * matrix.M22)); + return matrix; + } - // Compute the determinant of the linear part - float determinant = (m11 * ((m22 * m33) - (m23 * m32))) - - (m12 * ((m21 * m33) - (m23 * m31))) + - (m13 * ((m21 * m32) - (m22 * m31))); + /// + /// Normalizes a projective 4×4 matrix so that it operates in pixel space. + /// Applies the row-vector conjugation T(+0.5,+0.5,0) * M * T(-0.5,-0.5,0) + /// to align the transform with pixel centers. + /// + /// The projective matrix. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Matrix4x4 NormalizeToPixel(Matrix4x4 matrix) + { + const float dx = 0.5f, dy = 0.5f; - // Check if the determinant is approximately ±1 (no scaling) - if (Math.Abs(Math.Abs(determinant) - 1f) > epsilon) + // Fast path: affine (no perspective) + if (matrix.M14 == 0f && matrix.M24 == 0f && matrix.M34 == 0f && matrix.M44 == 1f) { - return false; + // t' = t + (-d + d·L) + matrix.M41 += (-dx) + ((dx * matrix.M11) + (dy * matrix.M21)); + matrix.M42 += (-dy) + ((dx * matrix.M12) + (dy * matrix.M22)); + return matrix; } - // All checks passed; the matrix represents rotation and/or skew (with possible translation) - return true; + Matrix4x4 tPos = Matrix4x4.Identity; + tPos.M41 = dx; + tPos.M42 = dy; + Matrix4x4 tNeg = Matrix4x4.Identity; + tNeg.M41 = -dx; + tNeg.M42 = -dy; + return tPos * matrix * tNeg; } } diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index 82b897ea5..74da44040 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -17,24 +17,9 @@ public class ProjectiveTransformBuilder /// Initializes a new instance of the class. /// public ProjectiveTransformBuilder() - : this(TransformSpace.Pixel) { } - /// - /// Initializes a new instance of the class. - /// - /// - /// The to use when applying the projective transform. - /// - public ProjectiveTransformBuilder(TransformSpace transformSpace) - => this.TransformSpace = transformSpace; - - /// - /// Gets the to use when applying the projective transform. - /// - public TransformSpace TransformSpace { get; } - /// /// Prepends a matrix that performs a tapering projective transform. /// @@ -43,7 +28,7 @@ public class ProjectiveTransformBuilder /// The amount to taper. /// The . public ProjectiveTransformBuilder PrependTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Prepend(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Prepend(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction)); /// /// Appends a matrix that performs a tapering projective transform. @@ -53,7 +38,7 @@ public class ProjectiveTransformBuilder /// The amount to taper. /// The . public ProjectiveTransformBuilder AppendTaper(TaperSide side, TaperCorner corner, float fraction) - => this.Append(size => TransformUtils.CreateTaperMatrix(size, side, corner, fraction)); + => this.Append(size => TransformUtilities.CreateTaperMatrix(size, side, corner, fraction)); /// /// Prepends a centered rotation matrix using the given rotation in degrees. @@ -69,7 +54,7 @@ public class ProjectiveTransformBuilder /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder PrependRotationRadians(float radians) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace))); + => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateRotationTransformMatrixRadians(radians, size))); /// /// Prepends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -87,7 +72,8 @@ public class ProjectiveTransformBuilder /// The rotation origin point. /// The . internal ProjectiveTransformBuilder PrependRotationRadians(float radians, Vector2 origin) - => this.PrependMatrix(Matrix4x4.CreateRotationZ(radians, new Vector3(origin, 0))); + => this.PrependMatrix( + Matrix4x4.CreateRotationZ(radians, new Vector3(origin, 0))); /// /// Appends a centered rotation matrix using the given rotation in degrees. @@ -103,7 +89,7 @@ public class ProjectiveTransformBuilder /// The amount of rotation, in radians. /// The . public ProjectiveTransformBuilder AppendRotationRadians(float radians) - => this.Append(size => new Matrix4x4(TransformUtils.CreateRotationTransformMatrixRadians(radians, size, this.TransformSpace))); + => this.Append(size => new Matrix4x4(TransformUtilities.CreateRotationTransformMatrixRadians(radians, size))); /// /// Appends a centered rotation matrix using the given rotation in degrees at the given origin. @@ -187,7 +173,7 @@ public class ProjectiveTransformBuilder /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder PrependSkewRadians(float radiansX, float radiansY) - => this.Prepend(size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace))); + => this.Prepend(size => new Matrix4x4(TransformUtilities.CreateSkewTransformMatrixRadians(radiansX, radiansY, size))); /// /// Prepends a skew matrix using the given angles in degrees at the given origin. @@ -225,7 +211,7 @@ public class ProjectiveTransformBuilder /// The Y angle, in radians. /// The . public ProjectiveTransformBuilder AppendSkewRadians(float radiansX, float radiansY) - => this.Append(size => new Matrix4x4(TransformUtils.CreateSkewTransformMatrixRadians(radiansX, radiansY, size, this.TransformSpace))); + => this.Append(size => new Matrix4x4(TransformUtilities.CreateSkewTransformMatrixRadians(radiansX, radiansY, size))); /// /// Appends a skew matrix using the given angles in degrees at the given origin. @@ -288,8 +274,12 @@ public class ProjectiveTransformBuilder /// The bottom-left corner point of the distorted quad. /// The . public ProjectiveTransformBuilder PrependQuadDistortion(PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) - => this.Prepend(size => TransformUtils.CreateQuadDistortionMatrix( - new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft, this.TransformSpace)); + => this.Prepend(size => TransformUtilities.CreateQuadDistortionMatrix( + new Rectangle(Point.Empty, size), + topLeft, + topRight, + bottomRight, + bottomLeft)); /// /// Appends a quad distortion matrix using the specified corner points. @@ -300,8 +290,12 @@ public class ProjectiveTransformBuilder /// The bottom-left corner point of the distorted quad. /// The . public ProjectiveTransformBuilder AppendQuadDistortion(PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) - => this.Append(size => TransformUtils.CreateQuadDistortionMatrix( - new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft, this.TransformSpace)); + => this.Append(size => TransformUtilities.CreateQuadDistortionMatrix( + new Rectangle(Point.Empty, size), + topLeft, + topRight, + bottomRight, + bottomLeft)); /// /// Prepends a raw matrix. @@ -383,15 +377,39 @@ public class ProjectiveTransformBuilder /// for linear transforms. /// /// The . - public Size GetTransformedSize(Rectangle sourceRectangle) + public SizeF GetTransformedSize(Rectangle sourceRectangle) { Matrix4x4 matrix = this.BuildMatrix(sourceRectangle); - return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace); + return GetTransformedSize(sourceRectangle, matrix); + } + + /// + /// Returns the size of a rectangle large enough to contain the transformed source rectangle. + /// + /// The rectangle in the source image. + /// The transformation matrix. + /// + /// The resultant matrix is degenerate containing one or more values equivalent + /// to or a zero determinant and therefore cannot be used + /// for linear transforms. + /// + /// The . + internal static SizeF GetTransformedSize(Rectangle sourceRectangle, Matrix4x4 matrix) + => TransformUtilities.GetRawTransformedSize(matrix, sourceRectangle.Size); + + /// + /// Clears all accumulated transform matrices, resetting the builder to its initial state. + /// + /// The . + public ProjectiveTransformBuilder Clear() + { + this.transformMatrixFactories.Clear(); + return this; } private static void CheckDegenerate(Matrix4x4 matrix) { - if (TransformUtils.IsDegenerate(matrix)) + if (TransformUtilities.IsDegenerate(matrix)) { throw new DegenerateTransformException("Matrix is degenerate. Check input values."); } diff --git a/src/ImageSharp/Processing/TransformSpace.cs b/src/ImageSharp/Processing/TransformSpace.cs deleted file mode 100644 index bca676bd8..000000000 --- a/src/ImageSharp/Processing/TransformSpace.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Processing; - -/// -/// Represents the different spaces used in transformation operations. -/// -public enum TransformSpace -{ - /// - /// Coordinate space is a continuous, mathematical grid where objects and positions - /// are defined with precise, often fractional values. This space allows for fine-grained - /// transformations like scaling, rotation, and translation with high precision. - /// In coordinate space, an image can span from (0,0) to (4,4) for a 4x4 image, including the boundaries. - /// - Coordinate, - - /// - /// Pixel space is a discrete grid where each position corresponds to a specific pixel on the screen. - /// In this space, positions are defined by whole numbers, with no fractional values. - /// A 4x4 image in pixel space covers exactly 4 pixels wide and 4 pixels tall, ranging from (0,0) to (3,3). - /// Pixel space is used when rendering images to ensure that everything aligns with the actual pixels on the screen. - /// - Pixel -} diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index e20717cdd..d5d5961b5 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -19,14 +19,12 @@ - - - - + + diff --git a/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs b/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs index 80b096344..dcd26c328 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/FromVector4.cs @@ -14,7 +14,6 @@ using SixLabors.ImageSharp.PixelFormats; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Bulk; -[Config(typeof(Config.Short))] public abstract class FromVector4 where TPixel : unmanaged, IPixel { @@ -62,14 +61,21 @@ public abstract class FromVector4 => PixelOperations.Instance.FromVector4Destructive(this.Configuration, this.Source.GetSpan(), this.Destination.GetSpan()); } +[Config(typeof(Config.Short))] public class FromVector4Rgba32 : FromVector4 { + /// + /// Measures the raw SIMD kernel that converts normalized RGBA vector components to bytes. + /// [Benchmark] public void UseHwIntrinsics() { Span sBytes = MemoryMarshal.Cast(this.Source.GetSpan()); Span dFloats = MemoryMarshal.Cast(this.Destination.GetSpan()); + // Four components per pixel make every configured count divisible by the supported 128-, 256-, and 512-bit + // byte-vector widths. Calling the block kernel directly therefore excludes PixelOperations contract handling + // and the general conversion wrapper's remainder dispatch, establishing the lower bound for the SIMD conversion. SimdUtils.HwIntrinsics.NormalizedFloatToByteSaturate(sBytes, dFloats); } @@ -121,36 +127,45 @@ public class FromVector4Rgba32 : FromVector4 } /* - BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3085/23H2/2023Update/SunValley3) - 11th Gen Intel Core i7-11370H 3.30GHz, 1 CPU, 8 logical and 4 physical cores - .NET SDK 8.0.200-preview.23624.5 - [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - Job-YJYLLR : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 - - Runtime=.NET 8.0 Arguments=/p:DebugType=portable IterationCount=3 - LaunchCount=1 WarmupCount=3 - - | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio | - |---------------------------- |------ |------------:|-------------:|-----------:|------:|--------:|----------:|------------:| - | PixelOperations_Base | 64 | 114.80 ns | 16.459 ns | 0.902 ns | 1.00 | 0.00 | - | NA | - | PixelOperations_Specialized | 64 | 28.91 ns | 80.482 ns | 4.411 ns | 0.25 | 0.04 | - | NA | - | FallbackIntrinsics128 | 64 | 133.60 ns | 23.750 ns | 1.302 ns | 1.16 | 0.02 | - | NA | - | ExtendedIntrinsic | 64 | 40.11 ns | 10.183 ns | 0.558 ns | 0.35 | 0.01 | - | NA | - | UseHwIntrinsics | 64 | 14.71 ns | 4.860 ns | 0.266 ns | 0.13 | 0.00 | - | NA | - | UseAvx2_Grouped | 64 | 20.23 ns | 11.619 ns | 0.637 ns | 0.18 | 0.00 | - | NA | - | | | | | | | | | | - | PixelOperations_Base | 256 | 387.94 ns | 31.591 ns | 1.732 ns | 1.00 | 0.00 | - | NA | - | PixelOperations_Specialized | 256 | 50.93 ns | 22.388 ns | 1.227 ns | 0.13 | 0.00 | - | NA | - | FallbackIntrinsics128 | 256 | 509.72 ns | 249.926 ns | 13.699 ns | 1.31 | 0.04 | - | NA | - | ExtendedIntrinsic | 256 | 140.32 ns | 9.353 ns | 0.513 ns | 0.36 | 0.00 | - | NA | - | UseHwIntrinsics | 256 | 41.99 ns | 16.000 ns | 0.877 ns | 0.11 | 0.00 | - | NA | - | UseAvx2_Grouped | 256 | 63.81 ns | 2.360 ns | 0.129 ns | 0.16 | 0.00 | - | NA | - | | | | | | | | | | - | PixelOperations_Base | 2048 | 2,979.49 ns | 2,023.706 ns | 110.926 ns | 1.00 | 0.00 | - | NA | - | PixelOperations_Specialized | 2048 | 326.19 ns | 19.077 ns | 1.046 ns | 0.11 | 0.00 | - | NA | - | FallbackIntrinsics128 | 2048 | 3,885.95 ns | 411.078 ns | 22.533 ns | 1.31 | 0.05 | - | NA | - | ExtendedIntrinsic | 2048 | 1,078.58 ns | 136.960 ns | 7.507 ns | 0.36 | 0.01 | - | NA | - | UseHwIntrinsics | 2048 | 312.07 ns | 68.662 ns | 3.764 ns | 0.10 | 0.00 | - | NA | - | UseAvx2_Grouped | 2048 | 451.83 ns | 41.742 ns | 2.288 ns | 0.15 | 0.01 | - | NA | + BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8737/25H2/2025Update/HudsonValley2) + AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores + .NET 8.0.28, X64 RyuJIT x86-64-v4 + + | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Allocated | + |---------------------------- |------ |------------:|-----------:|----------:|------:|--------:|----------:| + | PixelOperations_Base | 64 | 60.19 ns | 32.376 ns | 1.775 ns | 1.00 | 0.04 | - | + | PixelOperations_Specialized | 64 | 22.94 ns | 66.082 ns | 3.622 ns | 0.38 | 0.05 | - | + | UseHwIntrinsics | 64 | 10.64 ns | 9.535 ns | 0.523 ns | 0.18 | 0.01 | - | + | UseAvx2_Grouped | 64 | 11.69 ns | 0.997 ns | 0.055 ns | 0.19 | 0.01 | - | + | PixelOperations_Base | 256 | 232.65 ns | 37.948 ns | 2.080 ns | 1.00 | 0.01 | - | + | PixelOperations_Specialized | 256 | 33.75 ns | 3.906 ns | 0.214 ns | 0.15 | 0.00 | - | + | UseHwIntrinsics | 256 | 21.91 ns | 2.732 ns | 0.150 ns | 0.09 | 0.00 | - | + | UseAvx2_Grouped | 256 | 24.84 ns | 5.333 ns | 0.292 ns | 0.11 | 0.00 | - | + | PixelOperations_Base | 2048 | 1,500.98 ns | 951.510 ns | 52.155 ns | 1.00 | 0.04 | - | + | PixelOperations_Specialized | 2048 | 156.21 ns | 65.074 ns | 3.567 ns | 0.10 | 0.00 | - | + | UseHwIntrinsics | 2048 | 144.38 ns | 40.947 ns | 2.244 ns | 0.10 | 0.00 | - | + | UseAvx2_Grouped | 2048 | 189.80 ns | 54.098 ns | 2.965 ns | 0.13 | 0.00 | - | + */ +} + +/// +/// Measures bulk conversion from values to premultiplied pixels. +/// +[Config(typeof(Config.Analysis))] +public class FromVector4Bgra32P : FromVector4 +{ + /* + BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8737/25H2/2025Update/HudsonValley2) + AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores + .NET 8.0.28, X64 RyuJIT x86-64-v4 + + | Method | Count | Mean | Error | StdDev | Ratio | Code Size | Allocated | + |---------------------------- |------ |------------:|---------:|----------:|------:|----------:|----------:| + | PixelOperations_Base | 64 | 53.35 ns | 0.208 ns | 0.311 ns | 1.00 | 1,152 B | - | + | PixelOperations_Specialized | 64 | 46.61 ns | 0.195 ns | 0.280 ns | 0.87 | 2,975 B | - | + | PixelOperations_Base | 256 | 212.11 ns | 0.719 ns | 1.076 ns | 1.00 | 1,152 B | - | + | PixelOperations_Specialized | 256 | 77.12 ns | 0.824 ns | 1.233 ns | 0.36 | 2,992 B | - | + | PixelOperations_Base | 2048 | 1,435.87 ns | 9.402 ns | 13.781 ns | 1.00 | 1,152 B | - | + | PixelOperations_Specialized | 2048 | 250.89 ns | 9.354 ns | 13.416 ns | 0.17 | 2,992 B | - | */ } diff --git a/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs index 8728dd671..16529306a 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs @@ -8,7 +8,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Bulk; [Config(typeof(Config.HwIntrinsics_SSE_AVX))] public class Pad3Shuffle4Channel { - private static readonly DefaultPad3Shuffle4 Control = new(SimdUtils.Shuffle.MMShuffle1032); private byte[] source; private byte[] destination; @@ -25,11 +24,11 @@ public class Pad3Shuffle4Channel [Benchmark] public void Pad3Shuffle4() - => SimdUtils.Pad3Shuffle4(this.source, this.destination, Control); + => SimdUtils.Pad3Shuffle4(this.source, this.destination); [Benchmark] public void Pad3Shuffle4FastFallback() - => SimdUtils.Pad3Shuffle4(this.source, this.destination, default(XYZWPad3Shuffle4)); + => SimdUtils.Pad3Shuffle4(this.source, this.destination); } // 2020-10-30 diff --git a/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs index e4c12900f..e09c4ce6c 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs @@ -8,7 +8,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Bulk; [Config(typeof(Config.HwIntrinsics_SSE_AVX))] public class Shuffle3Channel { - private static readonly DefaultShuffle3 Control = new(SimdUtils.Shuffle.MMShuffle3102); private byte[] source; private byte[] destination; @@ -25,7 +24,7 @@ public class Shuffle3Channel [Benchmark] public void Shuffle3() - => SimdUtils.Shuffle3(this.source, this.destination, Control); + => SimdUtils.Shuffle3(this.source, this.destination); } // 2020-11-02 diff --git a/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs index 579e2c54d..4b694768d 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs @@ -8,7 +8,6 @@ namespace SixLabors.ImageSharp.Benchmarks.Bulk; [Config(typeof(Config.HwIntrinsics_SSE_AVX))] public class Shuffle4Slice3Channel { - private static readonly DefaultShuffle4Slice3 Control = new(SimdUtils.Shuffle.MMShuffle1032); private byte[] source; private byte[] destination; @@ -25,11 +24,11 @@ public class Shuffle4Slice3Channel [Benchmark] public void Shuffle4Slice3() - => SimdUtils.Shuffle4Slice3(this.source, this.destination, Control); + => SimdUtils.Shuffle4Slice3(this.source, this.destination); [Benchmark] public void Shuffle4Slice3FastFallback() - => SimdUtils.Shuffle4Slice3(this.source, this.destination, default(XYZWShuffle4Slice3)); + => SimdUtils.Shuffle4Slice3(this.source, this.destination); } // 2020-10-29 diff --git a/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs index 6a16bb571..f3ad974c4 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs @@ -24,7 +24,7 @@ public class ShuffleByte4Channel [Benchmark] public void Shuffle4Channel() - => SimdUtils.Shuffle4(this.source, this.destination, default); + => SimdUtils.Shuffle4(this.source, this.destination); } // 2020-10-29 diff --git a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs index 6499632b6..38a09483b 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/ToVector4_Bgra32.cs @@ -20,24 +20,48 @@ public class ToVector4_Bgra32 : ToVector4 this.Destination.GetSpan()); } - // RESULTS: - // Method | Runtime | Count | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated | - // ---------------------------- |-------- |------ |-----------:|------------:|-----------:|-------:|---------:|-------:|----------:| - // PixelOperations_Base | Clr | 64 | 339.9 ns | 138.30 ns | 7.8144 ns | 1.00 | 0.00 | 0.0072 | 24 B | - // PixelOperations_Specialized | Clr | 64 | 338.1 ns | 13.30 ns | 0.7515 ns | 0.99 | 0.02 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Core | 64 | 245.6 ns | 29.05 ns | 1.6413 ns | 1.00 | 0.00 | 0.0072 | 24 B | - // PixelOperations_Specialized | Core | 64 | 257.1 ns | 37.89 ns | 2.1407 ns | 1.05 | 0.01 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Clr | 256 | 972.7 ns | 61.98 ns | 3.5020 ns | 1.00 | 0.00 | 0.0057 | 24 B | - // PixelOperations_Specialized | Clr | 256 | 882.9 ns | 126.21 ns | 7.1312 ns | 0.91 | 0.01 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Core | 256 | 910.0 ns | 90.87 ns | 5.1346 ns | 1.00 | 0.00 | 0.0067 | 24 B | - // PixelOperations_Specialized | Core | 256 | 448.4 ns | 15.77 ns | 0.8910 ns | 0.49 | 0.00 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Clr | 2048 | 6,951.8 ns | 1,299.01 ns | 73.3963 ns | 1.00 | 0.00 | - | 24 B | - // PixelOperations_Specialized | Clr | 2048 | 5,852.3 ns | 630.56 ns | 35.6279 ns | 0.84 | 0.01 | - | 0 B | - // | | | | | | | | | | - // PixelOperations_Base | Core | 2048 | 6,937.5 ns | 1,692.19 ns | 95.6121 ns | 1.00 | 0.00 | - | 24 B | - // PixelOperations_Specialized | Core | 2048 | 2,994.5 ns | 1,126.65 ns | 63.6578 ns | 0.43 | 0.01 | - | 0 B | + // BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8737/25H2/2025Update/HudsonValley2) + // AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores + // .NET 8.0.28, X64 RyuJIT x86-64-v4 + // + // | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Allocated | + // |---------------------------- |------ |------------:|------------:|----------:|------:|--------:|----------:| + // | PixelOperations_Base | 64 | 58.30 ns | 20.13 ns | 1.103 ns | 1.00 | 0.02 | - | + // | PixelOperations_Specialized | 64 | 58.95 ns | 23.68 ns | 1.298 ns | 1.01 | 0.03 | - | + // | PixelOperations_Base | 256 | 226.95 ns | 84.03 ns | 4.606 ns | 1.00 | 0.02 | - | + // | PixelOperations_Specialized | 256 | 229.70 ns | 40.00 ns | 2.192 ns | 1.01 | 0.02 | - | + // | PixelOperations_Base | 2048 | 1,795.42 ns | 1,465.95 ns | 80.354 ns | 1.00 | 0.05 | - | + // | PixelOperations_Specialized | 2048 | 291.89 ns | 109.98 ns | 6.028 ns | 0.16 | 0.01 | - | +} + +/// +/// Measures bulk conversion from premultiplied pixels to values. +/// +[Config(typeof(Config.Analysis))] +public class ToVector4_Bgra32P : ToVector4 +{ + /// + /// Measures the scalar implementation used as the comparison baseline. + /// + [Benchmark(Baseline = true)] + public void PixelOperations_Base() + { + new PixelOperations().ToVector4( + this.Configuration, + this.Source.GetSpan(), + this.Destination.GetSpan()); + } + + // BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8737/25H2/2025Update/HudsonValley2) + // AMD RYZEN AI MAX+ 395 w/ Radeon 8060S 3.00GHz, 1 CPU, 32 logical and 16 physical cores + // .NET 8.0.28, X64 RyuJIT x86-64-v4 + // + // | Method | Count | Mean | Error | StdDev | Ratio | RatioSD | Code Size | Allocated | + // |---------------------------- |------ |------------:|---------:|---------:|------:|--------:|----------:|----------:| + // | PixelOperations_Base | 64 | 58.05 ns | 3.204 ns | 4.795 ns | 1.01 | 0.11 | 932 B | - | + // | PixelOperations_Specialized | 64 | 84.78 ns | 0.467 ns | 0.669 ns | 1.47 | 0.12 | 3,731 B | - | + // | PixelOperations_Base | 256 | 208.93 ns | 0.345 ns | 0.484 ns | 1.00 | 0.00 | 958 B | - | + // | PixelOperations_Specialized | 256 | 106.50 ns | 0.280 ns | 0.410 ns | 0.51 | 0.00 | 3,738 B | - | + // | PixelOperations_Base | 2048 | 1,617.39 ns | 3.380 ns | 4.848 ns | 1.00 | 0.00 | 958 B | - | + // | PixelOperations_Specialized | 2048 | 269.90 ns | 0.666 ns | 0.976 ns | 0.17 | 0.00 | 3,735 B | - | } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs b/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs new file mode 100644 index 000000000..45021cadc --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Exr/DecodeExr.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; + +using ImageMagick; +using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs; + +[MarkdownExporter] +[HtmlExporter] +[Config(typeof(Config.Short))] +public class DecodeExr +{ + private Configuration configuration; + + private byte[] imageBytes; + + private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + + [Params(TestImages.Exr.Benchmark)] + public string TestImage { get; set; } + + [GlobalSetup] + public void ReadImages() + { + this.configuration = Configuration.CreateDefaultInstance(); + new ExrConfigurationModule().Configure(this.configuration); + + this.imageBytes ??= File.ReadAllBytes(this.TestImageFullPath); + } + + [Benchmark(Description = "Magick Exr")] + public uint ExrImageMagick() + { + MagickReadSettings settings = new() { Format = MagickFormat.Exr }; + using MemoryStream memoryStream = new(this.imageBytes); + using MagickImage image = new(memoryStream, settings); + return image.Width; + } + + [Benchmark(Description = "ImageSharp Exr")] + public int ExrImageSharp() + { + using MemoryStream memoryStream = new(this.imageBytes); + using Image image = Image.Load(memoryStream); + return image.Height; + } + + /* Results 27.03.2026 + BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8037/25H2/2025Update/HudsonValley2) + Intel Core i7-14700T 1.30GHz, 1 CPU, 28 logical and 20 physical cores + .NET SDK 10.0.201 + [Host] : .NET 8.0.25 (8.0.25, 8.0.2526.11203), X64 RyuJIT x86-64-v3 + Job-VDWIGO : .NET 8.0.25 (8.0.25, 8.0.2526.11203), X64 RyuJIT x86-64-v3 + + Runtime=.NET 8.0 Arguments=/p:DebugType=portable IterationCount=3 + LaunchCount=1 WarmupCount=3 + + | Method | TestImage | Mean | Error | StdDev | Allocated | + |----------------- |----------------------------- |---------:|---------:|---------:|----------:| + | 'Magick Exr' | Exr/Calliphora_benchmark.exr | 20.37 ms | 0.790 ms | 0.043 ms | 12.98 KB | + | 'ImageSharp Exr' | Exr/Calliphora_benchmark.exr | 45.68 ms | 4.999 ms | 0.274 ms | 34.09 KB | + */ +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index a1ba3fb8d..ae4229807 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -9,40 +9,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class CmykColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8); + + /// + /// Initializes a new instance of the class. + /// public CmykColorConversion() : base(4) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one CMYK component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.CmykVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs index 436aa9bcd..106bb5ff6 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs @@ -51,6 +51,7 @@ public abstract class ColorConversionBenchmark // no need to dispose when buffer is not array owner buffers[i] = Configuration.Default.MemoryAllocator.Allocate2D(values.Length, 1); + values.CopyTo(buffers[i].DangerousGetRowSpan(0)); } return buffers; diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 3ade4279f..9e5bfed12 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -9,40 +9,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class GrayScaleColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8); + + /// + /// Initializes a new instance of the class. + /// public GrayScaleColorConversion() : base(1) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one grayscale component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.GrayScaleVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPacking.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPacking.cs new file mode 100644 index 000000000..5c36ae59f --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPacking.cs @@ -0,0 +1,178 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using SixLabors.ImageSharp.Formats.Jpeg.Components; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// Compares the previous scalar JPEG packing loops with the SIMD register-transpose implementation. +/// +[Config(typeof(Config.Short))] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] +[CategoriesColumn] +public class JpegColorPacking +{ + private const float MaximumValue = 255F; + private const float Scale = 1F / MaximumValue; + + private float[] x = null!; + private float[] y = null!; + private float[] z = null!; + private float[] w = null!; + private Vector3[] packed3 = null!; + private float[] destination3 = null!; + private float[] destination4 = null!; + + /// + /// Gets or sets the number of pixels transformed by each benchmark invocation. + /// + [Params(128, 1024, 4096)] + public int Length { get; set; } + + /// + /// Creates deterministic source and destination buffers outside the measured operations. + /// + [GlobalSetup] + public void Setup() + { + this.x = CreateSamples(this.Length, 1); + this.y = CreateSamples(this.Length, 2); + this.z = CreateSamples(this.Length, 3); + this.w = CreateSamples(this.Length, 4); + this.packed3 = new Vector3[this.Length]; + this.destination3 = new float[this.Length * 3]; + this.destination4 = new float[this.Length * 4]; + + for (int i = 0; i < this.packed3.Length; i++) + { + this.packed3[i] = new Vector3(this.x[i], this.y[i], this.z[i]); + } + } + + /// + /// Measures the previous scalar three-plane normalization and interleave loop. + /// + /// The last destination value, keeping the writes observable. + [Benchmark(Baseline = true)] + [BenchmarkCategory("Pack3")] + public float PackedNormalizeInterleave3Scalar() + { + JpegColorPackingScalar.PackedNormalizeInterleave3(this.x, this.y, this.z, this.destination3, Scale); + + return this.destination3[^1]; + } + + /// + /// Measures the SIMD three-plane normalization and interleave implementation. + /// + /// The last destination value, keeping the writes observable. + [Benchmark] + [BenchmarkCategory("Pack3")] + public float PackedNormalizeInterleave3Simd() + { + JpegColorConverterBase.PackedNormalizeInterleave3(this.x, this.y, this.z, this.destination3, Scale); + + return this.destination3[^1]; + } + + /// + /// Measures the previous scalar packed-three-channel deinterleave loop. + /// + /// A checksum containing the last value written to every destination plane. + [Benchmark(Baseline = true)] + [BenchmarkCategory("Unpack3")] + public float UnpackDeinterleave3Scalar() + { + JpegColorPackingScalar.UnpackDeinterleave3(this.packed3, this.x, this.y, this.z); + + return this.x[^1] + this.y[^1] + this.z[^1]; + } + + /// + /// Measures the SIMD packed-three-channel deinterleave implementation. + /// + /// A checksum containing the last value written to every destination plane. + [Benchmark] + [BenchmarkCategory("Unpack3")] + public float UnpackDeinterleave3Simd() + { + JpegColorConverterBase.UnpackDeinterleave3(this.packed3, this.x, this.y, this.z); + + return this.x[^1] + this.y[^1] + this.z[^1]; + } + + /// + /// Measures the previous scalar four-plane normalization and interleave loop. + /// + /// The last destination value, keeping the writes observable. + [Benchmark(Baseline = true)] + [BenchmarkCategory("Pack4")] + public float PackedNormalizeInterleave4Scalar() + { + JpegColorPackingScalar.PackedNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); + + return this.destination4[^1]; + } + + /// + /// Measures the SIMD four-plane normalization and interleave implementation. + /// + /// The last destination value, keeping the writes observable. + [Benchmark] + [BenchmarkCategory("Pack4")] + public float PackedNormalizeInterleave4Simd() + { + JpegColorConverterBase.PackedNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); + + return this.destination4[^1]; + } + + /// + /// Measures the previous scalar inverted four-plane normalization and interleave loop. + /// + /// The last destination value, keeping the writes observable. + [Benchmark(Baseline = true)] + [BenchmarkCategory("InvertPack4")] + public float PackedInvertNormalizeInterleave4Scalar() + { + JpegColorPackingScalar.PackedInvertNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); + + return this.destination4[^1]; + } + + /// + /// Measures the SIMD inverted four-plane normalization and interleave implementation. + /// + /// The last destination value, keeping the writes observable. + [Benchmark] + [BenchmarkCategory("InvertPack4")] + public float PackedInvertNormalizeInterleave4Simd() + { + JpegColorConverterBase.PackedInvertNormalizeInterleave4(this.x, this.y, this.z, this.w, this.destination4, MaximumValue); + + return this.destination4[^1]; + } + + /// + /// Creates deterministic, non-integral samples for one component plane. + /// + /// The number of samples to create. + /// The one-based component number used to distinguish the plane. + /// The generated samples. + private static float[] CreateSamples(int length, int component) + { + float[] samples = new float[length]; + + for (int i = 0; i < samples.Length; i++) + { + samples[i] = (((i * 37) + (component * 53)) % 251) + (component * 0.125F); + } + + return samples; + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPackingScalar.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPackingScalar.cs new file mode 100644 index 000000000..511abc0db --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorPackingScalar.cs @@ -0,0 +1,117 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; + +/// +/// Preserves the scalar JPEG packing loops that preceded the SIMD investigation. +/// +internal static class JpegColorPackingScalar +{ + /// + /// Normalizes and interleaves three planar component lanes using the previous scalar implementation. + /// + /// The planar X components. + /// The planar Y components. + /// The planar Z components. + /// The destination ordered as consecutive XYZ triples. + /// The normalization factor applied to every component. + public static void PackedNormalizeInterleave3(ReadOnlySpan xLane, ReadOnlySpan yLane, ReadOnlySpan zLane, Span packed, float scale) + { + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + ref float packedRef = ref MemoryMarshal.GetReference(packed); + + for (nuint i = 0; i < (nuint)xLane.Length; i++) + { + nuint packedOffset = i * 3; + Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, i) * scale; + Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, i) * scale; + Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, i) * scale; + } + } + + /// + /// Deinterleaves packed XYZ values using the previous scalar implementation. + /// + /// The source ordered as consecutive XYZ triples. + /// The destination X components. + /// The destination Y components. + /// The destination Z components. + public static void UnpackDeinterleave3(ReadOnlySpan packed, Span xLane, Span yLane, Span zLane) + { + ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(packed)); + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + + for (nuint i = 0; i < (nuint)packed.Length; i++) + { + nuint packedOffset = i * 3; + Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset); + Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 1); + Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 2); + } + } + + /// + /// Normalizes and interleaves four planar component lanes using the previous scalar implementation. + /// + /// The planar X components. + /// The planar Y components. + /// The planar Z components. + /// The planar W components. + /// The destination ordered as consecutive XYZW groups. + /// The maximum component value used to normalize each component. + public static void PackedNormalizeInterleave4(ReadOnlySpan xLane, ReadOnlySpan yLane, ReadOnlySpan zLane, ReadOnlySpan wLane, Span packed, float maximumValue) + { + float scale = 1F / maximumValue; + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); + ref float packedRef = ref MemoryMarshal.GetReference(packed); + + for (nuint i = 0; i < (nuint)xLane.Length; i++) + { + nuint packedOffset = i * 4; + Unsafe.Add(ref packedRef, packedOffset) = Unsafe.Add(ref xLaneRef, i) * scale; + Unsafe.Add(ref packedRef, packedOffset + 1) = Unsafe.Add(ref yLaneRef, i) * scale; + Unsafe.Add(ref packedRef, packedOffset + 2) = Unsafe.Add(ref zLaneRef, i) * scale; + Unsafe.Add(ref packedRef, packedOffset + 3) = Unsafe.Add(ref wLaneRef, i) * scale; + } + } + + /// + /// Inverts, normalizes, and interleaves four planar lanes using the previous scalar implementation. + /// + /// The inverted planar X components. + /// The inverted planar Y components. + /// The inverted planar Z components. + /// The inverted planar W components. + /// The destination ordered as consecutive conventional XYZW groups. + /// The maximum component value used for inversion and normalization. + public static void PackedInvertNormalizeInterleave4(ReadOnlySpan xLane, ReadOnlySpan yLane, ReadOnlySpan zLane, ReadOnlySpan wLane, Span packed, float maximumValue) + { + float scale = 1F / maximumValue; + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + ref float wLaneRef = ref MemoryMarshal.GetReference(wLane); + ref float packedRef = ref MemoryMarshal.GetReference(packed); + + for (nuint i = 0; i < (nuint)xLane.Length; i++) + { + nuint packedOffset = i * 4; + Unsafe.Add(ref packedRef, packedOffset) = (maximumValue - Unsafe.Add(ref xLaneRef, i)) * scale; + Unsafe.Add(ref packedRef, packedOffset + 1) = (maximumValue - Unsafe.Add(ref yLaneRef, i)) * scale; + Unsafe.Add(ref packedRef, packedOffset + 2) = (maximumValue - Unsafe.Add(ref zLaneRef, i)) * scale; + Unsafe.Add(ref packedRef, packedOffset + 3) = (maximumValue - Unsafe.Add(ref wLaneRef, i)) * scale; + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index 2916dcdce..b1d72e3ca 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -9,40 +9,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class RgbColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8); + + /// + /// Initializes a new instance of the class. + /// public RgbColorConversion() : base(3) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one RGB component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.RgbVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index fbd762af4..1355aecd1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -9,40 +9,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class YCbCrColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8); + + /// + /// Initializes a new instance of the class. + /// public YCbCrColorConversion() : base(3) { } + /// + /// Converts one YCbCr component row through the adaptive operator traversal. + /// [Benchmark] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrVector256(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.YCbCrVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index e6b04c152..261711d0b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -9,40 +9,24 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class YccKColorConverter : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8); + + /// + /// Initializes a new instance of the class. + /// public YccKColorConverter() : base(4) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one YccK component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.YccKVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs index 125b42680..69779731b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs @@ -40,19 +40,19 @@ public class EncodeIndexedPng this.bmpCore.Dispose(); } - [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] - public void PngCoreOctree() + [Benchmark(Baseline = true, Description = "ImageSharp Hexadecatree Png")] + public void PngCoreHexadecatree() { using MemoryStream memoryStream = new(); - PngEncoder options = new() { Quantizer = KnownQuantizers.Octree }; + PngEncoder options = new() { Quantizer = KnownQuantizers.Hexadecatree }; this.bmpCore.SaveAsPng(memoryStream, options); } - [Benchmark(Description = "ImageSharp Octree NoDither Png")] - public void PngCoreOctreeNoDither() + [Benchmark(Description = "ImageSharp Hexadecatree NoDither Png")] + public void PngCoreHexadecatreeNoDither() { using MemoryStream memoryStream = new(); - PngEncoder options = new() { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; + PngEncoder options = new() { Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs new file mode 100644 index 000000000..e5a434067 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs @@ -0,0 +1,99 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Png.Filters; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; + +/// +/// Measures the shared PNG filter map/reduce traversal. +/// +[Config(typeof(Config.Short))] +public class PngFilterEncode +{ + private const int BytesPerPixel = 4; + + private byte[] scanline; + private byte[] previousScanline; + private byte[] result; + + /// + /// Gets or sets the filter evaluated by each invocation. + /// + [Params(PngFilterMethod.Sub, PngFilterMethod.Up, PngFilterMethod.Average, PngFilterMethod.Paeth)] + public PngFilterMethod Filter { get; set; } + + /// + /// Gets or sets the number of scanline bytes. + /// + [Params(64, 1024, 16384)] + public int Count { get; set; } + + /// + /// Creates deterministic non-uniform inputs and a result buffer. + /// + [GlobalSetup] + public void Setup() + { + this.scanline = new byte[this.Count]; + this.previousScanline = new byte[this.Count]; + this.result = new byte[this.Count + 1]; + + Random random = new(12345678); + random.NextBytes(this.scanline); + random.NextBytes(this.previousScanline); + } + + /// + /// Executes the shared operator-driven map/reduce traversal. + /// + /// The filter variance sum. + [Benchmark] + public int Encode() + => this.Filter switch + { + PngFilterMethod.Sub => this.EncodeSub(), + PngFilterMethod.Up => this.EncodeUp(), + PngFilterMethod.Average => this.EncodeAverage(), + PngFilterMethod.Paeth => this.EncodePaeth(), + _ => throw new InvalidOperationException() + }; + + /// + /// Executes the current Sub encoder. + /// + private int EncodeSub() + { + SubFilter.Encode(this.scanline, this.result, BytesPerPixel, out int sum); + return sum; + } + + /// + /// Executes the current Up encoder. + /// + private int EncodeUp() + { + UpFilter.Encode(this.scanline, this.previousScanline, this.result, out int sum); + return sum; + } + + /// + /// Executes the current Average encoder. + /// + private int EncodeAverage() + { + AverageFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out int sum); + return sum; + } + + /// + /// Executes the current Paeth encoder. + /// + private int EncodePaeth() + { + PaethFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out int sum); + return sum; + } +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs index d6a6cf1fb..4c81aee6d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs @@ -27,7 +27,7 @@ public class DecodeTga => this.data = File.ReadAllBytes(this.TestImageFullPath); [Benchmark(Baseline = true, Description = "ImageMagick Tga")] - public int TgaImageMagick() + public uint TgaImageMagick() { MagickReadSettings settings = new() { Format = MagickFormat.Tga }; using MagickImage image = new(new MemoryStream(this.data), settings); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs index bba1bc187..a10f1527f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs @@ -42,7 +42,7 @@ public class DecodeWebp } [Benchmark(Description = "Magick Lossy Webp")] - public int WebpLossyMagick() + public uint WebpLossyMagick() { MagickReadSettings settings = new() { Format = MagickFormat.WebP }; using MemoryStream memoryStream = new(this.webpLossyBytes); @@ -59,7 +59,7 @@ public class DecodeWebp } [Benchmark(Description = "Magick Lossless Webp")] - public int WebpLosslessMagick() + public uint WebpLosslessMagick() { MagickReadSettings settings = new() { Format = MagickFormat.WebP }; diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index 190c245c9..0c46de470 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -8,8 +8,10 @@ using BenchmarkDotNet.Diagnostics.Windows; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Exporters.Json; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Toolchains.InProcess.Emit; namespace SixLabors.ImageSharp.Benchmarks; @@ -45,6 +47,43 @@ public partial class Config : ManualConfig .WithArguments([new MsBuildArgument("/p:DebugType=portable")])); } + /// + /// Configures a short diagnostic run that exports memory usage, generated code, and full measurement data. + /// Elevated Windows runs also include branch-instruction counters. + /// + public class Analysis : Config + { + public Analysis() + { + this.AddJob(Job.ShortRun.WithRuntime(CoreRuntime.Core80).WithArguments([new MsBuildArgument("/p:DebugType=portable")])); + + this.AddDiagnoser(new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig( + maxDepth: 3, + printSource: true, + exportGithubMarkdown: true, + exportCombinedDisassemblyReport: true))); + + this.AddExporter(JsonExporter.Full); + +#if OS_WINDOWS + if (this.IsElevated) + { + // ETW hardware counters require elevation, so ordinary benchmark runs omit them instead of requesting more access. + this.AddHardwareCounters(HardwareCounter.BranchMispredictions, HardwareCounter.BranchInstructions); + } +#endif + } + } + + public class StandardInProcess : Config + { + public StandardInProcess() => this.AddJob( + Job.Default + .WithRuntime(CoreRuntime.Core80) + .WithToolchain(InProcessEmitToolchain.Instance) + .WithArguments([new MsBuildArgument("/p:DebugType=portable")])); + } + #if OS_WINDOWS private bool IsElevated => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); #endif diff --git a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs deleted file mode 100644 index 64a8092c6..000000000 --- a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Compression.Zlib; -using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; - -namespace SixLabors.ImageSharp.Benchmarks.General; - -[Config(typeof(Config.Short))] -public class Adler32Benchmark -{ - private byte[] data; - private readonly SharpAdler32 adler = new(); - - [Params(1024, 2048, 4096)] - public int Count { get; set; } - - [GlobalSetup] - public void SetUp() - { - this.data = new byte[this.Count]; - new Random(1).NextBytes(this.data); - } - - [Benchmark(Baseline = true)] - public long SharpZipLibCalculate() - { - this.adler.Reset(); - this.adler.Update(this.data); - return this.adler.Value; - } - - [Benchmark] - public uint SixLaborsCalculate() - { - return Adler32.Calculate(this.data); - } -} - -// ########## 17/05/2020 ########## -// -// | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | -// |--------------------- |-------------- |------ |------------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:| -// | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 793.18 ns | 775.66 ns | 42.516 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET 4.7.2 | 1024 | 384.86 ns | 15.64 ns | 0.857 ns | 0.49 | 0.03 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 790.31 ns | 353.34 ns | 19.368 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 2.1 | 1024 | 465.28 ns | 652.41 ns | 35.761 ns | 0.59 | 0.03 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 877.25 ns | 97.89 ns | 5.365 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 3.1 | 1024 | 45.60 ns | 13.28 ns | 0.728 ns | 0.05 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 1,537.04 ns | 428.44 ns | 23.484 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET 4.7.2 | 2048 | 849.76 ns | 1,066.34 ns | 58.450 ns | 0.55 | 0.04 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 1,616.97 ns | 276.70 ns | 15.167 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 2.1 | 2048 | 790.77 ns | 691.71 ns | 37.915 ns | 0.49 | 0.03 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 1,735.11 ns | 1,374.22 ns | 75.325 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 3.1 | 2048 | 87.80 ns | 56.84 ns | 3.116 ns | 0.05 | 0.00 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 3,054.53 ns | 796.41 ns | 43.654 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET 4.7.2 | 4096 | 1,538.90 ns | 487.02 ns | 26.695 ns | 0.50 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 3,223.48 ns | 32.32 ns | 1.771 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 2.1 | 4096 | 1,547.60 ns | 309.72 ns | 16.977 ns | 0.48 | 0.01 | - | - | - | - | -// | | | | | | | | | | | | | -// | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 3,672.33 ns | 1,095.81 ns | 60.065 ns | 1.00 | 0.00 | - | - | - | - | -// | SixLaborsCalculate | .NET Core 3.1 | 4096 | 159.44 ns | 36.31 ns | 1.990 ns | 0.04 | 0.00 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversion.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversion.cs new file mode 100644 index 000000000..a7dcb9021 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PackedPixelConversion.cs @@ -0,0 +1,222 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +/// +/// Measures every optimized conversion between the six byte-packed RGB and RGBA pixel layouts. +/// +[Config(typeof(Config.Short))] +public class PackedPixelConversion +{ + private byte[] source3; + private byte[] source4; + private byte[] destination3; + private byte[] destination4; + + /// + /// Gets or sets the number of pixels converted by each invocation. + /// + [Params(7, 256, 4096)] + public int Count { get; set; } + + /// + /// Creates deterministic source buffers and correctly sized destination buffers. + /// + [GlobalSetup] + public void Setup() + { + this.source3 = new byte[this.Count * 3]; + this.source4 = new byte[this.Count * 4]; + this.destination3 = new byte[this.Count * 3]; + this.destination4 = new byte[this.Count * 4]; + + // Non-repeating channel values prevent the JIT or hardware from benefiting from + // zero-filled inputs while keeping both benchmark revisions byte-for-byte identical. + new Random(42).NextBytes(this.source3); + new Random(42).NextBytes(this.source4); + } + + /// + /// Converts RGBA pixels to ARGB pixels. + /// + [Benchmark] + public void Rgba32ToArgb32() => PixelConverter.FromRgba32.ToArgb32(this.source4, this.destination4); + + /// + /// Converts RGBA pixels to ABGR pixels. + /// + [Benchmark] + public void Rgba32ToAbgr32() => PixelConverter.FromRgba32.ToAbgr32(this.source4, this.destination4); + + /// + /// Converts RGBA pixels to BGRA pixels. + /// + [Benchmark] + public void Rgba32ToBgra32() => PixelConverter.FromRgba32.ToBgra32(this.source4, this.destination4); + + /// + /// Converts RGBA pixels to RGB pixels. + /// + [Benchmark] + public void Rgba32ToRgb24() => PixelConverter.FromRgba32.ToRgb24(this.source4, this.destination3); + + /// + /// Converts RGBA pixels to BGR pixels. + /// + [Benchmark] + public void Rgba32ToBgr24() => PixelConverter.FromRgba32.ToBgr24(this.source4, this.destination3); + + /// + /// Converts ARGB pixels to RGBA pixels. + /// + [Benchmark] + public void Argb32ToRgba32() => PixelConverter.FromArgb32.ToRgba32(this.source4, this.destination4); + + /// + /// Converts ARGB pixels to ABGR pixels. + /// + [Benchmark] + public void Argb32ToAbgr32() => PixelConverter.FromArgb32.ToAbgr32(this.source4, this.destination4); + + /// + /// Converts ARGB pixels to BGRA pixels. + /// + [Benchmark] + public void Argb32ToBgra32() => PixelConverter.FromArgb32.ToBgra32(this.source4, this.destination4); + + /// + /// Converts ARGB pixels to RGB pixels. + /// + [Benchmark] + public void Argb32ToRgb24() => PixelConverter.FromArgb32.ToRgb24(this.source4, this.destination3); + + /// + /// Converts ARGB pixels to BGR pixels. + /// + [Benchmark] + public void Argb32ToBgr24() => PixelConverter.FromArgb32.ToBgr24(this.source4, this.destination3); + + /// + /// Converts ABGR pixels to RGBA pixels. + /// + [Benchmark] + public void Abgr32ToRgba32() => PixelConverter.FromAbgr32.ToRgba32(this.source4, this.destination4); + + /// + /// Converts ABGR pixels to ARGB pixels. + /// + [Benchmark] + public void Abgr32ToArgb32() => PixelConverter.FromAbgr32.ToArgb32(this.source4, this.destination4); + + /// + /// Converts ABGR pixels to BGRA pixels. + /// + [Benchmark] + public void Abgr32ToBgra32() => PixelConverter.FromAbgr32.ToBgra32(this.source4, this.destination4); + + /// + /// Converts ABGR pixels to RGB pixels. + /// + [Benchmark] + public void Abgr32ToRgb24() => PixelConverter.FromAbgr32.ToRgb24(this.source4, this.destination3); + + /// + /// Converts ABGR pixels to BGR pixels. + /// + [Benchmark] + public void Abgr32ToBgr24() => PixelConverter.FromAbgr32.ToBgr24(this.source4, this.destination3); + + /// + /// Converts BGRA pixels to RGBA pixels. + /// + [Benchmark] + public void Bgra32ToRgba32() => PixelConverter.FromBgra32.ToRgba32(this.source4, this.destination4); + + /// + /// Converts BGRA pixels to ARGB pixels. + /// + [Benchmark] + public void Bgra32ToArgb32() => PixelConverter.FromBgra32.ToArgb32(this.source4, this.destination4); + + /// + /// Converts BGRA pixels to ABGR pixels. + /// + [Benchmark] + public void Bgra32ToAbgr32() => PixelConverter.FromBgra32.ToAbgr32(this.source4, this.destination4); + + /// + /// Converts BGRA pixels to RGB pixels. + /// + [Benchmark] + public void Bgra32ToRgb24() => PixelConverter.FromBgra32.ToRgb24(this.source4, this.destination3); + + /// + /// Converts BGRA pixels to BGR pixels. + /// + [Benchmark] + public void Bgra32ToBgr24() => PixelConverter.FromBgra32.ToBgr24(this.source4, this.destination3); + + /// + /// Converts RGB pixels to RGBA pixels. + /// + [Benchmark] + public void Rgb24ToRgba32() => PixelConverter.FromRgb24.ToRgba32(this.source3, this.destination4); + + /// + /// Converts RGB pixels to ARGB pixels. + /// + [Benchmark] + public void Rgb24ToArgb32() => PixelConverter.FromRgb24.ToArgb32(this.source3, this.destination4); + + /// + /// Converts RGB pixels to ABGR pixels. + /// + [Benchmark] + public void Rgb24ToAbgr32() => PixelConverter.FromRgb24.ToAbgr32(this.source3, this.destination4); + + /// + /// Converts RGB pixels to BGRA pixels. + /// + [Benchmark] + public void Rgb24ToBgra32() => PixelConverter.FromRgb24.ToBgra32(this.source3, this.destination4); + + /// + /// Converts RGB pixels to BGR pixels. + /// + [Benchmark] + public void Rgb24ToBgr24() => PixelConverter.FromRgb24.ToBgr24(this.source3, this.destination3); + + /// + /// Converts BGR pixels to RGBA pixels. + /// + [Benchmark] + public void Bgr24ToRgba32() => PixelConverter.FromBgr24.ToRgba32(this.source3, this.destination4); + + /// + /// Converts BGR pixels to ARGB pixels. + /// + [Benchmark] + public void Bgr24ToArgb32() => PixelConverter.FromBgr24.ToArgb32(this.source3, this.destination4); + + /// + /// Converts BGR pixels to ABGR pixels. + /// + [Benchmark] + public void Bgr24ToAbgr32() => PixelConverter.FromBgr24.ToAbgr32(this.source3, this.destination4); + + /// + /// Converts BGR pixels to BGRA pixels. + /// + [Benchmark] + public void Bgr24ToBgra32() => PixelConverter.FromBgr24.ToBgra32(this.source3, this.destination4); + + /// + /// Converts BGR pixels to RGB pixels. + /// + [Benchmark] + public void Bgr24ToRgb24() => PixelConverter.FromBgr24.ToRgb24(this.source3, this.destination3); +} diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs index 232d8b3e2..c69510bc4 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs @@ -7,7 +7,6 @@ using System.Runtime.InteropServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; @@ -225,8 +224,8 @@ public class PixelConversion_Rgba32_To_Bgra32 // [Benchmark] public void Bitops_Simd() { - ref Octet sBase = ref Unsafe.As>(ref this.source[0]); - ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); + ref InlineArray8 sBase = ref Unsafe.As>(ref this.source[0]); + ref InlineArray8 dBase = ref Unsafe.As>(ref this.dest[0]); for (nuint i = 0; i < (uint)this.Count / 8; i++) { @@ -249,9 +248,9 @@ public class PixelConversion_Rgba32_To_Bgra32 #pragma warning restore SA1132 // Do not combine fields [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void BitopsSimdImpl(ref Octet s, ref Octet d) + private static void BitopsSimdImpl(ref InlineArray8 s, ref InlineArray8 d) { - Vector sVec = Unsafe.As, Vector>(ref s); + Vector sVec = Unsafe.As, Vector>(ref s); Vector aMask = new(0xFF00FF00); Vector bMask = new(0x00FF00FF); @@ -274,7 +273,7 @@ public class PixelConversion_Rgba32_To_Bgra32 Vector cc = Unsafe.As>(ref c); Vector dd = aa + cc; - d = Unsafe.As, Octet>(ref dd); + d = Unsafe.As, InlineArray8>(ref dd); } // [Benchmark] diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs new file mode 100644 index 000000000..d671117b6 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; + +/// +/// Measures the operator-driven affine vector transforms. +/// +[Config(typeof(Config.Short))] +public class Vector4AffineTransform +{ + private static readonly Vector4 Multiplier = new(255F, 2F, 65535F, .5F); + private static readonly Vector4 Offset = new(17F, -1F, 32768F, 3F); + private static readonly Vector4 Divisor = new(255F, 2F, 65535F, .5F); + + private Vector4[] current; + + /// + /// Gets or sets the number of vectors transformed by each invocation. + /// + [Params(1, 3, 4, 17, 256, 4096)] + public int Count { get; set; } + + /// + /// Creates a non-uniform input buffer. + /// + [GlobalSetup] + public void Setup() + { + this.current = new Vector4[this.Count]; + + for (int i = 0; i < this.current.Length; i++) + { + this.current[i] = new Vector4(i + .25F, i + .5F, i + .75F, i + 1F); + } + } + + /// + /// Executes the operator-driven multiply-then-add traversal. + /// + [Benchmark] + public void MultiplyThenAdd() + => Vector4Converters.MultiplyThenAdd(this.current, Multiplier, Offset); + + /// + /// Executes the operator-driven add-then-divide traversal. + /// + [Benchmark] + public void AddThenDivide() + => Vector4Converters.AddThenDivide(this.current, Offset, Divisor); +} diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs index da7ddae41..fd17262de 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs @@ -5,8 +5,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Tuples; - namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization; [Config(typeof(Config.Short))] @@ -30,12 +28,22 @@ public class WidenBytesToUInt32 { const int N = Count / 8; - ref Octet sBase = ref Unsafe.As>(ref this.source[0]); - ref Octet dBase = ref Unsafe.As>(ref this.dest[0]); + ref InlineArray8 sBase = ref Unsafe.As>(ref this.source[0]); + ref InlineArray8 dBase = ref Unsafe.As>(ref this.dest[0]); for (nuint i = 0; i < N; i++) { - Unsafe.Add(ref dBase, i).LoadFrom(ref Unsafe.Add(ref sBase, i)); + ref InlineArray8 source = ref Unsafe.Add(ref sBase, i); + ref InlineArray8 destination = ref Unsafe.Add(ref dBase, i); + + destination[0] = source[0]; + destination[1] = source[1]; + destination[2] = source[2]; + destination[3] = source[3]; + destination[4] = source[4]; + destination[5] = source[5]; + destination[6] = source[6]; + destination[7] = source[7]; } } diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index c92bb6a6b..c3733c73c 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -19,7 +19,7 @@ BenchmarkDotNet requires a certain structure to the code, as such, some of these rules cannot be implemented. --> - + @@ -39,12 +39,12 @@ - net8.0;net9.0 + net10.0;net11.0 - net8.0 + net10.0 @@ -57,8 +57,9 @@ - - + + + diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index f8bf19d57..815a145f7 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -6,6 +6,7 @@ using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using ImageMagick; using PhotoSauce.MagicScaler; using SixLabors.ImageSharp.Formats; @@ -27,6 +28,7 @@ public enum JpegKind Any = Baseline | Progressive } +[SupportedOSPlatform("windows")] public class LoadResizeSaveStressRunner { private const int Quality = 75; @@ -158,7 +160,7 @@ public class LoadResizeSaveStressRunner this.outputDirectory, Path.GetFileNameWithoutExtension(inputPath) + "-" + postfix + Path.GetExtension(inputPath)); - private (int Width, int Height) ScaledSize(int inWidth, int inHeight, int outSize) + private static (int Width, int Height) ScaledSize(int inWidth, int inHeight, int outSize) { int width, height; if (inWidth > inHeight) @@ -180,7 +182,7 @@ public class LoadResizeSaveStressRunner using SystemDrawingImage image = SystemDrawingImage.FromFile(input, true); this.LogImageProcessed(image.Width, image.Height); - (int width, int height) = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize); + (int width, int height) = ScaledSize(image.Width, image.Height, this.ThumbnailSize); Bitmap resized = new(width, height); using Graphics graphics = Graphics.FromImage(resized); using ImageAttributes attributes = new(); @@ -248,10 +250,10 @@ public class LoadResizeSaveStressRunner public void MagickResize(string input) { using MagickImage image = new(input); - this.LogImageProcessed(image.Width, image.Height); + this.LogImageProcessed((int)image.Width, (int)image.Height); // Resize it to fit a 150x150 square - image.Resize(this.ThumbnailSize, this.ThumbnailSize); + image.Resize((uint)this.ThumbnailSize, (uint)this.ThumbnailSize); // Reduce the size of the file image.Strip(); @@ -282,7 +284,7 @@ public class LoadResizeSaveStressRunner { using SKBitmap original = SKBitmap.Decode(input); this.LogImageProcessed(original.Width, original.Height); - (int width, int height) = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); + (int width, int height) = ScaledSize(original.Width, original.Height, this.ThumbnailSize); using SKSurface surface = SKSurface.Create(new SKImageInfo(width, height, original.ColorType, original.AlphaType)); using SKPaint paint = new() { FilterQuality = SKFilterQuality.High }; SKCanvas canvas = surface.Canvas; @@ -300,7 +302,7 @@ public class LoadResizeSaveStressRunner { using SKBitmap original = SKBitmap.Decode(input); this.LogImageProcessed(original.Width, original.Height); - (int width, int height) = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); + (int width, int height) = ScaledSize(original.Width, original.Height, this.ThumbnailSize); using SKBitmap resized = original.Resize(new SKImageInfo(width, height), SKFilterQuality.High); if (resized == null) { @@ -319,7 +321,7 @@ public class LoadResizeSaveStressRunner SKImageInfo info = codec.Info; this.LogImageProcessed(info.Width, info.Height); - (int width, int height) = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize); + (int width, int height) = ScaledSize(info.Width, info.Height, this.ThumbnailSize); SKSizeI supportedScale = codec.GetScaledDimensions((float)width / info.Width); using SKBitmap original = SKBitmap.Decode(codec, new SKImageInfo(supportedScale.Width, supportedScale.Height)); diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/AssociatedAlphaPixelBlenderBenchmark.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/AssociatedAlphaPixelBlenderBenchmark.cs new file mode 100644 index 000000000..02f25f2ea --- /dev/null +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/AssociatedAlphaPixelBlenderBenchmark.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.PixelBlenders; + +/// +/// Compares straight-alpha and associated-alpha bulk pixel blending. +/// +[Config(typeof(Config.Short))] +public class AssociatedAlphaPixelBlenderBenchmark +{ + private const int PixelCount = 1024; + + private readonly Rgba32[] unassociatedDestination = new Rgba32[PixelCount]; + private readonly Rgba32[] unassociatedBackground = new Rgba32[PixelCount]; + private readonly Rgba32[] unassociatedSource = new Rgba32[PixelCount]; + private readonly Rgba32P[] associatedDestination = new Rgba32P[PixelCount]; + private readonly Rgba32P[] associatedBackground = new Rgba32P[PixelCount]; + private readonly Rgba32P[] associatedSource = new Rgba32P[PixelCount]; + private readonly float[] amounts = new float[PixelCount]; + private readonly Vector4[] unassociatedWorkingBuffer = new Vector4[PixelCount * 3]; + private readonly Vector4[] associatedWorkingBuffer = new Vector4[PixelCount * 3]; + private PixelBlender unassociatedBlender; + private PixelBlender associatedBlender; + + /// + /// Gets or sets the color-blending mode measured by the benchmark. + /// + [ParamsAllValues] + public PixelColorBlendingMode ColorMode { get; set; } + + /// + /// Gets or sets the alpha-composition mode measured by the benchmark. + /// + [ParamsAllValues] + public PixelAlphaCompositionMode AlphaMode { get; set; } + + /// + /// Initializes equivalent straight-alpha and associated-alpha rows. + /// + [GlobalSetup] + public void Setup() + { + this.unassociatedBlender = PixelOperations.Instance.GetPixelBlender(this.ColorMode, this.AlphaMode); + this.associatedBlender = PixelOperations.Instance.GetPixelBlender(this.ColorMode, this.AlphaMode); + + Random random = new(42); + + for (int i = 0; i < PixelCount; i++) + { + Rgba32 background = new((byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(64, 256)); + Rgba32 source = new((byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(64, 256)); + + this.unassociatedBackground[i] = background; + this.unassociatedSource[i] = source; + this.associatedBackground[i] = Rgba32P.FromRgba32(background); + this.associatedSource[i] = Rgba32P.FromRgba32(source); + this.amounts[i] = random.NextSingle(); + } + } + + /// + /// Blends one row stored with straight alpha. + /// + /// The last destination pixel. + [Benchmark(Description = "Straight alpha", Baseline = true)] + public Rgba32 BlendUnassociated() + { + this.unassociatedBlender.Blend( + Configuration.Default, + this.unassociatedDestination, + this.unassociatedBackground, + this.unassociatedSource, + this.amounts, + this.unassociatedWorkingBuffer); + + return this.unassociatedDestination[^1]; + } + + /// + /// Blends one row stored with associated alpha. + /// + /// The last destination pixel. + [Benchmark(Description = "Associated alpha")] + public Rgba32P BlendAssociated() + { + this.associatedBlender.Blend( + Configuration.Default, + this.associatedDestination, + this.associatedBackground, + this.associatedSource, + this.amounts, + this.associatedWorkingBuffer); + + return this.associatedDestination[^1]; + } +} diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs index ecf8b125f..fe43ce5e7 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsSingleVector.cs @@ -18,8 +18,8 @@ public class PorterDuffBulkVsSingleVector [GlobalSetup] public void Setup() { - this.backdrop = new Vector4[8 * 20]; - this.source = new Vector4[8 * 20]; + this.backdrop = new Vector4[8 * 40]; + this.source = new Vector4[8 * 40]; FillRandom(this.backdrop); FillRandom(this.source); @@ -49,7 +49,7 @@ public class PorterDuffBulkVsSingleVector return result; } - [Benchmark(Description = "Avx")] + [Benchmark(Description = "Avx2")] public Vector256 OverlayValueFunction_Avx() { ref Vector256 backdrop = ref Unsafe.As>(ref MemoryMarshal.GetReference(this.backdrop)); @@ -65,4 +65,21 @@ public class PorterDuffBulkVsSingleVector return result; } + + [Benchmark(Description = "Avx512")] + public Vector512 OverlayValueFunction_Avx512() + { + ref Vector512 backdrop = ref Unsafe.As>(ref MemoryMarshal.GetReference(this.backdrop)); + ref Vector512 source = ref Unsafe.As>(ref MemoryMarshal.GetReference(this.source)); + + Vector512 result = default; + Vector512 opacity = Vector512.Create(.5F); + int count = this.backdrop.Length / 4; + for (nuint i = 0; i < (uint)count; i++) + { + result = PorterDuffFunctions.NormalSrcOver(Unsafe.Add(ref backdrop, i), Unsafe.Add(ref source, i), opacity); + } + + return result; + } } diff --git a/tests/ImageSharp.Benchmarks/Processing/ColorMatchingCaches.cs b/tests/ImageSharp.Benchmarks/Processing/ColorMatchingCaches.cs new file mode 100644 index 000000000..6e5c531f5 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Processing/ColorMatchingCaches.cs @@ -0,0 +1,302 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Runtime.CompilerServices; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Benchmarks.Processing; + +[Config(typeof(Config.Standard))] +public class ColorMatchingCaches +{ + // IterationSetup forces BenchmarkDotNet to use a single benchmark invocation per iteration. + // Repeated lookups can safely replay a smaller working set because that workload is explicitly + // meant to model steady-state cache hits after warmup. + private const int RepeatedLookupCount = 262_144; + + // DitherLike should avoid replaying the same stream across multiple passes because that warms + // the caches in a way real high-churn input usually does not. Make the single pass larger instead. + private const int DitherLikeLookupCount = 1_048_576; + private const int RepeatedPassCount = 16; + + private Rgba32[] palette; + private Rgba32[] repeatedSeedColors; + private Rgba32[] repeatedLookups; + private Rgba32[] ditherLookups; + + private PixelMap coarse; + private PixelMap legacyCoarse; + private PixelMap exact; + private PixelMap uncached; + + [Params(16, 256)] + public int PaletteSize { get; set; } + + [Params(CacheWorkload.Repeated, CacheWorkload.DitherLike)] + public CacheWorkload Workload { get; set; } + + [GlobalSetup] + public void Setup() + { + this.palette = CreatePalette(this.PaletteSize); + this.repeatedSeedColors = CreateRepeatedSeedColors(this.palette); + this.repeatedLookups = CreateRepeatedLookups(this.repeatedSeedColors); + this.ditherLookups = CreateDitherLikeLookups(); + + this.coarse = CreatePixelMap(this.palette); + this.legacyCoarse = CreatePixelMap(this.palette); + this.exact = CreatePixelMap(this.palette); + this.uncached = CreatePixelMap(this.palette); + } + + [IterationSetup] + public void ResetCaches() + { + // Each benchmark iteration should start from the same cache state so we measure + // the cache policy itself rather than warm state carried over from a previous iteration. + this.coarse.Clear(this.palette); + this.legacyCoarse.Clear(this.palette); + this.exact.Clear(this.palette); + this.uncached.Clear(this.palette); + + if (this.Workload == CacheWorkload.Repeated) + { + // Prime the repeated workload so the benchmark reflects steady-state hit behavior + // instead of mostly measuring the first-wave fill cost. + Prime(this.coarse, this.repeatedSeedColors); + Prime(this.legacyCoarse, this.repeatedSeedColors); + Prime(this.exact, this.repeatedSeedColors); + Prime(this.uncached, this.repeatedSeedColors); + } + } + + [GlobalCleanup] + public void Cleanup() + { + this.coarse.Dispose(); + this.legacyCoarse.Dispose(); + this.exact.Dispose(); + this.uncached.Dispose(); + } + + [Benchmark(Baseline = true, Description = "Coarse")] + public int Coarse() => this.Run(this.coarse); + + [Benchmark(Description = "Legacy Coarse")] + public int LegacyCoarse() => this.Run(this.legacyCoarse); + + [Benchmark(Description = "Exact Cached")] + public int Exact() => this.Run(this.exact); + + [Benchmark(Description = "Exact Uncached")] + public int Uncached() => this.Run(this.uncached); + + public enum CacheWorkload + { + // A small working set that is intentionally reused after priming to measure hit-heavy behavior. + Repeated, + + // A deterministic high-churn stream intended to resemble dithered lookups where exact repeats are rare. + DitherLike + } + + private int Run(PixelMap map) + { + Rgba32[] lookups = this.Workload == CacheWorkload.Repeated ? this.repeatedLookups : this.ditherLookups; + int passCount = this.Workload == CacheWorkload.Repeated ? RepeatedPassCount : 1; + int checksum = 0; + + // Repeated intentionally replays the same lookup stream to measure steady-state hit behavior. + // DitherLike runs as a single larger pass so we do not turn a churn-heavy workload into an + // artificially warmed cache benchmark by replaying the exact same sequence. + for (int pass = 0; pass < passCount; pass++) + { + for (int i = 0; i < lookups.Length; i++) + { + checksum = unchecked((checksum * 31) + map.GetClosestColor(lookups[i], out _)); + } + } + + return checksum; + } + + private static PixelMap CreatePixelMap(Rgba32[] palette) + where TCache : struct, IColorIndexCache + => new EuclideanPixelMap(Configuration.Default, palette); + + private static void Prime(PixelMap map, Rgba32[] colors) + { + for (int i = 0; i < colors.Length; i++) + { + map.GetClosestColor(colors[i], out _); + } + } + + private static Rgba32[] CreatePalette(int count) + { + Rgba32[] result = new Rgba32[count]; + + for (int i = 0; i < result.Length; i++) + { + // Use the Knuth/golden-ratio multiplicative hash constant to spread colors across + // RGBA space without clustering into a gradient. That keeps the benchmark from + // accidentally favoring any cache because the palette itself is too regular. + uint value = unchecked((uint)(i + 1) * 2654435761U); + result[i] = new Rgba32( + (byte)value, + (byte)(value >> 8), + (byte)(value >> 16), + (byte)((value >> 24) | 0x80)); + } + + return result; + } + + private static Rgba32[] CreateRepeatedSeedColors(Rgba32[] palette) + { + // Reuse colors derived from the palette but perturb them slightly so the workload still + // exercises nearest-color matching rather than only exact palette-entry hits. + int count = Math.Min(64, palette.Length * 2); + Rgba32[] result = new Rgba32[count]; + + for (int i = 0; i < result.Length; i++) + { + Rgba32 source = palette[(i * 17) % palette.Length]; + result[i] = new Rgba32( + (byte)(source.R + ((i * 3) & 0x07)), + (byte)(source.G + ((i * 5) & 0x07)), + (byte)(source.B + ((i * 7) & 0x07)), + source.A); + } + + return result; + } + + private static Rgba32[] CreateRepeatedLookups(Rgba32[] seedColors) + { + Rgba32[] result = new Rgba32[RepeatedLookupCount]; + + // Cycle a small seed set to produce a stable, hit-heavy stream after priming. + for (int i = 0; i < result.Length; i++) + { + result[i] = seedColors[i % seedColors.Length]; + } + + return result; + } + + private static Rgba32[] CreateDitherLikeLookups() + { + Rgba32[] result = new Rgba32[DitherLikeLookupCount]; + + // Generate a deterministic pseudo-image signal with independent channel slopes so nearby + // samples are correlated but exact repeats are uncommon, which is closer to dithered input. + for (int i = 0; i < result.Length; i++) + { + int x = i & 511; + int y = i >> 9; + + result[i] = new Rgba32( + (byte)((x * 17) + (y * 13)), + (byte)((x * 29) + (y * 7)), + (byte)((x * 11) + (y * 23)), + (byte)(255 - ((x * 3) + (y * 5)))); + } + + return result; + } + + /// + /// Preserves the original direct-mapped coarse cache implementation for side-by-side benchmarks. + /// + private unsafe struct LegacyCoarseCache : IColorIndexCache + { + private const int IndexRBits = 5; + private const int IndexGBits = 5; + private const int IndexBBits = 5; + private const int IndexABits = 6; + private const int IndexRCount = 1 << IndexRBits; + private const int IndexGCount = 1 << IndexGBits; + private const int IndexBCount = 1 << IndexBBits; + private const int IndexACount = 1 << IndexABits; + private const int TotalBins = IndexRCount * IndexGCount * IndexBCount * IndexACount; + + private readonly IMemoryOwner binsOwner; + private readonly short* binsPointer; + private MemoryHandle binsHandle; + + private LegacyCoarseCache(MemoryAllocator allocator) + { + this.binsOwner = allocator.Allocate(TotalBins); + this.binsOwner.GetSpan().Fill(-1); + this.binsHandle = this.binsOwner.Memory.Pin(); + this.binsPointer = (short*)this.binsHandle.Pointer; + } + + public static LegacyCoarseCache Create(MemoryAllocator allocator) => new(allocator); + + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool TryAdd(Rgba32 color, short value) + { + this.binsPointer[GetCoarseIndex(color)] = value; + return true; + } + + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool TryGetValue(Rgba32 color, out short value) + { + value = this.binsPointer[GetCoarseIndex(color)]; + return value > -1; + } + + public readonly void Clear() => this.binsOwner.GetSpan().Fill(-1); + + public void Dispose() + { + this.binsHandle.Dispose(); + this.binsOwner.Dispose(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static int GetCoarseIndex(Rgba32 color) + { + int rIndex = color.R >> (8 - IndexRBits); + int gIndex = color.G >> (8 - IndexGBits); + int bIndex = color.B >> (8 - IndexBBits); + int aIndex = color.A >> (8 - IndexABits); + + return (aIndex * IndexRCount * IndexGCount * IndexBCount) + + (rIndex * IndexGCount * IndexBCount) + + (gIndex * IndexBCount) + + bIndex; + } + } + + /// + /// Preserves the uncached path for exact-cache comparison benchmarks. + /// + private readonly struct UncachedCache : IColorIndexCache + { + public static UncachedCache Create(MemoryAllocator allocator) => default; + + public bool TryAdd(Rgba32 color, short value) => true; + + public bool TryGetValue(Rgba32 color, out short value) + { + value = -1; + return false; + } + + public void Clear() + { + } + + public void Dispose() + { + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Processing/ParallelProcessing.cs b/tests/ImageSharp.Benchmarks/Processing/ParallelProcessing.cs new file mode 100644 index 000000000..4f2089713 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Processing/ParallelProcessing.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks; + +public class ParallelProcessing +{ + private Image image; + private Image foreground; + private Configuration configuration; + + public static IEnumerable MaxDegreeOfParallelismValues() + { + int processorCount = Environment.ProcessorCount; + for (int p = 1; p <= processorCount; p *= 2) + { + yield return p; + } + + if ((processorCount & (processorCount - 1)) != 0) + { + yield return processorCount; + } + } + + [ParamsSource(nameof(MaxDegreeOfParallelismValues))] + public int MaxDegreeOfParallelism { get; set; } + + [GlobalSetup] + public void Setup() + { + this.image = new Image(2048, 2048); + this.foreground = new Image(2048, 2048); + this.configuration = Configuration.Default.Clone(); + this.configuration.MaxDegreeOfParallelism = this.MaxDegreeOfParallelism; + } + + [Benchmark] + public void DetectEdges() => this.image.Mutate(this.configuration, x => x.DetectEdges()); + + [Benchmark] + public void DrawImage() => this.image.Mutate(this.configuration, x => x.DrawImage(this.foreground, 0.5f)); + + [Benchmark] + public void Crop() + { + Rectangle bounds = this.image.Bounds; + bounds = new Rectangle(1, 1, bounds.Width - 2, bounds.Height - 2); + this.image + .Clone(this.configuration, x => x.Crop(bounds)) + .Dispose(); + } + + [GlobalCleanup] + public void Cleanup() + { + this.image.Dispose(); + this.foreground.Dispose(); + } +} diff --git a/tests/ImageSharp.Benchmarks/Processing/Resize.cs b/tests/ImageSharp.Benchmarks/Processing/Resize.cs index 356027221..3cc10afb7 100644 --- a/tests/ImageSharp.Benchmarks/Processing/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Resize.cs @@ -12,7 +12,7 @@ using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks; -[Config(typeof(Config.Standard))] +[Config(typeof(Config.StandardInProcess))] public abstract class Resize where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.PublicApi.Tests/ImageSharp.PublicApi.Tests.csproj b/tests/ImageSharp.PublicApi.Tests/ImageSharp.PublicApi.Tests.csproj new file mode 100644 index 000000000..bab8095c5 --- /dev/null +++ b/tests/ImageSharp.PublicApi.Tests/ImageSharp.PublicApi.Tests.csproj @@ -0,0 +1,23 @@ + + + + + SixLabors.ImageSharp.PublicApi.Tests + SixLabors.ImageSharp.PublicApi.Tests + Debug;Release + + + + + + net10.0;net11.0 + + + + + net10.0 + + + + + diff --git a/tests/ImageSharp.PublicApi.Tests/MemoryAllocatorExtensibilityTests.cs b/tests/ImageSharp.PublicApi.Tests/MemoryAllocatorExtensibilityTests.cs new file mode 100644 index 000000000..766e07341 --- /dev/null +++ b/tests/ImageSharp.PublicApi.Tests/MemoryAllocatorExtensibilityTests.cs @@ -0,0 +1,210 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.PublicApi.Tests; + +/// +/// Verifies that a fully functional can be implemented outside the ImageSharp assembly. +/// +public class MemoryAllocatorExtensibilityTests +{ + private const int OneMegabyte = 1 << 20; + + /// + /// Verifies that an external allocator can apply from its constructor + /// and that the applied limits are readable through the public properties. + /// + [Fact] + public void ExternalAllocatorCanApplyOptionsFromConstructor() + { + ExternalArrayMemoryAllocator allocator = new(new MemoryAllocatorOptions + { + AllocationLimitMegabytes = 8, + SingleBufferAllocationLimitMegabytes = 2, + AccumulativeAllocationLimitMegabytes = 8 + }); + + Assert.Equal(8L * OneMegabyte, allocator.MemoryGroupAllocationLimitBytes); + Assert.Equal(2 * OneMegabyte, allocator.SingleBufferAllocationLimitBytes); + Assert.Equal(8L * OneMegabyte, allocator.AccumulativeAllocationLimitBytes); + } + + /// + /// Verifies that the applied single buffer limit is capped to the group allocation limit. + /// + [Fact] + public void ExternalAllocatorAppliedSingleBufferLimitIsCappedToGroupLimit() + { + ExternalArrayMemoryAllocator allocator = new(new MemoryAllocatorOptions + { + AllocationLimitMegabytes = 4, + SingleBufferAllocationLimitMegabytes = 8 + }); + + Assert.Equal(4 * OneMegabyte, allocator.SingleBufferAllocationLimitBytes); + } + + /// + /// Verifies that an external allocator can set the limit properties directly + /// and that the base class validates allocations against the configured values. + /// + [Fact] + public void ExternalAllocatorCanSetSingleBufferLimit() + { + ExternalArrayMemoryAllocator allocator = new(); + allocator.SetLimits( + memoryGroupAllocationLimitBytes: 4096, + singleBufferAllocationLimitBytes: 1024, + accumulativeAllocationLimitBytes: 4096); + + allocator.Allocate(1024).Dispose(); + Assert.Throws(() => allocator.Allocate(1025)); + } + + /// + /// Verifies that owners produced by an external allocator participate in accumulative allocation tracking. + /// + [Fact] + public void ExternalAllocatorTracksAccumulativeAllocations() + { + ExternalArrayMemoryAllocator allocator = new(); + allocator.SetLimits( + memoryGroupAllocationLimitBytes: 4096, + singleBufferAllocationLimitBytes: 4096, + accumulativeAllocationLimitBytes: 4096); + + IMemoryOwner owner = allocator.Allocate(4096); + + // The full accumulative budget is reserved while the owner is live. + Assert.Throws(() => allocator.Allocate(1)); + + // Disposing the owner releases the reservation. + owner.Dispose(); + allocator.Allocate(4096).Dispose(); + } + + /// + /// Verifies that an external allocator can back image creation through , + /// for both discontiguous and contiguous buffer preferences, and that disposal reaches the external owners. + /// + /// The contiguous buffer preference to apply. + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ExternalAllocatorBacksImageCreation(bool preferContiguousImageBuffers) + { + ExternalArrayMemoryAllocator allocator = new(); + Configuration configuration = Configuration.Default.Clone(); + configuration.MemoryAllocator = allocator; + configuration.PreferContiguousImageBuffers = preferContiguousImageBuffers; + + using (Image image = new(configuration, 16, 16, Color.Red.ToPixel())) + { + Assert.True(allocator.CreatedOwners > 0); + Assert.Equal(Color.Red.ToPixel(), image[8, 8]); + } + + Assert.Equal(0, allocator.LiveOwners); + } + + /// + /// A implemented with only the public API surface, backed by managed arrays. + /// + private sealed class ExternalArrayMemoryAllocator : MemoryAllocator + { + /// + /// Initializes a new instance of the class with default limits. + /// + public ExternalArrayMemoryAllocator() + { + } + + /// + /// Initializes a new instance of the class with custom limits. + /// + /// The to apply. + public ExternalArrayMemoryAllocator(MemoryAllocatorOptions options) => this.ApplyOptions(options); + + /// + /// Gets the total number of owners created by this allocator. + /// + public int CreatedOwners { get; private set; } + + /// + /// Gets the number of owners created by this allocator that are not yet disposed. + /// + public int LiveOwners { get; private set; } + + /// + /// Sets the protected limit properties directly, as a derived allocator can. + /// + /// The group allocation limit, in bytes. + /// The single buffer allocation limit, in bytes. + /// The accumulative allocation limit, in bytes. + public void SetLimits( + long memoryGroupAllocationLimitBytes, + int singleBufferAllocationLimitBytes, + long accumulativeAllocationLimitBytes) + { + this.MemoryGroupAllocationLimitBytes = memoryGroupAllocationLimitBytes; + this.SingleBufferAllocationLimitBytes = singleBufferAllocationLimitBytes; + this.AccumulativeAllocationLimitBytes = accumulativeAllocationLimitBytes; + } + + /// + protected override int GetBufferCapacityInBytes() => int.MaxValue; + + /// + protected override AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) + { + this.CreatedOwners++; + this.LiveOwners++; + return new ExternalArrayMemoryManager(new T[length], this); + } + + /// + /// Records the disposal of an owner created by this allocator. + /// + internal void OnOwnerDisposed() => this.LiveOwners--; + } + + /// + /// An implemented with only the public API surface. + /// + /// The element type. + private sealed class ExternalArrayMemoryManager : AllocationTrackedMemoryManager + where T : struct + { + private readonly T[] array; + private readonly ExternalArrayMemoryAllocator allocator; + + /// + /// Initializes a new instance of the class. + /// + /// The array that backs this owner. + /// The allocator that created this owner. + public ExternalArrayMemoryManager(T[] array, ExternalArrayMemoryAllocator allocator) + { + this.array = array; + this.allocator = allocator; + } + + /// + public override Span GetSpan() => this.array; + + /// + public override MemoryHandle Pin(int elementIndex = 0) => throw new NotSupportedException("Pinning is not required by these tests."); + + /// + public override void Unpin() + { + } + + /// + protected override void DisposeCore(bool disposing) => this.allocator.OnOwnerDisposed(); + } +} diff --git a/tests/ImageSharp.PublicApi.Tests/PixelOperationsExtensibilityTests.cs b/tests/ImageSharp.PublicApi.Tests/PixelOperationsExtensibilityTests.cs new file mode 100644 index 000000000..a1916831f --- /dev/null +++ b/tests/ImageSharp.PublicApi.Tests/PixelOperationsExtensibilityTests.cs @@ -0,0 +1,163 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.PublicApi.Tests; + +/// +/// Verifies that representation-aware pixel operations can be implemented outside the ImageSharp assembly. +/// +public class PixelOperationsExtensibilityTests +{ + /// + /// Verifies that an external subclass can override and invoke every representation-aware hook. + /// + [Fact] + public void PixelOperationsExposesAllRepresentationHooksToExternalSubclasses() + { + ExternalRgba32PixelOperations operations = new(); + + operations.InvokeRepresentationHooks(); + + Assert.Equal(RepresentationHooks.All, operations.InvokedHooks); + } + + /// + /// Verifies that an external subclass can override and invoke every representation-aware hook. + /// + [Fact] + public void AssociatedAlphaPixelOperationsExposesAllRepresentationHooksToExternalSubclasses() + { + ExternalRgba32PPixelOperations operations = new(); + + operations.InvokeRepresentationHooks(); + + Assert.Equal(RepresentationHooks.All, operations.InvokedHooks); + } + + [Flags] + private enum RepresentationHooks + { + None = 0, + ToUnassociatedVector4 = 1 << 0, + ToAssociatedVector4 = 1 << 1, + ToUnassociatedScaledVector4 = 1 << 2, + ToAssociatedScaledVector4 = 1 << 3, + FromUnassociatedVector4Destructive = 1 << 4, + FromAssociatedVector4Destructive = 1 << 5, + FromUnassociatedScaledVector4Destructive = 1 << 6, + FromAssociatedScaledVector4Destructive = 1 << 7, + All = ToUnassociatedVector4 + | ToAssociatedVector4 + | ToUnassociatedScaledVector4 + | ToAssociatedScaledVector4 + | FromUnassociatedVector4Destructive + | FromAssociatedVector4Destructive + | FromUnassociatedScaledVector4Destructive + | FromAssociatedScaledVector4Destructive + } + + /// + /// Exercises the representation-aware hooks inherited directly from . + /// + private sealed class ExternalRgba32PixelOperations : PixelOperations + { + /// + /// Gets the hooks invoked through this external subclass. + /// + public RepresentationHooks InvokedHooks { get; private set; } + + /// + /// Invokes every representation-aware operation exposed to derived classes. + /// + public void InvokeRepresentationHooks() + { + // Empty spans isolate accessibility and virtual dispatch; conversion behavior is covered by the pixel-operation test suite. + this.ToUnassociatedVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToAssociatedVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToUnassociatedScaledVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToAssociatedScaledVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.FromUnassociatedVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromAssociatedVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromUnassociatedScaledVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromAssociatedScaledVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + } + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToUnassociatedVector4; + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToAssociatedVector4; + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToUnassociatedScaledVector4; + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToAssociatedScaledVector4; + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromUnassociatedVector4Destructive; + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromAssociatedVector4Destructive; + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromUnassociatedScaledVector4Destructive; + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromAssociatedScaledVector4Destructive; + } + + /// + /// Exercises the representation-aware hooks inherited through . + /// + private sealed class ExternalRgba32PPixelOperations : AssociatedAlphaPixelOperations + { + /// + /// Gets the hooks invoked through this external subclass. + /// + public RepresentationHooks InvokedHooks { get; private set; } + + /// + /// Invokes every representation-aware operation exposed to derived classes. + /// + public void InvokeRepresentationHooks() + { + // Empty spans isolate accessibility and virtual dispatch; conversion behavior is covered by the pixel-operation test suite. + this.ToUnassociatedVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToAssociatedVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToUnassociatedScaledVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.ToAssociatedScaledVector4(Configuration.Default, ReadOnlySpan.Empty, Span.Empty); + this.FromUnassociatedVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromAssociatedVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromUnassociatedScaledVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + this.FromAssociatedScaledVector4Destructive(Configuration.Default, Span.Empty, Span.Empty); + } + + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToUnassociatedVector4; + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToAssociatedVector4; + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToUnassociatedScaledVector4; + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) => this.InvokedHooks |= RepresentationHooks.ToAssociatedScaledVector4; + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromUnassociatedVector4Destructive; + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromAssociatedVector4Destructive; + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromUnassociatedScaledVector4Destructive; + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) => this.InvokedHooks |= RepresentationHooks.FromAssociatedScaledVector4Destructive; + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index 832f3d171..658e32999 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -8,7 +8,6 @@ false SixLabors.ImageSharp.Tests.ProfilingSandbox win-x64 - SixLabors.ImageSharp.Tests.ProfilingSandbox.Program false false @@ -19,12 +18,12 @@ - net8.0;net9.0 + net10.0;net11.0 - net8.0 + net10.0 diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 6850756df..5e21b7cc1 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; +using System.Runtime.Versioning; using System.Text; using CommandLine; using CommandLine.Text; @@ -13,7 +14,8 @@ using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; // See ImageSharp.Benchmarks/LoadResizeSave/README.md -internal class LoadResizeSaveParallelMemoryStress +[SupportedOSPlatform("windows")] +internal sealed class LoadResizeSaveParallelMemoryStress { private LoadResizeSaveParallelMemoryStress() { @@ -206,14 +208,15 @@ internal class LoadResizeSaveParallelMemoryStress StringBuilder bld = new(); bld.AppendLine($"| {nameof(this.TotalSeconds)} | {nameof(this.MegapixelsPerSec)} | {nameof(this.MegapixelsPerSecPerCpu)} |"); bld.AppendLine( + CultureInfo.InvariantCulture, $"| {L(nameof(this.TotalSeconds))} | {L(nameof(this.MegapixelsPerSec))} | {L(nameof(this.MegapixelsPerSecPerCpu))} |"); bld.Append("| "); - bld.AppendFormat(F(nameof(this.TotalSeconds)), this.TotalSeconds); + bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.TotalSeconds)), this.TotalSeconds); bld.Append(" | "); - bld.AppendFormat(F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); + bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.MegapixelsPerSec)), this.MegapixelsPerSec); bld.Append(" | "); - bld.AppendFormat(F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); + bld.AppendFormat(CultureInfo.InvariantCulture, F(nameof(this.MegapixelsPerSecPerCpu)), this.MegapixelsPerSecPerCpu); bld.AppendLine(" |"); return bld.ToString(); @@ -223,7 +226,7 @@ internal class LoadResizeSaveParallelMemoryStress } } - private class CommandLineOptions + private sealed class CommandLineOptions { [Option('a', "async-imagesharp", Required = false, Default = false, HelpText = "Async ImageSharp without benchmark switching")] public bool AsyncImageSharp { get; set; } diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs b/tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs new file mode 100644 index 000000000..e85a3ee97 --- /dev/null +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ProcessorThroughputBenchmark.cs @@ -0,0 +1,262 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics; +using CommandLine; +using CommandLine.Text; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; + +public sealed class ProcessorThroughputBenchmark +{ + private readonly CommandLineOptions options; + private readonly Configuration configuration; + private ulong totalProcessedPixels; + + private ProcessorThroughputBenchmark(CommandLineOptions options) + { + this.options = options; + this.configuration = Configuration.Default.Clone(); + this.configuration.MaxDegreeOfParallelism = options.ProcessorParallelism > 0 + ? options.ProcessorParallelism + : Environment.ProcessorCount; + } + + public static Task RunAsync(string[] args) + { + CommandLineOptions options = null; + if (args.Length > 0) + { + options = CommandLineOptions.Parse(args); + if (options == null) + { + return Task.CompletedTask; + } + } + + options ??= new CommandLineOptions(); + return new ProcessorThroughputBenchmark(options.Normalize()) + .RunAsync(); + } + + private async Task RunAsync() + { + SemaphoreSlim semaphore = new(this.options.ConcurrentRequests); + Console.WriteLine(this.options.Method); + Func action = this.options.Method switch + { + Method.Crop => this.Crop, + Method.Edges => this.DetectEdges, + Method.EdgesCompass => this.EdgesCompass, + Method.DrawImage => this.DrawImage, + Method.BinaryThreshold => this.BinaryThreshold, + Method.Histogram => this.Histogram, + Method.OilPaint => this.OilPaint, + Method.GaussianBlur => this.GaussianBlur, + _ => throw new NotImplementedException(), + }; + + Console.WriteLine(this.options); + Console.WriteLine($"Running {this.options.Method} for {this.options.Seconds} seconds ..."); + TimeSpan runFor = TimeSpan.FromSeconds(this.options.Seconds); + + // inFlight starts at 1 to represent the dispatch loop itself + int inFlight = 1; + TaskCompletionSource drainTcs = new(TaskCreationOptions.RunContinuationsAsynchronously); + + Stopwatch stopwatch = Stopwatch.StartNew(); + while (stopwatch.Elapsed < runFor && !drainTcs.Task.IsCompleted) + { + await semaphore.WaitAsync(); + + if (stopwatch.Elapsed >= runFor) + { + semaphore.Release(); + break; + } + + Interlocked.Increment(ref inFlight); + + _ = ProcessImage(); + + async Task ProcessImage() + { + try + { + if (stopwatch.Elapsed >= runFor || drainTcs.Task.IsCompleted) + { + return; + } + + await Task.Yield(); // "emulate IO", i.e., make sure the processing code is async + ulong pixels = (ulong)action(); + Interlocked.Add(ref this.totalProcessedPixels, pixels); + } + catch (Exception ex) + { + Console.WriteLine(ex); + drainTcs.TrySetException(ex); + } + finally + { + semaphore.Release(); + if (Interlocked.Decrement(ref inFlight) == 0) + { + drainTcs.TrySetResult(); + } + } + } + } + + // Release the dispatch loop's own count; if no work is in flight, this completes immediately + if (Interlocked.Decrement(ref inFlight) == 0) + { + drainTcs.TrySetResult(); + } + + await drainTcs.Task; + stopwatch.Stop(); + + double totalMegaPixels = this.totalProcessedPixels / 1_000_000.0; + double totalSeconds = stopwatch.ElapsedMilliseconds / 1000.0; + double megapixelsPerSec = totalMegaPixels / totalSeconds; + Console.WriteLine($"TotalSeconds: {totalSeconds:F2}"); + Console.WriteLine($"MegaPixelsPerSec: {megapixelsPerSec:F2}"); + } + + private int OilPaint() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, x => x.OilPaint()); + return image.Width * image.Height; + } + + private int DetectEdges() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, x => x.DetectEdges()); + return image.Width * image.Height; + } + + private int EdgesCompass() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, x => x.DetectEdges(EdgeDetectorCompassKernel.Kirsch)); + return image.Width * image.Height; + } + + private int Crop() + { + using Image image = new(this.options.Width, this.options.Height); + Rectangle bounds = image.Bounds; + bounds = new Rectangle(1, 1, bounds.Width - 2, bounds.Height - 2); + image.Clone(this.configuration, x => x.Crop(bounds)).Dispose(); + return image.Width * image.Height; + } + + private int DrawImage() + { + using Image image = new(this.options.Width, this.options.Height); + using Image foreground = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, c => c.DrawImage(foreground, 0.5f)); + return image.Width * image.Height; + } + + private int BinaryThreshold() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, c => c.BinaryThreshold(0.5f)); + return image.Width * image.Height; + } + + private int Histogram() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, c => c.HistogramEqualization()); + return image.Width * image.Height; + } + + private int GaussianBlur() + { + using Image image = new(this.options.Width, this.options.Height); + image.Mutate(this.configuration, c => c.GaussianBlur()); + return image.Width * image.Height; + } + + private enum Method + { + Edges, + EdgesCompass, + Crop, + DrawImage, + BinaryThreshold, + Histogram, + OilPaint, + GaussianBlur, + } + + private sealed class CommandLineOptions + { + [Option('m', "method", Required = false, Default = Method.Edges, HelpText = "The stress test method to run (Edges, Crop)")] + public Method Method { get; set; } = Method.Edges; + + [Option('p', "processor-parallelism", Required = false, Default = -1, HelpText = "Level of parallelism for the image processor")] + public int ProcessorParallelism { get; set; } = -1; + + [Option('c', "concurrent-requests", Required = false, Default = -1, HelpText = "Number of concurrent in-flight requests")] + public int ConcurrentRequests { get; set; } = -1; + + [Option('w', "width", Required = false, Default = 4000, HelpText = "Width of the test image")] + public int Width { get; set; } = 4000; + + [Option('h', "height", Required = false, Default = 4000, HelpText = "Height of the test image")] + public int Height { get; set; } = 4000; + + [Option('s', "seconds", Required = false, Default = 5, HelpText = "Duration of the stress test in seconds")] + public int Seconds { get; set; } = 5; + + public override string ToString() => string.Join( + "|", + $"method: {this.Method}", + $"processor-parallelism: {this.ProcessorParallelism}", + $"concurrent-requests: {this.ConcurrentRequests}", + $"width: {this.Width}", + $"height: {this.Height}", + $"seconds: {this.Seconds}"); + + public CommandLineOptions Normalize() + { + if (this.ProcessorParallelism < 0) + { + this.ProcessorParallelism = Environment.ProcessorCount; + } + + if (this.ConcurrentRequests < 0) + { + this.ConcurrentRequests = Environment.ProcessorCount; + } + + return this; + } + + public static CommandLineOptions Parse(string[] args) + { + CommandLineOptions result = null; + using Parser parser = new(settings => settings.CaseInsensitiveEnumValues = true); + ParserResult parserResult = parser.ParseArguments(args).WithParsed(o => + { + result = o; + }); + + if (result == null) + { + Console.WriteLine(HelpText.RenderUsageText(parserResult)); + } + + return result; + } + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 8ba862560..96eb86793 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -1,76 +1,44 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Reflection; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; -using Xunit.Abstractions; +using SixLabors.ImageSharp.Tests.ProfilingSandbox; +using Xunit; // in this file, comments are used for disabling stuff for local execution #pragma warning disable SA1515 #pragma warning disable SA1512 -namespace SixLabors.ImageSharp.Tests.ProfilingSandbox; +// LoadResizeSaveParallelMemoryStress.Run(args); +// ParallelProcessingStress.RunExperiment(args); +// ParallelProcessingStress.Run(args); +await ProcessorThroughputBenchmark.RunAsync(args); -public class Program -{ - private class ConsoleOutput : ITestOutputHelper - { - public void WriteLine(string message) => Console.WriteLine(message); - - public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); - } +// RunToVector4ProfilingTest(); +// RunResizeProfilingTest(); - /// - /// The main entry point. Useful for executing benchmarks and performance unit tests manually, - /// when the IDE test runners lack some of the functionality. Eg.: it's not possible to run JetBrains memory profiler for unit tests. - /// - /// - /// The arguments to pass to the program. - /// - public static void Main(string[] args) - { - try - { - LoadResizeSaveParallelMemoryStress.Run(args); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } +static void RunResizeProfilingTest() +{ + ResizeProfilingBenchmarks test = new(new ConsoleOutput()); + test.ResizeBicubic(4000, 4000); +} - // RunJpegEncoderProfilingTests(); - // RunJpegColorProfilingTests(); - // RunDecodeJpegProfilingTests(); - // RunToVector4ProfilingTest(); - // RunResizeProfilingTest(); +static void RunToVector4ProfilingTest() +{ + PixelOperationsTests.Rgba32_OperationsTests tests = new(new ConsoleOutput()); + tests.Benchmark_ToVector4(); +} - // Console.ReadLine(); - } +sealed class ConsoleOutput : ITestOutputHelper +{ + public void Write(string message) => Console.Write(message); - private static Version GetNetCoreVersion() - { - Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; - Console.WriteLine(assembly.Location); - string[] assemblyPath = assembly.Location.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries); - int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); - if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) - { - return Version.Parse(assemblyPath[netCoreAppIndex + 1]); - } + public void Write(string format, params object[] args) => Console.Write(format, args); - return null; - } + public void WriteLine(string message) => Console.WriteLine(message); - private static void RunResizeProfilingTest() - { - ResizeProfilingBenchmarks test = new(new ConsoleOutput()); - test.ResizeBicubic(4000, 4000); - } + public void WriteLine(string format, params object[] args) => Console.WriteLine(format, args); - private static void RunToVector4ProfilingTest() - { - PixelOperationsTests.Rgba32_OperationsTests tests = new(new ConsoleOutput()); - tests.Benchmark_ToVector4(); - } + public string Output { get; } } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs index 2eb821b61..889746453 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs @@ -35,6 +35,20 @@ public partial class ColorTests Assert.Equal(source, data); } + [Fact] + public void Rgba32P() + { + Rgba32P source = new(64, 32, 16, 128); + + // Act: + Color color = Color.FromPixel(source); + + // Assert: + Assert.Equal(PixelAlphaRepresentation.Associated, color.AlphaRepresentation); + Assert.Equal(source.ToScaledVector4(), color.ToScaledVector4()); + Assert.Equal(source, color.ToPixel()); + } + [Fact] public void Argb32() { diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index 2e2f0d07d..66537007f 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -114,6 +114,65 @@ public partial class ColorTests Assert.Equal(new L8(255), color.ToPixel()); } + [Fact] + public void AssociatedVectorConstructor() + { + Rgba32P expected = new(64, 32, 16, 128); + + // Act: + Color color = Color.FromScaledVector(expected.ToScaledVector4(), PixelAlphaRepresentation.Associated); + + // Assert: + Assert.Equal(PixelAlphaRepresentation.Associated, color.AlphaRepresentation); + Assert.Equal(expected.ToScaledVector4(), color.ToScaledVector4()); + Assert.Equal(expected, color.ToPixel()); + Assert.Equal(new Rgba32(128, 64, 32, 128), color.ToPixel()); + } + + [Fact] + public void UnassociatedPixelToAssociatedPixel() + { + Color color = Color.FromPixel(new Rgba32(128, 64, 32, 128)); + + // Act: + Rgba32P actual = color.ToPixel(); + + // Assert: + Assert.Equal(new Rgba32P(64, 32, 16, 128), actual); + } + + [Fact] + public void AssociatedVectorSpanConstructor() + { + Rgba32P expected = new(64, 32, 16, 128); + Vector4[] source = [expected.ToScaledVector4()]; + Color[] destination = new Color[source.Length]; + + // Act: + Color.FromScaledVector(source, destination, PixelAlphaRepresentation.Associated); + + // Assert: + Assert.Equal(PixelAlphaRepresentation.Associated, destination[0].AlphaRepresentation); + Assert.Equal(expected.ToScaledVector4(), destination[0].ToScaledVector4()); + Assert.Equal(expected, destination[0].ToPixel()); + } + + [Fact] + public void AssociatedPixelSpanRoundTrip() + { + Rgba32P[] source = [new(64, 32, 16, 128), new(24, 12, 6, 96)]; + Color[] colors = new Color[source.Length]; + Rgba32P[] destination = new Rgba32P[source.Length]; + + // Act: + Color.FromPixel(source, colors); + Color.ToPixel(colors, destination); + + // Assert: + Assert.Equal(source, destination); + Assert.All(colors, color => Assert.Equal(PixelAlphaRepresentation.Associated, color.AlphaRepresentation)); + } + [Fact] public void GenericPixelRoundTrip() { diff --git a/tests/ImageSharp.Tests/Color/ColorTests.cs b/tests/ImageSharp.Tests/Color/ColorTests.cs index c482fc998..6c205888a 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests; @@ -18,6 +19,58 @@ public partial class ColorTests Assert.Equal(expected, c2.ToPixel()); } + [Fact] + public void WithAlphaPreservesAssociatedRepresentation() + { + Color color = Color.FromPixel(new Rgba32P(64, 32, 16, 128)); + + // Act: + Color actual = color.WithAlpha(64F / 255F); + + // Assert: + Assert.Equal(PixelAlphaRepresentation.Associated, actual.AlphaRepresentation); + Assert.Equal(new Rgba32P(32, 16, 8, 64), actual.ToPixel()); + Assert.Equal(new Rgba32(128, 64, 32, 64), actual.ToPixel()); + } + + [Fact] + public void ToHexConvertsAssociatedRepresentation() + { + Color color = Color.FromPixel(new Rgba32P(64, 32, 16, 128)); + + Assert.Equal("80402080", color.ToHex()); + } + + [Fact] + public void ToScaledVector4ConvertsUnassociatedToAssociated() + { + Color color = Color.FromScaledVector(new Vector4(1F, 0.5F, 0.25F, 0.5F)); + + Vector4 actual = color.ToScaledVector4(PixelAlphaRepresentation.Associated); + + Assert.Equal(new Vector4(0.5F, 0.25F, 0.125F, 0.5F), actual); + } + + [Fact] + public void ToScaledVector4ConvertsAssociatedToUnassociated() + { + Color color = Color.FromScaledVector(new Vector4(0.5F, 0.25F, 0.125F, 0.5F), PixelAlphaRepresentation.Associated); + + Vector4 actual = color.ToScaledVector4(PixelAlphaRepresentation.Unassociated); + + Assert.Equal(new Vector4(1F, 0.5F, 0.25F, 0.5F), actual); + } + + [Fact] + public void EqualityDoesNotDependOnInternalAssociationStorage() + { + Color fromPixel = Color.FromPixel(new Rgba32P(64, 32, 16, 128)); + Color fromVector = Color.FromScaledVector(fromPixel.ToScaledVector4(), PixelAlphaRepresentation.Associated); + + Assert.Equal(fromPixel, fromVector); + Assert.Equal(fromPixel.GetHashCode(), fromVector.GetHashCode()); + } + [Theory] [InlineData(false)] [InlineData(true)] diff --git a/tests/ImageSharp.Tests/Color/RgbaDouble.cs b/tests/ImageSharp.Tests/Color/RgbaDouble.cs index 76fdf365c..2487d5fde 100644 --- a/tests/ImageSharp.Tests/Color/RgbaDouble.cs +++ b/tests/ImageSharp.Tests/Color/RgbaDouble.cs @@ -106,6 +106,47 @@ public struct RgbaDouble : IPixel /// public static PixelOperations CreatePixelOperations() => new(); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() + { + Vector4 vector = this.ToScaledVector4(); + Numerics.Premultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() => this.ToVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToAssociatedScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaDouble FromUnassociatedScaledVector4(Vector4 source) => FromScaledVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaDouble FromAssociatedScaledVector4(Vector4 source) + { + Numerics.UnPremultiply(ref source); + return FromScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaDouble FromUnassociatedVector4(Vector4 source) => FromVector4(source); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RgbaDouble FromAssociatedVector4(Vector4 source) => FromAssociatedScaledVector4(source); + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RgbaDouble FromScaledVector4(Vector4 source) => FromVector4(source); diff --git a/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs b/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs index 6c56dc682..c171b725a 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs @@ -6,7 +6,6 @@ using SixLabors.ImageSharp.ColorProfiles; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using Wacton.Unicolour; using Wacton.Unicolour.Icc; -using Xunit.Abstractions; using Rgb = SixLabors.ImageSharp.ColorProfiles.Rgb; namespace SixLabors.ImageSharp.Tests.ColorProfiles.Icc; @@ -42,7 +41,7 @@ public class ColorProfileConverterTests(ITestOutputHelper testOutputHelper) [InlineData(TestIccProfiles.RommRgb, TestIccProfiles.StandardRgbV4)] // CMYK -> LAB -> CMYK (different bit depth v2 LUTs, 16-bit vs 8-bit) [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.StandardRgbV2, 0.0005)] // CMYK -> LAB -> XYZ -> RGB (different LUT tags, A2B vs TRC) --- tolerance slightly higher due to difference in inverse curve implementation [InlineData(TestIccProfiles.StandardRgbV2, TestIccProfiles.Fogra39)] // RGB -> XYZ -> LAB -> CMYK (different LUT tags, TRC vs A2B) - public void CanConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.00005) + public void CanConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.000005) { List actual = Inputs.ConvertAll(input => GetActualTargetValues(input, sourceProfile, targetProfile)); AssertConversion(sourceProfile, targetProfile, actual, tolerance, testOutputHelper); @@ -63,7 +62,7 @@ public class ColorProfileConverterTests(ITestOutputHelper testOutputHelper) [InlineData(TestIccProfiles.Fogra39, TestIccProfiles.StandardRgbV2, 0.0005)] // CMYK -> LAB -> XYZ -> RGB (different LUT tags, A2B vs TRC) --- tolerance slightly higher due to difference in inverse curve implementation [InlineData(TestIccProfiles.StandardRgbV2, TestIccProfiles.Fogra39)] // RGB -> XYZ -> LAB -> CMYK (different LUT tags, TRC vs A2B) [InlineData(TestIccProfiles.Issue129, TestIccProfiles.StandardRgbV4)] // CMYK -> LAB -> -> XYZ -> RGB - public void CanBulkConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.00005) + public void CanBulkConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.000005) { List actual = GetBulkActualTargetValues(Inputs, sourceProfile, targetProfile); AssertConversion(sourceProfile, targetProfile, actual, tolerance, testOutputHelper); @@ -189,7 +188,7 @@ public class ColorProfileConverterTests(ITestOutputHelper testOutputHelper) { case IccColorSpaceType.Cmyk: { - Span inputSpan = inputs.Select(x => new Cmyk(new Vector4(x))).ToArray(); + Span inputSpan = [.. inputs.Select(x => new Cmyk(new Vector4(x)))]; switch (targetDataSpace) { @@ -214,7 +213,7 @@ public class ColorProfileConverterTests(ITestOutputHelper testOutputHelper) case IccColorSpaceType.Rgb: { - Span inputSpan = inputs.Select(x => new Rgb(new Vector3(x))).ToArray(); + Span inputSpan = [.. inputs.Select(x => new Rgb(new Vector3(x)))]; switch (targetDataSpace) { diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbAndHslConversionTest.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbAndHslConversionTest.cs index e5874c3d1..7ec72a36e 100644 --- a/tests/ImageSharp.Tests/ColorProfiles/RgbAndHslConversionTest.cs +++ b/tests/ImageSharp.Tests/ColorProfiles/RgbAndHslConversionTest.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Tests.ColorProfiles; /// /// Test data generated using: /// -/// +/// /// [Trait("Color", "Conversion")] public class RgbAndHslConversionTest diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbWorkingSpaceTests.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbWorkingSpaceTests.cs new file mode 100644 index 000000000..3896418e8 --- /dev/null +++ b/tests/ImageSharp.Tests/ColorProfiles/RgbWorkingSpaceTests.cs @@ -0,0 +1,122 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces; + +namespace SixLabors.ImageSharp.Tests.ColorProfiles; + +/// +/// Tests the class. +/// +[Trait("Color", "Conversion")] +public class RgbWorkingSpaceTests +{ + private static readonly ApproximateFloatComparer TolerantComparer = new(1e-5F); + + public static readonly TheoryData WorkingSpaces = new() + { + KnownRgbWorkingSpaces.SRgb, + KnownRgbWorkingSpaces.SRgbSimplified, + KnownRgbWorkingSpaces.Rec709, + KnownRgbWorkingSpaces.Rec2020, + KnownRgbWorkingSpaces.ECIRgbv2, + KnownRgbWorkingSpaces.AdobeRgb1998, + KnownRgbWorkingSpaces.ApplesRgb, + KnownRgbWorkingSpaces.BestRgb, + KnownRgbWorkingSpaces.BetaRgb, + KnownRgbWorkingSpaces.BruceRgb, + KnownRgbWorkingSpaces.CIERgb, + KnownRgbWorkingSpaces.ColorMatchRgb, + KnownRgbWorkingSpaces.DonRgb4, + KnownRgbWorkingSpaces.EktaSpacePS5, + KnownRgbWorkingSpaces.NTSCRgb, + KnownRgbWorkingSpaces.PALSECAMRgb, + KnownRgbWorkingSpaces.ProPhotoRgb, + KnownRgbWorkingSpaces.SMPTECRgb, + KnownRgbWorkingSpaces.WideGamutRgb + }; + + [Fact] + public void RgbWorkingSpaceEqualityRequiresSameConcreteType() + { + RgbWorkingSpace sRgb = KnownRgbWorkingSpaces.SRgb; + RgbWorkingSpace sRgbSimplified = KnownRgbWorkingSpaces.SRgbSimplified; + RgbWorkingSpace rec709 = KnownRgbWorkingSpaces.Rec709; + + Assert.False(sRgb.Equals(sRgbSimplified)); + Assert.False(sRgbSimplified.Equals(sRgb)); + Assert.False(sRgb.Equals(rec709)); + Assert.False(rec709.Equals(sRgb)); + + Assert.NotEqual(sRgb.GetHashCode(), sRgbSimplified.GetHashCode()); + Assert.NotEqual(sRgb.GetHashCode(), rec709.GetHashCode()); + } + + [Fact] + public void RgbWorkingSpaceEqualityMatchesSameConcreteTypeValues() + { + RgbWorkingSpace x = new SRgbWorkingSpace( + KnownRgbWorkingSpaces.SRgb.WhitePoint, + KnownRgbWorkingSpaces.SRgb.ChromaticityCoordinates); + + RgbWorkingSpace y = new SRgbWorkingSpace( + KnownRgbWorkingSpaces.SRgb.WhitePoint, + KnownRgbWorkingSpaces.SRgb.ChromaticityCoordinates); + + Assert.Equal(x, y); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Fact] + public void GammaWorkingSpaceEqualityIncludesGamma() + { + GammaWorkingSpace x = new( + 2.2F, + KnownRgbWorkingSpaces.SRgbSimplified.WhitePoint, + KnownRgbWorkingSpaces.SRgbSimplified.ChromaticityCoordinates); + + GammaWorkingSpace y = new( + 1.8F, + KnownRgbWorkingSpaces.SRgbSimplified.WhitePoint, + KnownRgbWorkingSpaces.SRgbSimplified.ChromaticityCoordinates); + + Assert.NotEqual(x, y); + Assert.NotEqual(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [MemberData(nameof(WorkingSpaces))] + public void CompressAndExpand_RoundTripsWithTolerance(RgbWorkingSpace workingSpace) + { + Vector4[] linear = + [ + new(0F, .001F, .18F, 1F), // Endpoint, below the sRGB breakpoint, common middle gray, and opaque alpha. + new(.0031308F, .25F, .5F, .75F), // sRGB linear/gamma breakpoint with interior values and partial alpha. + new(.75F, .5F, .25F, .5F), // Reversed interior values ensure channel order does not hide errors. + new(1F, .8F, .2F, .25F) // Upper endpoint, high/low interior values, and low alpha. + ]; + + Vector4[] expectedCompressed = new Vector4[linear.Length]; + + for (int i = 0; i < linear.Length; i++) + { + Vector4 compressed = workingSpace.Compress(linear[i]); + Vector4 expanded = workingSpace.Expand(compressed); + + expectedCompressed[i] = compressed; + + Assert.Equal(linear[i], expanded, TolerantComparer); + } + + Vector4[] actualCompressed = [.. linear]; + workingSpace.Compress(actualCompressed); + + Assert.Equal(expectedCompressed, actualCompressed, TolerantComparer); + + workingSpace.Expand(actualCompressed); + + Assert.Equal(linear, actualCompressed, TolerantComparer); + } +} diff --git a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs index 8421c1fd7..943c643c1 100644 --- a/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/EncoderExtensionsTests.cs @@ -20,7 +20,7 @@ public class EncoderExtensionsTests [Fact] public void GetString_Buffer_ReturnsString() { - ReadOnlySpan buffer = new(new byte[] { 73, 109, 97, 103, 101, 83, 104, 97, 114, 112 }); + ReadOnlySpan buffer = new([73, 109, 97, 103, 101, 83, 104, 97, 114, 112]); string result = Encoding.UTF8.GetString(buffer); diff --git a/tests/ImageSharp.Tests/Common/NumericsTests.cs b/tests/ImageSharp.Tests/Common/NumericsTests.cs index ebee57060..6f35118e2 100644 --- a/tests/ImageSharp.Tests/Common/NumericsTests.cs +++ b/tests/ImageSharp.Tests/Common/NumericsTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using Xunit.Abstractions; - namespace SixLabors.ImageSharp.Tests.Common; public class NumericsTests @@ -55,4 +53,5 @@ public class NumericsTests Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})"); } + } diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs index ba37ee166..cbe2e4066 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs @@ -292,7 +292,7 @@ public partial class SimdUtilsTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } [Theory] @@ -303,56 +303,21 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle4 to the generic utils method. - WXYZShuffle4 wxyz = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wxyz), - SimdUtils.Shuffle.MMShuffle2103); - - WZYXShuffle4 wzyx = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wzyx), - SimdUtils.Shuffle.MMShuffle0123); + TestShuffleByte4Channel(size, (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle2103); - YZWXShuffle4 yzwx = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yzwx), - SimdUtils.Shuffle.MMShuffle0321); - - ZYXWShuffle4 zyxw = default; - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, zyxw), - SimdUtils.Shuffle.MMShuffle3012); + TestShuffleByte4Channel(size, (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0123); - DefaultShuffle4 xwyz = new(SimdUtils.Shuffle.MMShuffle2130); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, xwyz), - xwyz.Control); + TestShuffleByte4Channel(size, (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0321); - DefaultShuffle4 yyyy = new(SimdUtils.Shuffle.MMShuffle1111); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, yyyy), - yyyy.Control); + TestShuffleByte4Channel(size, (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3012); - DefaultShuffle4 wwww = new(SimdUtils.Shuffle.MMShuffle3333); - TestShuffleByte4Channel( - size, - (s, d) => SimdUtils.Shuffle4(s.Span, d.Span, wwww), - wwww.Control); + TestShuffleByte4Channel(size, (s, d) => SimdUtils.Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle1230); } FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); } [Theory] @@ -363,38 +328,13 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle3 to the generic utils method. - DefaultShuffle3 zyx = new(SimdUtils.Shuffle.MMShuffle3012); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zyx), - zyx.Control); - - DefaultShuffle3 xyz = new(SimdUtils.Shuffle.MMShuffle3210); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, xyz), - xyz.Control); - - DefaultShuffle3 yyy = new(SimdUtils.Shuffle.MMShuffle3111); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, yyy), - yyy.Control); - - DefaultShuffle3 zzz = new(SimdUtils.Shuffle.MMShuffle3222); - TestShuffleByte3Channel( - size, - (s, d) => SimdUtils.Shuffle3(s.Span, d.Span, zzz), - zzz.Control); + TestShuffleByte3Channel(size, (s, d) => SimdUtils.Shuffle3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3012); } FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); } [Theory] @@ -405,38 +345,19 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IPad3Shuffle4 to the generic utils method. - XYZWPad3Shuffle4 xyzw = default; - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xyzw), - SimdUtils.Shuffle.MMShuffle3210); + TestPad3Shuffle4Channel(size, (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3210); - DefaultPad3Shuffle4 xwyz = new(SimdUtils.Shuffle.MMShuffle2130); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, xwyz), - xwyz.Control); + TestPad3Shuffle4Channel(size, (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle2103); - DefaultPad3Shuffle4 yyyy = new(SimdUtils.Shuffle.MMShuffle1111); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, yyyy), - yyyy.Control); + TestPad3Shuffle4Channel(size, (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0123); - DefaultPad3Shuffle4 wwww = new(SimdUtils.Shuffle.MMShuffle3333); - TestPad3Shuffle4Channel( - size, - (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span, wwww), - wwww.Control); + TestPad3Shuffle4Channel(size, (s, d) => SimdUtils.Pad3Shuffle4(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3012); } FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); } [Theory] @@ -447,38 +368,19 @@ public partial class SimdUtilsTests { int size = FeatureTestRunner.Deserialize(serialized); - // These cannot be expressed as a theory as you cannot - // use RemoteExecutor within generic methods nor pass - // IShuffle4Slice3 to the generic utils method. - XYZWShuffle4Slice3 xyzw = default; - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xyzw), - SimdUtils.Shuffle.MMShuffle3210); + TestShuffle4Slice3Channel(size, (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3210); - DefaultShuffle4Slice3 xwyz = new(SimdUtils.Shuffle.MMShuffle2130); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, xwyz), - xwyz.Control); + TestShuffle4Slice3Channel(size, (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0321); - DefaultShuffle4Slice3 yyyy = new(SimdUtils.Shuffle.MMShuffle1111); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, yyyy), - yyyy.Control); + TestShuffle4Slice3Channel(size, (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle0123); - DefaultShuffle4Slice3 wwww = new(SimdUtils.Shuffle.MMShuffle3333); - TestShuffle4Slice3Channel( - size, - (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span, wwww), - wwww.Control); + TestShuffle4Slice3Channel(size, (s, d) => SimdUtils.Shuffle4Slice3(s.Span, d.Span), SimdUtils.Shuffle.MMShuffle3012); } FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } private static void TestShuffleFloat4Channel( diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs index 36b301264..b4fbf5fd8 100644 --- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs +++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs @@ -3,11 +3,12 @@ using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Common; @@ -133,7 +134,7 @@ public partial class SimdUtilsTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE41); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX); } [Theory] @@ -142,17 +143,41 @@ public partial class SimdUtilsTests count, (s, d) => SimdUtils.ByteToNormalizedFloat(s.Span, d.Span)); + [Fact] + public void VectorMultiplyAddUsesRuntimeOrderAndFusedContract() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunVectorMultiplyAddUsesRuntimeOrderAndFusedContract, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void RunVectorMultiplyAddUsesRuntimeOrderAndFusedContract() + { + Assert.Equal(Vector128.Create(11F), Vector128.MultiplyAddEstimate(Vector128.Create(2F), Vector128.Create(3F), Vector128.Create(5F))); + Assert.Equal(Vector256.Create(11F), Vector256.MultiplyAddEstimate(Vector256.Create(2F), Vector256.Create(3F), Vector256.Create(5F))); + Assert.Equal(Vector512.Create(11F), Vector512.MultiplyAddEstimate(Vector512.Create(2F), Vector512.Create(3F), Vector512.Create(5F))); + + // These associated-alpha components produce an exact midpoint that a separate multiply and add rounds incorrectly. + float left = (68F / byte.MaxValue) * .625F; + float right = 1F - (160F / byte.MaxValue); + float addend = ((50F / byte.MaxValue) + (50F / byte.MaxValue)) * left; + float expected = MathF.FusedMultiplyAdd(left, right, addend); + + Assert.Equal(Vector128.Create(expected), Vector128.FusedMultiplyAdd(Vector128.Create(left), Vector128.Create(right), Vector128.Create(addend))); + Assert.Equal(Vector256.Create(expected), Vector256.FusedMultiplyAdd(Vector256.Create(left), Vector256.Create(right), Vector256.Create(addend))); + Assert.Equal(Vector512.Create(expected), Vector512.FusedMultiplyAdd(Vector512.Create(left), Vector512.Create(right), Vector512.Create(addend))); + } + private static void TestImpl_BulkConvertByteToNormalizedFloat( int count, Action, Memory> convert) { - byte[] source = new Random(count).GenerateRandomByteArray(count); + byte[] source = [.. Enumerable.Range(0, count).Select(i => (byte)i)]; float[] result = new float[count]; - float[] expected = source.Select(b => b / 255f).ToArray(); + // A double-precision oracle keeps this expectation independent from either production implementation. + float[] expected = [.. source.Select(b => (float)(b / (double)byte.MaxValue))]; convert(source, result); - Assert.Equal(expected, result, new ApproximateFloatComparer(1e-5f)); + Assert.Equal(expected, result); } [Theory] @@ -171,7 +196,7 @@ public partial class SimdUtilsTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, count, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512BW | HwIntrinsics.DisableAVX2); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2); } [Theory] @@ -193,6 +218,45 @@ public partial class SimdUtilsTests } } + [Fact] + public void BulkConvertNormalizedFloatToByteRoundsMidpointsAwayFromZero() + { + float[] midpointValues = [.. Enumerable.Range(0, byte.MaxValue).Select(x => (x + 0.5F) / byte.MaxValue)]; + float[] source = new float[1024]; + byte[] expected = new byte[source.Length]; + byte[] actual = new byte[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + int value = i % midpointValues.Length; + source[i] = midpointValues[value]; + expected[i] = (byte)(value + 1); + } + + SimdUtils.NormalizedFloatToByteSaturate(source, actual); + + Assert.Equal(expected, actual); + } + + [Fact] + public void BulkConvertFloatToByteRoundsMidpointsAwayFromZeroAndClampsOverflows() + { + float[] source = new float[1027]; + byte[] expected = new byte[source.Length]; + byte[] actual = new byte[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + float value = (i % 258) - .5F; + source[i] = value; + expected[i] = (byte)Math.Min(byte.MaxValue, Math.Max(0, value + .5F)); + } + + SimdUtils.FloatToByteSaturate(source, actual); + + Assert.Equal(expected, actual); + } + [Theory] [MemberData(nameof(ArbitraryArraySizes))] public void PackFromRgbPlanes_Rgb24(int count) => TestPackFromRgbPlanes( @@ -215,9 +279,9 @@ public partial class SimdUtilsTests return; } - byte[] r = Enumerable.Range(0, 32).Select(x => (byte)x).ToArray(); - byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); - byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); + byte[] r = [.. Enumerable.Range(0, 32).Select(x => (byte)x)]; + byte[] g = [.. Enumerable.Range(100, 32).Select(x => (byte)x)]; + byte[] b = [.. Enumerable.Range(200, 32).Select(x => (byte)x)]; const int padding = 4; Rgb24[] d = new Rgb24[32 + padding]; @@ -249,9 +313,9 @@ public partial class SimdUtilsTests return; } - byte[] r = Enumerable.Range(0, 32).Select(x => (byte)x).ToArray(); - byte[] g = Enumerable.Range(100, 32).Select(x => (byte)x).ToArray(); - byte[] b = Enumerable.Range(200, 32).Select(x => (byte)x).ToArray(); + byte[] r = [.. Enumerable.Range(0, 32).Select(x => (byte)x)]; + byte[] g = [.. Enumerable.Range(100, 32).Select(x => (byte)x)]; + byte[] b = [.. Enumerable.Range(200, 32).Select(x => (byte)x)]; Rgba32[] d = new Rgba32[32]; @@ -304,7 +368,7 @@ public partial class SimdUtilsTests { seed = seed > 0 ? seed : count; float[] source = new Random(seed).GenerateRandomFloatArray(count, -0.2f, 1.2f); - byte[] expected = source.Select(NormalizedFloatToByte).ToArray(); + byte[] expected = [.. source.Select(NormalizedFloatToByte)]; byte[] actual = new byte[count]; convert(source, actual); diff --git a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs index 5ea7afaf8..2cf62c10c 100644 --- a/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Common/StreamExtensionsTests.cs @@ -79,7 +79,7 @@ public class StreamExtensionsTests { public override bool CanSeek => false; - public List Counts = new(); + public List Counts = []; public NonSeekableStream() : base(4) diff --git a/tests/ImageSharp.Tests/Compression/Zlib/ChunkedWriteStreamTests.cs b/tests/ImageSharp.Tests/Compression/Zlib/ChunkedWriteStreamTests.cs new file mode 100644 index 000000000..09b7a46db --- /dev/null +++ b/tests/ImageSharp.Tests/Compression/Zlib/ChunkedWriteStreamTests.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Compression.Zlib; + +namespace SixLabors.ImageSharp.Tests.Compression.Zlib; + +public class ChunkedWriteStreamTests +{ + [Theory] + [InlineData(1)] + [InlineData(7)] + [InlineData(64)] + [InlineData(1000)] + public void Write_EmitsFixedLengthSegments_AndPartialTailOnDispose(int writeSize) + { + const int SegmentLength = 64; + byte[] data = new byte[250]; + new Random(42).NextBytes(data); + + List segments = []; + using (ChunkedWriteStream stream = new(Configuration.Default.MemoryAllocator, SegmentLength, segment => segments.Add(segment.ToArray()))) + { + for (int offset = 0; offset < data.Length; offset += writeSize) + { + stream.Write(data, offset, Math.Min(writeSize, data.Length - offset)); + } + + // Nothing but full segments is emitted before disposal. + Assert.Equal(3, segments.Count); + Assert.All(segments, s => Assert.Equal(SegmentLength, s.Length)); + } + + Assert.Equal(4, segments.Count); + Assert.Equal(250 - (3 * SegmentLength), segments[3].Length); + Assert.Equal(data, segments.SelectMany(s => s).ToArray()); + } + + [Fact] + public void Write_ExactMultipleOfSegmentLength_DoesNotEmitEmptyTail() + { + const int SegmentLength = 16; + byte[] data = new byte[SegmentLength * 3]; + + int count = 0; + using (ChunkedWriteStream stream = new(Configuration.Default.MemoryAllocator, SegmentLength, _ => count++)) + { + stream.Write(data); + } + + Assert.Equal(3, count); + } + + [Fact] + public void WriteByte_FillsSegments() + { + const int SegmentLength = 4; + List segments = []; + using (ChunkedWriteStream stream = new(Configuration.Default.MemoryAllocator, SegmentLength, segment => segments.Add(segment.ToArray()))) + { + for (byte i = 0; i < 6; i++) + { + stream.WriteByte(i); + } + } + + Assert.Equal(2, segments.Count); + Assert.Equal(new byte[] { 0, 1, 2, 3 }, segments[0]); + Assert.Equal(new byte[] { 4, 5 }, segments[1]); + } + + [Fact] + public void Flush_DoesNotEmitPartialSegment() + { + int count = 0; + using (ChunkedWriteStream stream = new(Configuration.Default.MemoryAllocator, 16, _ => count++)) + { + stream.Write(new byte[5]); + stream.Flush(); + Assert.Equal(0, count); + } + + Assert.Equal(1, count); + } + + [Fact] + public void Dispose_WithoutWrites_EmitsNothing() + { + int count = 0; + using (ChunkedWriteStream stream = new(Configuration.Default.MemoryAllocator, 16, _ => count++)) + { + } + + Assert.Equal(0, count); + } +} diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index bd6520084..c0145895d 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -20,7 +20,7 @@ public class ConfigurationTests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 12; + private readonly int expectedDefaultConfigurationCount = 14; public ConfigurationTests() { diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index 1d0fdf62d..7f87111e8 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -293,4 +293,21 @@ public class DrawImageTests appendPixelTypeToFileName: false, appendSourceFileOrDescription: false); } + + [Theory] + [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] + public void DrawImageAnimatedForegroundRepeatCount(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image background = provider.GetImage(); + using Image foreground = Image.Load(TestFile.Create(TestImages.Gif.Giphy).Bytes); + + Size size = new(foreground.Width / 4, foreground.Height / 4); + foreground.Mutate(x => x.Resize(size.Width, size.Height, KnownResamplers.Bicubic)); + + background.Mutate(x => x.DrawImage(foreground, Point.Empty, 1F, 0)); + + background.DebugSaveMultiFrame(provider); + background.CompareToReferenceOutputMultiFrame(provider, ImageComparer.TolerantPercentage(0.01f)); + } } diff --git a/tests/ImageSharp.Tests/Formats/Ani/AniDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Ani/AniDecoderTests.cs new file mode 100644 index 000000000..8ad5a0a2d --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Ani/AniDecoderTests.cs @@ -0,0 +1,210 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Ani; +using SixLabors.ImageSharp.PixelFormats; +using static SixLabors.ImageSharp.Tests.TestImages.Ani; + +namespace SixLabors.ImageSharp.Tests.Formats.Ani; + +[Trait("Format", "Ani")] +[ValidateDisposedMemoryAllocations] +public class AniDecoderTests +{ + /// + /// Verifies that ANI animation steps and embedded CUR resolution variants are flattened with their ANI metadata. + /// + [Theory] + [WithFile(Work, PixelTypes.Rgba32, 17, 1, 6U, 6U)] + [WithFile(MultiFramesInEveryIconChunk, PixelTypes.Rgba32, 54, 3, 3U, 3U)] + [WithFile(Help, PixelTypes.Rgba32, 4, 1, 10U, 12U)] + public void AniDecoder_Decode( + TestImageProvider provider, + int expectedFrameCount, + int variantsPerStep, + uint expectedDisplayRate, + uint expectedFrameDelay) + { + using Image image = provider.GetImage(AniDecoder.Instance); + + Assert.Equal(expectedFrameCount, image.Frames.Count); + Assert.Equal(expectedDisplayRate, image.Metadata.GetAniMetadata().DisplayRate); + + for (int i = 0; i < image.Frames.Count; i++) + { + AniFrameMetadata metadata = image.Frames[i].Metadata.GetAniMetadata(); + + Assert.Equal((i / variantsPerStep) + 1, metadata.SequenceNumber); + Assert.Equal(expectedFrameDelay, metadata.FrameDelay); + Assert.Equal(AniFrameFormat.Cur, metadata.FrameFormat); + Assert.NotEqual(0, (int)metadata.BmpBitsPerPixel); + } + } + + /// + /// Verifies that identification exposes the same flattened ANI frame structure without decoding pixels. + /// + [Theory] + [InlineData(Work, 17)] + [InlineData(MultiFramesInEveryIconChunk, 54)] + [InlineData(Help, 4)] + public void AniDecoder_Identify(string path, int expectedFrameCount) + { + TestFile file = TestFile.Create(path); + using MemoryStream stream = new(file.Bytes, false); + + ImageInfo info = AniDecoder.Instance.Identify(DecoderOptions.Default, stream); + + Assert.Equal(expectedFrameCount, info.FrameMetadataCollection.Count); + + for (int i = 0; i < info.FrameMetadataCollection.Count; i++) + { + Assert.True(info.FrameMetadataCollection[i].GetAniMetadata().SequenceNumber > 0); + } + } + + /// + /// Verifies that invalid sequence metadata follows the ancillary-segment integrity policy. + /// + [Fact] + public void AniDecoder_InvalidSequenceReference_FollowsIntegrityHandling() + { + byte[] data = [.. TestFile.Create(Help).Bytes]; + int sequenceOffset = data.AsSpan().IndexOf("seq "u8); + Assert.True(sequenceOffset >= 0); + + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(sequenceOffset + AniConstants.ChunkHeaderSize), uint.MaxValue); + + using MemoryStream strictStream = new(data, false); + DecoderOptions strict = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => AniDecoder.Instance.Decode(strict, strictStream)); + + using MemoryStream ignoreStream = new(data, false); + DecoderOptions ignore = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreAncillary }; + using Image image = AniDecoder.Instance.Decode(ignore, ignoreStream); + + Assert.Equal(3, image.Frames.Count); + } + + /// + /// Verifies that ignored corrupt resources retain their sequence-table slot. + /// + [Fact] + public void AniDecoder_UnsupportedResource_FollowsIntegrityHandling() + { + byte[] data = [.. TestFile.Create(Work).Bytes]; + int resourceOffset = data.AsSpan().IndexOf("icon"u8); + Assert.True(resourceOffset >= 0); + + int iconTypeOffset = resourceOffset + AniConstants.ChunkHeaderSize + sizeof(ushort); + BinaryPrimitives.WriteUInt16LittleEndian(data.AsSpan(iconTypeOffset), ushort.MaxValue); + + using MemoryStream strictStream = new(data, false); + DecoderOptions strict = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => AniDecoder.Instance.Decode(strict, strictStream)); + + using MemoryStream ignoreStream = new(data, false); + DecoderOptions ignore = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData }; + using Image image = AniDecoder.Instance.Decode(ignore, ignoreStream); + + Assert.Equal(16, image.Frames.Count); + Assert.Equal(2, image.Frames.RootFrame.Metadata.GetAniMetadata().SequenceNumber); + } + + /// + /// Verifies that a malformed rate chunk follows the ancillary-segment integrity policy. + /// + [Fact] + public void AniDecoder_InvalidRateChunk_FollowsIntegrityHandling() + { + byte[] source = [.. TestFile.Create(Help).Bytes]; + int rateOffset = source.AsSpan().IndexOf("rate"u8); + Assert.True(rateOffset >= 0); + + int rateSizeOffset = rateOffset + sizeof(uint); + int rateSize = (int)BinaryPrimitives.ReadUInt32LittleEndian(source.AsSpan(rateSizeOffset)); + int rateEnd = rateOffset + AniConstants.ChunkHeaderSize + rateSize; + byte[] data = new byte[source.Length + 2]; + source.AsSpan(0, rateEnd).CopyTo(data); + source.AsSpan(rateEnd).CopyTo(data.AsSpan(rateEnd + 2)); + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(rateSizeOffset), (uint)rateSize + 2); + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(sizeof(uint)), (uint)data.Length - 8); + + using MemoryStream defaultStream = new(data, false); + using Image image = AniDecoder.Instance.Decode(DecoderOptions.Default, defaultStream); + + Assert.Equal(4, image.Frames.Count); + foreach (ImageFrame frame in image.Frames) + { + Assert.Equal(10U, frame.Metadata.GetAniMetadata().FrameDelay); + } + + using MemoryStream strictStream = new(data, false); + DecoderOptions strict = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => AniDecoder.Instance.Decode(strict, strictStream)); + } + + /// + /// Verifies that oversized control arrays are rejected before allocation and follow ancillary integrity handling. + /// + /// to append a sequence chunk; otherwise, a rate chunk. + [Theory] + [InlineData(false)] + [InlineData(true)] + public void AniDecoder_OversizedControlChunk_FollowsIntegrityHandling(bool sequence) + { + byte[] source = [.. TestFile.Create(Help).Bytes]; + int chunkOffset = (source.Length + 1) & ~1; + int payloadSize = AniConstants.MaxAncillaryChunkSize + sizeof(uint); + byte[] data = new byte[chunkOffset + AniConstants.ChunkHeaderSize + payloadSize]; + source.CopyTo(data, 0); + + ReadOnlySpan identifier = sequence ? "seq "u8 : "rate"u8; + identifier.CopyTo(data.AsSpan(chunkOffset)); + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(chunkOffset + sizeof(uint)), (uint)payloadSize); + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(sizeof(uint)), (uint)data.Length - AniConstants.ChunkHeaderSize); + + using MemoryStream defaultStream = new(data, false); + using Image image = AniDecoder.Instance.Decode(DecoderOptions.Default, defaultStream); + + Assert.Equal(4, image.Frames.Count); + + using MemoryStream strictStream = new(data, false); + DecoderOptions strict = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => AniDecoder.Instance.Decode(strict, strictStream)); + } + + /// + /// Verifies that oversized information text is rejected before allocation and follows ancillary integrity handling. + /// + [Fact] + public void AniDecoder_OversizedInformationText_FollowsIntegrityHandling() + { + byte[] source = [.. TestFile.Create(Help).Bytes]; + int listOffset = (source.Length + 1) & ~1; + int textSize = AniConstants.MaxAncillaryChunkSize + 1; + int paddedTextSize = textSize + (textSize & 1); + int listSize = sizeof(uint) + AniConstants.ChunkHeaderSize + paddedTextSize; + byte[] data = new byte[listOffset + AniConstants.ChunkHeaderSize + listSize]; + source.CopyTo(data, 0); + + "LIST"u8.CopyTo(data.AsSpan(listOffset)); + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(listOffset + sizeof(uint)), (uint)listSize); + "INFO"u8.CopyTo(data.AsSpan(listOffset + AniConstants.ChunkHeaderSize)); + int textOffset = listOffset + AniConstants.ChunkHeaderSize + sizeof(uint); + "INAM"u8.CopyTo(data.AsSpan(textOffset)); + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(textOffset + sizeof(uint)), (uint)textSize); + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(sizeof(uint)), (uint)data.Length - AniConstants.ChunkHeaderSize); + + using MemoryStream defaultStream = new(data, false); + using Image image = AniDecoder.Instance.Decode(DecoderOptions.Default, defaultStream); + + Assert.Equal(4, image.Frames.Count); + + using MemoryStream strictStream = new(data, false); + DecoderOptions strict = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => AniDecoder.Instance.Decode(strict, strictStream)); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Ani/AniEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Ani/AniEncoderTests.cs new file mode 100644 index 000000000..8b0246c50 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Ani/AniEncoderTests.cs @@ -0,0 +1,242 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; +using SixLabors.ImageSharp.Formats.Ani; +using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Icon; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestDataIcc; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using static SixLabors.ImageSharp.Tests.TestImages.Ani; + +namespace SixLabors.ImageSharp.Tests.Formats.Ani; + +[Trait("Format", "Ani")] +[ValidateDisposedMemoryAllocations] +public class AniEncoderTests +{ + /// + /// Verifies that ANI resources, including multi-resolution CUR resources, survive an encode/decode round trip. + /// + [Theory] + [WithFile(Work, PixelTypes.Rgba32)] + [WithFile(MultiFramesInEveryIconChunk, PixelTypes.Rgba32)] + [WithFile(Help, PixelTypes.Rgba32)] + public void AniEncoder_RoundTrips(TestImageProvider provider) + { + using Image image = provider.GetImage(AniDecoder.Instance); + using MemoryStream stream = new(); + + image.Save(stream, new AniEncoder()); + + // The RIFF size covers everything after its identifier and size field. + Assert.Equal(stream.Length - 8, BinaryPrimitives.ReadUInt32LittleEndian(stream.GetBuffer().AsSpan(4, sizeof(uint)))); + + stream.Position = 0; + using Image decoded = Image.Load(stream); + + ImageComparer.Exact.VerifySimilarity(image, decoded); + Assert.Equal(image.Frames.Count, decoded.Frames.Count); + + for (int i = 0; i < image.Frames.Count; i++) + { + AniFrameMetadata expected = image.Frames[i].Metadata.GetAniMetadata(); + AniFrameMetadata actual = decoded.Frames[i].Metadata.GetAniMetadata(); + + Assert.Equal(expected.SequenceNumber, actual.SequenceNumber); + Assert.Equal(expected.FrameDelay, actual.FrameDelay); + Assert.Equal(expected.FrameFormat, actual.FrameFormat); + Assert.Equal(expected.EncodingWidth, actual.EncodingWidth); + Assert.Equal(expected.EncodingHeight, actual.EncodingHeight); + Assert.Equal(expected.Compression, actual.Compression); + Assert.Equal(expected.BmpBitsPerPixel, actual.BmpBitsPerPixel); + Assert.Equal(expected.HotspotX, actual.HotspotX); + Assert.Equal(expected.HotspotY, actual.HotspotY); + Assert.Equal(expected.ColorTable?.ToArray(), actual.ColorTable?.ToArray()); + } + } + + /// + /// Verifies that per-step rates and RIFF information metadata are emitted and decoded. + /// + [Theory] + [WithFile(Work, PixelTypes.Rgba32)] + public void AniEncoder_WritesVariableRatesAndInformation(TestImageProvider provider) + { + using Image image = provider.GetImage(AniDecoder.Instance); + AniMetadata imageMetadata = image.Metadata.GetAniMetadata(); + imageMetadata.Name = "ImageSharp ANI"; + imageMetadata.Artist = "Six Labors"; + + for (int i = 0; i < image.Frames.Count; i++) + { + image.Frames[i].Metadata.GetAniMetadata().FrameDelay = (uint)(i + 1); + } + + using MemoryStream stream = new(); + image.Save(stream, new AniEncoder()); + + Assert.Equal(stream.Length - 8, BinaryPrimitives.ReadUInt32LittleEndian(stream.GetBuffer().AsSpan(4, sizeof(uint)))); + + stream.Position = 0; + using Image decoded = Image.Load(stream); + AniMetadata decodedMetadata = decoded.Metadata.GetAniMetadata(); + + Assert.Equal(imageMetadata.Name, decodedMetadata.Name); + Assert.Equal(imageMetadata.Artist, decodedMetadata.Artist); + + for (int i = 0; i < decoded.Frames.Count; i++) + { + Assert.Equal((uint)(i + 1), decoded.Frames[i].Metadata.GetAniMetadata().FrameDelay); + } + } + + /// + /// Verifies the encoder and decoder paths for embedded ICO and BMP resources. + /// + [Theory] + [InlineData(AniFrameFormat.Ico)] + [InlineData(AniFrameFormat.Bmp)] + public void AniEncoder_RoundTripsOtherFrameFormats(AniFrameFormat frameFormat) + { + using Image image = new(16, 16, Color.Red.ToPixel()); + AniMetadata imageMetadata = image.Metadata.GetAniMetadata(); + imageMetadata.DisplayRate = 6; + imageMetadata.BitCount = 32; + imageMetadata.Planes = 1; + + AniFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetAniMetadata(); + frameMetadata.FrameDelay = 6; + frameMetadata.SequenceNumber = 1; + frameMetadata.FrameFormat = frameFormat; + frameMetadata.Compression = IconFrameCompression.Bmp; + + using MemoryStream stream = new(); + image.Save(stream, new AniEncoder()); + + Assert.Equal(stream.Length - 8, BinaryPrimitives.ReadUInt32LittleEndian(stream.GetBuffer().AsSpan(4, sizeof(uint)))); + + if (frameFormat is AniFrameFormat.Bmp) + { + ReadOnlySpan encoded = stream.GetBuffer().AsSpan(0, (int)stream.Length); + int frameChunkOffset = encoded.IndexOf("icon"u8); + Assert.True(frameChunkOffset >= 0); + + // AF_ICON-clear resources contain a headerless BMP DIB, not a standalone file beginning with BITMAPFILEHEADER. + ReadOnlySpan frameData = encoded[(frameChunkOffset + AniConstants.ChunkHeaderSize)..]; + Assert.False(frameData.StartsWith("BM"u8)); + } + + stream.Position = 0; + using Image decoded = Image.Load(stream); + + ImageComparer.Exact.VerifySimilarity(image, decoded); + Assert.Equal(frameFormat, decoded.Frames.RootFrame.Metadata.GetAniMetadata().FrameFormat); + } + + /// + /// Verifies that metadata suppression is propagated to every embedded resource encoder. + /// + [Theory] + [InlineData(AniFrameFormat.Ico)] + [InlineData(AniFrameFormat.Cur)] + [InlineData(AniFrameFormat.Bmp)] + public void AniEncoder_SkipMetadataPropagatesToEmbeddedEncoder(AniFrameFormat frameFormat) + { + using Image image = new(16, 16, Color.Red.ToPixel()); + image.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.ProfileRandomArray); + + AniMetadata imageMetadata = image.Metadata.GetAniMetadata(); + imageMetadata.BitCount = 32; + imageMetadata.Planes = 1; + + AniFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetAniMetadata(); + frameMetadata.FrameFormat = frameFormat; + frameMetadata.Compression = IconFrameCompression.Bmp; + frameMetadata.BmpBitsPerPixel = BmpBitsPerPixel.Bit32; + + using MemoryStream stream = new(); + image.Save(stream, new AniEncoder { SkipMetadata = true }); + + ReadOnlySpan encoded = stream.GetBuffer().AsSpan(0, (int)stream.Length); + int frameChunkOffset = encoded.IndexOf("icon"u8); + Assert.True(frameChunkOffset >= 0); + + ReadOnlySpan resource = encoded[(frameChunkOffset + AniConstants.ChunkHeaderSize)..]; + int dibOffset = 0; + + if (frameFormat is not AniFrameFormat.Bmp) + { + dibOffset = checked((int)BinaryPrimitives.ReadUInt32LittleEndian(resource[(IconDir.Size + IconDirEntry.Size - sizeof(uint))..])); + } + + // Metadata-free ICO/CUR bitmaps use BITMAPINFOHEADER, while raw transparent ANI bitmaps require BITMAPV4HEADER. + int expectedHeaderSize = frameFormat is AniFrameFormat.Bmp ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3; + Assert.Equal(expectedHeaderSize, BinaryPrimitives.ReadInt32LittleEndian(resource[dibOffset..])); + } + + /// + /// Verifies that an independent frame cannot collide with an explicit sequence group. + /// + [Fact] + public void AniEncoder_NonPositiveSequenceDoesNotCollideWithExplicitGroup() + { + using Image image = new(16, 16, Color.Red.ToPixel()); + image.Frames.AddFrame(image.Frames.RootFrame); + image.Frames[1].Metadata.GetAniMetadata().SequenceNumber = 1; + + using MemoryStream stream = new(); + image.Save(stream, new AniEncoder()); + + stream.Position = 0; + using Image decoded = Image.Load(stream); + + Assert.Equal(2, decoded.Frames.Count); + Assert.Equal(1, decoded.Frames[0].Metadata.GetAniMetadata().SequenceNumber); + Assert.Equal(2, decoded.Frames[1].Metadata.GetAniMetadata().SequenceNumber); + } + + /// + /// Verifies that an explicit source sequence is preserved as an identity table after playback-order expansion. + /// + [Fact] + public void AniEncoder_PreservesExplicitSequence() + { + using Image image = new(16, 16, Color.Red.ToPixel()); + image.Frames.AddFrame(image.Frames.RootFrame); + image.Metadata.GetAniMetadata().Flags = AniHeaderFlags.IsIcon | AniHeaderFlags.ContainsSequence; + + using MemoryStream stream = new(); + image.Save(stream, new AniEncoder()); + + ReadOnlySpan data = stream.GetBuffer().AsSpan(0, (int)stream.Length); + int sequenceOffset = data.IndexOf("seq "u8); + + Assert.True(sequenceOffset >= 0); + Assert.Equal((uint)(2 * sizeof(uint)), BinaryPrimitives.ReadUInt32LittleEndian(data[(sequenceOffset + sizeof(uint))..])); + Assert.Equal(0U, BinaryPrimitives.ReadUInt32LittleEndian(data[(sequenceOffset + AniConstants.ChunkHeaderSize)..])); + Assert.Equal(1U, BinaryPrimitives.ReadUInt32LittleEndian(data[(sequenceOffset + AniConstants.ChunkHeaderSize + sizeof(uint))..])); + + stream.Position = 0; + using Image decoded = Image.Load(stream); + + Assert.True(decoded.Metadata.GetAniMetadata().Flags.HasFlag(AniHeaderFlags.ContainsSequence)); + } + + /// + /// Verifies that unsupported public frame metadata is rejected before any container data is written. + /// + [Fact] + public void AniEncoder_UnsupportedFrameFormatThrowsBeforeWriting() + { + using Image image = new(16, 16); + image.Frames.RootFrame.Metadata.GetAniMetadata().FrameFormat = (AniFrameFormat)byte.MaxValue; + + using MemoryStream stream = new(); + + Assert.Throws(() => image.Save(stream, new AniEncoder())); + Assert.Equal(0, stream.Length); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Ani/AniMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Ani/AniMetadataTests.cs new file mode 100644 index 000000000..676556827 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Ani/AniMetadataTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Ani; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Tests.Formats.Ani; + +[Trait("Format", "Ani")] +public class AniMetadataTests +{ + /// + /// Verifies that resizing scales the ANI-owned encoding dimensions exactly once. + /// + [Fact] + public void AfterFrameApply_ScalesEncodingDimensionsOnce() + { + using Image image = new(32, 32); + AniFrameMetadata metadata = image.Frames.RootFrame.Metadata.GetAniMetadata(); + metadata.EncodingWidth = 32; + metadata.EncodingHeight = 32; + metadata.FrameFormat = AniFrameFormat.Cur; + + image.Mutate(context => context.Resize(64, 64)); + + AniFrameMetadata resized = image.Frames.RootFrame.Metadata.GetAniMetadata(); + Assert.Equal((byte)64, resized.EncodingWidth); + Assert.Equal((byte)64, resized.EncodingHeight); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 94cfe85ee..e85c6bcdf 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -91,13 +91,22 @@ public class BmpDecoderTests { using Image image = provider.GetImage(BmpDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToReferenceOutput(provider); } [Theory] [WithFile(Bit16Inverted, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_16Bit_Inverted(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(BmpDecoder.Instance); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + } + + [Theory] [WithFile(Bit8Inverted, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider) + public void BmpDecoder_CanDecode_8Bit_Inverted(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(BmpDecoder.Instance); @@ -156,7 +165,7 @@ public class BmpDecoderTests { using Image image = provider.GetImage(BmpDecoder.Instance); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder(BmpFormat.Instance)); } [Theory] @@ -186,12 +195,12 @@ public class BmpDecoderTests public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta(TestImageProvider provider) where TPixel : unmanaged, IPixel { - RleSkippedPixelHandling skippedPixelHandling = TestEnvironment.IsWindows ? RleSkippedPixelHandling.Black : RleSkippedPixelHandling.FirstColorOfPalette; + RleSkippedPixelHandling skippedPixelHandling = RleSkippedPixelHandling.Black; BmpDecoderOptions options = new() { RleSkippedPixelHandling = skippedPixelHandling }; using Image image = provider.GetImage(BmpDecoder.Instance, options); image.DebugSave(provider); - image.CompareToOriginal(provider); + image.CompareToReferenceOutput(provider); } [Theory] @@ -224,8 +233,8 @@ public class BmpDecoderTests } } + // An RLE-compressed image that uses “delta” codes, to skip over some pixels. [Theory] - [WithFile(RLE8Cut, PixelTypes.Rgba32)] [WithFile(RLE8Delta, PixelTypes.Rgba32)] public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder(TestImageProvider provider) where TPixel : unmanaged, IPixel @@ -236,11 +245,21 @@ public class BmpDecoderTests image.CompareToOriginal(provider, MagickReferenceDecoder.Png); } + // An RLE-compressed image that uses “delta” codes, and early EOL & EOBMP markers, to skip over some pixels. + [Theory] + [WithFile(RLE8Cut, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDeltaAndEOL_MagickRefDecoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; + using Image image = provider.GetImage(BmpDecoder.Instance, options); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + } + [Theory] [WithFile(RLE8, PixelTypes.Rgba32, false)] - [WithFile(RLE8Inverted, PixelTypes.Rgba32, false)] [WithFile(RLE8, PixelTypes.Rgba32, true)] - [WithFile(RLE8Inverted, PixelTypes.Rgba32, true)] public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider, bool enforceDiscontiguousBuffers) where TPixel : unmanaged, IPixel { @@ -255,6 +274,25 @@ public class BmpDecoderTests image.CompareToOriginal(provider, MagickReferenceDecoder.Png); } + [Theory] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, false)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_Inverted(TestImageProvider provider, bool enforceDiscontiguousBuffers) + where TPixel : unmanaged, IPixel + { + if (enforceDiscontiguousBuffers) + { + provider.LimitAllocatorBufferCapacity().InBytesSqrt(400); + } + + BmpDecoderOptions options = new() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }; + using Image image = provider.GetImage(BmpDecoder.Instance, options); + image.DebugSave(provider); + + // The Reference decoder does not support decoding compressed bmp which are inverted (with negative height). + image.CompareToReferenceOutput(provider); + } + [Theory] [WithFile(RLE24, PixelTypes.Rgba32, false)] [WithFile(RLE24Cut, PixelTypes.Rgba32, false)] @@ -571,4 +609,56 @@ public class BmpDecoderTests }); Assert.IsType(ex.InnerException); } + + // https://github.com/SixLabors/ImageSharp/issues/3067 + [Fact] + public void BmpDecoder_ThrowsException_Issue3067() + { + // Construct minimal BMP with bitsPerPixel = 0 + byte[] bmp = new byte[54]; + bmp[0] = (byte)'B'; + bmp[1] = (byte)'M'; + BitConverter.GetBytes(54).CopyTo(bmp, 2); + BitConverter.GetBytes(54).CopyTo(bmp, 10); + BitConverter.GetBytes(40).CopyTo(bmp, 14); + BitConverter.GetBytes(1).CopyTo(bmp, 18); + BitConverter.GetBytes(1).CopyTo(bmp, 22); + BitConverter.GetBytes((short)1).CopyTo(bmp, 26); + BitConverter.GetBytes((short)0).CopyTo(bmp, 28); // bitsPerPixel = 0 + + using MemoryStream stream = new(bmp); + + Assert.Throws(() => + { + using Image image = BmpDecoder.Instance.Decode(DecoderOptions.Default, stream); + }); + } + + // https://github.com/SixLabors/ImageSharp/issues/3074 + [Fact] + public void BmpDecoder_ThrowsException_Issue3074() + { + // Crafted BMP: pixel data offset = 0x7FFFFFFF, actual file = 35 bytes + byte[] data = + [ + 0x42, 0x4D, // "BM" signature + 0x3A, 0x00, 0x00, 0x00, // file size: 58 + 0x00, 0x00, 0x00, 0x00, // reserved + 0xFF, 0xFF, 0xFF, 0x7F, // pixel offset: 0x7FFFFFFF (2,147,483,647) + 0x28, 0x00, 0x00, 0x00, // DIB header size: 40 + 0x01, 0x00, 0x00, 0x00, // width: 1 + 0x01, 0xFF, 0x00, 0x00, // height: 65281 + 0x01, 0x00, // color planes: 1 + 0x08, 0x00, // bits per pixel: 8 + 0x00, 0x00, 0x00, 0x00, // compression: RGB + 0x00, 0x00, 0x00 // (truncated) + ]; + + using MemoryStream stream = new(data); + + Assert.Throws(() => + { + using Image image = Image.Load(stream); + }); + } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 5ebcc8bb9..6bd7e0103 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -292,7 +292,7 @@ public class BmpEncoderTests [Theory] [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) + public void Encode_8BitColor_WithHexadecatreeQuantizer(TestImageProvider provider) where TPixel : unmanaged, IPixel { if (!TestEnvironment.Is64BitProcess) @@ -304,7 +304,7 @@ public class BmpEncoderTests BmpEncoder encoder = new() { BitsPerPixel = BmpBitsPerPixel.Bit8, - Quantizer = new OctreeQuantizer() + Quantizer = new HexadecatreeQuantizer() }; string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); @@ -385,7 +385,7 @@ public class BmpEncoderTests { BitsPerPixel = bitsPerPixel, SupportTransparency = false, - Quantizer = KnownQuantizers.Octree + Quantizer = KnownQuantizers.Hexadecatree }; image.SaveAsBmp(reencodedStream, encoder); reencodedStream.Seek(0, SeekOrigin.Begin); @@ -478,7 +478,7 @@ public class BmpEncoderTests { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency, - Quantizer = quantizer ?? KnownQuantizers.Octree + Quantizer = quantizer ?? KnownQuantizers.Hexadecatree }; // Does DebugSave & load reference CompareToReferenceInput(): diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs index 64564ae1d..bbfa4ac59 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpMetadataTests.cs @@ -1,8 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming @@ -46,7 +49,7 @@ public class BmpMetadataTests } [Theory] - [WithFile(IccProfile, PixelTypes.Rgba32)] + [WithFile(TestImages.Bmp.IccProfile, PixelTypes.Rgba32)] public void Decoder_CanReadColorProfile(TestImageProvider provider) where TPixel : unmanaged, IPixel { @@ -56,4 +59,40 @@ public class BmpMetadataTests Assert.NotNull(metaData.IccProfile); Assert.Equal(16, metaData.IccProfile.Entries.Length); } + + [Fact] + public void Identify_MalformedIccProfile_IgnoresNonCriticalErrorsByDefault() + { + ImageInfo info = Image.Identify(CreateBmpWithMalformedIccProfile()); + Assert.Equal(1, info.Width); + Assert.Equal(1, info.Height); + } + + [Fact] + public void Decode_MalformedIccProfile_IgnoresNonCriticalErrorsByDefault() + { + using Image image = Image.Load(CreateBmpWithMalformedIccProfile()); + Assert.Equal(1, image.Width); + Assert.Equal(1, image.Height); + } + + [Fact] + public void Identify_MalformedIccProfile_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => Image.Identify(options, CreateBmpWithMalformedIccProfile())); + } + + [Fact] + public void Decode_MalformedIccProfile_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => + { + using Image image = Image.Load(options, CreateBmpWithMalformedIccProfile()); + }); + } + + private static byte[] CreateBmpWithMalformedIccProfile() + => CorruptedMetadataImageFactory.CreateImageWithMalformedIccProfile(new BmpEncoder()); } diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs new file mode 100644 index 000000000..77aadae38 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs @@ -0,0 +1,274 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +/// +/// Security regression tests for the EXR decoder (Findings EXR-1, EXR-2, EXR-3). +/// The EXR decoder was merged to main but not yet included in a tagged NuGet release. +/// Each test demonstrates a crafted-input crash present in the unfixed code. +/// +[Trait("Format", "Exr")] +[ValidateDisposedMemoryAllocations] +public class ExrDecoderSecurityTests +{ + /// + /// EXR-1 — EXR DataWindow Integer Overflow Produces Negative Image Dimensions (DoS) + /// + /// Width and Height are computed from attacker-controlled DataWindow attributes + /// using unchecked int subtraction: + /// this.Width = XMax - XMin + 1 // overflows to -2147483647 + /// + /// The negative Width is then passed to the Image<TPixel> constructor, which calls + /// Guard.MustBeGreaterThan(width, 0) → ArgumentOutOfRangeException. + /// + /// After a fix this should throw InvalidImageContentException instead. + /// + /// Affected file: + /// src/ImageSharp/Formats/Exr/ExrDecoderCore.cs lines 600–601 + /// + [Fact] + public void Decode_DataWindowOverflow_NegativeWidth_Throws() + { + // XMin = -1073741825, XMax = 1073741823 + // Width = 1073741823 - (-1073741825) + 1 = 2^31 + 1 → wraps to -2147483647 + byte[] data = BuildMinimalExr(xMin: -1073741825, yMin: 0, xMax: 1073741823, yMax: 0); + + using MemoryStream stream = new(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + /// + /// EXR-2 — EXR Row Offset Table Unvalidated Seek (DoS) + /// + /// Row offsets are read from the file and used unconditionally to seek the stream: + /// ulong rowOffset = this.ReadUnsignedLong(stream); + /// stream.Position = (long)rowOffset; // no bounds check + /// + /// A crafted offset of 0xFFFFFFFFFFFFFFFF casts to −1 as long, causing + /// an ArgumentOutOfRangeException when setting stream.Position. + /// + /// After a fix this should throw InvalidImageContentException instead. + /// + /// Affected file: + /// src/ImageSharp/Formats/Exr/ExrDecoderCore.cs lines 170–175 (scanline) + /// lines 243–248 (tile) + /// + [Fact] + public void Decode_CraftedRowOffsets_OutOfBounds_Throws() + { + // Valid 2×2 image (XMin=0,YMin=0,XMax=1,YMax=1 → Width=2,Height=2). + // Row offset table immediately follows the header null byte: + // 2 rows × 8 bytes each, all set to 0xFFFFFFFFFFFFFFFF. + byte[] invalidOffsets = new byte[16]; + BinaryPrimitives.WriteUInt64LittleEndian(invalidOffsets, 0xFFFFFFFFFFFFFFFF); + BinaryPrimitives.WriteUInt64LittleEndian(invalidOffsets.AsSpan(8), 0xFFFFFFFFFFFFFFFF); + + byte[] data = BuildMinimalExr( + xMin: 0, yMin: 0, xMax: 1, yMax: 1, + rowOffsetTableAppend: invalidOffsets); + + using MemoryStream stream = new(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + [Fact] + public void Decode_CraftedRowOffsets_IntoHeader_Throws() + { + // Offset 0 points back into the EXR file header and must be rejected + // before the decoder seeks to attacker-controlled non-pixel data. + byte[] headerOffsets = new byte[16]; + + byte[] data = BuildMinimalExr( + xMin: 0, yMin: 0, xMax: 1, yMax: 1, + rowOffsetTableAppend: headerOffsets); + + using MemoryStream stream = new(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + [Fact] + public void Decode_CraftedRowOffsets_IntoOffsetTable_Throws() + { + byte[] data = BuildMinimalExr( + xMin: 0, yMin: 0, xMax: 1, yMax: 1, + rowOffsetTableAppend: new byte[16]); + + // Point the first row offset at the second row offset entry. + BinaryPrimitives.WriteUInt64LittleEndian(data.AsSpan(data.Length - 16), (ulong)(data.Length - 8)); + + using MemoryStream stream = new(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + /// + /// EXR-3 — Oversized EXR RGBA row sizing is rejected as invalid image content. + /// + /// With 4 RGBA HALF channels and Width = 2^29, the decoded row staging and + /// bytes-per-row arithmetic both exceed the supported buffer sizing limits. + /// The decoder must reject this as InvalidImageContentException before any allocation. + /// + /// Affected file: + /// src/ImageSharp/Formats/Exr/ExrDecoderCore.cs lines 142–150, 215–223 + /// src/ImageSharp/Formats/Exr/ExrUtils.cs CalculateBytesPerRow + /// + [Fact] + public void Decode_RgbaRowSizingExceedsBufferLimits_Throws() + { + // 4 RGBA HALF channels at this width cannot be represented by the decoder's + // int-sized row staging or block buffers. + byte[] data = BuildMinimalRgbaExr(xMin: 0, yMin: 0, xMax: 536870911, yMax: 0); + + using MemoryStream stream = new(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + [Fact] + public void Decode_DataWindowWidthExceedsRowBufferLimit_Throws() + { + // A single HALF channel keeps bytesPerBlock below int.MaxValue, but the decoder + // still stages four color planes and must reject widths that overflow width × 4. + byte[] data = BuildMinimalExr(xMin: 0, yMin: 0, xMax: int.MaxValue / 4, yMax: 0); + + using MemoryStream stream = new(data); + Assert.Throws( + () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream)); + } + + [Fact] + public void Identify_RowOffsetTableExceedsStream_Throws() + { + // Identify parses the header only, so this verifies the offset table bound is + // validated before scanline decoding reads from the table. + byte[] data = BuildMinimalExr(xMin: 0, yMin: 0, xMax: 1, yMax: 1); + + using MemoryStream stream = new(data); + Assert.Throws( + () => ExrDecoder.Instance.Identify(DecoderOptions.Default, stream)); + } + + // ------------------------------------------------------------------------- + // Helpers: construct minimal valid-enough EXR scanline files. + // + // Required attributes per ParseHeaderAttributes validation: + // channels, compression, dataWindow, displayWindow, + // lineOrder, pixelAspectRatio, screenWindowCenter, screenWindowWidth + // ------------------------------------------------------------------------- + + private static byte[] BuildMinimalExr( + int xMin, int yMin, int xMax, int yMax, + byte[] rowOffsetTableAppend = null) + { + // channels: single "R" HALF channel with xSampling=1, ySampling=1 + // Layout per ReadChannelInfo: name\0 (2) + pixelType (4) + pLinear+reserved (4) + // + xSampling (4) + ySampling (4) = 18 bytes/channel + // + list-null (1) = 19 total + byte[] channelData = + [ + 0x52, 0x00, // "R\0" + 0x01, 0x00, 0x00, 0x00, // pixelType = Half (1) + 0x00, 0x00, 0x00, 0x00, // pLinear + 3 reserved bytes + 0x01, 0x00, 0x00, 0x00, // xSampling = 1 + 0x01, 0x00, 0x00, 0x00, // ySampling = 1 + 0x00, // channel-list null terminator + ]; + + return BuildExrWithChannels(xMin, yMin, xMax, yMax, channelData, rowOffsetTableAppend); + } + + private static byte[] BuildMinimalRgbaExr(int xMin, int yMin, int xMax, int yMax) + { + // 4 HALF channels in alphabetical order (A, B, G, R) per EXR spec. + // 18 bytes per channel × 4 channels + 1 list-null = 73 bytes total. + byte[] channelData = + [ + 0x41, 0x00, // "A\0" + 0x01, 0x00, 0x00, 0x00, // pixelType = Half (1) + 0x00, 0x00, 0x00, 0x00, // pLinear + 3 reserved bytes + 0x01, 0x00, 0x00, 0x00, // xSampling = 1 + 0x01, 0x00, 0x00, 0x00, // ySampling = 1 + 0x42, 0x00, // "B\0" + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x47, 0x00, // "G\0" + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x52, 0x00, // "R\0" + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, // channel-list null terminator + ]; + + return BuildExrWithChannels(xMin, yMin, xMax, yMax, channelData); + } + + private static byte[] BuildExrWithChannels( + int xMin, int yMin, int xMax, int yMax, + byte[] channelData, + byte[] rowOffsetTableAppend = null) + { + using MemoryStream ms = new(); + using BinaryWriter bw = new(ms, System.Text.Encoding.ASCII, leaveOpen: true); + + // Magic (0x01312F76 LE) + version 2, scanline (flags = 0x00) + bw.Write(new byte[] { 0x76, 0x2F, 0x31, 0x01, 0x02, 0x00, 0x00, 0x00 }); + + void WriteAttr(string name, string type, byte[] payload) + { + foreach (char c in name) bw.Write((byte)c); + bw.Write((byte)0); + foreach (char c in type) bw.Write((byte)c); + bw.Write((byte)0); + bw.Write(payload.Length); + bw.Write(payload); + } + + WriteAttr("channels", "chlist", channelData); + + WriteAttr("compression", "compression", [0x00]); // None + + byte[] dw = new byte[16]; + BinaryPrimitives.WriteInt32LittleEndian(dw, xMin); + BinaryPrimitives.WriteInt32LittleEndian(dw.AsSpan(4), yMin); + BinaryPrimitives.WriteInt32LittleEndian(dw.AsSpan(8), xMax); + BinaryPrimitives.WriteInt32LittleEndian(dw.AsSpan(12), yMax); + WriteAttr("dataWindow", "box2i", dw); + + WriteAttr("displayWindow", "box2i", new byte[16]); // all zeros (0,0,0,0) + + WriteAttr("lineOrder", "lineOrder", [0x00]); // IncreasingY + + byte[] aspect = new byte[4]; + BinaryPrimitives.WriteSingleLittleEndian(aspect, 1.0f); + WriteAttr("pixelAspectRatio", "float", aspect); + + WriteAttr("screenWindowCenter", "v2f", new byte[8]); // (0f, 0f) + + byte[] sww = new byte[4]; + BinaryPrimitives.WriteSingleLittleEndian(sww, 1.0f); + WriteAttr("screenWindowWidth", "float", sww); + + bw.Write((byte)0x00); // end-of-header sentinel + + if (rowOffsetTableAppend is not null) + bw.Write(rowOffsetTableAppend); + + return ms.ToArray(); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs new file mode 100644 index 000000000..1ee0cc582 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderTests.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +[Trait("Format", "Exr")] +[ValidateDisposedMemoryAllocations] +public class ExrDecoderTests +{ + private static MagickReferenceDecoder ReferenceDecoder => MagickReferenceDecoder.Exr; + + [Theory] + [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Half(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + Assert.Equal(ExrPixelType.Half, exrMetaData.PixelType); + } + + [Theory] + [WithFile(TestImages.Exr.UncompressedFloatRgb, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Float(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); + image.DebugSave(provider); + + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + Assert.Equal(ExrPixelType.Float, exrMetaData.PixelType); + } + + [Theory] + [WithFile(TestImages.Exr.UncompressedUintRgb, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); + image.DebugSave(provider); + + // Compare to referene output, since the reference decoder does not support this pixel type. + image.CompareToReferenceOutput(provider); + Assert.Equal(ExrPixelType.UnsignedInt, exrMetaData.PixelType); + } + + [Theory] + [WithFile(TestImages.Exr.UintRgba, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + ExrMetadata exrMetaData = image.Metadata.GetExrMetadata(); + image.DebugSave(provider); + + // Compare to referene output, since the reference decoder does not support this pixel type. + image.CompareToReferenceOutput(provider); + Assert.Equal(ExrPixelType.UnsignedInt, exrMetaData.PixelType); + } + + [Theory] + [WithFile(TestImages.Exr.Rgb, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Rgb(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + } + + [Theory] + [WithFile(TestImages.Exr.Gray, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Gray(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + } + + [Theory] + [WithFile(TestImages.Exr.Zip, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_ZipCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + } + + [Theory] + [WithFile(TestImages.Exr.Zips, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_ZipsCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + } + + [Theory] + [WithFile(TestImages.Exr.Rle, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_RunLengthCompressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + } + + [Theory] + [WithFile(TestImages.Exr.Pxr24Half, PixelTypes.Rgba32)] + [WithFile(TestImages.Exr.Pxr24Float, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Pxr24Compressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact, ReferenceDecoder); + } + + [Theory] + [WithFile(TestImages.Exr.Pxr24Uint, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + + // Compare to Reference here instead using the reference decoder, since uint pixel type is not supported by the Reference decoder. + image.CompareToReferenceOutput(provider); + } + + [Theory] + [WithFile(TestImages.Exr.B44, PixelTypes.Rgba32)] + public void ExrDecoder_CanDecode_B44Compressed(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(ExrDecoder.Instance); + image.DebugSave(provider); + + // Note: There is a 0,1190% difference to the reference decoder. + image.CompareToOriginal(provider, ImageComparer.Tolerant(0.011f), ReferenceDecoder); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs new file mode 100644 index 000000000..2f507ea51 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrEncoderTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +[Trait("Format", "Exr")] +[ValidateDisposedMemoryAllocations] +public class ExrEncoderTests +{ + protected static readonly IImageDecoder ReferenceDecoder = new MagickReferenceDecoder(ExrFormat.Instance); + + [Theory] + [InlineData(null, ExrPixelType.Half)] + [InlineData(ExrPixelType.Float, ExrPixelType.Float)] + [InlineData(ExrPixelType.Half, ExrPixelType.Half)] + [InlineData(ExrPixelType.UnsignedInt, ExrPixelType.UnsignedInt)] + public void EncoderOptions_SetPixelType_Works(ExrPixelType? pixelType, ExrPixelType? expectedPixelType) + { + // arrange + ExrEncoder exrEncoder = new() { PixelType = pixelType }; + using Image input = new Image(10, 10); + using MemoryStream memStream = new(); + + // act + input.Save(memStream, exrEncoder); + + // assert + memStream.Position = 0; + using Image output = Image.Load(memStream); + ExrMetadata exrMetaData = output.Metadata.GetExrMetadata(); + Assert.Equal(expectedPixelType, exrMetaData.PixelType); + } + + [Theory] + [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] + public void ExrEncoder_WithNoCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "NoCompression", compression: ExrCompression.None); + + [Theory] + [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] + public void ExrEncoder_WithZipCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "ZipCompression", compression: ExrCompression.Zip); + + [Theory] + [WithFile(TestImages.Exr.Uncompressed, PixelTypes.Rgba32)] + public void ExrEncoder_WithZipsCompression_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestExrEncoderCore(provider, "ZipsCompression", compression: ExrCompression.Zips); + + protected static void TestExrEncoderCore( + TestImageProvider provider, + object testOutputDetails, + ExrCompression compression = ExrCompression.None, + bool useExactComparer = true, + float compareTolerance = 0.001f, + IImageDecoder imageDecoder = null) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + ExrEncoder encoder = new() + { + Compression = compression, + }; + + // Does DebugSave & load reference CompareToReferenceInput(): + image.VerifyEncoder( + provider, + "exr", + testOutputDetails: testOutputDetails, + encoder: encoder, + customComparer: useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), + referenceDecoder: imageDecoder ?? ReferenceDecoder); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs new file mode 100644 index 000000000..cb527cf3b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Exr/ExrMetadataTests.cs @@ -0,0 +1,129 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.Formats.Exr.Constants; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +[Trait("Format", "Exr")] +public class ExrMetadataTests +{ + [Fact] + public void CloneIsDeep() + { + ExrMetadata meta = new() + { ImageDataType = ExrImageDataType.Rgb, PixelType = ExrPixelType.Half, Compression = ExrCompression.None }; + ExrMetadata clone = (ExrMetadata)meta.DeepClone(); + + clone.ImageDataType = ExrImageDataType.Gray; + clone.PixelType = ExrPixelType.Float; + clone.Compression = ExrCompression.Zip; + + Assert.False(meta.ImageDataType.Equals(clone.ImageDataType)); + Assert.False(meta.PixelType.Equals(clone.PixelType)); + Assert.False(meta.Compression.Equals(clone.Compression)); + } + + [Theory] + [InlineData(TestImages.Exr.Uncompressed, 199, 297)] + public void Identify_DetectsCorrectWidthAndHeight(string imagePath, int expectedWidth, int expectedHeight) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); + } + + [Theory] + [InlineData(TestImages.Exr.Uncompressed, ExrPixelType.Half)] + [InlineData(TestImages.Exr.UncompressedFloatRgb, ExrPixelType.Float)] + [InlineData(TestImages.Exr.UncompressedUintRgb, ExrPixelType.UnsignedInt)] + public void Identify_DetectsCorrectPixelType(string imagePath, ExrPixelType expectedPixelType) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + ExrMetadata metadata = imageInfo.Metadata.GetExrMetadata(); + Assert.NotNull(metadata); + Assert.Equal(expectedPixelType, metadata.PixelType); + } + + [Theory] + [InlineData(TestImages.Exr.UncompressedRgba, ExrImageDataType.Rgba)] + [InlineData(TestImages.Exr.Rgb, ExrImageDataType.Rgb)] + [InlineData(TestImages.Exr.Gray, ExrImageDataType.Gray)] + public void Identify_DetectsCorrectImageDataType(string imagePath, ExrImageDataType expectedImageDataType) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + ExrMetadata metadata = imageInfo.Metadata.GetExrMetadata(); + Assert.NotNull(metadata); + Assert.Equal(expectedImageDataType, metadata.ImageDataType); + } + + [Theory] + [InlineData(ExrImageDataType.Rgba, PixelAlphaRepresentation.Associated)] + [InlineData(ExrImageDataType.Rgb, PixelAlphaRepresentation.None)] + [InlineData(ExrImageDataType.Gray, PixelAlphaRepresentation.None)] + public void GetPixelTypeInfo_ReturnsExpectedAlphaRepresentation(ExrImageDataType imageDataType, PixelAlphaRepresentation expected) + { + ExrMetadata metadata = new() { ImageDataType = imageDataType }; + + Assert.Equal(expected, metadata.GetPixelTypeInfo().AlphaRepresentation); + } + + [Theory] + [InlineData(TestImages.Exr.UncompressedRgba, ExrCompression.None)] + [InlineData(TestImages.Exr.B44, ExrCompression.B44)] + [InlineData(TestImages.Exr.Rle, ExrCompression.RunLengthEncoded)] + public void Identify_DetectsCorrectCompression(string imagePath, ExrCompression expectedCompression) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + ExrMetadata metadata = imageInfo.Metadata.GetExrMetadata(); + Assert.NotNull(metadata); + Assert.Equal(expectedCompression, metadata.Compression); + } + + [Theory] + [InlineData(PixelColorType.Binary, 1, ExrImageDataType.Unknown, ExrPixelType.Half)] + [InlineData(PixelColorType.Indexed, 8, ExrImageDataType.Unknown, ExrPixelType.Half)] + [InlineData(PixelColorType.Luminance, 16, ExrImageDataType.Gray, ExrPixelType.Half)] + [InlineData(PixelColorType.RGB, 48, ExrImageDataType.Rgb, ExrPixelType.Float)] + [InlineData(PixelColorType.BGR, 48, ExrImageDataType.Rgb, ExrPixelType.Float)] + [InlineData(PixelColorType.RGB | PixelColorType.Alpha, 64, ExrImageDataType.Rgba, ExrPixelType.Float)] + [InlineData(PixelColorType.BGR | PixelColorType.Alpha, 64, ExrImageDataType.Rgba, ExrPixelType.Float)] + [InlineData(PixelColorType.Luminance | PixelColorType.Alpha, 32, ExrImageDataType.Rgba, ExrPixelType.Float)] + [InlineData(PixelColorType.YCbCr, 48, ExrImageDataType.Unknown, ExrPixelType.Float)] + [InlineData(PixelColorType.CMYK, 64, ExrImageDataType.Unknown, ExrPixelType.Float)] + [InlineData(PixelColorType.YCCK, 64, ExrImageDataType.Unknown, ExrPixelType.Float)] + public void FromFormatConnectingMetadata_ConvertColorTypeAsExpected(PixelColorType pixelColorType, int bitsPerPixel, ExrImageDataType expectedImageDataType, ExrPixelType expectedPixelType) + { + FormatConnectingMetadata formatConnectingMetadata = new() + { + PixelTypeInfo = new PixelTypeInfo(bitsPerPixel) + { + ColorType = pixelColorType, + }, + }; + + ExrMetadata actual = ExrMetadata.FromFormatConnectingMetadata(formatConnectingMetadata); + + Assert.Equal(expectedImageDataType, actual.ImageDataType); + Assert.Equal(expectedPixelType, actual.PixelType); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs new file mode 100644 index 000000000..cd23e1ccd --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Exr/ImageExtensionsTest.cs @@ -0,0 +1,136 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Exr; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Exr; + +[Trait("Format", "Exr")] +public class ImageExtensionsTest +{ + [Fact] + public void SaveAsExr_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsExr_Path.exr"); + + using (Image image = new(10, 10)) + { + image.SaveAsExr(file); + } + + IImageFormat format = Image.DetectFormat(file); + Assert.True(format is ExrFormat); + } + + [Fact] + public async Task SaveAsExrAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsExrAsync_Path.exr"); + + using (Image image = new(10, 10)) + { + await image.SaveAsExrAsync(file); + } + + IImageFormat format = Image.DetectFormat(file); + Assert.True(format is ExrFormat); + } + + [Fact] + public void SaveAsExr_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsExr_Path_Encoder.exr"); + + using (Image image = new(10, 10)) + { + image.SaveAsExr(file, new ExrEncoder()); + } + + IImageFormat format = Image.DetectFormat(file); + Assert.True(format is ExrFormat); + } + + [Fact] + public async Task SaveAsExrAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsExrAsync_Path_Encoder.exr"); + + using (Image image = new(10, 10)) + { + await image.SaveAsExrAsync(file, new ExrEncoder()); + } + + IImageFormat format = Image.DetectFormat(file); + Assert.True(format is ExrFormat); + } + + [Fact] + public void SaveAsExr_Stream() + { + using MemoryStream memoryStream = new(); + + using (Image image = new(10, 10)) + { + image.SaveAsExr(memoryStream); + } + + memoryStream.Position = 0; + + IImageFormat format = Image.DetectFormat(memoryStream); + Assert.True(format is ExrFormat); + } + + [Fact] + public async Task SaveAsExrAsync_StreamAsync() + { + using MemoryStream memoryStream = new(); + + using (Image image = new(10, 10)) + { + await image.SaveAsExrAsync(memoryStream); + } + + memoryStream.Position = 0; + + IImageFormat format = Image.DetectFormat(memoryStream); + Assert.True(format is ExrFormat); + } + + [Fact] + public void SaveAsExr_Stream_Encoder() + { + using MemoryStream memoryStream = new(); + + using (Image image = new(10, 10)) + { + image.SaveAsExr(memoryStream, new ExrEncoder()); + } + + memoryStream.Position = 0; + + IImageFormat format = Image.DetectFormat(memoryStream); + Assert.True(format is ExrFormat); + } + + [Fact] + public async Task SaveAsExrAsync_Stream_Encoder() + { + using MemoryStream memoryStream = new(); + + using (Image image = new(10, 10)) + { + await image.SaveAsExrAsync(memoryStream, new ExrEncoder()); + } + + memoryStream.Position = 0; + + IImageFormat format = Image.DetectFormat(memoryStream); + Assert.True(format is ExrFormat); + } +} diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 072b04fa0..2b91c4dbf 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -125,7 +125,7 @@ public class GeneralFormatTests public static readonly TheoryData QuantizerNames = new() { - nameof(KnownQuantizers.Octree), + nameof(KnownQuantizers.Hexadecatree), nameof(KnownQuantizers.WebSafe), nameof(KnownQuantizers.Werner), nameof(KnownQuantizers.Wu) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 7bb711f3f..8a8b4866a 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -398,4 +398,64 @@ public class GifDecoderTests Assert.Throws(() => Image.Identify(options, testFile.FullPath)); Assert.Throws(() => Image.Load(options, testFile.FullPath)); } + + [Theory] + [WithFile(TestImages.Gif.Issues.Issue2980, PixelTypes.Rgba32)] + public void Issue2980(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + image.DebugSaveMultiFrame(provider); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + } + + [Fact] + public void Identify_MalformedApplicationExtension_IgnoresNonCriticalErrorsByDefault() + { + ImageInfo info = Image.Identify(CreateGifWithMalformedApplicationExtension()); + Assert.Equal(1, info.Width); + Assert.Equal(1, info.Height); + } + + [Fact] + public void Decode_MalformedApplicationExtension_IgnoresNonCriticalErrorsByDefault() + { + using Image image = Image.Load(CreateGifWithMalformedApplicationExtension()); + Assert.Equal(1, image.Width); + Assert.Equal(1, image.Height); + } + + [Fact] + public void Identify_MalformedApplicationExtension_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => Image.Identify(options, CreateGifWithMalformedApplicationExtension())); + } + + [Fact] + public void Decode_MalformedApplicationExtension_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => + { + using Image image = Image.Load(options, CreateGifWithMalformedApplicationExtension()); + }); + } + + private static byte[] CreateGifWithMalformedApplicationExtension() + { + // The application extension declares a 5-byte application block (0x05), but GIF application + // extensions require the fixed identifier/authentication block to be 11 bytes long. + return + [ + (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a', + 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, + 0x21, 0xFF, 0x05, (byte)'B', (byte)'a', (byte)'d', (byte)'A', (byte)'p', 0x00, + 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, + 0x02, 0x02, 0x44, 0x01, 0x00, + 0x3B + ]; + } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 370106ca3..a465b073b 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; @@ -115,7 +116,7 @@ public class GifEncoderTests GifEncoder encoder = new() { ColorTableMode = FrameColorTableMode.Global, - Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) + Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null }) }; // Always save as we need to compare the encoded output. @@ -124,7 +125,7 @@ public class GifEncoderTests encoder = new GifEncoder { ColorTableMode = FrameColorTableMode.Local, - Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }), + Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null }), }; provider.Utility.SaveTestOutputFile(image, "gif", encoder, "local"); @@ -191,7 +192,7 @@ public class GifEncoderTests GifEncoder encoder = new() { ColorTableMode = colorMode, - Quantizer = new OctreeQuantizer(new QuantizerOptions { MaxColors = maxColors }) + Quantizer = new HexadecatreeQuantizer(new QuantizerOptions { MaxColors = maxColors }) }; image.Save(outStream, encoder); @@ -349,7 +350,7 @@ public class GifEncoderTests public static string[] Animated => TestImages.Gif.Animated; - [Theory(Skip = "Enable for visual animated testing")] + [Theory]//(Skip = "Enable for visual animated testing")] [WithFileCollection(nameof(Animated), PixelTypes.Rgba32)] public void Encode_Animated_VisualTest(TestImageProvider provider) where TPixel : unmanaged, IPixel @@ -436,4 +437,21 @@ public class GifEncoderTests static bool Predicate(int i, int _) => i % 8 == 0; // Image has many frames, only compare a selection of them. image.CompareDebugOutputToReferenceOutputMultiFrame(provider, ImageComparer.Exact, extension: "gif", predicate: Predicate); } + + [Theory] + [WithFile(TestImages.Gif.Issues.Issue3142, PixelTypes.Rgba32)] + public void GifEncoder_CanDecode_AndEncode_Issue3142(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + // Save the image for visual inspection. + provider.Utility.SaveTestOutputFile(image, "gif", new GifEncoder() { Quantizer = KnownQuantizers.Wu }, "animated"); + + // Now compare the debug output with the reference output. + // We do this because the gif encoding is lossy and encoding will lead to differences in the 10s of percent. + // From the unencoded image, we can see that the image is visually the same. + static bool Predicate(int i, int _) => i % 8 == 0; // Image has many frames, only compare a selection of them. + image.CompareDebugOutputToReferenceOutputMultiFrame(provider, ImageComparer.Exact, extension: "gif", predicate: Predicate); + } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs index bac52fc72..80d721ba9 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurDecoderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Icon; @@ -13,6 +14,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Icon.Cur; [ValidateDisposedMemoryAllocations] public class CurDecoderTests { + [Fact] + public void CurFormat_HasCorrectName() + => Assert.Equal("CUR", CurFormat.Instance.Name); + + [Fact] + public void CurDetector_RejectsIco() + { + TestFile file = TestFile.Create(TestImages.Ico.Flutter); + CurImageFormatDetector detector = new(); + + Assert.False(detector.TryDetectFormat(file.Bytes, out _)); + } + [Theory] [WithFile(WindowsMouse, PixelTypes.Rgba32)] public void CurDecoder_Decode(TestImageProvider provider) @@ -38,4 +52,30 @@ public class CurDecoderTests Assert.Equal(IconFrameCompression.Bmp, meta.Compression); Assert.Equal(BmpBitsPerPixel.Bit32, meta.BmpBitsPerPixel); } + + [Fact] + public void CurFrameMetadata_DeepClonePreservesColorTable() + { + Color[] colors = [Color.Red, Color.Green]; + CurFrameMetadata metadata = new() { ColorTable = colors }; + + CurFrameMetadata clone = metadata.DeepClone(); + colors[0] = Color.Blue; + + Assert.Equal(Color.Red, clone.ColorTable.Value.Span[0]); + Assert.Equal(Color.Green, clone.ColorTable.Value.Span[1]); + } + + [Fact] + public void CurFrameMetadata_ScalesZeroEncodingDimensionsFrom256() + { + using Image source = new(256, 256); + using Image destination = new(128, 128); + CurFrameMetadata metadata = new() { EncodingWidth = 0, EncodingHeight = 0 }; + + metadata.AfterFrameApply(source.Frames.RootFrame, destination.Frames.RootFrame, Matrix4x4.Identity); + + Assert.Equal((byte)128, metadata.EncodingWidth); + Assert.Equal((byte)128, metadata.EncodingHeight); + } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs index f895afbd5..305adb989 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Cur/CurEncoderTests.cs @@ -2,10 +2,12 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Ico; -using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Icon; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using static SixLabors.ImageSharp.Tests.TestImages.Cur; using static SixLabors.ImageSharp.Tests.TestImages.Ico; @@ -120,15 +122,42 @@ public class CurEncoderTests for (int x = 0; x < accessor.Width; x++) { - if (expectedColor != rowSpan[x]) - { - int xx = 0; - } - - Assert.Equal(expectedColor, rowSpan[x]); } } }); } + + [Fact] + public void Encode_UsesBitmapDepthInsteadOfHotspotForQuantizerSelection() + { + using Image image = new(32, 1); + for (int x = 0; x < image.Width; x++) + { + image[x, 0] = new Rgba32((byte)(x * 8), (byte)(255 - (x * 8)), (byte)(x * 4)); + } + + CurFrameMetadata metadata = image.Frames.RootFrame.Metadata.GetCurMetadata(); + metadata.Compression = IconFrameCompression.Bmp; + metadata.BmpBitsPerPixel = BmpBitsPerPixel.Bit8; + metadata.HotspotY = 32; + + CurEncoder encoder = new() + { + Quantizer = new WuQuantizer(new QuantizerOptions { MaxColors = 2 }) + }; + + using MemoryStream stream = new(); + image.Save(stream, encoder); + stream.Position = 0; + + using Image decoded = Image.Load(stream); + HashSet colors = []; + for (int x = 0; x < decoded.Width; x++) + { + colors.Add(decoded[x, 0]); + } + + Assert.InRange(colors.Count, 1, 2); + } } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index 85ff51b18..46cd99aa3 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers.Binary; +using System.Numerics; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Ico; @@ -14,6 +16,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Icon.Ico; [ValidateDisposedMemoryAllocations] public class IcoDecoderTests { + [Fact] + public void IcoDetector_RejectsCur() + { + TestFile file = TestFile.Create(TestImages.Cur.WindowsMouse); + IcoImageFormatDetector detector = new(); + + Assert.False(detector.TryDetectFormat(file.Bytes, out _)); + } + [Theory] [WithFile(Flutter, PixelTypes.Rgba32)] public void IcoDecoder_Decode(TestImageProvider provider) @@ -204,12 +215,19 @@ public class IcoDecoderTests } [Theory] - [WithFile(InvalidAll, PixelTypes.Rgba32)] [WithFile(InvalidBpp, PixelTypes.Rgba32)] + public void InvalidThrows_InvalidImageContentException(TestImageProvider provider) + => Assert.Throws(() => + { + using Image image = provider.GetImage(IcoDecoder.Instance); + }); + + [Theory] + [WithFile(InvalidAll, PixelTypes.Rgba32)] [WithFile(InvalidCompression, PixelTypes.Rgba32)] [WithFile(InvalidRLE4, PixelTypes.Rgba32)] [WithFile(InvalidRLE8, PixelTypes.Rgba32)] - public void InvalidTest(TestImageProvider provider) + public void InvalidThows_NotSupportedException(TestImageProvider provider) => Assert.Throws(() => { using Image image = provider.GetImage(IcoDecoder.Instance); @@ -299,6 +317,69 @@ public class IcoDecoderTests Assert.Single(imageInfo.FrameMetadataCollection); } + [Fact] + public void TruncatedEntry_FollowsImageDataIntegrityHandling() + { + byte[] data = [.. TestFile.Create(Flutter).Bytes]; + ushort entryCount = BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(4)); + Assert.True(entryCount > 1); + + // Limit the first resource below the PNG signature length. A bounded child decoder must not read into following data. + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(IconDir.Size + 8), 4); + + using MemoryStream defaultStream = new(data, false); + Assert.Throws(() => IcoDecoder.Instance.Decode(DecoderOptions.Default, defaultStream)); + + DecoderOptions ignore = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData }; + + using MemoryStream decodeStream = new(data, false); + using Image image = IcoDecoder.Instance.Decode(ignore, decodeStream); + Assert.Equal(entryCount - 1, image.Frames.Count); + + using MemoryStream identifyStream = new(data, false); + ImageInfo info = IcoDecoder.Instance.Identify(ignore, identifyStream); + Assert.Equal(entryCount - 1, info.FrameMetadataCollection.Count); + } + + /// + /// Verifies that a non-empty resource cannot begin at the exclusive end of its containing icon stream. + /// + [Fact] + public void EntryAtEndOfStream_ThrowsInvalidResourceRange() + { + byte[] data = [.. TestFile.Create(Bpp32Size1x1).Bytes]; + Assert.Equal(1, BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(4))); + + // ImageOffset is the final DWORD in the sole directory entry. The stream length is an exclusive boundary, + // so accepting this value would create an empty child stream despite the entry declaring non-empty data. + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(IconDir.Size + 12), (uint)data.Length); + + using MemoryStream decodeStream = new(data, false); + InvalidImageContentException decodeException = Assert.Throws( + () => IcoDecoder.Instance.Decode(DecoderOptions.Default, decodeStream)); + + Assert.Equal("The icon directory contains an invalid image resource range.", decodeException.Message); + + using MemoryStream identifyStream = new(data, false); + InvalidImageContentException identifyException = Assert.Throws( + () => IcoDecoder.Instance.Identify(DecoderOptions.Default, identifyStream)); + + Assert.Equal("The icon directory contains an invalid image resource range.", identifyException.Message); + } + + [Fact] + public void IcoFrameMetadata_ScalesZeroEncodingDimensionsFrom256() + { + using Image source = new(256, 256); + using Image destination = new(128, 128); + IcoFrameMetadata metadata = new() { EncodingWidth = 0, EncodingHeight = 0 }; + + metadata.AfterFrameApply(source.Frames.RootFrame, destination.Frames.RootFrame, Matrix4x4.Identity); + + Assert.Equal((byte)128, metadata.EncodingWidth); + Assert.Equal((byte)128, metadata.EncodingHeight); + } + [Theory] [WithFile(MultiSizeMultiBitsA, PixelTypes.Rgba32)] [WithFile(MultiSizeMultiBitsB, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs index 4c7438d56..08bb537ad 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoEncoderTests.cs @@ -2,8 +2,11 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Cur; using SixLabors.ImageSharp.Formats.Ico; +using SixLabors.ImageSharp.Formats.Icon; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using static SixLabors.ImageSharp.Tests.TestImages.Cur; @@ -121,4 +124,52 @@ public class IcoEncoderTests } }); } + + [Fact] + public void PngEntry_PreservesExifProfile() + { + using Image image = new(16, 16); + image.Metadata.ExifProfile = new ExifProfile(); + image.Metadata.ExifProfile.SetValue(ExifTag.Software, "ImageSharp"); + image.Frames.RootFrame.Metadata.GetIcoMetadata().Compression = IconFrameCompression.Png; + + using MemoryStream stream = new(); + image.Save(stream, Encoder); + + stream.Position = 0; + using Image decoded = Image.Load(stream); + + Assert.NotNull(decoded.Metadata.ExifProfile); + Assert.Equal(image.Metadata.ExifProfile.Values, decoded.Metadata.ExifProfile.Values); + } + + /// + /// Verifies that the final partial AND-mask byte contains every pixel when the bitmap width is not byte-aligned. + /// + /// The bitmap width to encode. + [Theory] + [InlineData(1)] + [InlineData(7)] + [InlineData(9)] + [InlineData(15)] + public void BmpEntry_WritesPartialAlphaMaskByte(int width) + { + using Image image = new(width, 1, Color.Red.ToPixel()); + image[width - 1, 0] = Color.Transparent.ToPixel(); + + IcoFrameMetadata metadata = image.Frames.RootFrame.Metadata.GetIcoMetadata(); + metadata.Compression = IconFrameCompression.Bmp; + metadata.BmpBitsPerPixel = BmpBitsPerPixel.Bit32; + + using MemoryStream stream = new(); + image.Save(stream, Encoder); + + // These widths produce one DWORD-aligned mask row at the end of the bitmap resource. + ReadOnlySpan mask = stream.GetBuffer().AsSpan(checked((int)stream.Length) - sizeof(uint), sizeof(uint)); + int pixelIndex = width - 1; + int byteIndex = pixelIndex / 8; + int bitIndex = pixelIndex % 8; + + Assert.Equal((byte)(0b10000000 >> bitIndex), mask[byteIndex]); + } } diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index a8ee56c8f..146898b46 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -3,6 +3,7 @@ using Moq; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Ani; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Heif; @@ -31,6 +32,7 @@ public class ImageFormatManagerTests [Fact] public void IfAutoLoadWellKnownFormatsIsTrueAllFormatsAreLoaded() { + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); @@ -41,6 +43,7 @@ public class ImageFormatManagerTests Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index ab205c8a3..fe9d5ed6d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -7,7 +7,6 @@ using System.Runtime.Intrinsics; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; @@ -181,54 +180,6 @@ public partial class Block8x8FTests : JpegFixture } } - [Theory] - [InlineData(1)] - [InlineData(2)] - public void NormalizeColorsAndRoundVector256(int seed) - { - if (this.SkipOnNonVector256Runner()) - { - return; - } - - Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); - - Block8x8F expected = source; - expected.NormalizeColorsInPlace(255); - expected.RoundInPlace(); - - Block8x8F actual = source; - actual.NormalizeColorsAndRoundInPlaceVector256(255); - - this.Output.WriteLine(expected.ToString()); - this.Output.WriteLine(actual.ToString()); - this.CompareBlocks(expected, actual, 0); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void NormalizeColorsAndRoundVector128(int seed) - { - if (this.SkipOnNonVector128Runner()) - { - return; - } - - Block8x8F source = CreateRandomFloatBlock(-200, 200, seed); - - Block8x8F expected = source; - expected.NormalizeColorsInPlace(255); - expected.RoundInPlace(); - - Block8x8F actual = source; - actual.NormalizeColorsAndRoundInPlaceVector128(255); - - this.Output.WriteLine(expected.ToString()); - this.Output.WriteLine(actual.ToString()); - this.CompareBlocks(expected, actual, 0); - } - [Theory] [InlineData(1, 2)] [InlineData(2, 1)] @@ -267,7 +218,7 @@ public partial class Block8x8FTests : JpegFixture RunTest, srcSeed, qtSeed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } [Fact] @@ -290,29 +241,6 @@ public partial class Block8x8FTests : JpegFixture } } - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void RoundInPlaceSlow(int seed) - { - Block8x8F s = CreateRandomFloatBlock(-500, 500, seed); - - Block8x8F d = s; - d.RoundInPlace(); - - this.Output.WriteLine(s.ToString()); - this.Output.WriteLine(d.ToString()); - - for (int i = 0; i < 64; i++) - { - float expected = (float)Math.Round(s[i]); - float actual = d[i]; - - Assert.Equal(expected, actual); - } - } - [Fact] public void MultiplyInPlace_ByOtherBlock() { @@ -462,7 +390,7 @@ public partial class Block8x8FTests : JpegFixture // 3. DisableAvx2 - call fallback code of float implementation FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index fb1e062f2..c5a24b484 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -4,7 +4,6 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 062a8a5ee..b0e35c7b0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -4,7 +4,6 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg; @@ -152,7 +151,7 @@ public static class DCTTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } [Theory] @@ -193,7 +192,7 @@ public static class DCTTests { for (int x = 0; x < 4; x++) { - AssertScaledElementEquality(expectedSpan.Slice((y * 16) + (x * 2)), actualSpan.Slice((y * 8) + x)); + AssertScaledElementEquality(expectedSpan[((y * 16) + (x * 2))..], actualSpan[((y * 8) + x)..]); } } @@ -210,7 +209,7 @@ public static class DCTTests } } - average2x2 = MathF.Round(average2x2 / 4f); + average2x2 /= 4f; Assert.Equal((int)average2x2, (int)actual[0]); } @@ -254,7 +253,7 @@ public static class DCTTests { for (int x = 0; x < 2; x++) { - AssertScaledElementEquality(expectedSpan.Slice((y * 32) + (x * 4)), actualSpan.Slice((y * 8) + x)); + AssertScaledElementEquality(expectedSpan[((y * 32) + (x * 4))..], actualSpan[((y * 8) + x)..]); } } @@ -271,7 +270,7 @@ public static class DCTTests } } - average4x4 = MathF.Round(average4x4 / 16f); + average4x4 /= 16f; Assert.Equal((int)average4x4, (int)actual[0]); } @@ -311,7 +310,7 @@ public static class DCTTests NormalizationValue, MaxOutputValue); - float expected = MathF.Round(Numerics.Clamp(expectedDest[0] + NormalizationValue, 0, MaxOutputValue)); + float expected = Numerics.Clamp(expectedDest[0] + NormalizationValue, 0, MaxOutputValue); Assert.Equal((int)actual, (int)expected); } @@ -352,15 +351,14 @@ public static class DCTTests Assert.Equal(expectedDest, actualDest, new ApproximateFloatComparer(1f)); } - // 4 paths: - // 1. AllowAll - call avx/fma implementation - // 2. DisableFMA - call avx without fma implementation - // 3. DisableAvx - call Vector4 implementation - // 4. DisableHWIntrinsic - call scalar fallback implementation + // 3 paths: + // 1. AllowAll - call avx implementation + // 2. DisableAvx - call Vector4 implementation + // 3. DisableHWIntrinsic - call scalar fallback implementation FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs index 36b792fa1..2cc0a961f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/HuffmanScanEncoderTests.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index d58ff9823..de42e85d7 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -2,11 +2,12 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Tests.ColorProfiles; +using SixLabors.ImageSharp.Tests.ColorProfiles.Icc; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; @@ -14,39 +15,40 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg; public class JpegColorConverterTests { private const float MaxColorChannelValue = 255F; + private const float ColorProfileTolerance = 0.1F / MaxColorChannelValue; + private const float ToRgbTolerance = 0.0001F; + private const float FromRgbTolerance = 0.01F; - private const float Precision = 0.1F / 255; - - private const int TestBufferLength = 40; - - private const HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2; - - private static readonly ApproximateColorProfileComparer ColorSpaceComparer = new(epsilon: Precision); - - private static readonly ColorProfileConverter ColorSpaceConverter = new(); - - public static readonly TheoryData Seeds = new() { 1, 2, 3 }; - - public JpegColorConverterTests(ITestOutputHelper output) - => this.Output = output; - - private ITestOutputHelper Output { get; } + // Independent model checks compare normalized colors at one tenth of a byte-domain sample. + private static readonly ApproximateColorProfileComparer ColorSpaceComparer = new(epsilon: ColorProfileTolerance); + /// + /// Verifies that unsupported color spaces are rejected by the converter factory. + /// [Fact] public void GetConverterThrowsExceptionOnInvalidColorSpace() { const JpegColorSpace invalidColorSpace = (JpegColorSpace)(-1); + Assert.Throws(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8)); } + /// + /// Verifies that unsupported JPEG sample precisions are rejected by the converter factory. + /// [Fact] public void GetConverterThrowsExceptionOnInvalidPrecision() { - // Valid precisions: 8 & 12 bit const int invalidPrecision = 9; + Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision)); } + /// + /// Verifies that every supported color-space and precision pair resolves to the shared operator converter. + /// + /// The JPEG color space. + /// The JPEG sample precision. [Theory] [InlineData(JpegColorSpace.Grayscale, 8)] [InlineData(JpegColorSpace.Grayscale, 12)] @@ -58,799 +60,570 @@ public class JpegColorConverterTests [InlineData(JpegColorSpace.RGB, 12)] [InlineData(JpegColorSpace.YCbCr, 8)] [InlineData(JpegColorSpace.YCbCr, 12)] + [InlineData(JpegColorSpace.TiffCmyk, 8)] + [InlineData(JpegColorSpace.TiffCmyk, 12)] + [InlineData(JpegColorSpace.TiffYccK, 8)] + [InlineData(JpegColorSpace.TiffYccK, 12)] internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int precision) { JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, precision); - Assert.NotNull(converter); Assert.True(converter.IsAvailable); Assert.Equal(colorSpace, converter.ColorSpace); Assert.Equal(precision, converter.Precision); } - [Fact] - public void GetConverterReturnsCorrectConverterWithRgbColorSpace() + /// + /// Verifies that the converter factory closes the shared traversal over the matching color-model operator. + /// + /// The JPEG color space. + /// The expected closed converter type. + [Theory] + [InlineData(JpegColorSpace.Grayscale, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.RGB, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.Cmyk, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.YCbCr, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.Ycck, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.TiffCmyk, typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData(JpegColorSpace.TiffYccK, typeof(JpegColorConverterBase.JpegColorConverter))] + internal void GetConverterReturnsClosedOperatorConverter(JpegColorSpace colorSpace, Type expectedType) { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = typeof(JpegColorConverterBase.RgbScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.RgbVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.RgbVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.RgbVector128); - } - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8); - Type actualType = converter.GetType(); + JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); - // assert - Assert.Equal(expectedType, actualType); - } + Assert.Equal(expectedType, converter.GetType()); } - [Fact] - public void GetConverterReturnsCorrectConverterWithGrayScaleColorSpace() + /// + /// Verifies the replacement converter against independent definitions of the established JPEG color models. + /// + /// The JPEG color space. + /// The number of component planes owned by the color model. + [Theory] + [InlineData(JpegColorSpace.Grayscale, 1)] + [InlineData(JpegColorSpace.RGB, 3)] + [InlineData(JpegColorSpace.Cmyk, 4)] + [InlineData(JpegColorSpace.YCbCr, 3)] + [InlineData(JpegColorSpace.Ycck, 4)] + internal void ConvertToRgbMatchesColorModelDefinition(JpegColorSpace colorSpace, int componentCount) { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = typeof(JpegColorConverterBase.GrayScaleScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.GrayScaleVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.GrayScaleVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.GrayScaleVector128); - } + const int length = 128; + JpegColorConverterBase.ComponentValues source = CreateRandomValues(length, componentCount, 8); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, 8); + JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8); - Type actualType = converter.GetType(); + converter.ConvertToRgbInPlace(actual); - // assert - Assert.Equal(expectedType, actualType); + for (int i = 0; i < length; i++) + { + AssertColorModelDefinition(colorSpace, source, actual, i); } } - [Fact] - public void GetConverterReturnsCorrectConverterWithCmykColorSpace() + /// + /// Verifies the adaptive traversal against each operator's scalar definition around every SIMD boundary. + /// + /// The JPEG color space. + /// The number of component planes owned by the color model. + /// The JPEG sample precision. + [Theory] + [InlineData(JpegColorSpace.Grayscale, 1, 8)] + [InlineData(JpegColorSpace.Grayscale, 1, 12)] + [InlineData(JpegColorSpace.RGB, 3, 8)] + [InlineData(JpegColorSpace.RGB, 3, 12)] + [InlineData(JpegColorSpace.Cmyk, 4, 8)] + [InlineData(JpegColorSpace.Cmyk, 4, 12)] + [InlineData(JpegColorSpace.YCbCr, 3, 8)] + [InlineData(JpegColorSpace.YCbCr, 3, 12)] + [InlineData(JpegColorSpace.Ycck, 4, 8)] + [InlineData(JpegColorSpace.Ycck, 4, 12)] + [InlineData(JpegColorSpace.TiffCmyk, 4, 8)] + [InlineData(JpegColorSpace.TiffCmyk, 4, 12)] + [InlineData(JpegColorSpace.TiffYccK, 4, 8)] + [InlineData(JpegColorSpace.TiffYccK, 4, 12)] + internal void OperatorTraversalMatchesScalarDefinition(JpegColorSpace colorSpace, int componentCount, int precision) { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) + switch (colorSpace) { - // arrange - Type expectedType = typeof(JpegColorConverterBase.CmykScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.CmykVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.CmykVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.CmykVector128); - } - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); + case JpegColorSpace.Grayscale: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.RGB: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.Cmyk: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.YCbCr: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.Ycck: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.TiffCmyk: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.TiffYccK: + ValidateOperator(componentCount, precision); + break; + default: + Assert.Fail($"Unexpected JPEG color space: {colorSpace}."); + break; } } - [Fact] - public void GetConverterReturnsCorrectConverterWithYCbCrColorSpace() + /// + /// Verifies the flattened ICC traversal against independently composed color-model and profile conversions. + /// + /// The JPEG color space. + /// The number of component planes owned by the color model. + /// The JPEG sample precision. + [Theory] + [InlineData(JpegColorSpace.Grayscale, 1, 8)] + [InlineData(JpegColorSpace.Grayscale, 1, 12)] + [InlineData(JpegColorSpace.RGB, 3, 8)] + [InlineData(JpegColorSpace.RGB, 3, 12)] + [InlineData(JpegColorSpace.YCbCr, 3, 8)] + [InlineData(JpegColorSpace.YCbCr, 3, 12)] + [InlineData(JpegColorSpace.Cmyk, 4, 8)] + [InlineData(JpegColorSpace.Cmyk, 4, 12)] + [InlineData(JpegColorSpace.Ycck, 4, 8)] + [InlineData(JpegColorSpace.Ycck, 4, 12)] + [InlineData(JpegColorSpace.TiffCmyk, 4, 8)] + [InlineData(JpegColorSpace.TiffCmyk, 4, 12)] + [InlineData(JpegColorSpace.TiffYccK, 4, 8)] + [InlineData(JpegColorSpace.TiffYccK, 4, 12)] + internal void ConvertToRgbWithIccMatchesColorProfileDefinition(JpegColorSpace colorSpace, int componentCount, int precision) { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic); + const int length = 8; + JpegColorConverterBase.ComponentValues source = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, precision); - static void RunTest(string arg) + // YccK and CMYK profiles describe the conventional CMYK values produced after JPEG inversion + // and any YccK model transform. Three-component models use an RGB profile, while grayscale + // needs a one-channel profile so the profile's declared data space agrees with the Y input. + IccProfile profile = colorSpace switch { - // arrange - Type expectedType = typeof(JpegColorConverterBase.YCbCrScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YCbCrVector512); - } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) - { - expectedType = typeof(JpegColorConverterBase.YCbCrVector256); - } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) + JpegColorSpace.Grayscale => CreateGrayProfile(), + JpegColorSpace.Cmyk or JpegColorSpace.Ycck or JpegColorSpace.TiffCmyk or JpegColorSpace.TiffYccK + => TestIccProfiles.GetProfile(TestIccProfiles.Fogra39), + _ => CompactSrgbV4Profile.Profile, + }; + + converter.ConvertToRgbInPlaceWithIcc(Configuration.Default, actual, profile); + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter profileConverter = new(options); + ColorProfileConverter modelConverter = new(); + float maximumValue = MathF.Pow(2, precision) - 1; + Rgb[] grayscaleExpected = null; + + if (colorSpace == JpegColorSpace.Grayscale) + { + // The profile converter has distinct scalar and span entry points. Production converts the complete + // grayscale row, so the independent reference must exercise that same public span contract. + Y[] luminance = new Y[length]; + grayscaleExpected = new Rgb[length]; + + for (int i = 0; i < length; i++) { - expectedType = typeof(JpegColorConverterBase.YCbCrVector128); + luminance[i] = new Y(source.Component0[i] / maximumValue); } - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); + profileConverter.Convert(luminance, grayscaleExpected); } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithYcckColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableHWIntrinsic); - static void RunTest(string arg) + for (int i = 0; i < length; i++) { - // arrange - Type expectedType = typeof(JpegColorConverterBase.YccKScalar); - if (JpegColorConverterBase.JpegColorConverterVector512.IsSupported) + float c0 = source.Component0[i] / maximumValue; + float c1 = componentCount >= 2 ? source.Component1[i] / maximumValue : 0; + float c2 = componentCount >= 3 ? source.Component2[i] / maximumValue : 0; + float c3 = componentCount == 4 ? source.Component3[i] / maximumValue : 0; + Rgb expected; + + switch (colorSpace) { - expectedType = typeof(JpegColorConverterBase.YccKVector512); + case JpegColorSpace.Grayscale: + expected = grayscaleExpected[i]; + break; + + case JpegColorSpace.RGB: + expected = profileConverter.Convert(new Rgb(c0, c1, c2)); + break; + + case JpegColorSpace.YCbCr: + Rgb rgb = modelConverter.Convert(new YCbCr(c0, c1, c2)); + expected = profileConverter.Convert(rgb); + break; + + case JpegColorSpace.Cmyk: + expected = profileConverter.Convert(new Cmyk(1F - c0, 1F - c1, 1F - c2, 1F - c3)); + break; + + case JpegColorSpace.Ycck: + Cmyk cmyk = modelConverter.Convert(new YccK(1F - c0, 1F - c1, 1F - c2, 1F - c3)); + expected = profileConverter.Convert(cmyk); + break; + + case JpegColorSpace.TiffCmyk: + expected = profileConverter.Convert(new Cmyk(c0, c1, c2, c3)); + break; + + case JpegColorSpace.TiffYccK: + Cmyk tiffCmyk = modelConverter.Convert(new YccK(c0, c1, c2, c3)); + expected = profileConverter.Convert(tiffCmyk); + break; + + default: + Assert.Fail($"Unexpected JPEG color space: {colorSpace}."); + return; } - else if (JpegColorConverterBase.JpegColorConverterVector256.IsSupported) + + if (colorSpace == JpegColorSpace.Grayscale) { - expectedType = typeof(JpegColorConverterBase.YccKVector256); + // Grayscale exposes one physical component plane through all three component views. Deinterleaving + // therefore leaves the final blue write in that plane, matching the established converter contract. + Assert.Equal(expected.B, actual.Component0[i], ToRgbTolerance); } - else if (JpegColorConverterBase.JpegColorConverterVector128.IsSupported) + else { - expectedType = typeof(JpegColorConverterBase.YccKVector128); + Assert.Equal(expected.R, actual.Component0[i], ToRgbTolerance); + Assert.Equal(expected.G, actual.Component1[i], ToRgbTolerance); + Assert.Equal(expected.B, actual.Component2[i], ToRgbTolerance); } - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); } } - [Theory] - [InlineData(JpegColorSpace.Grayscale, 1)] - [InlineData(JpegColorSpace.Ycck, 4)] - [InlineData(JpegColorSpace.Cmyk, 4)] - [InlineData(JpegColorSpace.RGB, 3)] - [InlineData(JpegColorSpace.YCbCr, 3)] - internal void ConvertToRgbWithSelectedConverter(JpegColorSpace colorSpace, int componentCount) - { - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); - ValidateConversionToRgb( - converter, - componentCount, - 1); - } - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.YCbCrScalar(8), 3, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector512(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector256(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector128(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector512(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector256(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector128(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.CmykScalar(8), 4, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector512(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector256(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector128(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector512(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector256(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector128(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.GrayScaleScalar(8), 1, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector512(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector256(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector128(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector512(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector256(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector128(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.RgbScalar(8), 3, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector512(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector256(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector128(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector512(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector256(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector128(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.YccKScalar(8), 4, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector512(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); + /// + /// Verifies that the shared converter retains its scalar behavior when hardware intrinsics are disabled. + /// + [Fact] + public void OperatorTraversalMatchesScalarWithoutHardwareIntrinsics() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunWithoutHardwareIntrinsics, HwIntrinsics.DisableHWIntrinsic); - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector256(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); + /// + /// Verifies TIFF YccK encoding against the canonical normalized color-profile conversion. + /// + [Fact] + public void TiffYccKOperatorFromRgbMatchesColorProfileDefinition() + { + const int maximumLength = 40; + const float maximumValue = 255F; + const float halfValue = 128F; + float[] rSeed = [0, 255, 255, 0, 0, 127, 32, 240, 0, 255, 255, 0, 0, 127, 32, 240, 0, 255, 64, 192]; + float[] gSeed = [0, 255, 0, 255, 0, 127, 160, 16, 0, 255, 0, 255, 0, 127, 160, 16, 255, 0, 128, 96]; + float[] bSeed = [0, 255, 0, 0, 255, 127, 224, 80, 255, 0, 255, 0, 0, 127, 224, 80, 0, 255, 192, 32]; + float[] r = new float[maximumLength]; + float[] g = new float[maximumLength]; + float[] b = new float[maximumLength]; + JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.TiffYccK, 8); + + // Repeating the color set provides enough lanes to exercise every SIMD width and mixed-width tail. + rSeed.CopyTo(r, 0); + rSeed.CopyTo(r, rSeed.Length); + gSeed.CopyTo(g, 0); + gSeed.CopyTo(g, gSeed.Length); + bSeed.CopyTo(b, 0); + bSeed.CopyTo(b, bSeed.Length); + + ColorProfileConverter reference = new(); + int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40]; + + foreach (int length in lengths) + { + float[] y = new float[length]; + float[] cb = new float[length]; + float[] cr = new float[length]; + float[] k = new float[length]; + JpegColorConverterBase.ComponentValues values = new(4, y, cb, cr, k); - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector128(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); + converter.ConvertFromRgb(values, r.AsSpan(0, length), g.AsSpan(0, length), b.AsSpan(0, length)); - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector512(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); + for (int i = 0; i < length; i++) + { + Rgb rgb = new(r[i] / maximumValue, g[i] / maximumValue, b[i] / maximumValue); + YccK expected = reference.Convert(rgb); - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector256(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); + Assert.Equal(expected.Y * maximumValue, y[i], ToRgbTolerance); - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector128(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); - - private void TestConversionToRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter = null) - { - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; + // JPEG centers chroma on the integer midpoint, while the color-profile definition uses exactly 0.5. + Assert.Equal(halfValue + ((expected.Cb - 0.5F) * maximumValue), cb[i], ToRgbTolerance); + Assert.Equal(halfValue + ((expected.Cr - 0.5F) * maximumValue), cr[i], ToRgbTolerance); + Assert.Equal(expected.K * maximumValue, k[i], ToRgbTolerance); + } } - - ValidateConversionToRgb( - converter, - componentCount, - seed, - baseLineConverter); } - private void TestConversionFromRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter, - int precision) + /// + /// Runs the disabled-intrinsics scalar comparison inside the feature-test process. + /// + /// The unused feature-test argument. + private static void RunWithoutHardwareIntrinsics(string arg) + => ValidateOperator(3, 8); + + /// + /// Checks one closed operator converter at every scalar and SIMD transition length. + /// + /// The color-model operator under test. + /// The number of component planes owned by the operator. + /// The JPEG sample precision. + private static void ValidateOperator(int componentCount, int precision) + where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { - if (!converter.IsAvailable) + JpegColorConverterBase converter = new JpegColorConverterBase.JpegColorConverter(precision); + int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40, 64, 128]; + + // Adjacent values around 4/8/16 lanes verify every prefix, mixed-width tail, and scalar remainder. + foreach (int length in lengths) { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; + ValidateConversionToRgb(converter, length, componentCount, precision); + ValidateConversionFromRgb(converter, length, componentCount, precision); } - - ValidateConversionFromRgb( - converter, - componentCount, - seed, - baseLineConverter, - precision); } - private static JpegColorConverterBase.ComponentValues CreateRandomValues( - int length, - int componentCount, - int seed) + /// + /// Compares the adaptive component-to-RGB traversal with repeated scalar operator calls. + /// + /// The color-model operator under test. + /// The adaptive converter. + /// The number of samples to convert. + /// The number of source component planes. + /// The JPEG sample precision. + private static void ValidateConversionToRgb(JpegColorConverterBase converter, int length, int componentCount, int precision) + where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { - Random rnd = new(seed); + JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); + float maximumValue = MathF.Pow(2, precision) - 1; + float halfValue = MathF.Ceiling(maximumValue * 0.5F); + float scale = 1F / maximumValue; - Buffer2D[] buffers = new Buffer2D[componentCount]; - for (int i = 0; i < componentCount; i++) + for (int i = 0; i < length; i++) { - float[] values = new float[length]; - - for (int j = 0; j < values.Length; j++) - { - values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; - } + ref float c0 = ref expected.Component0[i]; + ref float c1 = ref expected.Component1[i]; + ref float c2 = ref expected.Component2[i]; + float c3 = componentCount == 4 ? expected.Component3[i] : 0; - // no need to dispose when buffer is not array owner - Memory memory = new(values); - MemoryGroup source = MemoryGroup.Wrap(memory); - buffers[i] = new Buffer2D(source, values.Length, 1); + TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scale); } - return new JpegColorConverterBase.ComponentValues(buffers, 0); - } - - private static float[] CreateRandomValues(int length, Random rnd) - { - float[] values = new float[length]; - - for (int j = 0; j < values.Length; j++) - { - values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; - } + converter.ConvertToRgbInPlace(actual); - return values; + // YCbCr conversion rounds in the integer sample domain before normalization. Fused SIMD arithmetic can + // cross a half-way boundary differently from the scalar expression, so allow one source-sample quantum + // in addition to the ordinary floating-point tolerance at both supported sample precisions. + float tolerance = scale + ToRgbTolerance; + CompareSequence(expected.Component0, actual.Component0, tolerance); + CompareSequence(expected.Component1, actual.Component1, tolerance); + CompareSequence(expected.Component2, actual.Component2, tolerance); } - private static void ValidateConversionToRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter = null) + /// + /// Compares the adaptive RGB-to-component traversal with repeated scalar operator calls. + /// + /// The color-model operator under test. + /// The adaptive converter. + /// The number of samples to convert. + /// The number of destination component planes. + /// The JPEG sample precision. + private static void ValidateConversionFromRgb(JpegColorConverterBase converter, int length, int componentCount, int precision) + where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { - JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); - JpegColorConverterBase.ComponentValues actual = new( - original.ComponentCount, - original.Component0.ToArray(), - original.Component1.ToArray(), - original.Component2.ToArray(), - original.Component3.ToArray()); - - converter.ConvertToRgbInPlace(actual); - - for (int i = 0; i < TestBufferLength; i++) - { - Validate(converter.ColorSpace, original, actual, i); - } - - // Compare conversion result to a baseline, should be the scalar version. - if (baseLineConverter != null) + JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); + Random random = new(precision); + float[] r = CreateRandomValues(length, random); + float[] g = CreateRandomValues(length, random); + float[] b = CreateRandomValues(length, random); + float maximumValue = MathF.Pow(2, precision) - 1; + float halfValue = MathF.Ceiling(maximumValue * 0.5F); + float scale = 1F / maximumValue; + + for (int i = 0; i < length; i++) { - JpegColorConverterBase.ComponentValues expected = new( - original.ComponentCount, - original.Component0.ToArray(), - original.Component1.ToArray(), - original.Component2.ToArray(), - original.Component3.ToArray()); - baseLineConverter.ConvertToRgbInPlace(expected); - if (componentCount == 1) - { - Assert.True(expected.Component0.SequenceEqual(actual.Component0)); - } + TOperator.ConvertFromRgb(r[i], g[i], b[i], maximumValue, halfValue, scale, out expected.Component0[i], out float c1, out float c2, out float c3); - if (componentCount == 2) + if (componentCount >= 2) { - Assert.True(expected.Component1.SequenceEqual(actual.Component1)); + expected.Component1[i] = c1; } - if (componentCount == 3) + if (componentCount >= 3) { - Assert.True(expected.Component2.SequenceEqual(actual.Component2)); + expected.Component2[i] = c2; } if (componentCount == 4) { - Assert.True(expected.Component3.SequenceEqual(actual.Component3)); + expected.Component3[i] = c3; } } - } - private static void ValidateConversionFromRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter, - int precision = 4) - { - // arrange - JpegColorConverterBase.ComponentValues actual = CreateRandomValues(TestBufferLength, componentCount, seed); - JpegColorConverterBase.ComponentValues expected = CreateRandomValues(TestBufferLength, componentCount, seed); - Random rnd = new(seed); - float[] rLane = CreateRandomValues(TestBufferLength, rnd); - float[] gLane = CreateRandomValues(TestBufferLength, rnd); - float[] bLane = CreateRandomValues(TestBufferLength, rnd); - - // act - converter.ConvertFromRgb(actual, rLane, gLane, bLane); - baseLineConverter.ConvertFromRgb(expected, rLane, gLane, bLane); - - // assert - if (componentCount == 1) - { - CompareSequenceWithTolerance(expected.Component0, actual.Component0, precision); - } + converter.ConvertFromRgb(actual, r, g, b); + CompareSequence(expected.Component0, actual.Component0, FromRgbTolerance); - if (componentCount == 2) + if (componentCount >= 2) { - CompareSequenceWithTolerance(expected.Component1, actual.Component1, precision); + CompareSequence(expected.Component1, actual.Component1, FromRgbTolerance); } - if (componentCount == 3) + if (componentCount >= 3) { - CompareSequenceWithTolerance(expected.Component2, actual.Component2, precision); + CompareSequence(expected.Component2, actual.Component2, FromRgbTolerance); } if (componentCount == 4) { - CompareSequenceWithTolerance(expected.Component3, actual.Component3, precision); + CompareSequence(expected.Component3, actual.Component3, FromRgbTolerance); } } - private static void CompareSequenceWithTolerance(Span expected, Span actual, int precision) + /// + /// Creates deterministic component planes in the configured JPEG sample domain. + /// + /// The number of samples in each plane. + /// The number of independent component planes. + /// The JPEG sample precision and deterministic random seed. + /// The generated component planes. + private static JpegColorConverterBase.ComponentValues CreateRandomValues(int length, int componentCount, int precision) { - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i], precision: precision); - } + Random random = new(precision); + float maximumValue = MathF.Pow(2, precision) - 1; + float[] c0 = CreateRandomValues(length, random, maximumValue); + float[] c1 = componentCount >= 2 ? CreateRandomValues(length, random, maximumValue) : c0; + float[] c2 = componentCount >= 3 ? CreateRandomValues(length, random, maximumValue) : c0; + float[] c3 = componentCount == 4 ? CreateRandomValues(length, random, maximumValue) : []; + + return new JpegColorConverterBase.ComponentValues(componentCount, c0, c1, c2, c3); } - private static void Validate( - JpegColorSpace colorSpace, - in JpegColorConverterBase.ComponentValues original, - in JpegColorConverterBase.ComponentValues result, - int i) + /// + /// Creates deterministic RGB samples in ImageSharp's byte-scaled encoder domain. + /// + /// The number of samples. + /// The deterministic random source. + /// The generated samples. + private static float[] CreateRandomValues(int length, Random random) + => CreateRandomValues(length, random, 255F); + + /// + /// Creates deterministic samples between zero and the supplied inclusive domain maximum. + /// + /// The number of samples. + /// The deterministic random source. + /// The upper bound of the sample domain. + /// The generated samples. + private static float[] CreateRandomValues(int length, Random random, float maximumValue) { - switch (colorSpace) + float[] values = new float[length]; + + for (int i = 0; i < values.Length; i++) { - case JpegColorSpace.Grayscale: - ValidateGrayScale(original, result, i); - break; - case JpegColorSpace.Ycck: - ValidateYccK(original, result, i); - break; - case JpegColorSpace.Cmyk: - ValidateCmyk(original, result, i); - break; - case JpegColorSpace.RGB: - ValidateRgb(original, result, i); - break; - case JpegColorSpace.YCbCr: - ValidateYCbCr(original, result, i); - break; - default: - Assert.Fail($"Invalid Colorspace enum value: {colorSpace}."); - break; + values[i] = random.NextSingle() * maximumValue; } + + return values; } - private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + /// + /// Creates an identity-transfer grayscale profile for the one-channel ICC test path. + /// + /// The grayscale ICC profile. + private static IccProfile CreateGrayProfile() { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128; - float cr = values.Component2[i] - 128; - - float r = (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); - float g = (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); - float b = (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); - - r /= MaxColorChannelValue; - g /= MaxColorChannelValue; - b /= MaxColorChannelValue; - - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + IccProfileHeader header = new() + { + Class = IccProfileClass.InputDevice, + DataColorSpace = IccColorSpaceType.Gray, + ProfileConnectionSpace = IccColorSpaceType.CieXyz, + RenderingIntent = IccRenderingIntent.MediaRelativeColorimetric, + Version = new IccVersion(4, 3, 0), + }; + + // An empty ICC curve is the standard identity transfer function. Tagging it as GrayTrc gives + // the profile converter a complete one-channel device-to-PCS path without test-only LUT data. + IccTagDataEntry[] entries = [new IccCurveTagDataEntry(IccProfileTag.GrayTrc)]; + return new IccProfile(header, entries); } - private static void ValidateYccK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + /// + /// Compares one converted sample with an independent definition of its JPEG color model. + /// + /// The JPEG color space. + /// The unmodified source component planes. + /// The converted RGB planes. + /// The sample index. + private static void AssertColorModelDefinition(JpegColorSpace colorSpace, in JpegColorConverterBase.ComponentValues source, in JpegColorConverterBase.ComponentValues actual, int index) { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128F; - float cr = values.Component2[i] - 128F; - float k = values.Component3[i] / 255F; + float c0 = source.Component0[index]; + float c1 = source.Component1[index]; + float c2 = source.Component2[index]; + float c3 = 0; + Rgb expected; - float r = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - float g = (255F - (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; - float b = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; - - r /= MaxColorChannelValue; - g /= MaxColorChannelValue; - b /= MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); + switch (colorSpace) + { + case JpegColorSpace.Grayscale: + float luminance = c0 / MaxColorChannelValue; + expected = new Rgb(luminance, luminance, luminance); + break; + case JpegColorSpace.RGB: + expected = new Rgb(c0 / MaxColorChannelValue, c1 / MaxColorChannelValue, c2 / MaxColorChannelValue); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); + break; + case JpegColorSpace.Cmyk: + c3 = source.Component3[index] / MaxColorChannelValue; + expected = new Rgb(c0 * c3 / MaxColorChannelValue, c1 * c3 / MaxColorChannelValue, c2 * c3 / MaxColorChannelValue); - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + break; + case JpegColorSpace.YCbCr: + c1 -= 128F; + c2 -= 128F; - private static void ValidateRgb(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float r = values.Component0[i] / MaxColorChannelValue; - float g = values.Component1[i] / MaxColorChannelValue; - float b = values.Component2[i] / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); + // JPEG applies the BT.601 matrix in the integer sample domain and rounds before normalization. + expected = new Rgb(MathF.Round(c0 + (1.402F * c2), MidpointRounding.AwayFromZero) / MaxColorChannelValue, MathF.Round(c0 - (0.344136F * c1) - (0.714136F * c2), MidpointRounding.AwayFromZero) / MaxColorChannelValue, MathF.Round(c0 + (1.772F * c1), MidpointRounding.AwayFromZero) / MaxColorChannelValue); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); + break; + case JpegColorSpace.Ycck: + c1 -= 128F; + c2 -= 128F; + c3 = source.Component3[index] / MaxColorChannelValue; - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + // Adobe YccK reconstructs inverted RGB first, then applies the normalized black component. + expected = new Rgb((MaxColorChannelValue - MathF.Round(c0 + (1.402F * c2), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue, (MaxColorChannelValue - MathF.Round(c0 - (0.344136F * c1) - (0.714136F * c2), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue, (MaxColorChannelValue - MathF.Round(c0 + (1.772F * c1), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue); - private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i] / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(y, y, y)); + break; + default: + Assert.Fail($"Unexpected JPEG color space: {colorSpace}."); + return; + } - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component0[i], result.Component0[i])); + // Color-space comparison intentionally clamps both sides because JPEG reconstruction can overshoot + // the normalized RGB gamut and saturation belongs to the eventual pixel conversion. + Rgb clampedExpected = Rgb.Clamp(expected); + Rgb clampedActual = Rgb.Clamp(new Rgb(actual.Component0[index], actual.Component1[index], actual.Component2[index])); - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + Assert.True(ColorSpaceComparer.Equals(clampedExpected, clampedActual), $"Colors {clampedExpected} and {clampedActual} are not equal at index {index}."); } - private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + /// + /// Compares two component planes using an absolute floating-point tolerance. + /// + /// The scalar reference values. + /// The adaptive traversal values. + /// The maximum permitted absolute difference. + private static void CompareSequence(Span expected, Span actual, float tolerance) { - float c = values.Component0[i]; - float m = values.Component1[i]; - float y = values.Component2[i]; - float k = values.Component3[i] / MaxColorChannelValue; - - float r = c * k / MaxColorChannelValue; - float g = m * k / MaxColorChannelValue; - float b = y * k / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); - - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); + Assert.Equal(expected.Length, actual.Length); - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i], tolerance); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorPackingTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorPackingTests.cs new file mode 100644 index 000000000..20b4f1679 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorPackingTests.cs @@ -0,0 +1,168 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.Formats.Jpg; + +/// +/// Tests the planar and packed buffer transformations used around JPEG color-profile conversion. +/// +[Trait("Format", "Jpg")] +public class JpegColorPackingTests +{ + private static readonly int[] Lengths = [0, 1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 31, 32, 33, 129]; + + /// + /// Verifies every packing operation against its scalar definition with and without hardware intrinsics. + /// + [Fact] + public void PackingOperationsMatchScalarDefinitions() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ValidatePackingOperations, HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Exercises every SIMD transition and scalar remainder for the packing operations. + /// + private static void ValidatePackingOperations() + { + foreach (int length in Lengths) + { + ValidatePackedNormalizeInterleave3(length); + ValidateUnpackDeinterleave3(length); + ValidatePackedNormalizeInterleave4(length); + ValidatePackedInvertNormalizeInterleave4(length); + } + } + + /// + /// Compares normalized three-plane interleaving with the original scalar loop. + /// + /// The number of samples in each component plane. + private static void ValidatePackedNormalizeInterleave3(int length) + { + const float scale = 1F / 255F; + float[] x = CreateSamples(length, 1); + float[] y = CreateSamples(length, 2); + float[] z = CreateSamples(length, 3); + float[] expected = new float[length * 3]; + float[] actual = new float[length * 3]; + + for (int i = 0; i < length; i++) + { + int packedOffset = i * 3; + expected[packedOffset] = x[i] * scale; + expected[packedOffset + 1] = y[i] * scale; + expected[packedOffset + 2] = z[i] * scale; + } + + JpegColorConverterBase.PackedNormalizeInterleave3(x, y, z, actual, scale); + + Assert.Equal(expected, actual); + } + + /// + /// Compares packed three-channel deinterleaving with the original scalar loop. + /// + /// The number of packed values. + private static void ValidateUnpackDeinterleave3(int length) + { + Vector3[] packed = new Vector3[length]; + float[] expectedX = CreateSamples(length, 1); + float[] expectedY = CreateSamples(length, 2); + float[] expectedZ = CreateSamples(length, 3); + float[] actualX = new float[length]; + float[] actualY = new float[length]; + float[] actualZ = new float[length]; + + for (int i = 0; i < length; i++) + { + packed[i] = new Vector3(expectedX[i], expectedY[i], expectedZ[i]); + } + + JpegColorConverterBase.UnpackDeinterleave3(packed, actualX, actualY, actualZ); + + Assert.Equal(expectedX, actualX); + Assert.Equal(expectedY, actualY); + Assert.Equal(expectedZ, actualZ); + } + + /// + /// Compares normalized four-plane interleaving with the original scalar loop. + /// + /// The number of samples in each component plane. + private static void ValidatePackedNormalizeInterleave4(int length) + { + const float maximumValue = 255F; + const float scale = 1F / maximumValue; + float[] x = CreateSamples(length, 1); + float[] y = CreateSamples(length, 2); + float[] z = CreateSamples(length, 3); + float[] w = CreateSamples(length, 4); + float[] expected = new float[length * 4]; + float[] actual = new float[length * 4]; + + for (int i = 0; i < length; i++) + { + int packedOffset = i * 4; + expected[packedOffset] = x[i] * scale; + expected[packedOffset + 1] = y[i] * scale; + expected[packedOffset + 2] = z[i] * scale; + expected[packedOffset + 3] = w[i] * scale; + } + + JpegColorConverterBase.PackedNormalizeInterleave4(x, y, z, w, actual, maximumValue); + + Assert.Equal(expected, actual); + } + + /// + /// Compares inverted normalized four-plane interleaving with the original scalar loop. + /// + /// The number of samples in each component plane. + private static void ValidatePackedInvertNormalizeInterleave4(int length) + { + const float maximumValue = 255F; + const float scale = 1F / maximumValue; + float[] x = CreateSamples(length, 1); + float[] y = CreateSamples(length, 2); + float[] z = CreateSamples(length, 3); + float[] w = CreateSamples(length, 4); + float[] expected = new float[length * 4]; + float[] actual = new float[length * 4]; + + for (int i = 0; i < length; i++) + { + int packedOffset = i * 4; + expected[packedOffset] = (maximumValue - x[i]) * scale; + expected[packedOffset + 1] = (maximumValue - y[i]) * scale; + expected[packedOffset + 2] = (maximumValue - z[i]) * scale; + expected[packedOffset + 3] = (maximumValue - w[i]) * scale; + } + + JpegColorConverterBase.PackedInvertNormalizeInterleave4(x, y, z, w, actual, maximumValue); + + Assert.Equal(expected, actual); + } + + /// + /// Creates deterministic, non-integral sample values that expose lane-order and arithmetic mistakes. + /// + /// The number of samples to create. + /// The one-based component number used to distinguish each plane. + /// The generated sample values. + private static float[] CreateSamples(int length, int component) + { + float[] samples = new float[length]; + + for (int i = 0; i < samples.Length; i++) + { + // The relatively prime multipliers produce a distinct sequence for each plane + // while keeping every value inside the eight-bit JPEG sample domain. + samples[i] = (((i * 37) + (component * 53)) % 251) + (component * 0.125F); + } + + return samples; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index f147e4132..e7e6d2033 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -30,7 +30,7 @@ public partial class JpegDecoderTests } using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); + image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr, appendPixelTypeToFileName: false); provider.Utility.TestName = DecodeBaselineJpegOutputName; image.CompareToReferenceOutput( diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index a5472e1ae..fff1e085b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -21,7 +21,7 @@ public partial class JpegDecoderTests where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(JpegDecoder.Instance); - image.DebugSave(provider); + image.DebugSave(provider, appendPixelTypeToFileName: false); provider.Utility.TestName = DecodeProgressiveJpegOutputName; image.CompareToReferenceOutput( diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 2856abe5c..f3005d124 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -9,7 +9,6 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg; @@ -402,6 +401,9 @@ public partial class JpegDecoderTests [WithFile(TestImages.Jpeg.ICC.ProPhoto, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.ICC.WideRGB, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.ICC.AppleRGB, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.ICC.SRgbGray, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.ICC.Perceptual, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.ICC.PerceptualcLUTOnly, PixelTypes.Rgba32)] public void Decode_RGB_ICC_Jpeg(TestImageProvider provider) where TPixel : unmanaged, IPixel { @@ -414,4 +416,111 @@ public partial class JpegDecoderTests image.DebugSave(provider); image.CompareToReferenceOutput(provider); } + + [Theory] + [WithFile(TestImages.Jpeg.ICC.Issue3064, PixelTypes.Rgba32)] + public void Decode_RGB_ICC_Jpeg_Issue3064(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + JpegDecoderOptions options = new() + { + GeneralOptions = new DecoderOptions { ColorProfileHandling = ColorProfileHandling.Convert } + }; + + using Image image = provider.GetImage(JpegDecoder.Instance, options); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + } + + // https://github.com/SixLabors/ImageSharp/issues/2948 + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue2948, PixelTypes.Rgb24)] + public void Issue2948_No_SOS_Decode_Throws_InvalidImageContentException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + => Assert.Throws(() => + { + using Image image = provider.GetImage(JpegDecoder.Instance); + }); + + // https://github.com/SixLabors/ImageSharp/issues/2948 + [Theory] + [InlineData(TestImages.Jpeg.Issues.Issue2948)] + public void Issue2948_No_SOS_Identify_Throws_InvalidImageContentException(string imagePath) + => Assert.Throws(() => _ = Image.Identify(TestFile.Create(imagePath).Bytes)); + + [Fact] + public void Issue_3071_Decode_TruncatedJpeg_Throws_InvalidImageContentException() + => Assert.Throws(() => + { + // SOI marker (FF D8) + garbage bytes — only 11 bytes + byte[] data = [0xFF, 0xD8, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30]; + using Image image = Image.Load(data); + }); + + // https://github.com/SixLabors/ImageSharp/issues/3118 + [Theory] + [WithFile(TestImages.Jpeg.Issues.Issue3118, PixelTypes.Rgb24)] + public void Issue3118_Multiple_SOF_WithSOS_DoesNotThrow(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(JpegDecoder.Instance); + image.DebugSave(provider); + } + + [Fact] + public void Identify_MalformedApp13Segment_IgnoresNonCriticalErrorsByDefault() + { + ImageInfo info = Image.Identify(CreateJpegWithMalformedApp13Segment()); + Assert.True(info.Width > 0); + Assert.True(info.Height > 0); + } + + [Fact] + public void Decode_MalformedApp13Segment_IgnoresNonCriticalErrorsByDefault() + { + using Image image = Image.Load(CreateJpegWithMalformedApp13Segment()); + Assert.True(image.Width > 0); + Assert.True(image.Height > 0); + } + + [Fact] + public void Identify_MalformedApp13Segment_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => Image.Identify(options, CreateJpegWithMalformedApp13Segment())); + } + + [Fact] + public void Decode_MalformedApp13Segment_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => + { + using Image image = Image.Load(options, CreateJpegWithMalformedApp13Segment()); + }); + } + + private static byte[] CreateJpegWithMalformedApp13Segment() + { + byte[] source = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora).Bytes; + + // This APP13 segment starts with the valid "Photoshop 3.0\0" identifier, but the remaining + // payload does not begin with the required "8BIM" image resource block signature. + byte[] malformedApp13 = + [ + 0xFF, 0xED, + 0x00, 0x1D, + (byte)'P', (byte)'h', (byte)'o', (byte)'t', (byte)'o', (byte)'s', (byte)'h', (byte)'o', (byte)'p', (byte)' ', (byte)'3', (byte)'.', (byte)'0', 0x00, + (byte)'B', (byte)'a', (byte)'d', (byte)'R', (byte)'e', (byte)'s', (byte)'o', (byte)'u', (byte)'r', (byte)'c', (byte)'e', (byte)'!', (byte)'!' + ]; + + byte[] payload = new byte[source.Length + malformedApp13.Length]; + payload[0] = source[0]; + payload[1] = source[1]; + + Buffer.BlockCopy(malformedApp13, 0, payload, 2, malformedApp13.Length); + Buffer.BlockCopy(source, 2, payload, 2 + malformedApp13.Length, source.Length - 2); + + return payload; + } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index ede147dbe..5fae85cc8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -49,7 +49,7 @@ public partial class JpegEncoderTests public static readonly TheoryData CmykEncodingSetups = new() { - { JpegColorType.Cmyk, 100, 0.0159f / 100 }, + { JpegColorType.Cmyk, 100, 0.0164f / 100 }, { JpegColorType.Cmyk, 80, 0.3922f / 100 }, { JpegColorType.Cmyk, 40, 0.6488f / 100 }, }; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs index 37be0f8f5..6ea528818 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs @@ -71,7 +71,7 @@ public class JpegMetadataTests public void Comment_OnlyComment() { string comment = "test comment"; - Collection expectedCollection = new() { comment }; + Collection expectedCollection = [comment]; JpegMetadata meta = new(); meta.Comments.Add(JpegComData.FromString(comment)); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs index b6a59fa93..a14df945a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ParseStreamTests.cs @@ -6,7 +6,6 @@ using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs index c593a029a..d049472c8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.AccurateDCT.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 2fcd953ae..80b42cce6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -4,7 +4,6 @@ // ReSharper disable InconsistentNaming using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs index b44734972..b6c5ba1c9 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index 2e97b9596..f7968ea6b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using Xunit.Abstractions; - // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 903a6b076..c3bb7f8dd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -9,7 +9,6 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg; @@ -36,7 +35,7 @@ public class SpectralJpegTests TestImages.Jpeg.Progressive.Bad.ExifUndefType ]; - public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); + public static readonly string[] AllTestJpegs = [.. BaselineTestJpegs, .. ProgressiveTestJpegs]; [Theory(Skip = "Debug only, enable manually!")] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] @@ -119,7 +118,7 @@ public class SpectralJpegTests this.Output.WriteLine($"Component{i}: [total: {total} | average: {average}]"); averageDifference += average; totalDifference += total; - Size s = libJpegComponent.SpectralBlocks.Size(); + Size s = libJpegComponent.SpectralBlocks.Size; tolerance += s.Width * s.Height; } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs index 13300ee80..14c7aacd5 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs @@ -7,7 +7,6 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index 33e95c5aa..57866989a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -6,7 +6,6 @@ using System.Text; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs index 888459112..370736c40 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.cs @@ -121,7 +121,7 @@ internal static partial class LibJpegTools { rdr.Read(buffer, 0, buffer.Length); - short[] block = MemoryMarshal.Cast(buffer.AsSpan()).ToArray(); + short[] block = [.. MemoryMarshal.Cast(buffer.AsSpan())]; c.MakeBlock(block, y, x); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs index 7c7f47f94..35ba1b3c3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.LLM_FloatingPoint_DCT.cs @@ -5,8 +5,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using Xunit.Abstractions; - // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs index 7a31e35d0..14213efa8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/VerifyJpeg.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; @@ -34,7 +33,7 @@ internal static class VerifyJpeg int xBc2, int yBc2) { - IJpegComponent[] c = components.ToArray(); + IJpegComponent[] c = [.. components]; Assert.Equal(3, components.Count()); VerifySize(c[0], xBc0, yBc0); diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs index 476538ccd..778ad7589 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; // ReSharper disable InconsistentNaming @@ -26,6 +27,7 @@ public class PbmDecoderTests [InlineData(RgbPlain, PbmColorType.Rgb, PbmComponentType.Byte)] [InlineData(RgbPlainMagick, PbmColorType.Rgb, PbmComponentType.Byte)] [InlineData(RgbBinary, PbmColorType.Rgb, PbmComponentType.Byte)] + [InlineData(RgbBinaryWide, PbmColorType.Rgb, PbmComponentType.Short)] public void ImageLoadCanDecode(string imagePath, PbmColorType expectedColorType, PbmComponentType expectedComponentType) { // Arrange @@ -91,6 +93,7 @@ public class PbmDecoderTests [WithFile(RgbPlain, PixelTypes.Rgb24, "ppm")] [WithFile(RgbPlainNormalized, PixelTypes.Rgb24, "ppm")] [WithFile(RgbBinary, PixelTypes.Rgb24, "ppm")] + [WithFile(RgbBinaryWide, PixelTypes.Rgb48, "ppm")] public void DecodeReferenceImage(TestImageProvider provider, string extension) where TPixel : unmanaged, IPixel { @@ -101,6 +104,65 @@ public class PbmDecoderTests image.CompareToReferenceOutput(provider, grayscale: isGrayscale); } + [Theory] + [WithFile(GrayscaleBinaryWide, PixelTypes.Rgb48)] + [WithFile(RgbBinaryWide, PixelTypes.Rgb48)] + public void Decode_WideBinary_MatchesReferenceDecoder(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PbmDecoder.Instance); + image.CompareToOriginal(provider, ImageComparer.Exact, new MagickReferenceDecoder(PbmFormat.Instance)); + } + + [Fact] + public void Decode_WideBinaryGrayscale_SamplesAreBigEndian() + { + // Per the Netpbm specification, 16-bit samples store the most significant byte first. + byte[] header = Encoding.ASCII.GetBytes("P5\n2 1\n65535\n"); + byte[] samples = [0x80, 0x00, 0x00, 0x80]; + byte[] data = [.. header, .. samples]; + + using Image image = Image.Load(data); + + Assert.Equal(0x8000, image[0, 0].PackedValue); + Assert.Equal(0x0080, image[1, 0].PackedValue); + } + + [Fact] + public void Decode_WideBinaryRgb_SamplesAreBigEndian() + { + // Per the Netpbm specification, 16-bit samples store the most significant byte first. + byte[] header = Encoding.ASCII.GetBytes("P6\n1 1\n65535\n"); + byte[] samples = [0x81, 0xB5, 0x84, 0x91, 0x86, 0x71]; + byte[] data = [.. header, .. samples]; + + using Image image = Image.Load(data); + + Assert.Equal(new Rgb48(0x81B5, 0x8491, 0x8671), image[0, 0]); + } + + [Fact] + public void Decode_NonStandardByteMaxPixelValue_UpscalesToFullRange() + { + byte[] data = Encoding.ASCII.GetBytes("P2\n1 1\n100\n100"); + + using Image image = Image.Load(data); + + Assert.Equal(255, image[0, 0].PackedValue); + } + + [Fact] + public void Decode_NonStandardShortMaxPixelValue_UpscalesToFullRange() + { + byte[] header = Encoding.ASCII.GetBytes("P5\n1 1\n1000\n"); + byte[] samples = [0x03, 0xE8]; + byte[] data = [.. header, .. samples]; + + using Image image = Image.Load(data); + + Assert.Equal(65535, image[0, 0].PackedValue); + } + [Theory] [WithFile(RgbPlain, PixelTypes.Rgb24)] public void PbmDecoder_Decode_Resize(TestImageProvider provider) @@ -123,6 +185,19 @@ public class PbmDecoderTests appendPixelTypeToFileName: false); } + [Theory] + [InlineData("P2")] + [InlineData("P3")] + [InlineData("P5")] + [InlineData("P6")] + public void Decode_MaxPixelValueZero_ThrowsInvalidImageContentException(string magic) + { + byte[] bytes = Encoding.ASCII.GetBytes($"{magic} 1 1 0\n0"); + using MemoryStream stream = new(bytes); + + Assert.Throws(() => Image.Load(stream)); + } + [Fact] public void PlainText_PrematureEof() { diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs index 0fea65f6e..5ef075404 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs @@ -31,6 +31,7 @@ public class PbmEncoderTests { GrayscaleBinaryWide, PbmColorType.Grayscale }, { GrayscalePlain, PbmColorType.Grayscale }, { RgbBinary, PbmColorType.Rgb }, + { RgbBinaryWide, PbmColorType.Rgb }, { RgbPlain, PbmColorType.Rgb }, }; @@ -122,6 +123,45 @@ public class PbmEncoderTests public void PbmEncoder_P6_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Binary); + [Fact] + public void PbmEncoder_WideBinaryGrayscale_WritesBigEndianSamples() + { + // Per the Netpbm specification, 16-bit samples store the most significant byte first. + using Image image = new(2, 1); + image[0, 0] = new L16(0x8000); + image[1, 0] = new L16(0x1234); + + using MemoryStream memStream = new(); + image.Save(memStream, new PbmEncoder + { + ColorType = PbmColorType.Grayscale, + ComponentType = PbmComponentType.Short, + Encoding = PbmEncoding.Binary + }); + + byte[] encoded = memStream.ToArray(); + Assert.Equal(new byte[] { 0x80, 0x00, 0x12, 0x34 }, encoded[^4..]); + } + + [Fact] + public void PbmEncoder_WideBinaryRgb_WritesBigEndianSamples() + { + // Per the Netpbm specification, 16-bit samples store the most significant byte first. + using Image image = new(1, 1); + image[0, 0] = new Rgb48(0x8000, 0x1234, 0x00FF); + + using MemoryStream memStream = new(); + image.Save(memStream, new PbmEncoder + { + ColorType = PbmColorType.Rgb, + ComponentType = PbmComponentType.Short, + Encoding = PbmEncoding.Binary + }); + + byte[] encoded = memStream.ToArray(); + Assert.Equal(new byte[] { 0x80, 0x00, 0x12, 0x34, 0x00, 0xFF }, encoded[^6..]); + } + private static void TestPbmEncoderCore( TestImageProvider provider, PbmColorType colorType, diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs index 6524b3506..6fca91e0d 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs @@ -54,6 +54,40 @@ public class PbmRoundTripTests ImageComparer.Exact.VerifySimilarity(originalImage, encodedImage); } + [Theory] + [InlineData(GrayscaleBinaryWide)] + public void PbmWideGrayscaleImageCanRoundTrip(string imagePath) + { + // Arrange + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + + // Act + using Image originalImage = Image.Load(stream); + using Image encodedImage = this.RoundTrip(originalImage); + + // Assert + Assert.NotNull(encodedImage); + ImageComparer.Exact.VerifySimilarity(originalImage, encodedImage); + } + + [Theory] + [InlineData(RgbBinaryWide)] + public void PbmWideColorImageCanRoundTrip(string imagePath) + { + // Arrange + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + + // Act + using Image originalImage = Image.Load(stream); + using Image encodedImage = this.RoundTrip(originalImage); + + // Assert + Assert.NotNull(encodedImage); + ImageComparer.Exact.VerifySimilarity(originalImage, encodedImage); + } + private Image RoundTrip(Image originalImage) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs deleted file mode 100644 index c2b640a1d..000000000 --- a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Compression.Zlib; -using SixLabors.ImageSharp.Tests.TestUtilities; -using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; - -namespace SixLabors.ImageSharp.Tests.Formats.Png; - -[Trait("Format", "Png")] -public class Adler32Tests -{ - [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(2)] - public void CalculateAdler_ReturnsCorrectWhenEmpty(uint input) => Assert.Equal(input, Adler32.Calculate(input, default)); - - [Theory] - [InlineData(0)] - [InlineData(8)] - [InlineData(215)] - [InlineData(1024)] - [InlineData(1024 + 15)] - [InlineData(2034)] - [InlineData(4096)] - public void CalculateAdler_MatchesReference(int length) => CalculateAdlerAndCompareToReference(length); - - private static void CalculateAdlerAndCompareToReference(int length) - { - // arrange - byte[] data = GetBuffer(length); - SharpAdler32 adler = new(); - adler.Update(data); - long expected = adler.Value; - - // act - long actual = Adler32.Calculate(data); - - // assert - Assert.Equal(expected, actual); - } - - private static byte[] GetBuffer(int length) - { - byte[] data = new byte[length]; - new Random(1).NextBytes(data); - - return data; - } - - [Fact] - public void RunCalculateAdlerTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll); - - [Fact] - public void RunCalculateAdlerTest_WithAvxDisabled_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); - - [Fact] - public void RunCalculateAdlerTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.DisableHWIntrinsic); - - private static void RunCalculateAdlerTest() - { - int[] testData = [0, 8, 215, 1024, 1024 + 15, 2034, 4096]; - for (int i = 0; i < testData.Length; i++) - { - CalculateAdlerAndCompareToReference(testData[i]); - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs new file mode 100644 index 000000000..863c7fc4f --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngCgbiProcessorTests.cs @@ -0,0 +1,105 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.Formats.Png; + +[Trait("Format", "Png")] +public class PngCgbiProcessorTests +{ + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(3)] + [InlineData(4)] + [InlineData(7)] + [InlineData(8)] + [InlineData(15)] + [InlineData(16)] + [InlineData(17)] + [InlineData(31)] + [InlineData(32)] + [InlineData(33)] + [InlineData(64)] + public void ApplyTransform_RgbWithAlpha_MatchesScalar(int pixelCount) => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + serialized => AssertApplyTransformMatchesScalar(FeatureTestRunner.Deserialize(serialized)), + pixelCount, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertApplyTransformMatchesScalar(int pixelCount) + { + byte[] input = CreateBgraScanline(pixelCount); + byte[] processorOutput = (byte[])input.Clone(); + byte[] scalarOutput = (byte[])input.Clone(); + + PngCgbiProcessor.ApplyTransform(Configuration.Default, processorOutput, PngColorType.RgbWithAlpha); + ApplyCgbiTransformScalarReference(scalarOutput); + + Assert.Equal(scalarOutput, processorOutput); + } + + /// + /// Builds synthetic input for tests. Produces inputs that exercise all three alpha cases. + /// + /// Channel values laid out as a defiltered CgBI scanline in premultiplied BGRA order + private static byte[] CreateBgraScanline(int pixelCount) + { + // The distinct strides keep the channels from being equal to each other or constant across pixels + // So a bug that e.g. swaps two channels or reuses one channel's value doesn't accidentally pass + const int alphaCaseCount = 7; + const int redStride = 13; + const int greenStride = 29; + const int blueStride = 53; + + byte[] bytes = new byte[pixelCount * 4]; + for (int p = 0; p < pixelCount; p++) + { + // Cycling alpha through [0..255], and an odd partial value every pixel rotation + // ensures all three branches get covered within any scanline of 3 or more pixels + byte a = (p % alphaCaseCount) switch + { + 0 => byte.MinValue, + 1 => byte.MaxValue, + _ => (byte)(((p * 37) + 23) | 1) // Produce a spread of alpha values and make sure to never get 0 + }; + + int offset = p * 4; + bytes[offset + 0] = Premultiply((byte)(p * blueStride), a); + bytes[offset + 1] = Premultiply((byte)(p * greenStride), a); + bytes[offset + 2] = Premultiply((byte)(p * redStride), a); + bytes[offset + 3] = a; + } + + return bytes; + + // CgBI stores channels premultiplied by alpha + static byte Premultiply(byte channel, byte alpha) => (byte)(channel * alpha / byte.MaxValue); + } + + private static void ApplyCgbiTransformScalarReference(Span scanline) + { + Span pixels = MemoryMarshal.Cast(scanline); + for (int i = 0; i < pixels.Length; i++) + { + ref Rgba32 pixel = ref pixels[i]; + pixel = new Rgba32(pixel.B, pixel.G, pixel.R, pixel.A); + + byte a = pixel.A; + if (a is 0 or byte.MaxValue) + { + continue; + } + + int half = a >> 1; + byte r = (byte)Math.Min(byte.MaxValue, ((pixel.R * byte.MaxValue) + half) / a); + byte g = (byte)Math.Min(byte.MaxValue, ((pixel.G * byte.MaxValue) + half) / a); + byte b = (byte)Math.Min(byte.MaxValue, ((pixel.B * byte.MaxValue) + half) / a); + pixel = new Rgba32(r, g, b, a); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs index 00db13d4b..6616b0477 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderFilterTests.cs @@ -171,7 +171,7 @@ public class PngDecoderFilterTests public void PaethFilter_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.AllowAll); [Fact] - public void PaethFilter_WithoutSsse3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.DisableSSSE3); + public void PaethFilter_WithoutSsse3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.DisableHWIntrinsic); [Fact] public void PaethFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPaethFilterTest, HwIntrinsics.DisableHWIntrinsic); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs index 8dfa98748..ed33f7163 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs @@ -20,40 +20,40 @@ public partial class PngDecoderTests private static readonly byte[] Raw1X1PngIhdrAndpHYs = [ // PNG Identifier - 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, - // IHDR - 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, - 0x00, 0x00, 0x00, + // IHDR + 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, + 0x00, 0x00, 0x00, - // IHDR CRC - 0x90, 0x77, 0x53, 0xDE, + // IHDR CRC + 0x90, 0x77, 0x53, 0xDE, - // pHYS - 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, - 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, + // pHYS + 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, + 0x00, 0x0E, 0xC3, 0x00, 0x00, 0x0E, 0xC3, 0x01, - // pHYS CRC - 0xC7, 0x6F, 0xA8, 0x64 + // pHYS CRC + 0xC7, 0x6F, 0xA8, 0x64 ]; // Contains the png marker, IDAT and IEND chunks of a 1x1 pixel 32bit png 1 a single black pixel. private static readonly byte[] Raw1X1PngIdatAndIend = [ // IDAT - 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, - 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, - 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0C, 0x49, 0x44, 0x41, 0x54, 0x18, + 0x57, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x01, - // IDAT CRC - 0x5C, 0xCD, 0xFF, 0x69, + // IDAT CRC + 0x5C, 0xCD, 0xFF, 0x69, - // IEND - 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, + // IEND + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, - // IEND CRC - 0xAE, 0x42, 0x60, 0x82 + // IEND CRC + 0xAE, 0x42, 0x60, 0x82 ]; [Theory] @@ -70,13 +70,136 @@ public partial class PngDecoderTests WriteChunk(memStream, chunkName); WriteDataChunk(memStream); - ImageFormatException exception = + InvalidImageContentException exception = Assert.Throws(() => PngDecoder.Instance.Decode(DecoderOptions.Default, memStream)); Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); } } + // https://github.com/SixLabors/ImageSharp/issues/3078 + [Fact] + public void Decode_TruncatedPhysChunk_ExceptionIsThrown() + { + // 24 bytes — PNG signature + truncated pHYs chunk + byte[] payload = Convert.FromHexString( + "89504e470d0a1a0a3030303070485973" + + "3030303030303030"); + + using MemoryStream stream = new(payload); + InvalidImageContentException exception = Assert.Throws(() => Image.Load(stream)); + + Assert.Equal("pHYs chunk is too short", exception.Message); + } + + // https://github.com/SixLabors/ImageSharp/issues/3093 + [Fact] + public void Decode_TruncatedFrameControlChunk_ExceptionIsThrown() + { + // PNG signature + truncated frame control chunk + byte[] payload = Convert.FromHexString( + "89504e470d0a1a0a424d3a00000000007f000000000028030405060000000100" + + "000101002000000000000000000000000000ff00006663544cff190000000000" + + "010000424d000100000101002000000000"); + + using MemoryStream stream = new(payload); + InvalidImageContentException exception = Assert.Throws(() => Image.Load(stream)); + + Assert.Equal("The frame control chunk does not contain enough data!", exception.Message); + } + + // https://github.com/SixLabors/ImageSharp/issues/3079 + [Fact] + public void Decode_CompressedTxtChunk_WithTruncatedData_DoesNotThrow() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, // data + 55, 110, 249, 36, // checksum + 0, 0, 0, 2, // chunk length + 122, 84, 88, 116, // chunk type zTXt + 1, 0, // truncated data + 100, 138, 166, 20, // crc + 0, 0, 0, 10, // chunk length 10 bytes + 73, 68, 65, 84, // chunk type IDAT + 120, 1, 99, 96, 0, 0, 0, 2, 0, 1, // data + 115, 117, 1, 24, // checksum + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]; // end chunk + + using MemoryStream stream = new(payload); + using Image image = Image.Load(stream); + } + + // https://github.com/SixLabors/ImageSharp/issues/3079 + [Fact] + public void Decode_InternationalText_WithTruncatedData_DoesNotThrow() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, // data + 55, 110, 249, 36, // checksum + 0, 0, 0, 2, // chunk length + 105, 84, 88, 116, // chunk type iTXt + 1, 0, // truncated data + 225, 200, 214, 33, // crc + 0, 0, 0, 10, // chunk length 10 bytes + 73, 68, 65, 84, // chunk type IDAT + 120, 1, 99, 96, 0, 0, 0, 2, 0, 1, // data + 115, 117, 1, 24, // checksum + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]; // end chunk + + using MemoryStream stream = new(payload); + using Image image = Image.Load(stream); + } + + // https://github.com/SixLabors/ImageSharp/issues/3079 + [Fact] + public void Decode_InternationalText_WithTruncatedDataAfterLanguageTag_DoesNotThrow() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, // data + 55, 110, 249, 36, // checksum + 0, 0, 0, 21, // chunk length + 105, 84, 88, 116, // chunk type iTXt + 73, 110, 116, 101, 114, 110, 97, 116, 105, 111, 110, 97, 108, 50, 0, 0, 0, 114, 117, 115, 0, // truncated data after language tag + 225, 200, 214, 33, // crc + 0, 0, 0, 10, // chunk length 10 bytes + 73, 68, 65, 84, // chunk type IDAT + 120, 1, 99, 96, 0, 0, 0, 2, 0, 1, // data + 115, 117, 1, 24, // checksum + 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]; // end chunk + + using MemoryStream stream = new(payload); + using Image image = Image.Load(stream); + } + + [Fact] + public void Decode_tRnsChunk_WithAlphaLengthGreaterColorTableLength_ShouldNotThrowException() + { + byte[] payload = [137, 80, 78, 71, 13, 10, 26, 10, // PNG signature + 0, 0, 0, 13, // chunk length 13 bytes + 73, 72, 68, 82, // chunk type IHDR + 0, 0, 0, 1, 0, 0, 0, 1, 8, 3, 0, 0, 0, // data + 40, 203, 52, 187, // crc + 0, 0, 0, 6, // chunk length 6 bytes + 80, 76, 84, 69, // chunk type palettte + 255, 0, 0, 0, 255, 0, // data + 210, 135, 239, 113, // crc + 0, 0, 0, 18, // chunk length + 116, 82, 78, 83, // chunk type tRns + 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, // data + 0, 0, 0, 10, // chunk length + 73, 68, 65, 84, // chunk type data + 120, 156, 99, 96, 0, 0, 0, 2, 0, 1, 72, 175, 164, 113]; // alpha.Length > colorTable.Length + + using MemoryStream stream = new(payload); + using Image image = Image.Load(stream); + } + private static string GetChunkTypeName(uint value) { byte[] data = new byte[4]; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 3589a25a2..88ed512d1 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers.Binary; using System.Runtime.Intrinsics.X86; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats; @@ -206,6 +207,22 @@ public partial class PngDecoderTests image.CompareToOriginal(provider, ImageComparer.Exact); } + [Theory] + [WithFile(TestImages.Png.Icc.Perceptual, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Icc.PerceptualcLUTOnly, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Icc.SRgbGray, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Icc.SRgbGrayInterlacedRgba32, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Icc.SRgbGrayInterlacedRgba64, PixelTypes.Rgba32)] + public void Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder.Instance, new DecoderOptions { ColorProfileHandling = ColorProfileHandling.Convert }); + + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + Assert.Null(image.Metadata.IccProfile); + } + [Theory] [WithFile(TestImages.Png.SubFilter3BytesPerPixel, PixelTypes.Rgba32)] [WithFile(TestImages.Png.SubFilter4BytesPerPixel, PixelTypes.Rgba32)] @@ -389,12 +406,97 @@ public partial class PngDecoderTests TestFile testFile = TestFile.Create(imagePath); using MemoryStream stream = new(testFile.Bytes, false); - ImageInfo imageInfo = Image.Identify(new DecoderOptions { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData }, stream); + ImageInfo imageInfo = Image.Identify(new DecoderOptions { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData }, stream); Assert.NotNull(imageInfo); Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); } + [Fact] + public void Identify_AnimatedPng_ReadsFrameCountCorrectly() + { + TestFile testFile = TestFile.Create(TestImages.Png.AnimatedFrameCount); + + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + Assert.Equal(48, imageInfo.FrameMetadataCollection.Count); + } + + [Fact] + public void Identify_AnimatedPngWithMaxFrames_ReadsFrameCountCorrectly() + { + TestFile testFile = TestFile.Create(TestImages.Png.AnimatedFrameCount); + + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo imageInfo = Image.Identify(new DecoderOptions { MaxFrames = 40 }, stream); + + Assert.NotNull(imageInfo); + Assert.Equal(40, imageInfo.FrameMetadataCollection.Count); + } + + [Fact] + public void Load_AnimatedPngWithMaxFrames_ReadsFrameCountCorrectly() + { + TestFile testFile = TestFile.Create(TestImages.Png.AnimatedFrameCount); + + using MemoryStream stream = new(testFile.Bytes, false); + using Image image = Image.Load(new DecoderOptions { MaxFrames = 40 }, stream); + + Assert.NotNull(image); + Assert.Equal(40, image.Frames.Count); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + [InlineData(10)] + [InlineData(100)] + public void Identify_AnimatedPng_FrameCount_MatchesDecode(int frameCount) + { + using Image image = new(10, 10, Color.Red.ToPixel()); + for (int i = 1; i < frameCount; i++) + { + using ImageFrame frame = new(Configuration.Default, 10, 10); + image.Frames.AddFrame(frame); + } + + using MemoryStream stream = new(); + image.Save(stream, new PngEncoder()); + stream.Position = 0; + + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + Assert.Equal(frameCount, imageInfo.FrameMetadataCollection.Count); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + [InlineData(10)] + [InlineData(100)] + public void Decode_AnimatedPng_FrameCount(int frameCount) + { + using Image image = new(10, 10, Color.Red.ToPixel()); + for (int i = 1; i < frameCount; i++) + { + using ImageFrame frame = new(Configuration.Default, 10, 10); + image.Frames.AddFrame(frame); + } + + using MemoryStream stream = new(); + image.Save(stream, new PngEncoder()); + stream.Position = 0; + + using Image decoded = Image.Load(stream); + + Assert.Equal(frameCount, decoded.Frames.Count); + } + [Theory] [WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)] public void Decode_MissingDataChunk_ThrowsException(TestImageProvider provider) @@ -493,7 +595,7 @@ public partial class PngDecoderTests public void Decode_InvalidDataChunkCrc_IgnoreCrcErrors(TestImageProvider provider, bool compare) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(PngDecoder.Instance, new DecoderOptions() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData }); + using Image image = provider.GetImage(PngDecoder.Instance, new DecoderOptions() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData }); image.DebugSave(provider); if (compare) @@ -613,26 +715,133 @@ public partial class PngDecoderTests Assert.Contains(metadata.ColorTable.Value.ToArray(), x => x.ToPixel().A < 255); } - // https://github.com/SixLabors/ImageSharp/issues/410 [Theory] - [WithFile(TestImages.Png.Bad.Issue410_MalformedApplePng, PixelTypes.Rgba32)] - public void Issue410_MalformedApplePng(TestImageProvider provider) + [WithFile(TestImages.Png.Cgbi.Issue410, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Colors, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Clocks, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Screen, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Flecks, PixelTypes.Rgb24)] + public void Decode_AppleCgBI(TestImageProvider provider) where TPixel : unmanaged, IPixel + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunDecodeAppleCgbi, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic, + provider, + provider.PixelType.ToString()); + + private static void RunDecodeAppleCgbi(string providerDump, string pixelType) { - Exception ex = Record.Exception( - () => - { - using Image image = provider.GetImage(PngDecoder.Instance); - image.DebugSave(provider); + if (Enum.Parse(pixelType) == PixelTypes.Rgb24) + { + TestImageProvider provider = + FeatureTestRunner.DeserializeForXunit>(providerDump); - // We don't have another x-plat reference decoder that can be compared for this image. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Png); - } - }); - Assert.NotNull(ex); - Assert.Contains("Proprietary Apple PNG detected!", ex.Message); + using Image image = provider.GetImage(PngDecoder.Instance); + image.DebugSave(provider); + image.CompareToReferenceOutput(provider, ImageComparer.Exact); + + return; + } + + TestImageProvider rgbaProvider = + FeatureTestRunner.DeserializeForXunit>(providerDump); + + using Image rgbaImage = rgbaProvider.GetImage(PngDecoder.Instance); + rgbaImage.DebugSave(rgbaProvider); + rgbaImage.CompareToReferenceOutput(rgbaProvider, ImageComparer.Exact); + } + + [Theory] + [InlineData(TestImages.Png.Cgbi.Colors, 120, 120)] + [InlineData(TestImages.Png.Cgbi.Issue410, 42, 26)] + [InlineData(TestImages.Png.Cgbi.Flecks, 510, 512)] + public void Identify_AppleCgBI(string imagePath, int expectedWidth, int expectedHeight) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + Assert.Equal(PngFormat.Instance, imageInfo.Metadata.DecodedImageFormat); + Assert.Equal(expectedWidth, imageInfo.Width); + Assert.Equal(expectedHeight, imageInfo.Height); + } + + [Theory] + [InlineData(TestImages.Png.Cgbi.BitDepth16)] + [InlineData(TestImages.Png.Cgbi.Palette)] + public void Identify_CgBI_IncompatibleHeader_ThrowsInvalidImageContentException(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + InvalidImageContentException ex = Assert.Throws(() => Image.Identify(stream)); + Assert.Contains("CgBI is only supported for 8-bit truecolor images", ex.Message); + } + + [Theory] + [WithFile(TestImages.Png.Cgbi.BitDepth16, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Cgbi.Palette, PixelTypes.Rgba32)] + public void Decode_CgBI_IncompatibleHeader_ThrowsInvalidImageContentException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + InvalidImageContentException ex = Assert.Throws( + () => { using Image image = provider.GetImage(PngDecoder.Instance); }); + Assert.Contains("CgBI is only supported for 8-bit truecolor images", ex.Message); + } + + [Theory] + [InlineData(TestImages.Png.Cgbi.BitDepth16)] + [InlineData(TestImages.Png.Cgbi.Palette)] + public void Identify_CgBI_AfterHeader_IncompatibleHeader_ThrowsInvalidImageContentException(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + byte[] reordered = MoveFirstPngChunkAfterSecond(testFile.Bytes); + using MemoryStream stream = new(reordered, false); + + InvalidImageContentException ex = Assert.Throws(() => Image.Identify(stream)); + Assert.Contains("CgBI is only supported for 8-bit truecolor images", ex.Message); + } + + [Theory] + [InlineData(TestImages.Png.Cgbi.BitDepth16)] + [InlineData(TestImages.Png.Cgbi.Palette)] + public void Decode_CgBI_AfterHeader_IncompatibleHeader_ThrowsInvalidImageContentException(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + byte[] reordered = MoveFirstPngChunkAfterSecond(testFile.Bytes); + using MemoryStream stream = new(reordered, false); + + InvalidImageContentException ex = Assert.Throws( + () => { using Image image = PngDecoder.Instance.Decode(DecoderOptions.Default, stream); }); + Assert.Contains("CgBI is only supported for 8-bit truecolor images", ex.Message); + } + + /// + /// Moves the first PNG chunk after the second while preserving each chunk's data and CRC. + /// + /// A PNG whose first two chunks should be exchanged. + /// A copy of the PNG with its first two chunks exchanged. + private static byte[] MoveFirstPngChunkAfterSecond(byte[] source) + { + const int signatureLength = 8; + const int chunkOverheadLength = 12; + + int firstChunkLength = BinaryPrimitives.ReadInt32BigEndian(source.AsSpan(signatureLength, 4)) + chunkOverheadLength; + int secondChunkOffset = signatureLength + firstChunkLength; + int secondChunkLength = BinaryPrimitives.ReadInt32BigEndian(source.AsSpan(secondChunkOffset, 4)) + chunkOverheadLength; + byte[] reordered = new byte[source.Length]; + + source.AsSpan(0, signatureLength).CopyTo(reordered); + source.AsSpan(secondChunkOffset, secondChunkLength).CopyTo(reordered.AsSpan(signatureLength)); + source.AsSpan(signatureLength, firstChunkLength).CopyTo(reordered.AsSpan(signatureLength + secondChunkLength)); + + // Chunk CRCs cover only each chunk's type and data, so moving complete chunks + // leaves both checksums valid and isolates ordering as the tested behavior. + source.AsSpan(secondChunkOffset + secondChunkLength) + .CopyTo(reordered.AsSpan(signatureLength + secondChunkLength + firstChunkLength)); + + return reordered; } [Theory] @@ -674,7 +883,7 @@ public partial class PngDecoderTests public void Binary_PrematureEof() { PngDecoder decoder = PngDecoder.Instance; - PngDecoderOptions options = new() { GeneralOptions = new DecoderOptions { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData } }; + PngDecoderOptions options = new() { GeneralOptions = new DecoderOptions { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreImageData } }; using EofHitCounter eofHitCounter = EofHitCounter.RunDecoder(TestImages.Png.Bad.FlagOfGermany0000016446, decoder, options); // TODO: Try to reduce this to 1. diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs index a930426b6..d9fed19c4 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs @@ -6,7 +6,6 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png.Filters; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Png; @@ -51,7 +50,7 @@ public class PngEncoderFilterTests : MeasureFixture FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); } [Fact] @@ -63,9 +62,7 @@ public class PngEncoderFilterTests : MeasureFixture data.TestFilter(); } - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); + FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTest, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } [Fact] @@ -208,6 +205,95 @@ public class PngEncoderFilterTests : MeasureFixture HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } + /// + /// Verifies every encoder across pixel strides, register boundaries, and hardware widths. + /// + [Fact] + public void EncodeMatchesReferencesAcrossRegisterBoundaries() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertEncodersMatchReferences, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Compares every filter with its independent scalar reference across SIMD boundaries and pixel strides. + /// + private static void AssertEncodersMatchReferences() + { + int[] bytesPerPixels = [1, 2, 3, 4, 6, 8]; + int[] lengths = [8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 131, 132, 133, 135, 136, 137, 257]; + + foreach (int bytesPerPixel in bytesPerPixels) + { + foreach (int length in lengths) + { + if (length < bytesPerPixel) + { + continue; + } + + byte[] scanline = new byte[length]; + byte[] previousScanline = new byte[length]; + Random random = new((bytesPerPixel * 397) + length); + random.NextBytes(scanline); + random.NextBytes(previousScanline); + + AssertFilterMatchesReference(PngFilterMethod.Sub, scanline, previousScanline, bytesPerPixel); + + AssertFilterMatchesReference(PngFilterMethod.Up, scanline, previousScanline, bytesPerPixel); + + AssertFilterMatchesReference(PngFilterMethod.Average, scanline, previousScanline, bytesPerPixel); + + AssertFilterMatchesReference(PngFilterMethod.Paeth, scanline, previousScanline, bytesPerPixel); + } + } + } + + /// + /// Compares one filter result and variance sum with its scalar reference. + /// + /// The filter to evaluate. + /// The current scanline. + /// The preceding scanline. + /// The component distance between adjacent pixels. + private static void AssertFilterMatchesReference(PngFilterMethod filter, byte[] scanline, byte[] previousScanline, int bytesPerPixel) + { + byte[] expected = new byte[scanline.Length + 1]; + byte[] actual = new byte[scanline.Length + 1]; + int expectedSum; + int actualSum; + + switch (filter) + { + case PngFilterMethod.Sub: + ReferenceImplementations.EncodeSubFilter(scanline, expected, bytesPerPixel, out expectedSum); + SubFilter.Encode(scanline, actual, bytesPerPixel, out actualSum); + break; + + case PngFilterMethod.Up: + ReferenceImplementations.EncodeUpFilter(scanline, previousScanline, expected, out expectedSum); + UpFilter.Encode(scanline, previousScanline, actual, out actualSum); + break; + + case PngFilterMethod.Average: + ReferenceImplementations.EncodeAverageFilter(scanline, previousScanline, expected, bytesPerPixel, out expectedSum); + + AverageFilter.Encode(scanline, previousScanline, actual, (uint)bytesPerPixel, out actualSum); + + break; + + case PngFilterMethod.Paeth: + ReferenceImplementations.EncodePaethFilter(scanline, previousScanline, expected, bytesPerPixel, out expectedSum); + + PaethFilter.Encode(scanline, previousScanline, actual, bytesPerPixel, out actualSum); + + break; + + default: + throw new InvalidOperationException(); + } + + Assert.Equal(expectedSum, actualSum); + Assert.Equal(expected, actual); + } + public class TestData { private readonly PngFilterMethod filter; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 0d666d8c8..a73a7b2c0 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -200,14 +200,14 @@ public partial class PngEncoderTests return; } - foreach (object[] filterMethod in PngFilterMethods) + foreach (TheoryDataRow filterMethod in PngFilterMethods) { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( provider, pngColorType, - (PngFilterMethod)filterMethod[0], + filterMethod.Data, pngBitDepth, interlaceMode, appendPngColorType: true, @@ -236,14 +236,14 @@ public partial class PngEncoderTests public void WorksWithAllBitDepthsAndExcludeAllFilter(TestImageProvider provider, PngColorType pngColorType, PngBitDepth pngBitDepth) where TPixel : unmanaged, IPixel { - foreach (object[] filterMethod in PngFilterMethods) + foreach (TheoryDataRow filterMethod in PngFilterMethods) { foreach (PngInterlaceMode interlaceMode in InterlaceMode) { TestPngEncoderCore( provider, pngColorType, - (PngFilterMethod)filterMethod[0], + filterMethod.Data, pngBitDepth, interlaceMode, appendPngColorType: true, @@ -287,7 +287,7 @@ public partial class PngEncoderTests using MemoryStream ms = new(); image.Save(ms, PngEncoder); - byte[] data = ms.ToArray().Take(8).ToArray(); + byte[] data = [.. ms.ToArray().Take(8)]; byte[] expected = [ 0x89, // Set the high bit. @@ -618,7 +618,7 @@ public partial class PngEncoderTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.DisableSSSE3, + HwIntrinsics.DisableHWIntrinsic, provider); } @@ -680,7 +680,7 @@ public partial class PngEncoderTests PaletteQuantizer quantizer = new( palette.Select(Color.FromPixel).ToArray(), - new QuantizerOptions { ColorMatchingMode = ColorMatchingMode.Hybrid }); + new QuantizerOptions { ColorMatchingMode = ColorMatchingMode.Exact }); using MemoryStream ms = new(); image.Save(ms, new PngEncoder diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index 5cbc27611..574602704 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -31,16 +32,16 @@ public class PngMetadataTests ColorType = PngColorType.GrayscaleWithAlpha, InterlaceMethod = PngInterlaceMode.Adam7, Gamma = 2, - TextData = new List { new("name", "value", "foo", "bar") }, + TextData = [new PngTextData("name", "value", "foo", "bar")], RepeatCount = 123, AnimateRootFrame = false }; - PngMetadata clone = (PngMetadata)meta.DeepClone(); + PngMetadata clone = meta.DeepClone(); - Assert.True(meta.BitDepth == clone.BitDepth); - Assert.True(meta.ColorType == clone.ColorType); - Assert.True(meta.InterlaceMethod == clone.InterlaceMethod); + Assert.Equal(meta.BitDepth, clone.BitDepth); + Assert.Equal(meta.ColorType, clone.ColorType); + Assert.Equal(meta.InterlaceMethod, clone.InterlaceMethod); Assert.True(meta.Gamma.Equals(clone.Gamma)); Assert.False(meta.TextData.Equals(clone.TextData)); Assert.True(meta.TextData.SequenceEqual(clone.TextData)); @@ -53,15 +54,47 @@ public class PngMetadataTests clone.Gamma = 1; clone.RepeatCount = 321; - Assert.False(meta.BitDepth == clone.BitDepth); - Assert.False(meta.ColorType == clone.ColorType); - Assert.False(meta.InterlaceMethod == clone.InterlaceMethod); + Assert.NotEqual(meta.BitDepth, clone.BitDepth); + Assert.NotEqual(meta.ColorType, clone.ColorType); + Assert.NotEqual(meta.InterlaceMethod, clone.InterlaceMethod); Assert.False(meta.Gamma.Equals(clone.Gamma)); Assert.False(meta.TextData.Equals(clone.TextData)); Assert.True(meta.TextData.SequenceEqual(clone.TextData)); Assert.False(meta.RepeatCount == clone.RepeatCount); } + [Theory] + [WithFile(TestImages.Png.IptcMetadata, PixelTypes.Rgba32)] + public void Decoder_CanReadIptcProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder.Instance); + Assert.NotNull(image.Metadata.IptcProfile); + Assert.Equal("test1, test2", image.Metadata.IptcProfile.GetValues(IptcTag.Keywords)[0].Value); + Assert.Equal("\0\u0004", image.Metadata.IptcProfile.GetValues(IptcTag.RecordVersion)[0].Value); + } + + [Theory] + [WithFile(TestImages.Png.IptcMetadata, PixelTypes.Rgba32)] + public void Encoder_CanWriteIptcProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(PngDecoder.Instance); + Assert.NotNull(image.Metadata.IptcProfile); + Assert.Equal("test1, test2", image.Metadata.IptcProfile.GetValues(IptcTag.Keywords)[0].Value); + Assert.Equal("\0\u0004", image.Metadata.IptcProfile.GetValues(IptcTag.RecordVersion)[0].Value); + + using MemoryStream memoryStream = new(); + image.Save(memoryStream, new PngEncoder()); + + memoryStream.Position = 0; + + using Image decoded = PngDecoder.Instance.Decode(DecoderOptions.Default, memoryStream); + Assert.NotNull(decoded.Metadata.IptcProfile); + Assert.Equal("test1, test2", decoded.Metadata.IptcProfile.GetValues(IptcTag.Keywords)[0].Value); + Assert.Equal("\0\u0004", decoded.Metadata.IptcProfile.GetValues(IptcTag.RecordVersion)[0].Value); + } + [Theory] [WithFile(TestImages.Png.PngWithMetadata, PixelTypes.Rgba32)] public void Decoder_CanReadTextData(TestImageProvider provider) @@ -336,4 +369,28 @@ public class PngMetadataTests Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).Value); } + + [Theory] + [InlineData(PixelColorType.Binary, PngColorType.Palette)] + [InlineData(PixelColorType.Indexed, PngColorType.Palette)] + [InlineData(PixelColorType.Luminance, PngColorType.Grayscale)] + [InlineData(PixelColorType.RGB, PngColorType.Rgb)] + [InlineData(PixelColorType.BGR, PngColorType.Rgb)] + [InlineData(PixelColorType.YCbCr, PngColorType.RgbWithAlpha)] + [InlineData(PixelColorType.CMYK, PngColorType.RgbWithAlpha)] + [InlineData(PixelColorType.YCCK, PngColorType.RgbWithAlpha)] + public void FromFormatConnectingMetadata_ConvertColorTypeAsExpected(PixelColorType pixelColorType, PngColorType expectedPngColorType) + { + FormatConnectingMetadata formatConnectingMetadata = new() + { + PixelTypeInfo = new PixelTypeInfo(24) + { + ColorType = pixelColorType, + }, + }; + + PngMetadata actual = PngMetadata.FromFormatConnectingMetadata(formatConnectingMetadata); + + Assert.Equal(expectedPngColorType, actual.ColorType); + } } diff --git a/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs index 31ec27da0..5ec23b146 100644 --- a/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs +++ b/tests/ImageSharp.Tests/Formats/Qoi/ImageExtensionsTest.cs @@ -1,8 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.Formats.Qoi; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs index d19f27807..3bcf6c2c8 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs @@ -88,6 +88,21 @@ public class BigTiffMetadataTests Assert.Equal(ExifDataType.Long8, long8.DataType); } + [Fact] + public void ExifLong8Array_CanWriteValuesAsLong() + { + ExifLong8Array long8 = new(ExifTagValue.StripOffsets); + Assert.True(long8.TrySetValue(new long[] { 1, uint.MaxValue })); + Assert.Equal(ExifDataType.Long, long8.DataType); + + byte[] buffer = new byte[8]; + int written = ExifWriter.WriteValue(long8, buffer, 0); + + Assert.Equal(buffer.Length, written); + Assert.Equal(1U, BinaryPrimitives.ReadUInt32LittleEndian(buffer)); + Assert.Equal(uint.MaxValue, BinaryPrimitives.ReadUInt32LittleEndian(buffer.AsSpan(4))); + } + [Fact] public void ExifSignedLong8Array() { @@ -122,7 +137,7 @@ public class BigTiffMetadataTests }; // arrange - List values = new(); + List values = []; foreach (KeyValuePair tag in testTags) { ExifValue newExifValue = ExifValues.Create((ExifTagValue)(ushort)tag.Key, tag.Value.DataType, tag.Value.Value is Array); @@ -167,7 +182,7 @@ public class BigTiffMetadataTests ////{ new ExifTag((ExifTagValue)0xdd14), (ExifDataType.SignedLong8, new long[] { -1234, 56789L, long.MaxValue }) }, }; - List values = new(); + List values = []; foreach (KeyValuePair tag in testTags) { ExifValue newExifValue = ExifValues.Create((ExifTagValue)(ushort)tag.Key, tag.Value.DataType, tag.Value.Value is Array); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/CcittTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/CcittTiffCompressionTests.cs new file mode 100644 index 000000000..b27fbbc2b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/CcittTiffCompressionTests.cs @@ -0,0 +1,59 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.IO.Compression; +using SixLabors.ImageSharp.Formats; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression; + +[Trait("Format", "Tiff")] +public class CcittTiffCompressionTests +{ + private const string T4PocGzipBase64 = + "H4sICBOogmoCA3BvYy10aWZmLXQ0LnRpZgDty7ENwjAQhlFfjKKUoYGSPlPQZopskGVYiJFgA2zpFGUG9Kz3S19xXtelTKX0xaVEq2dbZPcNUY+u2bVtzO7vmvd72+3095792vJyfsQH" + + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgH33fP9Zj5xoBYAEA"; + + private const string ModifiedHuffmanPocGzipBase64 = + "H4sICBSogmoCA3BvYy10aWZmLW1oLnRpZgDty7ENgzAQhlEfRBElNKRMnylomSIbZBkWYiTYAFs6RZkhetb7pa84r+urDKW0xa1EraUustu66L/dZ3d19+z2prz/1M0/fx/Z2zsvx2cc" + + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+jcL3Buw0IBYAEA"; + + private const string TiledT4ReporterPocGzipBase64 = + "H4sIAAAAAAACCu3UKw7CUBCG0RkuIeC6AxQGj8TwEHWshyWxOhI2QC8Z0QWAIDnNmeQXran4xnEf64jYROQyMvo8RtYepltk++x+rXabblW7P6fZ++fZvtS+T3et/djVR8M2n/Btr1v6C/zCQbQQLUQLRAvRQrRAtBAtRAtEC9FCtEC0EC3+Mlpv/6RrkiQmAAA="; + + [Theory] + [InlineData(T4PocGzipBase64)] + [InlineData(ModifiedHuffmanPocGzipBase64)] + public void Decode_CcittStripWithOversizedRun_ThrowsImageFormatException(string gzipBase64) + { + using MemoryStream tiffStream = CreateTiffStream(gzipBase64); + + ImageFormatException exception = Assert.Throws(() => Image.Load(tiffStream)); + + Assert.Equal("CCITT compression parsing error: decoded more pixels than the image width.", exception.Message); + } + + [Fact] + public void Decode_TiledCcittReporterSample_ThrowsImageFormatException() + { + // This is the reporter's exact tiled T4 TIFF sample, gzip-compressed only to keep the test source small. + using MemoryStream tiffStream = CreateTiffStream(TiledT4ReporterPocGzipBase64); + + ImageFormatException exception = Assert.Throws(() => Image.Load(tiffStream)); + + Assert.Equal("CCITT compression parsing error: decoded more pixels than the image width.", exception.Message); + } + + private static MemoryStream CreateTiffStream(string gzipBase64) + { + // Keep the TIFF payloads compressed so the regression source remains small. + byte[] compressed = Convert.FromBase64String(gzipBase64); + using MemoryStream compressedStream = new(compressed); + using GZipStream gzipStream = new(compressedStream, CompressionMode.Decompress); + MemoryStream tiffStream = new(); + + gzipStream.CopyTo(tiffStream); + tiffStream.Position = 0; + + return tiffStream; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs index cc2faeab7..f36bcd6bd 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.IO.Compression; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors; using SixLabors.ImageSharp.Formats.Tiff.Constants; @@ -35,7 +36,7 @@ public class DeflateTiffCompressionTests Stream compressedStream = new MemoryStream(); using (Stream uncompressedStream = new MemoryStream(data), - deflateStream = new ZlibDeflateStream(Configuration.Default.MemoryAllocator, compressedStream, DeflateCompressionLevel.Level6)) + deflateStream = new ZLibStream(compressedStream, new ZLibCompressionOptions { CompressionLevel = (int)DeflateCompressionLevel.Level6 }, true)) { uncompressedStream.CopyTo(deflateStream); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs index a645f2edd..dbfa00950 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs @@ -89,7 +89,11 @@ public class PaletteTiffColorTests : PhotometricInterpretationTestBase public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) => AssertDecode(expectedResult, pixels => { - new PaletteTiffColor(new TiffBitsPerSample(bitsPerSample, 0, 0), colorMap).Decode(inputData, pixels, left, top, width, height); + new PaletteTiffColor( + new TiffBitsPerSample(bitsPerSample, 0, 0), + colorMap, + TiffExtraSampleType.UnspecifiedData) + .Decode(inputData, pixels, left, top, width, height); }); private static uint[][] GeneratePalette(int count) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbaTiffColorTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbaTiffColorTests.cs new file mode 100644 index 000000000..6a0afcd85 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbaTiffColorTests.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation; + +[Trait("Format", "Tiff")] +public class RgbaTiffColorTests : PhotometricInterpretationTestBase +{ + [Fact] + public void DecodeUsesAlphaChannelBitDepth() + { + // The single pixel packs 4-bit RGB followed by 8-bit alpha. Using the blue-channel width for alpha would consume + // only the first alpha nibble and leave the decoded opacity at 8 instead of 128. + byte[] input = [0xF0, 0x88, 0x00]; + Rgba32[][] expected = [[new Rgba32(255, 0, 136, 128)]]; + + AssertDecode(expected, pixels => + { + RgbaTiffColor color = new(TiffExtraSampleType.UnassociatedAlphaData, new TiffBitsPerSample(4, 4, 4, 8)); + color.Decode(input, pixels, 0, 0, 1, 1); + }); + } + + [Fact] + public void DecodeAssociatedAlphaRestoresLogicalColor() + { + byte[] input = [64, 32, 16, 128]; + Rgba32[][] expected = [[new Rgba32(128, 64, 32, 128)]]; + + AssertDecode(expected, pixels => + { + RgbaTiffColor color = new(TiffExtraSampleType.AssociatedAlphaData, new TiffBitsPerSample(8, 8, 8, 8)); + color.Decode(input, pixels, 0, 0, 1, 1); + }); + } + + [Fact] + public void DecodeUnassociatedAlphaAssociatesDestinationStorage() + { + byte[] input = [128, 64, 32, 128]; + using Image image = new(1, 1); + + RgbaTiffColor color = new(TiffExtraSampleType.UnassociatedAlphaData, new TiffBitsPerSample(8, 8, 8, 8)); + color.Decode(input, image.Frames.RootFrame.PixelBuffer, 0, 0, 1, 1); + + // The decoder receives straight TIFF channels, while Rgba32P stores RGB multiplied by its quantized alpha byte. + Assert.Equal(new Rgba32P(64, 32, 16, 128), image[0, 0]); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 5dd1f7884..ec6113be3 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -4,9 +4,12 @@ // ReSharper disable InconsistentNaming using System.Runtime.Intrinsics.X86; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; @@ -352,6 +355,37 @@ public class TiffDecoderTests : TiffDecoderBaseTester image.CompareToReferenceOutput(ImageComparer.Exact, provider); } + [Theory] + [WithFile(Icc.PerceptualCmyk, PixelTypes.Rgba32)] + [WithFile(Icc.PerceptualCieLab, PixelTypes.Rgba32)] + [WithFile(Icc.PerceptualRgb8, PixelTypes.Rgba32)] + [WithFile(Icc.PerceptualRgb16, PixelTypes.Rgba32)] + public void Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance, new DecoderOptions { ColorProfileHandling = ColorProfileHandling.Convert }); + + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + Assert.Null(image.Metadata.IccProfile); + } + + [Theory] + [WithFile(Icc.PerceptualRgb8, PixelTypes.Rgba32)] + [WithFile(Icc.PerceptualRgb16, PixelTypes.Rgba32)] + public void Decode_WhenColorProfileHandlingIsPreserve_PreservesIccProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; + using Image image = provider.GetImage(TiffDecoder.Instance, options); + + Assert.NotNull(image.Metadata.IccProfile); + Assert.NotSame(image.Frames.RootFrame.Metadata.IccProfile, image.Metadata.IccProfile); + Assert.Equal( + image.Frames.RootFrame.Metadata.IccProfile.ToByteArray(), + image.Metadata.IccProfile.ToByteArray()); + } + [Theory] [WithFile(Issues2454_A, PixelTypes.Rgba32)] [WithFile(Issues2454_B, PixelTypes.Rgba32)] @@ -360,6 +394,29 @@ public class TiffDecoderTests : TiffDecoderBaseTester { using Image image = provider.GetImage(TiffDecoder.Instance); image.DebugSave(provider); + + // ARM reports a 0.0000% difference, so we use a tolerant comparer here. + image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.0001F), provider); + } + + [Theory] + [WithFile(Issues3031, PixelTypes.Rgba64)] + [WithFile(Rgba16BitAssociatedAlphaBigEndian, PixelTypes.Rgba64)] + [WithFile(Rgba16BitAssociatedAlphaLittleEndian, PixelTypes.Rgba64)] + public void TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance); + PngEncoder encoder = new() + { + BitDepth = PngBitDepth.Bit16, + ColorType = PngColorType.RgbWithAlpha + }; + + // This exact Rgba64 comparison requires a 16-bit reference. The Windows reference encoder uses a 32-bit + // System.Drawing bitmap and would otherwise quantize each channel to 8 bits while producing the PNG. + image.DebugSave(provider, testOutputDetails: null, extension: "png", encoder: encoder); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); } @@ -677,17 +734,17 @@ public class TiffDecoderTests : TiffDecoderBaseTester [WithFile(YCbCrJpegCompressed2, PixelTypes.Rgba32)] [WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)] [WithFile(GrayscaleJpegCompressed, PixelTypes.Rgba32)] - [WithFile(Issues2123, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_JpegCompressed(TestImageProvider provider) - where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false); + [WithFile(Issues2123, PixelTypes.Rgba32, 0.110f)] + public void TiffDecoder_CanDecode_JpegCompressed(TestImageProvider provider, float tolerance = 0.001f) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false, compareTolerance: tolerance); [Theory] [WithFile(RgbOldJpegCompressed, PixelTypes.Rgba32)] - [WithFile(RgbOldJpegCompressed2, PixelTypes.Rgba32)] + [WithFile(RgbOldJpegCompressed2, PixelTypes.Rgba32, 0.1003f)] [WithFile(RgbOldJpegCompressed3, PixelTypes.Rgba32)] [WithFile(RgbOldJpegCompressedGray, PixelTypes.Rgba32)] [WithFile(YCbCrOldJpegCompressed, PixelTypes.Rgba32)] - public void TiffDecoder_CanDecode_OldJpegCompressed(TestImageProvider provider) + public void TiffDecoder_CanDecode_OldJpegCompressed(TestImageProvider provider, float tolerance = 0.001f) where TPixel : unmanaged, IPixel { DecoderOptions decoderOptions = new() @@ -698,7 +755,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester image.DebugSave(provider); image.CompareToOriginal( provider, - ImageComparer.Tolerant(0.001f), + ImageComparer.Tolerant(tolerance), ReferenceDecoder, decoderOptions); } @@ -754,7 +811,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester // The image is handcrafted to simulate issue 2679. ImageMagick will throw an expection here and wont decode, // so we compare to rererence output instead. - image.DebugSave(provider); + image.DebugSave(provider, appendPixelTypeToFileName: false); image.CompareToReferenceOutput( ImageComparer.Exact, provider, @@ -783,7 +840,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester // ImageMagick cannot decode this image. image.DebugSave(provider); image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.0018F), // NET 9+ Uses zlib-ng to decompress, which manages to decode 2 extra pixels. + ImageComparer.TolerantPercentage(0.0034F), // NET 10 Uses zlib-ng to decompress, which manages to decode 3 extra pixels. provider, appendPixelTypeToFileName: false); } @@ -833,4 +890,45 @@ public class TiffDecoderTests : TiffDecoderBaseTester [WithFile(ExtraSamplesUnspecified, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode_ExtraSamplesUnspecified(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(Issue2983, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode_Issue2983(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Fact] + public void Identify_MalformedIccProfile_IgnoresNonCriticalErrorsByDefault() + { + ImageInfo info = Image.Identify(CreateTiffWithMalformedIccProfile()); + Assert.Equal(1, info.Width); + Assert.Equal(1, info.Height); + } + + [Fact] + public void Decode_MalformedIccProfile_IgnoresNonCriticalErrorsByDefault() + { + using Image image = Image.Load(CreateTiffWithMalformedIccProfile()); + Assert.Equal(1, image.Width); + Assert.Equal(1, image.Height); + } + + [Fact] + public void Identify_MalformedIccProfile_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => Image.Identify(options, CreateTiffWithMalformedIccProfile())); + } + + [Fact] + public void Decode_MalformedIccProfile_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + Assert.Throws(() => + { + using Image image = Image.Load(options, CreateTiffWithMalformedIccProfile()); + }); + } + + private static byte[] CreateTiffWithMalformedIccProfile() + => CorruptedMetadataImageFactory.CreateImageWithMalformedIccProfile(new TiffEncoder()); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 20b3ec2bb..9f0f0e991 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestDataIcc; using SixLabors.ImageSharp.Tests.TestUtilities; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; @@ -61,16 +62,15 @@ public class TiffMetadataTests clone.PhotometricInterpretation = TiffPhotometricInterpretation.CieLab; clone.Predictor = TiffPredictor.Horizontal; - Assert.False(meta.BitsPerPixel == clone.BitsPerPixel); - Assert.False(meta.Compression == clone.Compression); - Assert.False(meta.PhotometricInterpretation == clone.PhotometricInterpretation); - Assert.False(meta.Predictor == clone.Predictor); + Assert.NotEqual(meta.BitsPerPixel, clone.BitsPerPixel); + Assert.NotEqual(meta.Compression, clone.Compression); + Assert.NotEqual(meta.PhotometricInterpretation, clone.PhotometricInterpretation); + Assert.NotEqual(meta.Predictor, clone.Predictor); } private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata frameMetaData) { Assert.NotNull(frameMetaData); - Assert.NotNull(frameMetaData.BitsPerPixel); Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaData.BitsPerPixel); Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); @@ -150,14 +150,23 @@ public class TiffMetadataTests Assert.NotNull(meta); if (ignoreMetadata) { + Assert.Null(image.Metadata.XmpProfile); + Assert.Null(image.Metadata.ExifProfile); Assert.Null(rootFrameMetaData.XmpProfile); Assert.Null(rootFrameMetaData.ExifProfile); } else { + Assert.NotNull(image.Metadata.XmpProfile); + Assert.NotNull(image.Metadata.ExifProfile); Assert.NotNull(rootFrameMetaData.XmpProfile); Assert.NotNull(rootFrameMetaData.ExifProfile); - Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); + + Assert.NotSame(rootFrameMetaData.XmpProfile, image.Metadata.XmpProfile); + Assert.NotSame(rootFrameMetaData.ExifProfile, image.Metadata.ExifProfile); + Assert.Equal(rootFrameMetaData.XmpProfile.Data, image.Metadata.XmpProfile.Data); + Assert.Equal(rootFrameMetaData.ExifProfile.ToByteArray(), image.Metadata.ExifProfile.ToByteArray()); + Assert.Equal(2596, rootFrameMetaData.XmpProfile.Data.Length); // padding bytes are trimmed Assert.Equal(25, rootFrameMetaData.ExifProfile.Values.Count); } } @@ -171,6 +180,10 @@ public class TiffMetadataTests IptcProfile iptcProfile = image.Frames.RootFrame.Metadata.IptcProfile; Assert.NotNull(iptcProfile); + Assert.NotNull(image.Metadata.IptcProfile); + Assert.NotSame(iptcProfile, image.Metadata.IptcProfile); + Assert.Equal(iptcProfile.Data, image.Metadata.IptcProfile.Data); + IptcValue byline = iptcProfile.Values.FirstOrDefault(data => data.Tag == IptcTag.Byline); Assert.NotNull(byline); Assert.Equal("Studio Mantyniemi", byline.Value); @@ -186,7 +199,7 @@ public class TiffMetadataTests Assert.Equal(32, rootFrame.Width); Assert.Equal(32, rootFrame.Height); Assert.NotNull(rootFrame.Metadata.XmpProfile); - Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length); + Assert.Equal(2596, rootFrame.Metadata.XmpProfile.Data.Length); // padding bytes are trimmed ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); @@ -336,8 +349,7 @@ public class TiffMetadataTests iptcProfile.SetValue(IptcTag.Name, "Test name"); rootFrameInput.Metadata.IptcProfile = iptcProfile; - IccProfileHeader iccProfileHeader = new() { Class = IccProfileClass.ColorSpace }; - IccProfile iccProfile = new(); + IccProfile iccProfile = new(IccTestDataProfiles.ProfileRandomArray); rootFrameInput.Metadata.IccProfile = iccProfile; TiffFrameMetadata frameMetaInput = rootFrameInput.Metadata.GetTiffMetadata(); @@ -409,4 +421,17 @@ public class TiffMetadataTests // Adding the IPTC and ICC profiles dynamically increments the number of values in the original EXIF profile by 2 Assert.Equal(exifProfileInput.Values.Count + 2, encodedImageExifProfile.Values.Count); } + + [Theory] + [WithFile(PaletteDeflateMultistrip, PixelTypes.Rgba32)] + [WithFile(PaletteUncompressed, PixelTypes.Rgba32)] + public void TiffDecoder_CanAssign_ColorPalette(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder.Instance); + ImageFrame frame = image.Frames.RootFrame; + TiffFrameMetadata tiffMeta = frame.Metadata.GetTiffMetadata(); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffMeta.PhotometricInterpretation); + Assert.NotNull(tiffMeta.LocalColorTable); + } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs new file mode 100644 index 000000000..104ab76a7 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffUtilitiesTest.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Tiff.Utils; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Utils; + +[Trait("Format", "Tiff")] +public class TiffUtilitiesTest +{ + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(42, 84, 128, 0)] + [InlineData(65535, 65535, 65535, 0)] + public void ColorFromRgba64Premultiplied_WithZeroAlpha_ReturnsDefaultPixel(ushort r, ushort g, ushort b, ushort a) + { + Rgba64 actual = TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a); + + Assert.Equal(default, actual); + } + + [Fact] + public void ColorScaleTo24BitPremultiplied_WithZeroAlpha_ReturnsDefaultPixel() + { + Rgba64 unassociated = TiffUtilities.ColorScaleTo24BitPremultiplied(0x123456, 0x654321, 0xABCDEF, 0); + Rgba32P associated = TiffUtilities.ColorScaleTo24BitPremultiplied(0x123456, 0x654321, 0xABCDEF, 0); + + Assert.Equal(default, unassociated); + Assert.Equal(default, associated); + } + + [Fact] + public void ColorScaleTo32BitPremultiplied_WithZeroAlpha_ReturnsDefaultPixel() + { + Rgba64 unassociated = TiffUtilities.ColorScaleTo32BitPremultiplied(0x12345678, 0x654321AB, 0xABCDEF12, 0); + Rgba32P associated = TiffUtilities.ColorScaleTo32BitPremultiplied(0x12345678, 0x654321AB, 0xABCDEF12, 0); + + Assert.Equal(default, unassociated); + Assert.Equal(default, associated); + } + + [Theory] + [InlineData(65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535)] + [InlineData(32767, 0, 0, 65535, 32767, 0, 0, 65535)] + [InlineData(0, 32767, 0, 65535, 0, 32767, 0, 65535)] + [InlineData(0, 0, 32767, 65535, 0, 0, 32767, 65535)] + public void ColorFromRgba64Premultiplied_WithNoAlpha_ReturnExpectedValues(ushort r, ushort g, ushort b, ushort a, ushort expectedR, ushort expectedG, ushort expectedB, ushort expectedA) + { + Rgba64 actual = TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a); + + Assert.Equal(new Rgba64(expectedR, expectedG, expectedB, expectedA), actual); + } + + [Theory] + [InlineData(32766, 0, 0, 32766, 65535, 0, 0, 32766)] // Red, 50% Alpha + [InlineData(0, 32766, 0, 32766, 0, 65535, 0, 32766)] // Green, 50% Alpha + [InlineData(0, 0, 32766, 32766, 0, 0, 65535, 32766)] // Blue, 50% Alpha + /* The exact 25% result lies below the 16-bit midpoint by less than one float ULP. The Vector4 conversion therefore + reaches the midpoint and applies the established round-to-even contract without requiring a comparison tolerance. */ + [InlineData(8191, 0, 0, 16383, 32766, 0, 0, 16383)] // Red, 25% Alpha + [InlineData(0, 8191, 0, 16383, 0, 32766, 0, 16383)] // Green, 25% Alpha + [InlineData(0, 0, 8191, 16383, 0, 0, 32766, 16383)] // Blue, 25% Alpha + [InlineData(8191, 0, 0, 0, 0, 0, 0, 0)] // Red, 0% Alpha + [InlineData(0, 8191, 0, 0, 0, 0, 0, 0)] // Green, 0% Alpha + [InlineData(0, 0, 8191, 0, 0, 0, 0, 0)] // Blue, 0% Alpha + public void ColorFromRgba64Premultiplied_WithAlpha_ReturnExpectedValues(ushort r, ushort g, ushort b, ushort a, ushort expectedR, ushort expectedG, ushort expectedB, ushort expectedA) + { + Rgba64 actual = TiffUtilities.ColorFromRgba64Premultiplied(r, g, b, a); + + Assert.Equal(new Rgba64(expectedR, expectedG, expectedB, expectedA), actual); + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs index 66f320143..9657859e0 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs @@ -71,7 +71,7 @@ public class ColorSpaceTransformUtilsTests public void CollectColorBlueTransforms_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.AllowAll); [Fact] - public void CollectColorBlueTransforms_WithoutVector128_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableSSE41); + public void CollectColorBlueTransforms_WithoutVector128_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableHWIntrinsic); [Fact] public void CollectColorBlueTransforms_WithoutVector256_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableAVX2); @@ -80,7 +80,7 @@ public class ColorSpaceTransformUtilsTests public void CollectColorRedTransforms_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.AllowAll); [Fact] - public void CollectColorRedTransforms_WithoutVector128_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableSSE41); + public void CollectColorRedTransforms_WithoutVector128_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableHWIntrinsic); [Fact] public void CollectColorRedTransforms_WithoutVector256_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableAVX2); diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index 982f0a5d5..4dcccb1a3 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -304,19 +304,19 @@ public class LosslessUtilsTests public void Predictor11_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.AllowAll); [Fact] - public void Predictor11_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.DisableSSE2); + public void Predictor11_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.DisableHWIntrinsic); [Fact] public void Predictor12_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.AllowAll); [Fact] - public void Predictor12_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.DisableSSE2); + public void Predictor12_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor12Test, HwIntrinsics.DisableHWIntrinsic); [Fact] public void Predictor13_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.AllowAll); [Fact] - public void Predictor13_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.DisableSSE2); + public void Predictor13_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor13Test, HwIntrinsics.DisableHWIntrinsic); [Fact] public void SubtractGreen_Works() => RunSubtractGreenTest(); @@ -331,7 +331,7 @@ public class LosslessUtilsTests public void SubtractGreen_Scalar_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableHWIntrinsic); [Fact] - public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3); + public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); [Fact] public void AddGreenToBlueAndRed_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.AllowAll); @@ -340,13 +340,13 @@ public class LosslessUtilsTests public void AddGreenToBlueAndRed_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2); [Fact] - public void AddGreenToBlueAndRed_WithoutAVX2OrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); + public void AddGreenToBlueAndRed_WithoutAVX2OrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); [Fact] public void TransformColor_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.AllowAll); [Fact] - public void TransformColor_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableSSE2); + public void TransformColor_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableHWIntrinsic); [Fact] public void TransformColor_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableAVX2); @@ -355,7 +355,7 @@ public class LosslessUtilsTests public void TransformColorInverse_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.AllowAll); [Fact] - public void TransformColorInverse_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableSSE2); + public void TransformColorInverse_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableHWIntrinsic); [Fact] public void TransformColorInverse_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableAVX2); diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index 23ef8e7b1..16990c357 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -24,7 +24,7 @@ public class PredictorEncoderTests [Fact] public void ColorSpaceTransform_WithPeakImage_WithoutSSE41_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithPeakImage_ProducesExpectedData, HwIntrinsics.DisableHWIntrinsic); [Fact] public void ColorSpaceTransform_WithBikeImage_WithHardwareIntrinsics_Works() @@ -32,7 +32,7 @@ public class PredictorEncoderTests [Fact] public void ColorSpaceTransform_WithBikeImage_WithoutSSE41_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableHWIntrinsic); [Fact] public void ColorSpaceTransform_WithBikeImage_WithoutAvx2_Works() diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs index fc7da8cf6..3808ba6d0 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs @@ -45,7 +45,7 @@ public class QuantEncTests public void QuantizeBlock_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.AllowAll); [Fact] - public void QuantizeBlock_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2); + public void QuantizeBlock_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableHWIntrinsic); [Fact] public void QuantizeBlock_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableAVX2); diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs index 990c58385..fa240c55b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs @@ -13,55 +13,59 @@ public class Vp8HistogramTests { get { - List result = new(); - result.Add(new object[] - { - new byte[] - { - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 24, 16, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204 - }, - new byte[] - { - 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 128, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, - 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 128, 127, 127, 128, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, - 129, 129, 129, 129, 129, 129, 128, 127, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, - 127, 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, - 129, 128, 129, 128, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 127, 127, 129, - 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 129, 129, 128, 128, 129, 129, 129, 129, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 129, 129, 129, 129, 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 129, 129, 129, 129, - 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, - 204, 204, 204, 204, 204, 204, 204, 204, 204 - } - }); + List result = + [ + [ + new byte[] + { + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 19, 16, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 19, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 24, 16, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204 + }, + new byte[] + { + 128, 128, 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 128, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, + 128, 128, 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 128, 127, + 127, 128, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, + 129, 129, 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 128, 127, 129, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 128, 128, 129, 129, + 129, 129, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 128, 129, 128, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 128, 128, 127, 127, 129, 129, 129, 129, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 129, 129, 128, 128, 129, 129, 129, 129, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 129, 129, 129, 129, 129, 129, 129, 129, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 129, 129, 129, 129, 129, 129, 129, 129, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204 + } + ] + ]; return result; } } @@ -72,7 +76,7 @@ public class Vp8HistogramTests Vp8Histogram histogram = new(); byte[] reference = - { + [ 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, @@ -99,9 +103,9 @@ public class Vp8HistogramTests 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130, 82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204 - }; + ]; byte[] pred = - { + [ 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, @@ -154,7 +158,7 @@ public class Vp8HistogramTests 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129 - }; + ]; int expectedAlpha = 146; // act diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs index 62c67c2e0..2a0821108 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs @@ -252,5 +252,5 @@ public class Vp8ResidualTests public void SetCoeffsTest_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll); [Fact] - public void SetCoeffsTest_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableSSE2); + public void SetCoeffsTest_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableHWIntrinsic); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs index ca1859e54..3962d4f37 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpCommonUtilsTests.cs @@ -23,7 +23,7 @@ public class WebpCommonUtilsTests [Fact] public void CheckNonOpaque_WithOpaquePixels_WithoutSse2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableSSE2); + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithOpaquePixelsTest, HwIntrinsics.DisableHWIntrinsic); [Fact] public void CheckNonOpaque_WithOpaquePixels_WithoutAvx2_Works() @@ -35,7 +35,7 @@ public class WebpCommonUtilsTests [Fact] public void CheckNonOpaque_WithNoneOpaquePixels_WithoutSse2_Works() - => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableSSE2); + => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCheckNoneOpaqueWithNoneOpaquePixelsTest, HwIntrinsics.DisableHWIntrinsic); [Fact] public void CheckNonOpaque_WithNoneOpaquePixels_WithoutAvx2_Works() diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 111544f7f..a3e3b81cf 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -314,6 +314,21 @@ public class WebpDecoderTests Assert.Equal(12, image.Frames.Count); } + [Theory] + [InlineData(Lossless.Animated)] + public void Info_AnimatedLossless_VerifyAllFrames(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo image = WebpDecoder.Instance.Identify(DecoderOptions.Default, stream); + WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); + WebpFrameMetadata frameMetaData = image.FrameMetadataCollection[0].GetWebpMetadata(); + + Assert.Equal(0, webpMetaData.RepeatCount); + Assert.Equal(150U, frameMetaData.FrameDelay); + Assert.Equal(12, image.FrameCount); + } + [Theory] [WithFile(Lossy.Animated, PixelTypes.Rgba32)] public void Decode_AnimatedLossy_VerifyAllFrames(TestImageProvider provider) @@ -331,6 +346,21 @@ public class WebpDecoderTests Assert.Equal(12, image.Frames.Count); } + [Theory] + [InlineData(Lossy.Animated)] + public void Info_AnimatedLossy_VerifyAllFrames(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + ImageInfo image = WebpDecoder.Instance.Identify(DecoderOptions.Default, stream); + WebpMetadata webpMetaData = image.Metadata.GetWebpMetadata(); + WebpFrameMetadata frameMetaData = image.FrameMetadataCollection[0].GetWebpMetadata(); + + Assert.Equal(0, webpMetaData.RepeatCount); + Assert.Equal(150U, frameMetaData.FrameDelay); + Assert.Equal(12, image.FrameCount); + } + [Theory] [WithFile(Lossless.Animated, PixelTypes.Rgba32)] public void Decode_AnimatedLossless_WithFrameDecodingModeFirst_OnlyDecodesOneFrame(TestImageProvider provider) @@ -578,4 +608,17 @@ public class WebpDecoderTests image.DebugSave(provider); image.CompareToOriginal(provider, ReferenceDecoder); } + + [Theory] + [WithFile(Icc.Perceptual, PixelTypes.Rgba32)] + [WithFile(Icc.PerceptualcLUTOnly, PixelTypes.Rgba32)] + public void Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(WebpDecoder.Instance, new DecoderOptions { ColorProfileHandling = ColorProfileHandling.Convert }); + + image.DebugSave(provider); + image.CompareToReferenceOutput(provider); + Assert.Null(image.Metadata.IccProfile); + } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index f9836ffb1..ee8268716 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -135,9 +135,9 @@ public class WebpEncoderTests // Alpha thresholding is 64/255F. GifEncoder gifEncoder = new() { - Quantizer = new OctreeQuantizer(options) + Quantizer = new HexadecatreeQuantizer(options) }; - provider.Utility.SaveTestOutputFile(image, "gif", gifEncoder, "octree"); + provider.Utility.SaveTestOutputFile(image, "gif", gifEncoder, "hexadecatree"); gifEncoder = new GifEncoder { @@ -152,8 +152,8 @@ public class WebpEncoderTests }; using Image cloned1 = image.Clone(); - cloned1.Mutate(c => c.Quantize(new OctreeQuantizer(options))); - provider.Utility.SaveTestOutputFile(cloned1, "webp", encoder, "octree"); + cloned1.Mutate(c => c.Quantize(new HexadecatreeQuantizer(options))); + provider.Utility.SaveTestOutputFile(cloned1, "webp", encoder, "hexadecatree"); using Image cloned2 = image.Clone(); cloned2.Mutate(c => c.Quantize(new WuQuantizer(options))); @@ -162,7 +162,7 @@ public class WebpEncoderTests // Now blend the images with a blue background and save as webp. using Image background1 = new(image.Width, image.Height, Color.White.ToPixel()); background1.Mutate(c => c.DrawImage(cloned1, 1)); - provider.Utility.SaveTestOutputFile(background1, "webp", encoder, "octree-blended"); + provider.Utility.SaveTestOutputFile(background1, "webp", encoder, "hexadecatree-blended"); using Image background2 = new(image.Width, image.Height, Color.White.ToPixel()); background2.Mutate(c => c.DrawImage(cloned2, 1)); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 020b42f37..394479f89 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -200,4 +200,32 @@ public class WebpMetaDataTests }); Assert.Null(ex); } + + [Fact] + public void Identify_InvalidExifChunk_IgnoresNonCriticalErrorsByDefault() + { + using MemoryStream stream = new(TestFile.Create(TestImages.Webp.Lossy.WithExifNotEnoughData).Bytes, false); + ImageInfo info = Image.Identify(stream); + Assert.True(info.Width > 0); + Assert.True(info.Height > 0); + } + + [Fact] + public void Identify_InvalidExifChunk_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + using MemoryStream stream = new(TestFile.Create(TestImages.Webp.Lossy.WithExifNotEnoughData).Bytes, false); + Assert.Throws(() => Image.Identify(options, stream)); + } + + [Fact] + public void Decode_InvalidExifChunk_ThrowsWithStrict() + { + DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; + using MemoryStream stream = new(TestFile.Create(TestImages.Webp.Lossy.WithExifNotEnoughData).Bytes, false); + Assert.Throws(() => + { + using Image image = Image.Load(options, stream); + }); + } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpVp8XTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpVp8XTests.cs new file mode 100644 index 000000000..78be0bf9f --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpVp8XTests.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Webp.Chunks; + +namespace SixLabors.ImageSharp.Tests.Formats.WebP; + +[Trait("Format", "Webp")] +public class WebpVp8XTests +{ + [Fact] + public void WebpVp8X_WriteTo_Writes_Reserved_Bytes() + { + // arrange + WebpVp8X header = new(false, false, false, false, false, 10, 40); + MemoryStream ms = new(); + byte[] expected = [86, 80, 56, 88, 10, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 39, 0, 0]; + + // act + header.WriteTo(ms); + + // assert + byte[] actual = ms.ToArray(); + Assert.Equal(expected, actual); + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index e86642fea..482c41b25 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -32,7 +32,7 @@ public class YuvConversionTests public void UpSampleYuvToRgb_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.AllowAll); [Fact] - public void UpSampleYuvToRgb_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.DisableSSE2); + public void UpSampleYuvToRgb_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.DisableHWIntrinsic); [Theory] [WithFile(TestImages.Webp.Yuv, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs index 0ccb80d3f..e9d45a1fd 100644 --- a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs +++ b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs @@ -13,7 +13,7 @@ public class GraphicsOptionsTests private readonly GraphicsOptions cloneGraphicsOptions = new GraphicsOptions().DeepClone(); [Fact] - public void CloneGraphicsOptionsIsNotNull() => Assert.True(this.cloneGraphicsOptions != null); + public void CloneGraphicsOptionsIsNotNull() => Assert.NotNull(this.cloneGraphicsOptions); [Fact] public void DefaultGraphicsOptionsAntialias() @@ -22,36 +22,28 @@ public class GraphicsOptionsTests Assert.True(this.cloneGraphicsOptions.Antialias); } - [Fact] - public void DefaultGraphicsOptionsAntialiasSuppixelDepth() - { - const int Expected = 16; - Assert.Equal(Expected, this.newGraphicsOptions.AntialiasSubpixelDepth); - Assert.Equal(Expected, this.cloneGraphicsOptions.AntialiasSubpixelDepth); - } - [Fact] public void DefaultGraphicsOptionsBlendPercentage() { - const float Expected = 1F; - Assert.Equal(Expected, this.newGraphicsOptions.BlendPercentage); - Assert.Equal(Expected, this.cloneGraphicsOptions.BlendPercentage); + const float expected = 1F; + Assert.Equal(expected, this.newGraphicsOptions.BlendPercentage); + Assert.Equal(expected, this.cloneGraphicsOptions.BlendPercentage); } [Fact] public void DefaultGraphicsOptionsColorBlendingMode() { - const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal; - Assert.Equal(Expected, this.newGraphicsOptions.ColorBlendingMode); - Assert.Equal(Expected, this.cloneGraphicsOptions.ColorBlendingMode); + const PixelColorBlendingMode expected = PixelColorBlendingMode.Normal; + Assert.Equal(expected, this.newGraphicsOptions.ColorBlendingMode); + Assert.Equal(expected, this.cloneGraphicsOptions.ColorBlendingMode); } [Fact] public void DefaultGraphicsOptionsAlphaCompositionMode() { - const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver; - Assert.Equal(Expected, this.newGraphicsOptions.AlphaCompositionMode); - Assert.Equal(Expected, this.cloneGraphicsOptions.AlphaCompositionMode); + const PixelAlphaCompositionMode expected = PixelAlphaCompositionMode.SrcOver; + Assert.Equal(expected, this.newGraphicsOptions.AlphaCompositionMode); + Assert.Equal(expected, this.cloneGraphicsOptions.AlphaCompositionMode); } [Fact] @@ -61,7 +53,6 @@ public class GraphicsOptionsTests { AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop, Antialias = false, - AntialiasSubpixelDepth = 23, BlendPercentage = .25F, ColorBlendingMode = PixelColorBlendingMode.HardLight, }; @@ -79,7 +70,6 @@ public class GraphicsOptionsTests actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop; actual.Antialias = false; - actual.AntialiasSubpixelDepth = 23; actual.BlendPercentage = .25F; actual.ColorBlendingMode = PixelColorBlendingMode.HardLight; diff --git a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs b/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs index 1bf137f34..903e997aa 100644 --- a/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ColorNumericsTests.cs @@ -18,11 +18,65 @@ public class ColorNumericsTests Vector4 vector = new(x, y, z, 0.0f); // act - int actual = ColorNumerics.GetBT709Luminance(ref vector, luminanceLevels); + int actual = ColorNumerics.GetBT709Luminance(vector, luminanceLevels); // assert Assert.Equal(expected, actual); } - // TODO: We need to test all ColorNumerics methods! + [Theory] + [InlineData((ushort)0, (byte)0)] + [InlineData((ushort)128, (byte)0)] + [InlineData((ushort)129, (byte)1)] + [InlineData((ushort)257, (byte)1)] + [InlineData((ushort)32896, (byte)128)] + [InlineData(ushort.MaxValue, byte.MaxValue)] + public void From16BitTo8Bit_ReturnsExpectedValue(ushort component, byte expected) + { + byte actual = ColorNumerics.From16BitTo8Bit(component); + + Assert.Equal(expected, actual); + } + + [Fact] + public void From16BitTo8Bit_RoundTripsAllExpanded8BitValues() + { + for (int i = 0; i <= byte.MaxValue; i++) + { + byte expected = (byte)i; + ushort component = ColorNumerics.From8BitTo16Bit(expected); + + byte actual = ColorNumerics.From16BitTo8Bit(component); + + Assert.Equal(expected, actual); + } + } + + [Theory] + [InlineData(0U, (byte)0)] + [InlineData(8421504U, (byte)0)] + [InlineData(8421505U, (byte)1)] + [InlineData(16843009U, (byte)1)] + [InlineData(2155905152U, (byte)128)] + [InlineData(uint.MaxValue, byte.MaxValue)] + public void From32BitTo8Bit_ReturnsExpectedValue(uint component, byte expected) + { + byte actual = ColorNumerics.From32BitTo8Bit(component); + + Assert.Equal(expected, actual); + } + + [Fact] + public void From32BitTo8Bit_RoundTripsAllExpanded8BitValues() + { + for (int i = 0; i <= byte.MaxValue; i++) + { + byte expected = (byte)i; + uint component = ColorNumerics.From8BitTo32Bit(expected); + + byte actual = ColorNumerics.From32BitTo8Bit(component); + + Assert.Equal(expected, actual); + } + } } diff --git a/tests/ImageSharp.Tests/Helpers/NumericsTests.cs b/tests/ImageSharp.Tests/Helpers/NumericsTests.cs index 1106144c4..0a498a0b7 100644 --- a/tests/ImageSharp.Tests/Helpers/NumericsTests.cs +++ b/tests/ImageSharp.Tests/Helpers/NumericsTests.cs @@ -11,6 +11,11 @@ public class NumericsTests private readonly ApproximateFloatComparer approximateFloatComparer = new(1e-6f); + /// + /// Gets lengths that straddle the Vector4 block boundaries used by the 128-, 256-, and 512-bit kernels. + /// + public static TheoryData AssociationSpanLengths => new() { 0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63 }; + [Theory] [InlineData(0)] [InlineData(1)] @@ -156,43 +161,79 @@ public class NumericsTests } [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - [InlineData(63)] + [MemberData(nameof(AssociationSpanLengths))] public void PremultiplyVectorSpan(int length) { Random rnd = new(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => + + for (int i = 0; i < source.Length; i++) { - Numerics.Premultiply(ref v); - return v; - }).ToArray(); + // Exact zero and one exercise the special alpha boundaries alongside the random fractional values. + source[i].W = i % 5 switch + { + 0 => 0F, + 1 => 1F, + _ => source[i].W + }; + } + + Vector4[] expected = + [ + .. source.Select(v => + { + Numerics.Premultiply(ref v); + return v; + }) + ]; Numerics.Premultiply(source); Assert.Equal(expected, source, this.approximateFloatComparer); + + for (int i = 0; i < source.Length; i++) + { + // Alpha is storage metadata here and must survive each SIMD width without any floating-point transformation. + Assert.Equal(BitConverter.SingleToInt32Bits(expected[i].W), BitConverter.SingleToInt32Bits(source[i].W)); + } } [Theory] - [InlineData(0)] - [InlineData(1)] - [InlineData(30)] - [InlineData(63)] + [MemberData(nameof(AssociationSpanLengths))] public void UnPremultiplyVectorSpan(int length) { Random rnd = new(42); Vector4[] source = rnd.GenerateRandomVectorArray(length, 0, 1); - Vector4[] expected = source.Select(v => + + for (int i = 0; i < source.Length; i++) { - Numerics.UnPremultiply(ref v); - return v; - }).ToArray(); + // A zero alpha preserves the complete source vector by contract; one also verifies the identity case. + source[i].W = i % 5 switch + { + 0 => 0F, + 1 => 1F, + _ => source[i].W + }; + } + + Vector4[] expected = + [ + .. source.Select(v => + { + Numerics.UnPremultiply(ref v); + return v; + }) + ]; Numerics.UnPremultiply(source); Assert.Equal(expected, source, this.approximateFloatComparer); + + for (int i = 0; i < source.Length; i++) + { + // Alpha is the divisor and must still survive each SIMD width without any floating-point transformation. + Assert.Equal(BitConverter.SingleToInt32Bits(expected[i].W), BitConverter.SingleToInt32Bits(source[i].W)); + } } [Theory] diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 4b06f877f..c1eb191c9 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -7,12 +7,12 @@ using Castle.Core.Configuration; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Helpers; public class ParallelRowIteratorTests { + public delegate void BufferedRowAction(int y, Span span); public delegate void RowIntervalAction(RowInterval rows, Span span); private readonly ITestOutputHelper output; @@ -100,7 +100,7 @@ public class ParallelRowIteratorTests Rectangle rectangle = new(0, minY, 10, maxY - minY); - int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + int[] expectedData = [.. Enumerable.Repeat(0, minY), .. Enumerable.Range(minY, maxY - minY)]; int[] actualData = new int[maxY]; void RowAction(RowInterval rows) @@ -179,7 +179,7 @@ public class ParallelRowIteratorTests Rectangle rectangle = new(0, minY, 10, maxY - minY); - int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); + int[] expectedData = [.. Enumerable.Repeat(0, minY), .. Enumerable.Range(minY, maxY - minY)]; int[] actualData = new int[maxY]; void RowAction(RowInterval rows, Span buffer) @@ -200,6 +200,29 @@ public class ParallelRowIteratorTests Assert.Equal(expectedData, actualData); } + [Fact] + public void IterateRows_MaxDegreeOfParallelismMinusOne_ShouldVisitAllRows() + { + ParallelExecutionSettings parallelSettings = new( + -1, + 10, + Configuration.Default.MemoryAllocator); + + Rectangle rectangle = new(0, 0, 10, 10); + int[] actualData = new int[rectangle.Height]; + + void RowAction(int y) => actualData[y]++; + + TestRowActionOperation operation = new(RowAction); + + ParallelRowIterator.IterateRows( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(Enumerable.Repeat(1, rectangle.Height), actualData); + } + public static TheoryData IterateRows_WithEffectiveMinimumPixelsLimit_Data = new() { @@ -296,6 +319,35 @@ public class ParallelRowIteratorTests Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); } + [Fact] + public void IterateRowIntervalsWithTempBuffer_MaxDegreeOfParallelismMinusOne_ShouldVisitAllRows() + { + ParallelExecutionSettings parallelSettings = new( + -1, + 10, + Configuration.Default.MemoryAllocator); + + Rectangle rectangle = new(0, 0, 10, 10); + int[] actualData = new int[rectangle.Height]; + + void RowAction(RowInterval rows, Span buffer) + { + for (int y = rows.Min; y < rows.Max; y++) + { + actualData[y]++; + } + } + + TestRowIntervalOperation operation = new(RowAction); + + ParallelRowIterator.IterateRowIntervals, Vector4>( + rectangle, + in parallelSettings, + in operation); + + Assert.Equal(Enumerable.Repeat(1, rectangle.Height), actualData); + } + public static readonly TheoryData IterateRectangularBuffer_Data = new() { @@ -445,6 +497,32 @@ public class ParallelRowIteratorTests } } + private readonly struct TestRowActionOperation : IRowOperation + { + private readonly Action action; + + public TestRowActionOperation(Action action) + => this.action = action; + + public void Invoke(int y) + => this.action(y); + } + + private readonly struct TestRowOperation : IRowOperation + where TBuffer : unmanaged + { + private readonly BufferedRowAction action; + + public TestRowOperation(BufferedRowAction action) + => this.action = action; + + public int GetRequiredBufferLength(Rectangle bounds) + => bounds.Width; + + public void Invoke(int y, Span span) + => this.action(y, span); + } + private readonly struct TestRowIntervalOperation : IRowIntervalOperation { private readonly Action action; diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs b/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs index a1eeb2597..56864680d 100644 --- a/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs +++ b/tests/ImageSharp.Tests/IO/LocalFileSystemTests.cs @@ -50,7 +50,12 @@ public class LocalFileSystemTests await using (FileStream stream = (FileStream)fs.OpenReadAsynchronous(path)) using (StreamReader reader = new(stream)) { + // .NET 11 reports IsAsync false for regular files on Unix: https://github.com/dotnet/runtime/pull/125220 +#if NET11_0_OR_GREATER + Assert.Equal(OperatingSystem.IsWindows(), stream.IsAsync); +#else Assert.True(stream.IsAsync); +#endif Assert.True(stream.CanRead); Assert.False(stream.CanWrite); @@ -105,7 +110,12 @@ public class LocalFileSystemTests await using (FileStream stream = (FileStream)fs.CreateAsynchronous(path)) await using (StreamWriter writer = new(stream)) { + // .NET 11 reports IsAsync false for regular files on Unix: https://github.com/dotnet/runtime/pull/125220 +#if NET11_0_OR_GREATER + Assert.Equal(OperatingSystem.IsWindows(), stream.IsAsync); +#else Assert.True(stream.IsAsync); +#endif Assert.True(stream.CanRead); Assert.True(stream.CanWrite); diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index d7f2c7d28..391cff506 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -64,7 +64,7 @@ public abstract partial class ImageFrameCollectionTests using ImageFrame addedFrame = this.Collection.AddFrame(Array.Empty()); }); - Assert.StartsWith($"Parameter \"data\" ({typeof(int)}) must be greater than or equal to {100}, was {0}", ex.Message); + Assert.StartsWith($"Parameter \"data\" ({typeof(long)}) must be greater than or equal to {100}, was {0}", ex.Message); } [Fact] @@ -181,7 +181,7 @@ public abstract partial class ImageFrameCollectionTests this.Image, [imageFrame1, imageFrame2]); - IPixelSource[] framesSnapShot = collection.OfType>().ToArray(); + IPixelSource[] framesSnapShot = [.. collection.OfType>()]; Assert.All(framesSnapShot, f => Assert.False(f.PixelBuffer.IsDisposed)); diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs index cd1213c23..de06a51a9 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -29,7 +29,7 @@ public abstract partial class ImageFrameCollectionTests } Rgba32[] expectedAllBlue = - Enumerable.Repeat(Color.Blue.ToPixel(), this.Image.Width * this.Image.Height).ToArray(); + [.. Enumerable.Repeat(Color.Blue.ToPixel(), this.Image.Width * this.Image.Height)]; Assert.Equal(2, this.Collection.Count); ImageFrame actualFrame = (ImageFrame)this.Collection[1]; @@ -50,7 +50,7 @@ public abstract partial class ImageFrameCollectionTests } Rgba32[] expectedAllBlue = - Enumerable.Repeat(Color.Blue.ToPixel(), this.Image.Width * this.Image.Height).ToArray(); + [.. Enumerable.Repeat(Color.Blue.ToPixel(), this.Image.Width * this.Image.Height)]; Assert.Equal(2, this.Collection.Count); ImageFrame actualFrame = (ImageFrame)this.Collection[0]; diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs index 0656f9e0b..368048a99 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Decode_Cancellation.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Tests.TestUtilities; +using XUnit; namespace SixLabors.ImageSharp.Tests; @@ -26,7 +27,7 @@ public partial class ImageTests //TestImages.Pbm.GrayscaleBinaryWide ]; - public static object[][] IdentifyData { get; } = TestFileForEachCodec.Select(f => new object[] { f }).ToArray(); + public static object[][] IdentifyData { get; } = [.. TestFileForEachCodec.Select(f => new object[] { f })]; [Theory] [MemberData(nameof(IdentifyData))] diff --git a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs index a1966e2bb..178f5375f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.DetectFormat.cs @@ -50,6 +50,10 @@ public partial class ImageTests Assert.Equal(this.LocalImageFormat, format); } + [Fact] + public void FromBytes_EmptySpan_Throws() + => Assert.Throws(() => Image.DetectFormat([])); + [Fact] public void FromFileSystemPath_GlobalConfiguration() { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index 490fa3b0b..433a1e101 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -38,6 +38,10 @@ public partial class ImageTests Assert.Equal(ExpectedGlobalFormat, info.Metadata.DecodedImageFormat); } + [Fact] + public void FromBytes_EmptySpan_Throws() + => Assert.Throws(() => Image.Identify([])); + [Fact] public void FromBytes_CustomConfiguration() { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs index 47ae0daf7..171aa6d4c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs @@ -52,5 +52,79 @@ public partial class ImageTests Assert.Equal(Color.White, Color.FromPixel(img[1, 0])); Assert.Equal(Color.Black, Color.FromPixel(img[1, 1])); } + + [Fact] + public void FromPixels_WithRowStride() + { + Rgba32[] data = + [ + Color.Black.ToPixel(), + Color.White.ToPixel(), + Color.Red.ToPixel(), // padding + Color.White.ToPixel(), + Color.Black.ToPixel(), + Color.Red.ToPixel() // padding + ]; + + using Image img = Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStride: 3); + Assert.NotNull(img); + Assert.Equal(Color.Black, Color.FromPixel(img[0, 0])); + Assert.Equal(Color.White, Color.FromPixel(img[0, 1])); + Assert.Equal(Color.White, Color.FromPixel(img[1, 0])); + Assert.Equal(Color.Black, Color.FromPixel(img[1, 1])); + } + + [Fact] + public void FromBytes_WithRowStrideInBytes() + { + byte[] data = + [ + 0, 0, 0, 255, // 0,0 + 255, 255, 255, 255, // 0,1 + 255, 0, 0, 255, // padding + 255, 255, 255, 255, // 1,0 + 0, 0, 0, 255, // 1,1 + 255, 0, 0, 255 // padding + ]; + + using Image img = Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStrideInBytes: 12); + Assert.NotNull(img); + Assert.Equal(Color.Black, Color.FromPixel(img[0, 0])); + Assert.Equal(Color.White, Color.FromPixel(img[0, 1])); + Assert.Equal(Color.White, Color.FromPixel(img[1, 0])); + Assert.Equal(Color.Black, Color.FromPixel(img[1, 1])); + } + + [Fact] + public void FromPixels_WithRowStride_InvalidStride_Throws() + { + Rgba32[] data = new Rgba32[6]; + Assert.ThrowsAny( + () => Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStride: 1)); + } + + [Fact] + public void FromPixels_WithRowStride_InvalidLength_Throws() + { + Rgba32[] data = new Rgba32[4]; + Assert.ThrowsAny( + () => Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStride: 3)); + } + + [Fact] + public void FromBytes_WithRowStrideInBytes_InvalidStride_Throws() + { + byte[] data = new byte[24]; + Assert.ThrowsAny( + () => Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStrideInBytes: 10)); + } + + [Fact] + public void FromBytes_WithRowStrideInBytes_InvalidLength_Throws() + { + byte[] data = new byte[19]; + Assert.ThrowsAny( + () => Image.LoadPixelData(data.AsSpan(), width: 2, height: 2, rowStrideInBytes: 12)); + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs index 3a47a0ea7..600bed010 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_PassLocalConfiguration.cs @@ -79,5 +79,16 @@ public partial class ImageTests this.TestFormat.VerifyAgnosticDecodeCall(this.Marker, this.TopLevelConfiguration); } + + [Fact] + public void FromBytes_EmptySpan_Throws() + { + DecoderOptions options = new() + { + Configuration = this.TopLevelConfiguration + }; + + Assert.Throws(() => Image.Load(options, [])); + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs index 00ec985ac..a0ce1e5d8 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromBytes_UseGlobalConfiguration.cs @@ -45,5 +45,9 @@ public partial class ImageTests VerifyDecodedImage(img); Assert.IsType(img.Metadata.DecodedImageFormat); } + + [Fact] + public void FromBytes_EmptySpan_Throws() + => Assert.ThrowsAny(() => Image.Load([])); } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs index a064b6472..c2609545d 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Load_FromStream_ThrowsRightException.cs @@ -32,6 +32,17 @@ public partial class ImageTests } }); - public void Dispose() => this.Stream?.Dispose(); + [Fact] + public void FromStream_Empty_Throws() + { + using MemoryStream ms = new(); + Assert.Throws(() => Image.Load(DecoderOptions.Default, ms)); + } + + public void Dispose() + { + this.Stream?.Dispose(); + GC.SuppressFinalize(this); + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 6322e65aa..f86ce344f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -141,6 +141,103 @@ public partial class ImageTests } } + [Fact] + public void WrapMemory_MemoryOfT_Strided_CreatedImageIsCorrect() + { + Rgba32[] source = + [ + new(1, 1, 1, 255), + new(2, 2, 2, 255), + new(3, 3, 3, 255), + new(90, 90, 90, 255), + new(4, 4, 4, 255), + new(5, 5, 5, 255), + new(6, 6, 6, 255), + new(91, 91, 91, 255) + ]; + + using Image image = Image.WrapMemory(source.AsMemory(), width: 3, height: 2, rowStride: 4); + + Assert.Equal(4, image.Frames.RootFrame.PixelBuffer.RowStride); + Assert.False(image.DangerousTryGetSinglePixelMemory(out Memory _)); + Assert.Equal(source[0], image[0, 0]); + Assert.Equal(source[2], image[2, 0]); + Assert.Equal(source[4], image[0, 1]); + Assert.Equal(source[6], image[2, 1]); + } + + [Fact] + public void WrapMemory_MemoryOfT_Strided_CopyPixelDataTo_UsesRowStrideLayout() + { + Rgba32[] source = + [ + new(1, 1, 1, 255), + new(2, 2, 2, 255), + new(3, 3, 3, 255), + new(90, 90, 90, 255), + new(4, 4, 4, 255), + new(5, 5, 5, 255), + new(6, 6, 6, 255), + new(91, 91, 91, 255) + ]; + + using Image image = Image.WrapMemory(source.AsMemory(), width: 3, height: 2, rowStride: 4); + + Rgba32 sentinel = new(250, 1, 1, 255); + Rgba32[] destination = [sentinel, sentinel, sentinel, sentinel, sentinel, sentinel, sentinel]; + image.CopyPixelDataTo(destination); + + Assert.Equal(source[0], destination[0]); + Assert.Equal(source[1], destination[1]); + Assert.Equal(source[2], destination[2]); + Assert.Equal(sentinel, destination[3]); + Assert.Equal(source[4], destination[4]); + Assert.Equal(source[5], destination[5]); + Assert.Equal(source[6], destination[6]); + Assert.ThrowsAny(() => image.CopyPixelDataTo(new Rgba32[6])); + } + + [Fact] + public void WrapMemory_MemoryOfByte_Strided_CreatedImageIsCorrect() + { + int pixelSize = Unsafe.SizeOf(); + byte[] sourceBytes = new byte[8 * pixelSize]; + Span source = MemoryMarshal.Cast(sourceBytes.AsSpan()); + + source[0] = new Rgba32(1, 1, 1, 255); + source[1] = new Rgba32(2, 2, 2, 255); + source[2] = new Rgba32(3, 3, 3, 255); + source[4] = new Rgba32(4, 4, 4, 255); + source[5] = new Rgba32(5, 5, 5, 255); + source[6] = new Rgba32(6, 6, 6, 255); + + using Image image = Image.WrapMemory( + sourceBytes.AsMemory(), + width: 3, + height: 2, + rowStrideInBytes: 4 * pixelSize); + + Assert.Equal(4, image.Frames.RootFrame.PixelBuffer.RowStride); + Assert.False(image.DangerousTryGetSinglePixelMemory(out Memory _)); + Assert.Equal(source[0], image[0, 0]); + Assert.Equal(source[2], image[2, 0]); + Assert.Equal(source[4], image[0, 1]); + Assert.Equal(source[6], image[2, 1]); + } + + [Fact] + public void WrapMemory_Strided_InvalidStride_Throws() + { + Rgba32[] pixelSource = new Rgba32[8]; + byte[] byteSource = new byte[8 * Unsafe.SizeOf()]; + + Assert.ThrowsAny(() => Image.WrapMemory(pixelSource.AsMemory(), width: 3, height: 2, rowStride: 2)); + Assert.ThrowsAny(() => Image.WrapMemory(pixelSource.AsMemory(0, 6), width: 3, height: 2, rowStride: 4)); + + Assert.ThrowsAny(() => Image.WrapMemory(byteSource.AsMemory(), width: 3, height: 2, rowStrideInBytes: (4 * Unsafe.SizeOf()) - 1)); + Assert.ThrowsAny(() => Image.WrapMemory(byteSource.AsMemory(0, 6 * Unsafe.SizeOf()), width: 3, height: 2, rowStrideInBytes: 4 * Unsafe.SizeOf())); + } + [Fact] public void WrapSystemDrawingBitmap_WhenObserved() { diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs index 27e42f84e..a5dfb0d23 100644 --- a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs @@ -313,7 +313,15 @@ public abstract class ProcessPixelRowsTestBase protected internal override int GetBufferCapacityInBytes() => int.MaxValue; - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) => - this.buffers.Pop() as IMemoryOwner; + protected override AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) + { + object buffer = this.buffers.Pop(); + if (buffer is AllocationTrackedMemoryManager trackedBuffer) + { + return trackedBuffer; + } + + throw new InvalidMemoryOperationException("The requested buffer type does not match the mock allocator buffer type."); + } } } diff --git a/tests/ImageSharp.Tests/ImageInfoTests.cs b/tests/ImageSharp.Tests/ImageInfoTests.cs index 322b0af19..748c8a4f6 100644 --- a/tests/ImageSharp.Tests/ImageInfoTests.cs +++ b/tests/ImageSharp.Tests/ImageInfoTests.cs @@ -54,4 +54,34 @@ public class ImageInfoTests Assert.Equal(meta, info.Metadata); Assert.Equal(frameMetadata.Count, info.FrameMetadataCollection.Count); } + + [Fact] + public void GetPixelMemorySize_UsesSingleFrameWhenFrameMetadataIsEmpty() + { + const int width = 10; + const int height = 20; + + ImageMetadata meta = new() { DecodedImageFormat = PngFormat.Instance }; + meta.GetPngMetadata(); + + ImageInfo info = new(new Size(width, height), meta); + + Assert.Equal(width * height * 4, info.GetPixelMemorySize()); + } + + [Fact] + public void GetPixelMemorySize_UsesFrameMetadataCountWhenAvailable() + { + const int width = 10; + const int height = 20; + IReadOnlyList frameMetadata = [new(), new(), new()]; + + ImageMetadata meta = new() { DecodedImageFormat = PngFormat.Instance }; + meta.GetPngMetadata(); + + ImageInfo info = new(new Size(width, height), meta, frameMetadata); + + Assert.Equal(width * height * 4 * frameMetadata.Count, info.GetPixelMemorySize()); + } + } diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index ce391fad2..9c9ee8da6 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -12,12 +12,12 @@ - net8.0;net9.0 + net10.0;net11.0 - net8.0 + net10.0 @@ -45,7 +45,6 @@ - diff --git a/tests/ImageSharp.Tests/Issues/Issue_396.cs b/tests/ImageSharp.Tests/Issues/Issue_396.cs new file mode 100644 index 000000000..0d76f0c80 --- /dev/null +++ b/tests/ImageSharp.Tests/Issues/Issue_396.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Tests.Issues; + +// https://github.com/SixLabors/ImageSharp.Drawing/discussions/396 +public class Issue_396 +{ + [Theory] + [WithFile(TestImages.Png.Issue396Dragon, PixelTypes.Rgba32)] + public void GeneratesGifForInspection(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Image[] handImages = + [ + Image.Load(TestFile.Create(TestImages.Png.Issue396Hand1).Bytes), + Image.Load(TestFile.Create(TestImages.Png.Issue396Hand2).Bytes) + ]; + + try + { + using Image inputImage = provider.GetImage(); + using Image outputImage = new(handImages[0].Width, handImages[0].Height); + + const float scaleFactor = 0.6F; + const float squashStepFactor = 0.22F; + + for (int i = 0; i < handImages.Length; i++) + { + using Image frameImage = new(handImages[i].Width, handImages[i].Height); + float squashFactorY = 1 - (i * squashStepFactor); + + frameImage.ProcessPixelRows(accessor => + { + Rgba32 gray = Color.Gray.ToPixel(); + + for (int y = 0; y < accessor.Height; y++) + { + accessor.GetRowSpan(y).Fill(gray); + } + }); + + Rectangle targetRect = new( + (int)((frameImage.Width - (frameImage.Width * scaleFactor)) / 2), + (int)(frameImage.Height * (((1 - scaleFactor) / 2) + (scaleFactor * (1 - squashFactorY)))), + (int)(frameImage.Width * scaleFactor), + (int)(frameImage.Height * scaleFactor * squashFactorY)); + + using Image dragonFrame = inputImage.CloneAs(); + dragonFrame.Mutate(context => context.Resize(targetRect.Size)); + + frameImage.Mutate(context => + { + context.DrawImage(dragonFrame, targetRect.Location, 1F); + context.DrawImage(handImages[i], Point.Empty, 1F); + }); + + ImageFrame outputFrame = outputImage.Frames.AddFrame(frameImage.Frames.RootFrame); + GifFrameMetadata gifFrameMetadata = outputFrame.Metadata.GetGifMetadata(); + gifFrameMetadata.FrameDelay = 40; + gifFrameMetadata.DisposalMode = FrameDisposalMode.RestoreToBackground; + } + + for (int i = handImages.Length - 1; i > 0; i--) + { + outputImage.Frames.AddFrame(outputImage.Frames[i]); + } + + outputImage.Frames.RemoveFrame(0); + GifMetadata gifMetadata = outputImage.Metadata.GetGifMetadata(); + gifMetadata.RepeatCount = 0; + + Assert.Equal((handImages.Length * 2) - 1, outputImage.Frames.Count); + foreach (ImageFrame frame in outputImage.Frames) + { + Assert.Equal(FrameDisposalMode.RestoreToBackground, frame.Metadata.GetGifMetadata().DisposalMode); + } + + outputImage.DebugSaveMultiFrame(provider, "Issue396-source-frames", appendPixelTypeToFileName: false); + provider.Utility.SaveTestOutputFile( + outputImage, + "gif", + new GifEncoder(), + "Issue396-encoded", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + // Save and decode the GIF so the encoder's optimized frame diffs can be inspected separately. + using MemoryStream gifStream = new(); + outputImage.SaveAsGif(gifStream); + gifStream.Position = 0; + + using Image decodedImage = Image.Load(gifStream); + decodedImage.DebugSaveMultiFrame(provider, "Issue396-decoded-frames", appendPixelTypeToFileName: false); + + Assert.Equal(outputImage.Frames.Count, decodedImage.Frames.Count); + } + finally + { + foreach (Image handImage in handImages) + { + handImage.Dispose(); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs index 72b90c238..848cd1c1a 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs @@ -23,7 +23,7 @@ public class SharedArrayPoolBufferTests } byte[] array = ArrayPool.Shared.Rent(900); - byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); + byte[] expected = [.. Enumerable.Repeat((byte)42, 900)]; Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); } @@ -48,7 +48,7 @@ public class SharedArrayPoolBufferTests buffer.ReleaseRef(); array = ArrayPool.Shared.Rent(900); - byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); + byte[] expected = [.. Enumerable.Repeat((byte)42, 900)]; Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); ArrayPool.Shared.Return(array); } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs index 0e791c5d9..c33e6d107 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -48,6 +48,50 @@ public class SimpleGcMemoryAllocatorTests } } + [Fact] + public void Allocate_AccumulativeLimit_ReleasesOnOwnerDispose() + { + SimpleGcMemoryAllocator allocator = new(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single owner. + IMemoryOwner b0 = allocator.Allocate(oneMb); + + // Additional allocation should exceed the limit while the owner is live. + Assert.Throws(() => allocator.Allocate(1)); + + // Disposing the owner releases the reservation. + b0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.Allocate(oneMb).Dispose(); + } + + [Fact] + public void AllocateGroup_AccumulativeLimit_ReleasesOnGroupDispose() + { + SimpleGcMemoryAllocator allocator = new(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single group. + MemoryGroup g0 = allocator.AllocateGroup(oneMb, 1024); + + // Additional allocation should exceed the limit while the group is live. + Assert.Throws(() => allocator.AllocateGroup(1, 1024)); + + // Disposing the group releases the reservation. + g0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.AllocateGroup(oneMb, 1024).Dispose(); + } + [StructLayout(LayoutKind.Explicit, Size = 512)] private struct BigStruct { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs index ec79d91c3..1cb55a9f3 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Memory.Internals; -using Xunit.Abstractions; +using XUnit; namespace SixLabors.ImageSharp.Tests.Memory.Allocators; @@ -18,8 +18,8 @@ public partial class UniformUnmanagedMemoryPoolTests private class CleanupUtil : IDisposable { private readonly UniformUnmanagedMemoryPool pool; - private readonly List handlesToDestroy = new(); - private readonly List ptrsToDestroy = new(); + private readonly List handlesToDestroy = []; + private readonly List ptrsToDestroy = []; public CleanupUtil(UniformUnmanagedMemoryPool pool) { @@ -151,8 +151,8 @@ public partial class UniformUnmanagedMemoryPoolTests { UniformUnmanagedMemoryPool pool = new(128, capacity); using CleanupUtil cleanup = new(pool); - HashSet allHandles = new(); - List handleUnits = new(); + HashSet allHandles = []; + List handleUnits = []; UnmanagedMemoryHandle[] handles; for (int i = 0; i < totalCount; i += rentUnit) @@ -261,7 +261,7 @@ public partial class UniformUnmanagedMemoryPoolTests pool.Release(); // Do some unmanaged allocations to make sure new pool buffers are different: - IntPtr[] dummy = Enumerable.Range(0, 100).Select(_ => Marshal.AllocHGlobal(16)).ToArray(); + IntPtr[] dummy = [.. Enumerable.Range(0, 100).Select(_ => Marshal.AllocHGlobal(16))]; cleanup.Register(dummy); if (bool.Parse(multipleInner)) @@ -312,7 +312,7 @@ public partial class UniformUnmanagedMemoryPoolTests Parallel.For(0, Environment.ProcessorCount, (int i) => { - List allHandles = new(); + List allHandles = []; int pauseAt = rnd.Next(100); for (int j = 0; j < 100; j++) { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 6e7e9fea7..b02956666 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.DotNet.RemoteExecutor; @@ -15,8 +16,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests { public class BufferTests1 : BufferTestSuite { - private static MemoryAllocator CreateMemoryAllocator() => - new UniformUnmanagedMemoryPoolMemoryAllocator( + private static UniformUnmanagedMemoryPoolMemoryAllocator CreateMemoryAllocator() => + new( sharedArrayPoolThresholdInBytes: 1024, poolBufferSizeInBytes: 2048, maxPoolSizeInBytes: 2048 * 4, @@ -30,8 +31,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests public class BufferTests2 : BufferTestSuite { - private static MemoryAllocator CreateMemoryAllocator() => - new UniformUnmanagedMemoryPoolMemoryAllocator( + private static UniformUnmanagedMemoryPoolMemoryAllocator CreateMemoryAllocator() => + new( sharedArrayPoolThresholdInBytes: 512, poolBufferSizeInBytes: 1024, maxPoolSizeInBytes: 1024 * 4, @@ -178,8 +179,8 @@ public class UniformUnmanagedPoolMemoryAllocatorTests g1.Dispose(); // Do some unmanaged allocations to make sure new non-pooled unmanaged allocations will grab different memory: - IntPtr dummy1 = Marshal.AllocHGlobal((IntPtr)B(8)); - IntPtr dummy2 = Marshal.AllocHGlobal((IntPtr)B(8)); + IntPtr dummy1 = Marshal.AllocHGlobal(checked((IntPtr)B(8))); + IntPtr dummy2 = Marshal.AllocHGlobal(checked((IntPtr)B(8))); using MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); using MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); @@ -273,67 +274,75 @@ public class UniformUnmanagedPoolMemoryAllocatorTests [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) { - if (TestEnvironment.IsMacOS) - { - // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 - return; - } - - if (TestEnvironment.OSArchitecture == Architecture.Arm64) - { - // Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342 - return; - } - - if (!TestEnvironment.RunsOnCI) - { - // This may fail in local runs resulting in high memory load. - // Remove the condition for local debugging! - return; - } - - // RunTest(length.ToString()); - RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + RemoteExecutor.Invoke(RunTest, length.ToString(CultureInfo.InvariantCulture)).Dispose(); static void RunTest(string lengthStr) { UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(512, 1024, 16 * 1024, 1024); - int lengthInner = int.Parse(lengthStr); - + int lengthInner = int.Parse(lengthStr, CultureInfo.InvariantCulture); + + // We want to verify that a leaked (not disposed) `MemoryGroup` still returns its + // unmanaged handles into the pool when it is finalized. + // + // We intentionally do NOT validate this by checking the contents of the re-rented memory + // (contents are not guaranteed to be preserved) nor by comparing pointer values + // (the pool may return a different handle while still correctly pooling). + // + // Instead, we validate that after a forced GC+finalization cycle, a subsequent allocation + // of the same size does not cause the number of outstanding unmanaged handles to *increase* + // compared to a known baseline. + + // Establish a baseline: create one allocation and dispose it so the pool is initialized. + // (This ensures subsequent observations are not biased by first-time pool growth.) + allocator.AllocateGroup(lengthInner, 100).Dispose(); + int baselineHandles = UnmanagedMemoryHandle.TotalOutstandingHandles; + + // Leak one allocation and force finalization. AllocateGroupAndForget(allocator, lengthInner); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); - AllocateGroupAndForget(allocator, lengthInner, true); - GC.Collect(); - GC.WaitForPendingFinalizers(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - using MemoryGroup g = allocator.AllocateGroup(lengthInner, 100); - Assert.Equal(42, g.First().Span[0]); + // Allocate again. If the leaked group was finalized correctly and returned to the pool, + // this should not require additional unmanaged allocations (ie, the handle count must not grow). + allocator.AllocateGroup(lengthInner, 100).Dispose(); + + // Note: we use "<=" instead of "==" here. + // + // After we record the baseline, the pool is allowed to legitimately *decrease* + // `UnmanagedMemoryHandle.TotalOutstandingHandles` by trimming retained buffers + // (eg. via the pool's trim timer/GC callbacks/high-pressure logic). + // + // What must not happen is the opposite: the leaked (non-disposed) group should be finalized + // and its handles returned to the pool such that allocating again does NOT require creating + // additional unmanaged handles. Therefore the only invariant we can reliably assert here is + // "no growth" relative to the baseline. + Assert.True(UnmanagedMemoryHandle.TotalOutstandingHandles <= baselineHandles); } } - private static void AllocateGroupAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + [MethodImpl(MethodImplOptions.NoInlining)] + private static void AllocateGroupAndForget(MemoryAllocator allocator, int length) { + // Allocate a group and drop the reference without disposing. + // The test relies on the group's finalizer to return the rented memory to the pool. MemoryGroup g = allocator.AllocateGroup(length, 100); - if (check) - { - Assert.Equal(42, g.First().Span[0]); - } - g.First().Span[0] = 42; + // Touch the memory to ensure the buffer is actually materialized/usable. + g[0].Span[0] = 42; if (length < 512) { - // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, - // repeat rental to make sure per-core buckets are also utilized. + // For ArrayPool.Shared, the first rented array may be stored in TLS on the finalizer thread. + // Repeat rental to increase the chance that per-core buckets are involved when length + // is small and allocations go through ArrayPool. MemoryGroup g1 = allocator.AllocateGroup(length, 100); - g1.First().Span[0] = 42; + g1[0].Span[0] = 42; + g1 = null; } + + g = null; } [Theory] @@ -341,81 +350,112 @@ public class UniformUnmanagedPoolMemoryAllocatorTests [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) { - if (TestEnvironment.IsMacOS) - { - // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887 - return; - } - - if (TestEnvironment.OSArchitecture == Architecture.Arm64) - { - // Skip on ARM64: https://github.com/SixLabors/ImageSharp/issues/2342 - return; - } - - if (!TestEnvironment.RunsOnCI) - { - // This may fail in local runs resulting in high memory load. - // Remove the condition for local debugging! - return; - } - - // RunTest(length.ToString()); - RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + RemoteExecutor.Invoke(RunTest, length.ToString(CultureInfo.InvariantCulture)).Dispose(); static void RunTest(string lengthStr) { UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(512, 1024, 16 * 1024, 1024); - int lengthInner = int.Parse(lengthStr); - + int lengthInner = int.Parse(lengthStr, CultureInfo.InvariantCulture); + + // This test verifies pooling behavior when an `IMemoryOwner` is leaked (not disposed) + // and must be returned to the pool by finalization. + // + // We do NOT use a sentinel byte value to prove reuse because the contents of pooled buffers + // are not required to be preserved across rentals. + // + // Instead, we assert that after forcing GC+finalization, renting the same size again does not + // increase `UnmanagedMemoryHandle.TotalOutstandingHandles` above a baseline. + + // Establish a baseline: allocate+dispose once so the pool has a chance to materialize/retain buffers. + allocator.Allocate(lengthInner).Dispose(); + int baselineHandles = UnmanagedMemoryHandle.TotalOutstandingHandles; + + // Leak one allocation and force finalization. AllocateSingleAndForget(allocator, lengthInner); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); - AllocateSingleAndForget(allocator, lengthInner, true); + // Allocate again. If the leaked owner was finalized correctly and returned to the pool, + // this should not require additional unmanaged allocations (ie, the handle count must not grow). + allocator.Allocate(lengthInner).Dispose(); + + // Note: we use "<=" rather than "==". The pool may legitimately trim and free retained buffers, + // reducing the handle count between baseline and check. The invariant is "no growth". + Assert.True(UnmanagedMemoryHandle.TotalOutstandingHandles <= baselineHandles); + } + } + + [Theory] + [InlineData(1)] // SharedArrayPoolBuffer + [InlineData(2)] // UniformUnmanagedMemoryPool buffer + public void Allocate_AccumulativeLimit_Finalization_ReleasesOwnerReservation(int megabytes) + { + RemoteExecutor.Invoke(RunTest, megabytes.ToString(CultureInfo.InvariantCulture)).Dispose(); + + static void RunTest(string megabytesStr) + { + int megabytesInner = int.Parse(megabytesStr, CultureInfo.InvariantCulture); + int length = megabytesInner * (1 << 20); + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = megabytesInner + }); + + AllocateSingleAndForget(allocator, length); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); - using IMemoryOwner g = allocator.Allocate(lengthInner); - Assert.Equal(42, g.GetSpan()[0]); - GC.KeepAlive(allocator); + allocator.Allocate(length).Dispose(); } } [MethodImpl(MethodImplOptions.NoInlining)] - private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + private static void AllocateSingleAndForget(MemoryAllocator allocator, int length) { + // Allocate and intentionally do not dispose. IMemoryOwner g = allocator.Allocate(length); - if (check) - { - Assert.Equal(42, g.GetSpan()[0]); - } + // Touch the memory to ensure the buffer is actually materialized/usable. g.GetSpan()[0] = 42; if (length < 512) { - // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, - // repeat rental to make sure per-core buckets are also utilized. + // For ArrayPool.Shared, the first rented array may be stored in TLS on the finalizer thread. + // Repeat rental to increase the chance that per-core buckets are involved when length + // is small and allocations go through ArrayPool. IMemoryOwner g1 = allocator.Allocate(length); g1.GetSpan()[0] = 42; + g1 = null; } + + g = null; } [Fact] - public void Issue2001_NegativeMemoryReportedByGc() + public void AllocateGroup_AccumulativeLimit_Finalization_ReleasesGroupReservation() { RemoteExecutor.Invoke(RunTest).Dispose(); static void RunTest() { - // Emulate GC.GetGCMemoryInfo() issue https://github.com/dotnet/runtime/issues/65466 - UniformUnmanagedMemoryPoolMemoryAllocator.GetTotalAvailableMemoryBytes = () => -402354176; - _ = MemoryAllocator.Create(); + const int megabytes = 5; + int length = megabytes * (1 << 20); + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = megabytes + }); + + AllocateGroupAndForget(allocator, length); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + allocator.AllocateGroup(length, 1024).Dispose(); } } @@ -443,6 +483,143 @@ public class UniformUnmanagedPoolMemoryAllocatorTests Assert.Throws(() => allocator.AllocateGroup(5 * oneMb, 1024)); } + [Fact] + public void Allocate_OverSingleBufferLimit_ThrowsInvalidMemoryOperationException() + { + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + SingleBufferAllocationLimitMegabytes = 2 + }); + const int oneMb = 1 << 20; + allocator.Allocate(2 * oneMb).Dispose(); // Should work + Assert.Throws(() => allocator.Allocate(3 * oneMb)); + + // The group limit is unchanged, so the same size still allocates as a discontiguous group. + allocator.AllocateGroup(3 * oneMb, 1024).Dispose(); + } + + [ConditionalFact(typeof(Environment), nameof(Environment.Is64BitProcess))] + public void MemoryAllocator_Create_RaisesSingleBufferLimit() + { + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + SingleBufferAllocationLimitMegabytes = 2047 + }); + + Assert.Equal(2047L * 1024 * 1024, (long)allocator.SingleBufferAllocationLimitBytes); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + [InlineData(2048)] + public void MemoryAllocatorOptions_InvalidSingleBufferLimit_Throws(int value) + { + MemoryAllocatorOptions options = default; + Assert.Throws(() => options.SingleBufferAllocationLimitMegabytes = value); + } + + [Fact] + public void Allocate_AccumulativeLimit_ReleasesOnOwnerDispose() + { + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single owner. + IMemoryOwner b0 = allocator.Allocate(oneMb); + + // Additional allocation should exceed the limit while the owner is live. + Assert.Throws(() => allocator.Allocate(1)); + + // Disposing the owner releases the reservation. + b0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.Allocate(oneMb).Dispose(); + } + + [Fact] + public void AllocateGroup_AccumulativeLimit_ReleasesOnGroupDispose() + { + MemoryAllocator allocator = MemoryAllocator.Create(new MemoryAllocatorOptions + { + AccumulativeAllocationLimitMegabytes = 1 + }); + const int oneMb = 1 << 20; + + // Reserve the full limit with a single group. + MemoryGroup g0 = allocator.AllocateGroup(oneMb, 1024); + + // Additional allocation should exceed the limit while the group is live. + Assert.Throws(() => allocator.AllocateGroup(1, 1024)); + + // Disposing the group releases the reservation. + g0.Dispose(); + + // Allocation should succeed after the reservation is released. + allocator.AllocateGroup(oneMb, 1024).Dispose(); + } + + [Fact] + public void AllocateGroup_AccumulativeLimit_NonPoolFallback_TracksOncePerGroup() + { + // Configure the pool with zero capacity so multi-segment requests bypass both the + // single-buffer-from-pool path and MemoryGroup.TryAllocate(pool, ...) and fall + // through to MemoryGroup.Allocate(nonPoolAllocator, ...). The unmanaged segment + // size is small enough that the request must span multiple segments, which is the + // path where per-segment double-counting could regress. + UniformUnmanagedMemoryPoolMemoryAllocator allocator = new( + sharedArrayPoolThresholdInBytes: 64 * 1024, + poolBufferSizeInBytes: 128 * 1024, + maxPoolSizeInBytes: 0, + unmanagedBufferSizeInBytes: 256 * 1024, + new MemoryAllocatorOptions { AccumulativeAllocationLimitMegabytes = 1 }); + + // 768 KB exceeds the pool buffer size, so the request takes the multi-segment + // non-pool fallback (three 256 KB segments). If tracking double-counted (group + // plus each segment), reservation would be 768 KB + 768 KB = 1.5 MB and exceed + // the 1 MB limit on allocation itself. + MemoryGroup g = allocator.AllocateGroup(768 * 1024, 1024); + Assert.True(g.Count > 1, "Test setup must exercise the multi-segment fallback path."); + + // Reservation should be exactly 768 KB; another 512 KB would push to 1.25 MB and throw. + Assert.Throws(() => allocator.Allocate(512 * 1024)); + + g.Dispose(); + + // After disposal the reservation is fully released; a second equivalent group succeeds. + allocator.AllocateGroup(768 * 1024, 1024).Dispose(); + } + + [Fact] + public void AllocateGroup_AccumulativeLimit_NonPoolFallback_Finalization_ReleasesGroupReservation() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + UniformUnmanagedMemoryPoolMemoryAllocator allocator = new( + sharedArrayPoolThresholdInBytes: 64 * 1024, + poolBufferSizeInBytes: 128 * 1024, + maxPoolSizeInBytes: 0, + unmanagedBufferSizeInBytes: 256 * 1024, + new MemoryAllocatorOptions { AccumulativeAllocationLimitMegabytes = 1 }); + + // This exercises the non-pool multi-segment fallback, where reservation ownership has + // to follow the finalizable segment guards because the MemoryGroup itself has no finalizer. + AllocateGroupAndForget(allocator, 768 * 1024); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + allocator.AllocateGroup(768 * 1024, 1024).Dispose(); + } + } + [ConditionalFact(typeof(Environment), nameof(Environment.Is64BitProcess))] public void MemoryAllocator_Create_SetHighLimit() { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs index 3b33eae5e..1fe33c346 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs @@ -76,7 +76,7 @@ public class UnmanagedBufferTests static List> FillList(int countInner) { - List> l = new(); + List> l = []; for (int i = 0; i < countInner; i++) { UnmanagedBuffer h = UnmanagedBuffer.Allocate(42); diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs index 59b793e01..8818597a9 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs @@ -47,7 +47,7 @@ public class UnmanagedMemoryHandleTests static void RunTest(string countStr) { int countInner = int.Parse(countStr); - List l = new(); + List l = []; for (int i = 0; i < countInner; i++) { Assert.Equal(i, UnmanagedMemoryHandle.TotalOutstandingHandles); diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.CopyTo.cs new file mode 100644 index 000000000..b19095be9 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.CopyTo.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory; + +public partial class Buffer2DTests +{ + [Fact] + public void CopyToSpan_StridedSource_WritesDestinationUsingSourceStride() + { + int[] source = [1, 2, 3, 777, 4, 5, 6, 888]; + int[] destination = [-1, -1, -1, -1, -1, -1, -1]; + + using Buffer2D buffer = Buffer2D.WrapMemory(source.AsMemory(), width: 3, height: 2, stride: 4); + buffer.CopyTo(destination); + + Assert.Equal(new[] { 1, 2, 3, -1, 4, 5, 6 }, destination); + } + + [Fact] + public void CopyToSpan_PackedSource_WritesPackedDestination() + { + int[] source = [1, 2, 3, 4, 5, 6]; + int[] destination = new int[6]; + + using Buffer2D buffer = Buffer2D.WrapMemory(source.AsMemory(), width: 3, height: 2); + buffer.CopyTo(destination); + + Assert.Equal(new[] { 1, 2, 3, 4, 5, 6 }, destination); + } + + [Fact] + public void CopyToSpan_StridedSource_ThrowsWhenDestinationTooShort() + { + int[] source = [1, 2, 3, 777, 4, 5, 6, 888]; + int[] destination = new int[6]; + + using Buffer2D buffer = Buffer2D.WrapMemory(source.AsMemory(), width: 3, height: 2, stride: 4); + Assert.Throws(() => buffer.CopyTo(destination)); + } +} diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs index e17c8c46f..6e9069a45 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Memory; @@ -15,26 +15,24 @@ public partial class Buffer2DTests [Fact] public void SwapOrCopyContent_WhenBothAllocated() { - using (Buffer2D a = this.memoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) - using (Buffer2D b = this.memoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) - { - a[1, 3] = 666; - b[1, 3] = 444; + using Buffer2D a = this.memoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean); + using Buffer2D b = this.memoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean); + a[1, 3] = 666; + b[1, 3] = 444; - Memory aa = a.FastMemoryGroup.Single(); - Memory bb = b.FastMemoryGroup.Single(); + Memory aa = a.FastMemoryGroup.Single(); + Memory bb = b.FastMemoryGroup.Single(); - Buffer2D.SwapOrCopyContent(a, b); + Buffer2D.SwapOrCopyContent(a, b); - Assert.Equal(bb, a.FastMemoryGroup.Single()); - Assert.Equal(aa, b.FastMemoryGroup.Single()); + Assert.Equal(bb, a.FastMemoryGroup.Single()); + Assert.Equal(aa, b.FastMemoryGroup.Single()); - Assert.Equal(new Size(3, 7), a.Size()); - Assert.Equal(new Size(10, 5), b.Size()); + Assert.Equal(new Size(3, 7), a.Size); + Assert.Equal(new Size(10, 5), b.Size); - Assert.Equal(666, b[1, 3]); - Assert.Equal(444, a[1, 3]); - } + Assert.Equal(666, b[1, 3]); + Assert.Equal(444, a[1, 3]); } [Fact] @@ -152,5 +150,74 @@ public partial class Buffer2DTests Assert.Equal(color, source.MemoryGroup[0].Span[10]); Assert.NotEqual(color, dest.MemoryGroup[0].Span[10]); } + + [Fact] + public void WhenDestIsNotMemoryOwner_DifferentSizeSameTotal_PackedLayout_ShouldCopy() + { + int[] data = new int[6]; + using TestMemoryManager destOwner = new(data); + using Buffer2D dest = new(MemoryGroup.Wrap(destOwner.Memory), 2, 3); + using Buffer2D source = this.memoryAllocator.Allocate2D(3, 2, AllocationOptions.Clean); + + source[0, 0] = 1; + source[1, 0] = 2; + source[2, 0] = 3; + source[0, 1] = 4; + source[1, 1] = 5; + source[2, 1] = 6; + + bool swap = Buffer2D.SwapOrCopyContent(dest, source); + + Assert.False(swap); + Assert.Equal(new Size(3, 2), dest.Size); + Assert.Equal(6, dest[2, 1]); + } + + [Fact] + public void WhenDestIsNotMemoryOwner_DifferentSizeSameTotal_StridedLayout_ShouldCopy() + { + int[] data = new int[5]; + using Buffer2D dest = Buffer2D.WrapMemory(data.AsMemory(), width: 2, height: 2, stride: 3); + using Buffer2D source = this.memoryAllocator.Allocate2D(1, 5, AllocationOptions.Clean); + + source[0, 0] = 1; + source[0, 1] = 2; + source[0, 2] = 3; + source[0, 3] = 4; + source[0, 4] = 5; + + bool swap = Buffer2D.SwapOrCopyContent(dest, source); + + Assert.False(swap); + Assert.Equal(new Size(1, 5), dest.Size); + Assert.Equal(1, dest[0, 0]); + Assert.Equal(2, dest[0, 1]); + Assert.Equal(3, dest[0, 2]); + Assert.Equal(4, dest[0, 3]); + Assert.Equal(5, dest[0, 4]); + } + + [Fact] + public void WhenDestIsNotMemoryOwner_DifferentSizeDifferentTotal_ButBothLayoutsFit_ShouldCopy() + { + int[] data = new int[5]; + using TestMemoryManager destOwner = new(data); + using Buffer2D dest = new(MemoryGroup.Wrap(destOwner.Memory), 1, 3); + using Buffer2D source = this.memoryAllocator.Allocate2D(2, 2, AllocationOptions.Clean); + + source[0, 0] = 1; + source[1, 0] = 2; + source[0, 1] = 3; + source[1, 1] = 4; + + bool swap = Buffer2D.SwapOrCopyContent(dest, source); + + Assert.False(swap); + Assert.Equal(new Size(2, 2), dest.Size); + Assert.Equal(1, dest[0, 0]); + Assert.Equal(2, dest[1, 0]); + Assert.Equal(3, dest[0, 1]); + Assert.Equal(4, dest[1, 1]); + } } } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.WrapMemory.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.WrapMemory.cs new file mode 100644 index 000000000..f65f193ea --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.WrapMemory.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Memory; + +public partial class Buffer2DTests +{ + [Fact] + public void WrapMemory_Packed_DangerousTryGetSinglePixelMemory_ReturnsTrue() + { + int[] data = [1, 2, 3, 4, 5, 6]; + + using Buffer2D buffer = Buffer2D.WrapMemory(data.AsMemory(), width: 3, height: 2, stride: 3); + + Assert.True(buffer.DangerousTryGetSingleMemory(out Memory memory)); + Assert.Equal(3, buffer.RowStride); + Assert.Equal(6, memory.Length); + Assert.True(Unsafe.AreSame(ref data[0], ref memory.Span[0])); + Assert.SpanPointsTo(buffer.DangerousGetRowSpan(1), data.AsMemory(), bufferOffset: 3); + } + + [Fact] + public void WrapMemory_Strided_DangerousTryGetSinglePixelMemory_ReturnsFalse() + { + int[] data = [1, 2, 3, 777, 4, 5, 6, 888]; + + using Buffer2D buffer = Buffer2D.WrapMemory(data.AsMemory(), width: 3, height: 2, stride: 4); + + Assert.False(buffer.DangerousTryGetSingleMemory(out Memory _)); + Assert.Equal(4, buffer.RowStride); + Assert.Equal(4, new Buffer2DRegion(buffer).Stride); + + Span row0 = buffer.DangerousGetRowSpan(0); + Span row1 = buffer.DangerousGetRowSpan(1); + + Assert.Equal(3, row0.Length); + Assert.Equal(3, row1.Length); + Assert.Equal(1, row0[0]); + Assert.Equal(3, row0[2]); + Assert.Equal(4, row1[0]); + Assert.Equal(6, row1[2]); + + Assert.SpanPointsTo(row0, data.AsMemory(), bufferOffset: 0); + Assert.SpanPointsTo(row1, data.AsMemory(), bufferOffset: 4); + } + + [Fact] + public void WrapMemory_Packed_WithTrailingData_DangerousTryGetSinglePixelMemory_IsLogicalSize() + { + int[] data = [1, 2, 3, 4, 5, 6, 777, 888]; + + using Buffer2D buffer = Buffer2D.WrapMemory(data.AsMemory(), width: 3, height: 2, stride: 3); + + Assert.True(buffer.DangerousTryGetSingleMemory(out Memory memory)); + Assert.Equal(6, memory.Length); + Assert.True(Unsafe.AreSame(ref data[0], ref memory.Span[0])); + } + + [Fact] + public void WrapMemory_Strided_ThrowsWhenStrideIsLessThanWidth() + { + int[] data = new int[10]; + + Assert.Throws(() => Buffer2D.WrapMemory(data.AsMemory(), width: 3, height: 2, stride: 2)); + } + + [Fact] + public void WrapMemory_Strided_ThrowsWhenInputMemoryTooSmall() + { + int[] data = new int[6]; + + Assert.Throws(() => Buffer2D.WrapMemory(data.AsMemory(), width: 3, height: 2, stride: 4)); + } + + [Theory] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(0, 0)] + [InlineData(-1, 1)] + [InlineData(1, -1)] + public void WrapMemory_ThrowsWhenWidthOrHeightIsNotPositive(int width, int height) + { + int[] data = new int[16]; + + Assert.Throws(() => Buffer2D.WrapMemory(data.AsMemory(), width, height)); + Assert.Throws(() => Buffer2D.WrapMemory(data.AsMemory(), width, height, stride: 4)); + } +} diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 6dfdd294c..8e3f3d286 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -50,17 +50,11 @@ public partial class Buffer2DTests [InlineData(Big, 1, 0)] [InlineData(60, 42, 0)] [InlineData(3, 0, 0)] - public unsafe void Construct_Empty(int bufferCapacity, int width, int height) + public unsafe void Construct_Empty_Throws(int bufferCapacity, int width, int height) { this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; - using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) - { - Assert.Equal(width, buffer.Width); - Assert.Equal(height, buffer.Height); - Assert.Equal(0, buffer.FastMemoryGroup.TotalLength); - Assert.Equal(0, buffer.DangerousGetSingleSpan().Length); - } + Assert.Throws(() => this.MemoryAllocator.Allocate2D(width, height)); } [Theory] @@ -339,25 +333,47 @@ public partial class Buffer2DTests Assert.NotSame(mgBefore, buffer1.MemoryGroup); } - public static TheoryData InvalidLengths { get; set; } = new() + public static TheoryData InvalidDimensions { get; set; } = new() { { new Size(-1, -1) }, + { new Size(0, 1) }, + { new Size(1, 0) }, + { new Size(0, 0) } + }; + + public static TheoryData OverflowDimensions { get; set; } = new() + { { new Size(32768, 32769) }, { new Size(32769, 32768) } }; [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException(Size size) + [MemberData(nameof(InvalidDimensions))] + public void Allocate_InvalidDimensions_ThrowsArgumentOutOfRangeException(Size size) + => Assert.Throws(() => this.MemoryAllocator.Allocate2D(size.Width, size.Height)); + + [Theory] + [MemberData(nameof(InvalidDimensions))] + public void Allocate_InvalidDimensions_SizeOverload_ThrowsArgumentOutOfRangeException(Size size) + => Assert.Throws(() => this.MemoryAllocator.Allocate2D(new Size(size))); + + [Theory] + [MemberData(nameof(InvalidDimensions))] + public void Allocate_InvalidDimensions_OverAligned_ThrowsArgumentOutOfRangeException(Size size) + => Assert.Throws(() => this.MemoryAllocator.Allocate2DOveraligned(size.Width, size.Height, 1)); + + [Theory] + [MemberData(nameof(OverflowDimensions))] + public void Allocate_OverflowDimensions_ThrowsInvalidMemoryOperationException(Size size) => Assert.Throws(() => this.MemoryAllocator.Allocate2D(size.Width, size.Height)); [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException_Size(Size size) + [MemberData(nameof(OverflowDimensions))] + public void Allocate_OverflowDimensions_SizeOverload_ThrowsInvalidMemoryOperationException(Size size) => Assert.Throws(() => this.MemoryAllocator.Allocate2D(new Size(size))); [Theory] - [MemberData(nameof(InvalidLengths))] - public void Allocate_IncorrectAmount_ThrowsCorrect_InvalidMemoryOperationException_OverAligned(Size size) + [MemberData(nameof(OverflowDimensions))] + public void Allocate_OverflowDimensions_OverAligned_ThrowsInvalidMemoryOperationException(Size size) => Assert.Throws(() => this.MemoryAllocator.Allocate2DOveraligned(size.Width, size.Height, 1)); } diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index be7edbacc..a44d6ce1a 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -17,7 +17,7 @@ public class BufferAreaTests Buffer2DRegion area = new(buffer, rectangle); Assert.Equal(buffer, area.Buffer); - Assert.Equal(rectangle, area.Rectangle); + Assert.Equal(rectangle, area.Bounds); } private Buffer2D CreateTestBuffer(int w, int h) @@ -90,7 +90,7 @@ public class BufferAreaTests Rectangle expectedRect = new(10, 12, 5, 5); Assert.Equal(buffer, area1.Buffer); - Assert.Equal(expectedRect, area1.Rectangle); + Assert.Equal(expectedRect, area1.Bounds); int value00 = (12 * 100) + 10; Assert.Equal(value00, area1[0, 0]); @@ -147,9 +147,9 @@ public class BufferAreaTests Assert.Equal(0, buffer[5, 5]); Assert.Equal(0, buffer[14, 14]); - for (int y = region.Rectangle.Y; y < region.Rectangle.Bottom; y++) + for (int y = region.Bounds.Y; y < region.Bounds.Bottom; y++) { - Span span = buffer.DangerousGetRowSpan(y).Slice(region.Rectangle.X, region.Width); + Span span = buffer.DangerousGetRowSpan(y).Slice(region.Bounds.X, region.Width); Assert.True(span.SequenceEqual(new int[region.Width])); } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index 678a089a8..04d2bedf4 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -98,7 +98,15 @@ public partial class MemoryGroupTests [InlineData(AllocationOptions.Clean)] public unsafe void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) { - UniformUnmanagedMemoryPool pool = new(10, 5); + // Disable trimming to avoid buffers being freed between Return and TryAllocate by the + // trim timer or the Gen2 GC callback. + UniformUnmanagedMemoryPool pool = new( + 10, + 5, + new UniformUnmanagedMemoryPool.TrimSettings + { + Rate = 0 + }); UnmanagedMemoryHandle[] buffers = pool.Rent(5); foreach (UnmanagedMemoryHandle b in buffers) { @@ -198,7 +206,7 @@ public partial class MemoryGroupTests { IReadOnlyList allocationLog = this.MemoryAllocator.AllocationLog; Assert.Equal(expectedBlockCount, allocationLog.Count); - bufferHashes = allocationLog.Select(l => l.HashCodeOfBuffer).ToHashSet(); + bufferHashes = [.. allocationLog.Select(l => l.HashCodeOfBuffer)]; Assert.Equal(expectedBlockCount, bufferHashes.Count); Assert.Equal(0, this.MemoryAllocator.ReturnLog.Count); diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs index c140571fc..ee5cd9cd0 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.CopyTo.cs @@ -9,100 +9,74 @@ public partial class MemoryGroupTests { public class CopyTo : MemoryGroupTestsBase { - public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = - CopyAndTransformData; - - [Theory] - [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] - public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) + [Fact] + public void GroupToSpan_StridedSource_DoesNotRequireTrailingPadding() { - using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); - using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); - - src.CopyTo(trg); + using MemoryGroup src = this.CreateTestGroup(totalLength: 7, bufferLength: 4, fillSequence: true); + int[] trg = new int[6]; - int pos = 0; - MemoryGroupIndex i = src.MinIndex(); - MemoryGroupIndex j = trg.MinIndex(); - for (; i < src.MaxIndex(); i += 1, j += 1, pos++) - { - int a = src.GetElementAt(i); - int b = trg.GetElementAt(j); + src.CopyTo( + sourceStride: 4, + trg, + targetStride: 3, + width: 3, + height: 2); - Assert.True(a == b, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); - } + Assert.Equal(new[] { 1, 2, 3, 5, 6, 7 }, trg); } [Fact] - public void WhenTargetBufferTooShort_Throws() + public void GroupToGroup_StridedCopy_DoesNotRequireTrailingPadding() { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - using MemoryGroup trg = this.CreateTestGroup(5, 20, false); - - Assert.Throws(() => src.CopyTo(trg)); + using MemoryGroup src = this.CreateTestGroup(totalLength: 11, bufferLength: 5, fillSequence: true); + using MemoryGroup trg = this.CreateTestGroup(totalLength: 13, bufferLength: 6, fillSequence: false); + + src.CopyTo( + sourceStride: 4, + trg, + targetStride: 5, + width: 3, + height: 3); + + Assert.Equal(1, GetElementAtLinearIndex(trg, 0)); + Assert.Equal(2, GetElementAtLinearIndex(trg, 1)); + Assert.Equal(3, GetElementAtLinearIndex(trg, 2)); + Assert.Equal(5, GetElementAtLinearIndex(trg, 5)); + Assert.Equal(6, GetElementAtLinearIndex(trg, 6)); + Assert.Equal(7, GetElementAtLinearIndex(trg, 7)); + Assert.Equal(9, GetElementAtLinearIndex(trg, 10)); + Assert.Equal(10, GetElementAtLinearIndex(trg, 11)); + Assert.Equal(11, GetElementAtLinearIndex(trg, 12)); } - [Theory] - [InlineData(30, 10, 40)] - [InlineData(42, 23, 42)] - [InlineData(1, 3, 10)] - [InlineData(0, 4, 0)] - public void GroupToSpan_Success(long totalLength, int bufferLength, int spanLength) + [Fact] + public void GroupToSpan_StridedSource_HeightOne() { - using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); - int[] trg = new int[spanLength]; - src.CopyTo(trg); + using MemoryGroup src = this.CreateTestGroup(totalLength: 3, bufferLength: 2, fillSequence: true); + int[] trg = new int[3]; - int expected = 1; - foreach (int val in trg.AsSpan().Slice(0, (int)totalLength)) - { - Assert.Equal(expected, val); - expected++; - } - } + src.CopyTo( + sourceStride: 8, + trg, + targetStride: 3, + width: 3, + height: 1); - [Theory] - [InlineData(20, 7, 19)] - [InlineData(2, 1, 1)] - public void GroupToSpan_OutOfRange(long totalLength, int bufferLength, int spanLength) - { - using MemoryGroup src = this.CreateTestGroup(totalLength, bufferLength, true); - int[] trg = new int[spanLength]; - Assert.ThrowsAny(() => src.CopyTo(trg)); + Assert.Equal(new[] { 1, 2, 3 }, trg); } - [Theory] - [InlineData(30, 35, 10)] - [InlineData(42, 23, 42)] - [InlineData(10, 3, 1)] - [InlineData(0, 3, 0)] - public void SpanToGroup_Success(long totalLength, int bufferLength, int spanLength) + private static int GetElementAtLinearIndex(MemoryGroup group, int index) { - int[] src = new int[spanLength]; - for (int i = 0; i < src.Length; i++) - { - src[i] = i + 1; - } - - using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength); - src.AsSpan().CopyTo(trg); - - int position = 0; - for (MemoryGroupIndex i = trg.MinIndex(); position < spanLength; i += 1, position++) + int pos = 0; + for (MemoryGroupIndex i = group.MinIndex(); i < group.MaxIndex(); i += 1, pos++) { - int expected = position + 1; - Assert.Equal(expected, trg.GetElementAt(i)); + if (pos == index) + { + return group.GetElementAt(i); + } } - } - [Theory] - [InlineData(10, 3, 11)] - [InlineData(0, 3, 1)] - public void SpanToGroup_OutOfRange(long totalLength, int bufferLength, int spanLength) - { - int[] src = new int[spanLength]; - using MemoryGroup trg = this.CreateTestGroup(totalLength, bufferLength, true); - Assert.ThrowsAny(() => src.AsSpan().CopyTo(trg)); + throw new ArgumentOutOfRangeException(nameof(index)); } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index fe1867f20..3cea414bf 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -26,82 +26,12 @@ public partial class MemoryGroupTests : MemoryGroupTestsBase Assert.False(g.IsValid); } -#pragma warning disable SA1509 - private static readonly TheoryData CopyAndTransformData = - new() - { - { 20, 10, 20, 10 }, - { 20, 5, 20, 4 }, - { 20, 4, 20, 5 }, - { 18, 6, 20, 5 }, - { 19, 10, 20, 10 }, - { 21, 10, 22, 2 }, - { 1, 5, 5, 4 }, - - { 30, 12, 40, 5 }, - { 30, 5, 40, 12 }, - }; - - public class TransformTo : MemoryGroupTestsBase - { - public static readonly TheoryData WhenSourceBufferIsShorterOrEqual_Data = - CopyAndTransformData; - - [Theory] - [MemberData(nameof(WhenSourceBufferIsShorterOrEqual_Data))] - public void WhenSourceBufferIsShorterOrEqual(int srcTotal, int srcBufLen, int trgTotal, int trgBufLen) - { - using MemoryGroup src = this.CreateTestGroup(srcTotal, srcBufLen, true); - using MemoryGroup trg = this.CreateTestGroup(trgTotal, trgBufLen, false); - - src.TransformTo(trg, MultiplyAllBy2); - - int pos = 0; - MemoryGroupIndex i = src.MinIndex(); - MemoryGroupIndex j = trg.MinIndex(); - for (; i < src.MaxIndex(); i += 1, j += 1, pos++) - { - int a = src.GetElementAt(i); - int b = trg.GetElementAt(j); - - Assert.True(b == 2 * a, $"Mismatch @ {pos} Expected: {a} Actual: {b}"); - } - } - - [Fact] - public void WhenTargetBufferTooShort_Throws() - { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - using MemoryGroup trg = this.CreateTestGroup(5, 20, false); - - Assert.Throws(() => src.TransformTo(trg, MultiplyAllBy2)); - } - } - - [Theory] - [InlineData(100, 5)] - [InlineData(100, 101)] - public void TransformInplace(int totalLength, int bufferLength) - { - using MemoryGroup src = this.CreateTestGroup(10, 20, true); - - src.TransformInplace(s => MultiplyAllBy2(s, s)); - - int cnt = 1; - for (MemoryGroupIndex i = src.MinIndex(); i < src.MaxIndex(); i += 1) - { - int val = src.GetElementAt(i); - Assert.Equal(expected: cnt * 2, val); - cnt++; - } - } - [Fact] public void Wrap() { - int[] data0 = { 1, 2, 3, 4 }; - int[] data1 = { 5, 6, 7, 8 }; - int[] data2 = { 9, 10 }; + int[] data0 = [1, 2, 3, 4]; + int[] data1 = [5, 6, 7, 8]; + int[] data2 = [9, 10]; using TestMemoryManager mgr0 = new(data0); using TestMemoryManager mgr1 = new(data1); @@ -116,7 +46,7 @@ public partial class MemoryGroupTests : MemoryGroupTestsBase Assert.True(group[2].Span.SequenceEqual(data2)); int cnt = 0; - int[][] allData = { data0, data1, data2 }; + int[][] allData = [data0, data1, data2]; foreach (Memory memory in group) { Assert.True(memory.Span.SequenceEqual(allData[cnt])); @@ -176,7 +106,7 @@ public partial class MemoryGroupTests : MemoryGroupTestsBase using MemoryGroup group = this.CreateTestGroup(100, 10, true); group.Fill(42); - int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + int[] expectedRow = [.. Enumerable.Repeat(42, 10)]; foreach (Memory memory in group) { Assert.True(memory.Span.SequenceEqual(expectedRow)); @@ -189,7 +119,7 @@ public partial class MemoryGroupTests : MemoryGroupTestsBase using MemoryGroup group = this.CreateTestGroup(100, 10, true); group.Fill(42); - int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + int[] expectedRow = [.. Enumerable.Repeat(42, 10)]; IReadOnlyList> groupAsList = group; foreach (Memory memory in groupAsList) { @@ -203,7 +133,7 @@ public partial class MemoryGroupTests : MemoryGroupTestsBase using MemoryGroup group = this.CreateTestGroup(100, 10, true); group.Fill(42); - int[] expectedRow = Enumerable.Repeat(42, 10).ToArray(); + int[] expectedRow = [.. Enumerable.Repeat(42, 10)]; IEnumerable groupAsList = group; foreach (Memory memory in groupAsList) { @@ -224,12 +154,4 @@ public partial class MemoryGroupTests : MemoryGroupTestsBase } } - private static void MultiplyAllBy2(ReadOnlySpan source, Span target) - { - Assert.Equal(source.Length, target.Length); - for (int k = 0; k < source.Length; k++) - { - target[k] = source[k] * 2; - } - } } diff --git a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs index 395dfd455..6c8dd2618 100644 --- a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs +++ b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs @@ -20,20 +20,13 @@ public static class MemoryAllocatorValidator private static void MemoryDiagnostics_MemoryReleased() { TestMemoryDiagnostics backing = LocalInstance.Value; - if (backing != null) - { - backing.TotalRemainingAllocated--; - } + backing?.OnReleased(); } private static void MemoryDiagnostics_MemoryAllocated() { TestMemoryDiagnostics backing = LocalInstance.Value; - if (backing != null) - { - backing.TotalAllocated++; - backing.TotalRemainingAllocated++; - } + backing?.OnAllocated(); } public static TestMemoryDiagnostics MonitorAllocations() @@ -48,11 +41,23 @@ public static class MemoryAllocatorValidator public static void ValidateAllocations(int expectedAllocationCount = 0) => LocalInstance.Value?.Validate(expectedAllocationCount); - public class TestMemoryDiagnostics : IDisposable + public sealed class TestMemoryDiagnostics : IDisposable { - public int TotalAllocated { get; set; } + private int totalAllocated; + private int totalRemainingAllocated; + + public int TotalAllocated => Volatile.Read(ref this.totalAllocated); + + public int TotalRemainingAllocated => Volatile.Read(ref this.totalRemainingAllocated); + + internal void OnAllocated() + { + Interlocked.Increment(ref this.totalAllocated); + Interlocked.Increment(ref this.totalRemainingAllocated); + } - public int TotalRemainingAllocated { get; set; } + internal void OnReleased() + => Interlocked.Decrement(ref this.totalRemainingAllocated); public void Validate(int expectedAllocationCount) { diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index cee37ca56..e50d0b617 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -74,7 +74,7 @@ public class ImageFrameMetadataTests Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile)); - Assert.True(metaData.XmpProfile.Data.Equals(clone.XmpProfile.Data)); + Assert.False(ReferenceEquals(metaData.XmpProfile.Data, clone.XmpProfile.Data)); Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata())); Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index c098ace09..a60b2f6da 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -374,7 +374,7 @@ public class ExifProfileTests Image image = new(100, 100); ExifProfile expectedProfile = CreateExifProfile(); - List expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); + List expectedProfileTags = [.. expectedProfile.Values.Select(x => x.Tag)]; expectedProfile.SetValue(tag, junk.ToString()); image.Metadata.ExifProfile = expectedProfile; @@ -470,9 +470,9 @@ public class ExifProfileTests public void ProfileToByteArray() { // Arrange - byte[] exifBytesWithoutExifCode = ExifConstants.LittleEndianByteOrderMarker.ToArray(); + byte[] exifBytesWithoutExifCode = [.. ExifConstants.LittleEndianByteOrderMarker]; ExifProfile expectedProfile = CreateExifProfile(); - List expectedProfileTags = expectedProfile.Values.Select(x => x.Tag).ToList(); + List expectedProfileTags = [.. expectedProfile.Values.Select(x => x.Tag)]; // Act byte[] actualBytes = expectedProfile.ToByteArray(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs index 983ff4493..bcd884ffa 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifReaderTests.cs @@ -11,7 +11,7 @@ public class ExifReaderTests [Fact] public void Read_DataIsEmpty_ReturnsEmptyCollection() { - ExifReader reader = new(Array.Empty()); + ExifReader reader = new([]); IList result = reader.ReadValues(); @@ -21,7 +21,7 @@ public class ExifReaderTests [Fact] public void Read_DataIsMinimal_ReturnsEmptyCollection() { - ExifReader reader = new(new byte[] { 69, 120, 105, 102, 0, 0 }); + ExifReader reader = new([69, 120, 105, 102, 0, 0]); IList result = reader.ReadValues(); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs index d7d6741ba..82f2bfa13 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs @@ -11,329 +11,329 @@ public class ExifValuesTests { public static TheoryData ByteTags => new() { - { ExifTag.FaxProfile }, - { ExifTag.ModeNumber }, - { ExifTag.GPSAltitudeRef } + (ExifTag)ExifTag.FaxProfile, + (ExifTag)ExifTag.ModeNumber, + (ExifTag)ExifTag.GPSAltitudeRef }; public static TheoryData ByteArrayTags => new() { - { ExifTag.ClipPath }, - { ExifTag.VersionYear }, - { ExifTag.XMP }, - { ExifTag.CFAPattern2 }, - { ExifTag.TIFFEPStandardID }, - { ExifTag.GPSVersionID }, + (ExifTag)ExifTag.ClipPath, + (ExifTag)ExifTag.VersionYear, + (ExifTag)ExifTag.XMP, + (ExifTag)ExifTag.CFAPattern2, + (ExifTag)ExifTag.TIFFEPStandardID, + (ExifTag)ExifTag.GPSVersionID, }; public static TheoryData DoubleArrayTags => new() { - { ExifTag.PixelScale }, - { ExifTag.IntergraphMatrix }, - { ExifTag.ModelTiePoint }, - { ExifTag.ModelTransform } + (ExifTag)ExifTag.PixelScale, + (ExifTag)ExifTag.IntergraphMatrix, + (ExifTag)ExifTag.ModelTiePoint, + (ExifTag)ExifTag.ModelTransform }; public static TheoryData LongTags => new() { - { ExifTag.SubfileType }, - { ExifTag.SubIFDOffset }, - { ExifTag.GPSIFDOffset }, - { ExifTag.T4Options }, - { ExifTag.T6Options }, - { ExifTag.XClipPathUnits }, - { ExifTag.YClipPathUnits }, - { ExifTag.ProfileType }, - { ExifTag.CodingMethods }, - { ExifTag.T82ptions }, - { ExifTag.JPEGInterchangeFormat }, - { ExifTag.JPEGInterchangeFormatLength }, - { ExifTag.MDFileTag }, - { ExifTag.StandardOutputSensitivity }, - { ExifTag.RecommendedExposureIndex }, - { ExifTag.ISOSpeed }, - { ExifTag.ISOSpeedLatitudeyyy }, - { ExifTag.ISOSpeedLatitudezzz }, - { ExifTag.FaxRecvParams }, - { ExifTag.FaxRecvTime }, - { ExifTag.ImageNumber }, + (ExifTag)ExifTag.SubfileType, + (ExifTag)ExifTag.SubIFDOffset, + (ExifTag)ExifTag.GPSIFDOffset, + (ExifTag)ExifTag.T4Options, + (ExifTag)ExifTag.T6Options, + (ExifTag)ExifTag.XClipPathUnits, + (ExifTag)ExifTag.YClipPathUnits, + (ExifTag)ExifTag.ProfileType, + (ExifTag)ExifTag.CodingMethods, + (ExifTag)ExifTag.T82ptions, + (ExifTag)ExifTag.JPEGInterchangeFormat, + (ExifTag)ExifTag.JPEGInterchangeFormatLength, + (ExifTag)ExifTag.MDFileTag, + (ExifTag)ExifTag.StandardOutputSensitivity, + (ExifTag)ExifTag.RecommendedExposureIndex, + (ExifTag)ExifTag.ISOSpeed, + (ExifTag)ExifTag.ISOSpeedLatitudeyyy, + (ExifTag)ExifTag.ISOSpeedLatitudezzz, + (ExifTag)ExifTag.FaxRecvParams, + (ExifTag)ExifTag.FaxRecvTime, + (ExifTag)ExifTag.ImageNumber, }; public static TheoryData LongArrayTags => new() { - { ExifTag.FreeOffsets }, - { ExifTag.FreeByteCounts }, - { ExifTag.ColorResponseUnit }, - { ExifTag.SMinSampleValue }, - { ExifTag.SMaxSampleValue }, - { ExifTag.JPEGQTables }, - { ExifTag.JPEGDCTables }, - { ExifTag.JPEGACTables }, - { ExifTag.StripRowCounts }, - { ExifTag.IntergraphRegisters } + (ExifTag)ExifTag.FreeOffsets, + (ExifTag)ExifTag.FreeByteCounts, + (ExifTag)ExifTag.ColorResponseUnit, + (ExifTag)ExifTag.SMinSampleValue, + (ExifTag)ExifTag.SMaxSampleValue, + (ExifTag)ExifTag.JPEGQTables, + (ExifTag)ExifTag.JPEGDCTables, + (ExifTag)ExifTag.JPEGACTables, + (ExifTag)ExifTag.StripRowCounts, + (ExifTag)ExifTag.IntergraphRegisters }; public static TheoryData NumberTags => new() { - { ExifTag.ImageWidth }, - { ExifTag.ImageLength }, - { ExifTag.TileWidth }, - { ExifTag.TileLength }, - { ExifTag.BadFaxLines }, - { ExifTag.ConsecutiveBadFaxLines }, - { ExifTag.PixelXDimension }, - { ExifTag.PixelYDimension } + (ExifTag)ExifTag.ImageWidth, + (ExifTag)ExifTag.ImageLength, + (ExifTag)ExifTag.TileWidth, + (ExifTag)ExifTag.TileLength, + (ExifTag)ExifTag.BadFaxLines, + (ExifTag)ExifTag.ConsecutiveBadFaxLines, + (ExifTag)ExifTag.PixelXDimension, + (ExifTag)ExifTag.PixelYDimension }; public static TheoryData NumberArrayTags => new() { - { ExifTag.StripOffsets }, - { ExifTag.StripByteCounts }, - { ExifTag.TileByteCounts }, - { ExifTag.TileOffsets }, - { ExifTag.ImageLayer } + (ExifTag)ExifTag.StripOffsets, + (ExifTag)ExifTag.StripByteCounts, + (ExifTag)ExifTag.TileByteCounts, + (ExifTag)ExifTag.TileOffsets, + (ExifTag)ExifTag.ImageLayer }; public static TheoryData RationalTags => new() { - { ExifTag.XPosition }, - { ExifTag.YPosition }, - { ExifTag.XResolution }, - { ExifTag.YResolution }, - { ExifTag.BatteryLevel }, - { ExifTag.ExposureTime }, - { ExifTag.FNumber }, - { ExifTag.MDScalePixel }, - { ExifTag.CompressedBitsPerPixel }, - { ExifTag.ApertureValue }, - { ExifTag.MaxApertureValue }, - { ExifTag.SubjectDistance }, - { ExifTag.FocalLength }, - { ExifTag.FlashEnergy2 }, - { ExifTag.FocalPlaneXResolution2 }, - { ExifTag.FocalPlaneYResolution2 }, - { ExifTag.ExposureIndex2 }, - { ExifTag.Humidity }, - { ExifTag.Pressure }, - { ExifTag.Acceleration }, - { ExifTag.FlashEnergy }, - { ExifTag.FocalPlaneXResolution }, - { ExifTag.FocalPlaneYResolution }, - { ExifTag.ExposureIndex }, - { ExifTag.DigitalZoomRatio }, - { ExifTag.GPSAltitude }, - { ExifTag.GPSDOP }, - { ExifTag.GPSSpeed }, - { ExifTag.GPSTrack }, - { ExifTag.GPSImgDirection }, - { ExifTag.GPSDestBearing }, - { ExifTag.GPSDestDistance }, - { ExifTag.GPSHPositioningError }, + (ExifTag)ExifTag.XPosition, + (ExifTag)ExifTag.YPosition, + (ExifTag)ExifTag.XResolution, + (ExifTag)ExifTag.YResolution, + (ExifTag)ExifTag.BatteryLevel, + (ExifTag)ExifTag.ExposureTime, + (ExifTag)ExifTag.FNumber, + (ExifTag)ExifTag.MDScalePixel, + (ExifTag)ExifTag.CompressedBitsPerPixel, + (ExifTag)ExifTag.ApertureValue, + (ExifTag)ExifTag.MaxApertureValue, + (ExifTag)ExifTag.SubjectDistance, + (ExifTag)ExifTag.FocalLength, + (ExifTag)ExifTag.FlashEnergy2, + (ExifTag)ExifTag.FocalPlaneXResolution2, + (ExifTag)ExifTag.FocalPlaneYResolution2, + (ExifTag)ExifTag.ExposureIndex2, + (ExifTag)ExifTag.Humidity, + (ExifTag)ExifTag.Pressure, + (ExifTag)ExifTag.Acceleration, + (ExifTag)ExifTag.FlashEnergy, + (ExifTag)ExifTag.FocalPlaneXResolution, + (ExifTag)ExifTag.FocalPlaneYResolution, + (ExifTag)ExifTag.ExposureIndex, + (ExifTag)ExifTag.DigitalZoomRatio, + (ExifTag)ExifTag.GPSAltitude, + (ExifTag)ExifTag.GPSDOP, + (ExifTag)ExifTag.GPSSpeed, + (ExifTag)ExifTag.GPSTrack, + (ExifTag)ExifTag.GPSImgDirection, + (ExifTag)ExifTag.GPSDestBearing, + (ExifTag)ExifTag.GPSDestDistance, + (ExifTag)ExifTag.GPSHPositioningError, }; public static TheoryData RationalArrayTags => new() { - { ExifTag.WhitePoint }, - { ExifTag.PrimaryChromaticities }, - { ExifTag.YCbCrCoefficients }, - { ExifTag.ReferenceBlackWhite }, - { ExifTag.GPSLatitude }, - { ExifTag.GPSLongitude }, - { ExifTag.GPSTimestamp }, - { ExifTag.GPSDestLatitude }, - { ExifTag.GPSDestLongitude }, - { ExifTag.LensSpecification } + (ExifTag)ExifTag.WhitePoint, + (ExifTag)ExifTag.PrimaryChromaticities, + (ExifTag)ExifTag.YCbCrCoefficients, + (ExifTag)ExifTag.ReferenceBlackWhite, + (ExifTag)ExifTag.GPSLatitude, + (ExifTag)ExifTag.GPSLongitude, + (ExifTag)ExifTag.GPSTimestamp, + (ExifTag)ExifTag.GPSDestLatitude, + (ExifTag)ExifTag.GPSDestLongitude, + (ExifTag)ExifTag.LensSpecification }; public static TheoryData ShortTags => new() { - { ExifTag.OldSubfileType }, - { ExifTag.Compression }, - { ExifTag.PhotometricInterpretation }, - { ExifTag.Thresholding }, - { ExifTag.CellWidth }, - { ExifTag.CellLength }, - { ExifTag.FillOrder }, - { ExifTag.Orientation }, - { ExifTag.SamplesPerPixel }, - { ExifTag.PlanarConfiguration }, - { ExifTag.Predictor }, - { ExifTag.GrayResponseUnit }, - { ExifTag.ResolutionUnit }, - { ExifTag.CleanFaxData }, - { ExifTag.InkSet }, - { ExifTag.NumberOfInks }, - { ExifTag.DotRange }, - { ExifTag.Indexed }, - { ExifTag.OPIProxy }, - { ExifTag.JPEGProc }, - { ExifTag.JPEGRestartInterval }, - { ExifTag.YCbCrPositioning }, - { ExifTag.Rating }, - { ExifTag.RatingPercent }, - { ExifTag.ExposureProgram }, - { ExifTag.Interlace }, - { ExifTag.SelfTimerMode }, - { ExifTag.SensitivityType }, - { ExifTag.MeteringMode }, - { ExifTag.LightSource }, - { ExifTag.FocalPlaneResolutionUnit2 }, - { ExifTag.SensingMethod2 }, - { ExifTag.Flash }, - { ExifTag.ColorSpace }, - { ExifTag.FocalPlaneResolutionUnit }, - { ExifTag.SensingMethod }, - { ExifTag.CustomRendered }, - { ExifTag.ExposureMode }, - { ExifTag.WhiteBalance }, - { ExifTag.FocalLengthIn35mmFilm }, - { ExifTag.SceneCaptureType }, - { ExifTag.GainControl }, - { ExifTag.Contrast }, - { ExifTag.Saturation }, - { ExifTag.Sharpness }, - { ExifTag.SubjectDistanceRange }, - { ExifTag.GPSDifferential } + (ExifTag)ExifTag.OldSubfileType, + (ExifTag)ExifTag.Compression, + (ExifTag)ExifTag.PhotometricInterpretation, + (ExifTag)ExifTag.Thresholding, + (ExifTag)ExifTag.CellWidth, + (ExifTag)ExifTag.CellLength, + (ExifTag)ExifTag.FillOrder, + (ExifTag)ExifTag.Orientation, + (ExifTag)ExifTag.SamplesPerPixel, + (ExifTag)ExifTag.PlanarConfiguration, + (ExifTag)ExifTag.Predictor, + (ExifTag)ExifTag.GrayResponseUnit, + (ExifTag)ExifTag.ResolutionUnit, + (ExifTag)ExifTag.CleanFaxData, + (ExifTag)ExifTag.InkSet, + (ExifTag)ExifTag.NumberOfInks, + (ExifTag)ExifTag.DotRange, + (ExifTag)ExifTag.Indexed, + (ExifTag)ExifTag.OPIProxy, + (ExifTag)ExifTag.JPEGProc, + (ExifTag)ExifTag.JPEGRestartInterval, + (ExifTag)ExifTag.YCbCrPositioning, + (ExifTag)ExifTag.Rating, + (ExifTag)ExifTag.RatingPercent, + (ExifTag)ExifTag.ExposureProgram, + (ExifTag)ExifTag.Interlace, + (ExifTag)ExifTag.SelfTimerMode, + (ExifTag)ExifTag.SensitivityType, + (ExifTag)ExifTag.MeteringMode, + (ExifTag)ExifTag.LightSource, + (ExifTag)ExifTag.FocalPlaneResolutionUnit2, + (ExifTag)ExifTag.SensingMethod2, + (ExifTag)ExifTag.Flash, + (ExifTag)ExifTag.ColorSpace, + (ExifTag)ExifTag.FocalPlaneResolutionUnit, + (ExifTag)ExifTag.SensingMethod, + (ExifTag)ExifTag.CustomRendered, + (ExifTag)ExifTag.ExposureMode, + (ExifTag)ExifTag.WhiteBalance, + (ExifTag)ExifTag.FocalLengthIn35mmFilm, + (ExifTag)ExifTag.SceneCaptureType, + (ExifTag)ExifTag.GainControl, + (ExifTag)ExifTag.Contrast, + (ExifTag)ExifTag.Saturation, + (ExifTag)ExifTag.Sharpness, + (ExifTag)ExifTag.SubjectDistanceRange, + (ExifTag)ExifTag.GPSDifferential }; public static TheoryData ShortArrayTags => new() { - { ExifTag.BitsPerSample }, - { ExifTag.MinSampleValue }, - { ExifTag.MaxSampleValue }, - { ExifTag.GrayResponseCurve }, - { ExifTag.ColorMap }, - { ExifTag.ExtraSamples }, - { ExifTag.PageNumber }, - { ExifTag.TransferFunction }, - { ExifTag.HalftoneHints }, - { ExifTag.SampleFormat }, - { ExifTag.TransferRange }, - { ExifTag.DefaultImageColor }, - { ExifTag.JPEGLosslessPredictors }, - { ExifTag.JPEGPointTransforms }, - { ExifTag.YCbCrSubsampling }, - { ExifTag.CFARepeatPatternDim }, - { ExifTag.IntergraphPacketData }, - { ExifTag.ISOSpeedRatings }, - { ExifTag.SubjectArea }, - { ExifTag.SubjectLocation } + (ExifTag)ExifTag.BitsPerSample, + (ExifTag)ExifTag.MinSampleValue, + (ExifTag)ExifTag.MaxSampleValue, + (ExifTag)ExifTag.GrayResponseCurve, + (ExifTag)ExifTag.ColorMap, + (ExifTag)ExifTag.ExtraSamples, + (ExifTag)ExifTag.PageNumber, + (ExifTag)ExifTag.TransferFunction, + (ExifTag)ExifTag.HalftoneHints, + (ExifTag)ExifTag.SampleFormat, + (ExifTag)ExifTag.TransferRange, + (ExifTag)ExifTag.DefaultImageColor, + (ExifTag)ExifTag.JPEGLosslessPredictors, + (ExifTag)ExifTag.JPEGPointTransforms, + (ExifTag)ExifTag.YCbCrSubsampling, + (ExifTag)ExifTag.CFARepeatPatternDim, + (ExifTag)ExifTag.IntergraphPacketData, + (ExifTag)ExifTag.ISOSpeedRatings, + (ExifTag)ExifTag.SubjectArea, + (ExifTag)ExifTag.SubjectLocation }; public static TheoryData SignedRationalTags => new() { - { ExifTag.ShutterSpeedValue }, - { ExifTag.BrightnessValue }, - { ExifTag.ExposureBiasValue }, - { ExifTag.AmbientTemperature }, - { ExifTag.WaterDepth }, - { ExifTag.CameraElevationAngle } + (ExifTag)ExifTag.ShutterSpeedValue, + (ExifTag)ExifTag.BrightnessValue, + (ExifTag)ExifTag.ExposureBiasValue, + (ExifTag)ExifTag.AmbientTemperature, + (ExifTag)ExifTag.WaterDepth, + (ExifTag)ExifTag.CameraElevationAngle }; public static TheoryData SignedRationalArrayTags => new() { - { ExifTag.Decode } + (ExifTag)ExifTag.Decode }; public static TheoryData SignedShortArrayTags => new() { - { ExifTag.TimeZoneOffset } + (ExifTag)ExifTag.TimeZoneOffset }; public static TheoryData StringTags => new() { - { ExifTag.ImageDescription }, - { ExifTag.Make }, - { ExifTag.Model }, - { ExifTag.Software }, - { ExifTag.DateTime }, - { ExifTag.Artist }, - { ExifTag.HostComputer }, - { ExifTag.Copyright }, - { ExifTag.DocumentName }, - { ExifTag.PageName }, - { ExifTag.InkNames }, - { ExifTag.TargetPrinter }, - { ExifTag.ImageID }, - { ExifTag.MDLabName }, - { ExifTag.MDSampleInfo }, - { ExifTag.MDPrepDate }, - { ExifTag.MDPrepTime }, - { ExifTag.MDFileUnits }, - { ExifTag.SEMInfo }, - { ExifTag.SpectralSensitivity }, - { ExifTag.DateTimeOriginal }, - { ExifTag.DateTimeDigitized }, - { ExifTag.SubsecTime }, - { ExifTag.SubsecTimeOriginal }, - { ExifTag.SubsecTimeDigitized }, - { ExifTag.RelatedSoundFile }, - { ExifTag.FaxSubaddress }, - { ExifTag.OffsetTime }, - { ExifTag.OffsetTimeOriginal }, - { ExifTag.OffsetTimeDigitized }, - { ExifTag.SecurityClassification }, - { ExifTag.ImageHistory }, - { ExifTag.ImageUniqueID }, - { ExifTag.OwnerName }, - { ExifTag.SerialNumber }, - { ExifTag.LensMake }, - { ExifTag.LensModel }, - { ExifTag.LensSerialNumber }, - { ExifTag.GDALMetadata }, - { ExifTag.GDALNoData }, - { ExifTag.GPSLatitudeRef }, - { ExifTag.GPSLongitudeRef }, - { ExifTag.GPSSatellites }, - { ExifTag.GPSStatus }, - { ExifTag.GPSMeasureMode }, - { ExifTag.GPSSpeedRef }, - { ExifTag.GPSTrackRef }, - { ExifTag.GPSImgDirectionRef }, - { ExifTag.GPSMapDatum }, - { ExifTag.GPSDestLatitudeRef }, - { ExifTag.GPSDestLongitudeRef }, - { ExifTag.GPSDestBearingRef }, - { ExifTag.GPSDestDistanceRef }, - { ExifTag.GPSDateStamp }, + (ExifTag)ExifTag.ImageDescription, + (ExifTag)ExifTag.Make, + (ExifTag)ExifTag.Model, + (ExifTag)ExifTag.Software, + (ExifTag)ExifTag.DateTime, + (ExifTag)ExifTag.Artist, + (ExifTag)ExifTag.HostComputer, + (ExifTag)ExifTag.Copyright, + (ExifTag)ExifTag.DocumentName, + (ExifTag)ExifTag.PageName, + (ExifTag)ExifTag.InkNames, + (ExifTag)ExifTag.TargetPrinter, + (ExifTag)ExifTag.ImageID, + (ExifTag)ExifTag.MDLabName, + (ExifTag)ExifTag.MDSampleInfo, + (ExifTag)ExifTag.MDPrepDate, + (ExifTag)ExifTag.MDPrepTime, + (ExifTag)ExifTag.MDFileUnits, + (ExifTag)ExifTag.SEMInfo, + (ExifTag)ExifTag.SpectralSensitivity, + (ExifTag)ExifTag.DateTimeOriginal, + (ExifTag)ExifTag.DateTimeDigitized, + (ExifTag)ExifTag.SubsecTime, + (ExifTag)ExifTag.SubsecTimeOriginal, + (ExifTag)ExifTag.SubsecTimeDigitized, + (ExifTag)ExifTag.RelatedSoundFile, + (ExifTag)ExifTag.FaxSubaddress, + (ExifTag)ExifTag.OffsetTime, + (ExifTag)ExifTag.OffsetTimeOriginal, + (ExifTag)ExifTag.OffsetTimeDigitized, + (ExifTag)ExifTag.SecurityClassification, + (ExifTag)ExifTag.ImageHistory, + (ExifTag)ExifTag.ImageUniqueID, + (ExifTag)ExifTag.OwnerName, + (ExifTag)ExifTag.SerialNumber, + (ExifTag)ExifTag.LensMake, + (ExifTag)ExifTag.LensModel, + (ExifTag)ExifTag.LensSerialNumber, + (ExifTag)ExifTag.GDALMetadata, + (ExifTag)ExifTag.GDALNoData, + (ExifTag)ExifTag.GPSLatitudeRef, + (ExifTag)ExifTag.GPSLongitudeRef, + (ExifTag)ExifTag.GPSSatellites, + (ExifTag)ExifTag.GPSStatus, + (ExifTag)ExifTag.GPSMeasureMode, + (ExifTag)ExifTag.GPSSpeedRef, + (ExifTag)ExifTag.GPSTrackRef, + (ExifTag)ExifTag.GPSImgDirectionRef, + (ExifTag)ExifTag.GPSMapDatum, + (ExifTag)ExifTag.GPSDestLatitudeRef, + (ExifTag)ExifTag.GPSDestLongitudeRef, + (ExifTag)ExifTag.GPSDestBearingRef, + (ExifTag)ExifTag.GPSDestDistanceRef, + (ExifTag)ExifTag.GPSDateStamp, }; public static TheoryData UndefinedTags => new() { - { ExifTag.FileSource }, - { ExifTag.SceneType } + (ExifTag)ExifTag.FileSource, + (ExifTag)ExifTag.SceneType }; public static TheoryData UndefinedArrayTags => new() { - { ExifTag.JPEGTables }, - { ExifTag.OECF }, - { ExifTag.ExifVersion }, - { ExifTag.ComponentsConfiguration }, - { ExifTag.MakerNote }, - { ExifTag.FlashpixVersion }, - { ExifTag.SpatialFrequencyResponse }, - { ExifTag.SpatialFrequencyResponse2 }, - { ExifTag.Noise }, - { ExifTag.CFAPattern }, - { ExifTag.DeviceSettingDescription }, - { ExifTag.ImageSourceData }, + (ExifTag)ExifTag.JPEGTables, + (ExifTag)ExifTag.OECF, + (ExifTag)ExifTag.ExifVersion, + (ExifTag)ExifTag.ComponentsConfiguration, + (ExifTag)ExifTag.MakerNote, + (ExifTag)ExifTag.FlashpixVersion, + (ExifTag)ExifTag.SpatialFrequencyResponse, + (ExifTag)ExifTag.SpatialFrequencyResponse2, + (ExifTag)ExifTag.Noise, + (ExifTag)ExifTag.CFAPattern, + (ExifTag)ExifTag.DeviceSettingDescription, + (ExifTag)ExifTag.ImageSourceData, }; public static TheoryData EncodedStringTags => new() { - { ExifTag.UserComment }, - { ExifTag.GPSProcessingMethod }, - { ExifTag.GPSAreaInformation } + (ExifTag)ExifTag.UserComment, + (ExifTag)ExifTag.GPSProcessingMethod, + (ExifTag)ExifTag.GPSAreaInformation }; public static TheoryData Ucs2StringTags => new() { - { ExifTag.XPTitle }, - { ExifTag.XPComment }, - { ExifTag.XPAuthor }, - { ExifTag.XPKeywords }, - { ExifTag.XPSubject }, + (ExifTag)ExifTag.XPTitle, + (ExifTag)ExifTag.XPComment, + (ExifTag)ExifTag.XPAuthor, + (ExifTag)ExifTag.XPKeywords, + (ExifTag)ExifTag.XPSubject, }; [Theory] @@ -458,7 +458,7 @@ public class ExifValuesTests [MemberData(nameof(NumberArrayTags))] public void ExifNumberArrayTests(ExifTag tag) { - Number[] expected = [new Number(uint.MaxValue)]; + Number[] expected = [new(uint.MaxValue)]; ExifValue value = ExifValues.Create(tag); Assert.False(value.TrySetValue(expected.ToString())); @@ -496,7 +496,7 @@ public class ExifValuesTests [MemberData(nameof(RationalArrayTags))] public void ExifRationalArrayTests(ExifTag tag) { - Rational[] expected = [new Rational(21, 42)]; + Rational[] expected = [new(21, 42)]; ExifValue value = ExifValues.Create(tag); Assert.False(value.TrySetValue(expected.ToString())); @@ -554,7 +554,7 @@ public class ExifValuesTests [MemberData(nameof(SignedRationalArrayTags))] public void ExifSignedRationalArrayTests(ExifTag tag) { - SignedRational[] expected = [new SignedRational(21, 42)]; + SignedRational[] expected = [new(21, 42)]; ExifValue value = ExifValues.Create(tag); Assert.False(value.TrySetValue(expected.ToString())); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs index 0a9b888ed..534134147 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/DataWriter/IccDataWriterPrimitivesTests.cs @@ -42,7 +42,7 @@ public class IccDataWriterPrimitivesTests byte[] output = writer.GetData(); Assert.Equal(0, count); - Assert.Equal([], output); + Assert.Empty(output); } [Fact] @@ -62,7 +62,7 @@ public class IccDataWriterPrimitivesTests byte[] output = writer.GetData(); Assert.Equal(0, count); - Assert.Equal([], output); + Assert.Empty(output); } [Theory] diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccLutTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccLutTests.cs new file mode 100644 index 000000000..f6705a1d0 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/ICC/Various/IccLutTests.cs @@ -0,0 +1,108 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.ICC.Various; + +[Trait("Profile", "Icc")] +public class IccLutTests +{ + /// + /// Gets lengths that exercise scalar execution, every SIMD width, mixed-width remainders, and multiple vectors. + /// + public static TheoryData LutLengths => new() + { + 0, + 1, + 7, + 8, + 9, + 15, + 16, + 17, + 31, + 32, + 33, + 63, + 64, + 65, + 255, + 256, + 257 + }; + + /// + /// Verifies that byte LUT construction preserves the scalar conversion result for every traversal shape. + /// + /// The number of LUT entries. + [Theory] + [MemberData(nameof(LutLengths))] + public void ByteConstructorMatchesScalarFormula(int length) + { + byte[] values = new byte[length]; + + for (int i = 0; i < values.Length; i++) + { + values[i] = (byte)((i * 73) + 19); + } + + IccLut actual = new(values); + + Assert.Equal(values.Length, actual.Values.Length); + + for (int i = 0; i < values.Length; i++) + { + float expected = values[i] / (float)byte.MaxValue; + Assert.Equal(BitConverter.SingleToInt32Bits(expected), BitConverter.SingleToInt32Bits(actual.Values[i])); + } + } + + /// + /// Verifies that unsigned-short LUT construction preserves the scalar conversion result for every traversal shape. + /// + /// The number of LUT entries. + [Theory] + [MemberData(nameof(LutLengths))] + public void UInt16ConstructorMatchesScalarFormula(int length) + { + ushort[] values = new ushort[length]; + + for (int i = 0; i < values.Length; i++) + { + values[i] = (ushort)((i * 12_347) + 1_019); + } + + IccLut actual = new(values); + + Assert.Equal(values.Length, actual.Values.Length); + + for (int i = 0; i < values.Length; i++) + { + float expected = values[i] / (float)ushort.MaxValue; + Assert.Equal(BitConverter.SingleToInt32Bits(expected), BitConverter.SingleToInt32Bits(actual.Values[i])); + } + } + + /// + /// Verifies bit-exact normalization for every possible unsigned-short input. + /// + [Fact] + public void UInt16ConstructorMatchesScalarFormulaForEveryValue() + { + ushort[] values = new ushort[ushort.MaxValue + 1]; + + for (int i = 0; i < values.Length; i++) + { + values[i] = (ushort)i; + } + + IccLut actual = new(values); + + for (int i = 0; i < values.Length; i++) + { + float expected = values[i] / (float)ushort.MaxValue; + Assert.Equal(BitConverter.SingleToInt32Bits(expected), BitConverter.SingleToInt32Bits(actual.Values[i])); + } + } +} diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs index 2dbe5d343..37f99517c 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs @@ -116,7 +116,7 @@ public class IptcProfileTests using (Image image = provider.GetImage(JpegDecoder.Instance)) { Assert.NotNull(image.Metadata.IptcProfile); - List iptcValues = image.Metadata.IptcProfile.Values.ToList(); + List iptcValues = [.. image.Metadata.IptcProfile.Values]; IptcProfileContainsExpectedValues(iptcValues); } } @@ -130,7 +130,7 @@ public class IptcProfileTests { IptcProfile iptc = image.Frames.RootFrame.Metadata.IptcProfile; Assert.NotNull(iptc); - List iptcValues = iptc.Values.ToList(); + List iptcValues = [.. iptc.Values]; IptcProfileContainsExpectedValues(iptcValues); } } @@ -182,7 +182,7 @@ public class IptcProfileTests IptcProfile profileFromBytes = new(profileBytes); // assert - List iptcValues = profileFromBytes.Values.ToList(); + List iptcValues = [.. profileFromBytes.Values]; ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); } @@ -203,10 +203,10 @@ public class IptcProfileTests // assert Assert.Equal(2, clone.Values.Count()); - List cloneValues = clone.Values.ToList(); + List cloneValues = [.. clone.Values]; ContainsIptcValue(cloneValues, IptcTag.CaptionWriter, captionWriter); ContainsIptcValue(cloneValues, IptcTag.Caption, "changed"); - ContainsIptcValue(profile.Values.ToList(), IptcTag.Caption, caption); + ContainsIptcValue([.. profile.Values], IptcTag.Caption, caption); } [Fact] @@ -240,7 +240,7 @@ public class IptcProfileTests // assert IptcProfile actual = reloadedImage.Metadata.IptcProfile; Assert.NotNull(actual); - List iptcValues = actual.Values.ToList(); + List iptcValues = [.. actual.Values]; ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, expectedCaptionWriter); ContainsIptcValue(iptcValues, IptcTag.Caption, expectedCaption); } @@ -272,7 +272,7 @@ public class IptcProfileTests profile.SetValue(tag, expectedValue2, false); // assert - List values = profile.Values.ToList(); + List values = [.. profile.Values]; Assert.Equal(2, values.Count); ContainsIptcValue(values, tag, expectedValue1); ContainsIptcValue(values, tag, expectedValue2); @@ -323,7 +323,7 @@ public class IptcProfileTests profile.SetValue(tag, expectedValue, false); // assert - List values = profile.Values.ToList(); + List values = [.. profile.Values]; Assert.Equal(1, values.Count); ContainsIptcValue(values, tag, expectedValue); } @@ -357,7 +357,7 @@ public class IptcProfileTests // assert Assert.True(result, "removed result should be true"); - ContainsIptcValue(profile.Values.ToList(), IptcTag.Byline, "test"); + ContainsIptcValue([.. profile.Values], IptcTag.Byline, "test"); } [Fact] diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs index e121d24f9..32d4bc7cd 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs @@ -78,6 +78,34 @@ public class XmpProfileTests } } + [Fact] + public void XmpProfile_CtorFromXDocument_Works() + { + // arrange + XDocument document = CreateMinimalXDocument(); + + // act + XmpProfile profile = new(document); + + // assert + XmpProfileContainsExpectedValues(profile); + } + + [Fact] + public void XmpProfile_ToXDocument_ReturnsValidDocument() + { + // arrange + XmpProfile profile = CreateMinimalXmlProfile(); + + // act + XDocument document = profile.ToXDocument(); + + // assert + Assert.NotNull(document); + Assert.Equal("xmpmeta", document.Root.Name.LocalName); + Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName); + } + [Fact] public void XmpProfile_ToFromByteArray_ReturnsClone() { @@ -97,11 +125,11 @@ public class XmpProfileTests { // arrange XmpProfile profile = CreateMinimalXmlProfile(); - byte[] original = profile.ToByteArray(); + byte[] original = profile.Data; // act XmpProfile clone = profile.DeepClone(); - byte[] actual = clone.ToByteArray(); + byte[] actual = clone.Data; // assert Assert.False(ReferenceEquals(original, actual)); @@ -218,7 +246,7 @@ public class XmpProfileTests private static void XmpProfileContainsExpectedValues(XmpProfile xmp) { Assert.NotNull(xmp); - XDocument document = xmp.GetDocument(); + XDocument document = xmp.ToXDocument(); Assert.NotNull(document); Assert.Equal("xmpmeta", document.Root.Name.LocalName); Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName); @@ -232,6 +260,8 @@ public class XmpProfileTests return profile; } + private static XDocument CreateMinimalXDocument() => CreateMinimalXmlProfile().ToXDocument(); + private static Image WriteAndRead(Image image, IImageEncoder encoder) { using (MemoryStream memStream = new()) diff --git a/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs new file mode 100644 index 000000000..b3e5b434b --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/AssociatedAlphaPixelTests.cs @@ -0,0 +1,1364 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// Verifies the shared contract for pixel formats that store associated alpha. +/// +/// The associated-alpha pixel format. +[Trait("Category", "PixelFormats")] +public abstract class AssociatedAlphaPixelTests + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the color channels described by the pixel format. + /// + protected virtual PixelColorType ExpectedColorType => PixelColorType.RGB | PixelColorType.Alpha; + + [Fact] + public void PixelInformationDescribesAssociatedAlpha() + { + PixelTypeInfo info = TPixel.GetPixelTypeInfo(); + PixelComponentInfo componentInfo = info.ComponentInfo.Value; + int expectedComponentPrecision = (Unsafe.SizeOf() * 8) / 4; + + Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); + Assert.Equal(PixelAlphaRepresentation.Associated, info.AlphaRepresentation); + Assert.Equal(this.ExpectedColorType, info.ColorType); + Assert.Equal(4, componentInfo.ComponentCount); + Assert.Equal(0, componentInfo.Padding); + + for (int i = 0; i < componentInfo.ComponentCount; i++) + { + Assert.Equal(expectedComponentPrecision, componentInfo.GetComponentPrecision(i)); + } + } +} + +/// +/// Tests the pixel format. +/// +public class Rgba32PTests : AssociatedAlphaPixelTests +{ + [Fact] + public void ByteLayoutAndPackedValue() + { + Rgba32P[] pixels = [new(1, 2, 3, 4)]; + + Assert.Equal(new byte[] { 1, 2, 3, 4 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray()); + Assert.Equal(0x04030201U, pixels[0].PackedValue); + } +} + +/// +/// Tests the pixel format. +/// +public class Bgra32PTests : AssociatedAlphaPixelTests +{ + /// + protected override PixelColorType ExpectedColorType => PixelColorType.BGR | PixelColorType.Alpha; + + [Fact] + public void ByteLayoutAndPackedValue() + { + Bgra32P[] pixels = [new(1, 2, 3, 4)]; + + Assert.Equal(new byte[] { 3, 2, 1, 4 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray()); + Assert.Equal(0x04010203U, pixels[0].PackedValue); + } +} + +/// +/// Tests the pixel format. +/// +public class Argb32PTests : AssociatedAlphaPixelTests +{ + [Fact] + public void ByteLayoutAndPackedValue() + { + Argb32P[] pixels = [new(1, 2, 3, 4)]; + + Assert.Equal(new byte[] { 4, 1, 2, 3 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray()); + Assert.Equal(0x03020104U, pixels[0].PackedValue); + } +} + +/// +/// Tests the pixel format. +/// +public class Abgr32PTests : AssociatedAlphaPixelTests +{ + /// + protected override PixelColorType ExpectedColorType => PixelColorType.BGR | PixelColorType.Alpha; + + [Fact] + public void ByteLayoutAndPackedValue() + { + Abgr32P[] pixels = [new(1, 2, 3, 4)]; + + Assert.Equal(new byte[] { 4, 3, 2, 1 }, MemoryMarshal.AsBytes(pixels.AsSpan()).ToArray()); + Assert.Equal(0x01020304U, pixels[0].PackedValue); + } +} + +/// +/// Tests the pixel format. +/// +public class NormalizedByte4PTests : AssociatedAlphaPixelTests +{ + [Fact] + public void PackedValueMatchesNormalizedByte4ForAssociatedVector() + { + Vector4 associated = new(.1F, -.3F, .5F, -.7F); + + Assert.Equal(new NormalizedByte4(associated).PackedValue, new NormalizedByte4P(associated).PackedValue); + } + + [Fact] + public void MinimumStorageCodeDecodesAsNegativeOne() + { + NormalizedByte4P pixel = new() { PackedValue = 0x80808080 }; + + Assert.Equal(-Vector4.One, pixel.ToVector4()); + Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); + Assert.Equal(-Vector4.One, pixel.ToUnassociatedVector4()); + Assert.Equal(Vector4.Zero, pixel.ToUnassociatedScaledVector4()); + } + + [Fact] + public void AssociatedScaledVectorsMatchUnassociatedVectorsWithinTwoUlpsForEveryValidComponentAndAlpha() + { + for (int alpha = 0; alpha < byte.MaxValue; alpha++) + { + for (int associated = 0; associated <= alpha; associated++) + { + uint component = (byte)(associated - 127); + uint alphaComponent = (byte)(alpha - 127); + NormalizedByte4P pixel = new() { PackedValue = component | (component << 8) | (component << 16) | (alphaComponent << 24) }; + Vector4 expected = pixel.ToUnassociatedScaledVector4(); + expected.X *= expected.W; + expected.Y *= expected.W; + expected.Z *= expected.W; + Vector4 actual = pixel.ToAssociatedScaledVector4(); + + // All 32,640 valid storage pairs are covered. Two ULP is the measured maximum from the independently ordered division and multiplication. + AlphaRepresentationTestAssertions.EqualWithinTwoUlps(expected, actual); + Assert.Equal(BitConverter.SingleToInt32Bits(expected.W), BitConverter.SingleToInt32Bits(actual.W)); + } + } + } + + [Fact] + public void BulkConversionsMatchScalarAcrossHardwareWidths() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertBulkConversionsMatchScalar, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertBulkConversionsMatchScalar() + { + // Values immediately below, at, and above each vector width force every hardware regime through its vector body and scalar tail. + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + foreach (int length in lengths) + { + NormalizedByte4P[] pixels = new NormalizedByte4P[length]; + Vector4[] expectedNative = new Vector4[length]; + Vector4[] expectedAssociatedNative = new Vector4[length]; + Vector4[] expectedAssociatedScaled = new Vector4[length]; + Vector4[] expectedUnassociatedNative = new Vector4[length]; + Vector4[] expectedUnassociatedScaled = new Vector4[length]; + Vector4[] actualNative = new Vector4[length]; + Vector4[] actualAssociatedNative = new Vector4[length]; + Vector4[] actualAssociatedScaled = new Vector4[length]; + Vector4[] actualUnassociatedNative = new Vector4[length]; + Vector4[] actualUnassociatedScaled = new Vector4[length]; + + for (int i = 0; i < length; i++) + { + pixels[i].PackedValue = (uint)((byte)(i * 37 + 128) | ((byte)(i * 73 + 64) << 8) | ((byte)(i * 109 + 192) << 16) | ((byte)(i * 151 + 128) << 24)); + expectedNative[i] = pixels[i].ToVector4(); + expectedAssociatedNative[i] = pixels[i].ToAssociatedVector4(); + expectedAssociatedScaled[i] = pixels[i].ToAssociatedScaledVector4(); + expectedUnassociatedNative[i] = pixels[i].ToUnassociatedVector4(); + expectedUnassociatedScaled[i] = pixels[i].ToUnassociatedScaledVector4(); + } + + if (length > 0) + { + // PackedValue permits every signed byte pattern. Explicit -128 components ensure bulk sign extension matches scalar decoding even though the packer emits no -128 values. + pixels[0].PackedValue = 0x80808080; + expectedNative[0] = pixels[0].ToVector4(); + expectedAssociatedNative[0] = pixels[0].ToAssociatedVector4(); + expectedAssociatedScaled[0] = pixels[0].ToAssociatedScaledVector4(); + expectedUnassociatedNative[0] = pixels[0].ToUnassociatedVector4(); + expectedUnassociatedScaled[0] = pixels[0].ToUnassociatedScaledVector4(); + } + + operations.ToVector4(Configuration.Default, pixels, actualNative, PixelConversionModifiers.None); + operations.ToVector4(Configuration.Default, pixels, actualAssociatedNative, PixelConversionModifiers.Premultiply); + operations.ToVector4(Configuration.Default, pixels, actualAssociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + operations.ToVector4(Configuration.Default, pixels, actualUnassociatedNative, PixelConversionModifiers.UnPremultiply); + operations.ToVector4(Configuration.Default, pixels, actualUnassociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + Assert.Equal(expectedNative, actualNative); + Assert.Equal(expectedAssociatedNative, actualAssociatedNative); + Assert.Equal(expectedAssociatedScaled, actualAssociatedScaled); + Assert.Equal(expectedUnassociatedNative, actualUnassociatedNative); + Assert.Equal(expectedUnassociatedScaled, actualUnassociatedScaled); + + Vector4[] unassociatedScaled = new Vector4[length]; + Vector4[] associatedScaled = new Vector4[length]; + Vector4[] unassociatedNative = new Vector4[length]; + Vector4[] associatedNative = new Vector4[length]; + NormalizedByte4P[] expectedFromUnassociatedNative = new NormalizedByte4P[length]; + NormalizedByte4P[] expectedFromAssociatedNative = new NormalizedByte4P[length]; + NormalizedByte4P[] expectedFromUnassociatedScaled = new NormalizedByte4P[length]; + NormalizedByte4P[] expectedFromAssociatedScaled = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromUnassociatedNative = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromAssociatedNative = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromUnassociatedScaled = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromAssociatedScaled = new NormalizedByte4P[length]; + + for (int i = 0; i < length; i++) + { + float alpha = ((i * 151) % 1021) / 1020F; + unassociatedScaled[i] = new Vector4(((i * 37) % 1021) / 1020F, ((i * 73) % 1021) / 1020F, ((i * 109) % 1021) / 1020F, alpha); + associatedScaled[i] = new Vector4(unassociatedScaled[i].X * alpha, unassociatedScaled[i].Y * alpha, unassociatedScaled[i].Z * alpha, alpha); + + // Signed-native formats encode the common scaled domain over [-1, 1], so native inputs must be derived before invoking the native entry points. + unassociatedNative[i] = (unassociatedScaled[i] * 2F) - Vector4.One; + associatedNative[i] = (associatedScaled[i] * 2F) - Vector4.One; + expectedFromUnassociatedNative[i] = NormalizedByte4P.FromUnassociatedVector4(unassociatedNative[i]); + expectedFromAssociatedNative[i] = NormalizedByte4P.FromAssociatedVector4(associatedNative[i]); + expectedFromUnassociatedScaled[i] = NormalizedByte4P.FromUnassociatedScaledVector4(unassociatedScaled[i]); + expectedFromAssociatedScaled[i] = NormalizedByte4P.FromAssociatedScaledVector4(associatedScaled[i]); + } + + Vector4[] unassociatedNativeSource = [.. unassociatedNative]; + Vector4[] associatedNativeSource = [.. associatedNative]; + Vector4[] unassociatedScaledSource = [.. unassociatedScaled]; + Vector4[] associatedScaledSource = [.. associatedScaled]; + + operations.FromVector4Destructive(Configuration.Default, unassociatedNativeSource, actualFromUnassociatedNative, PixelConversionModifiers.UnPremultiply); + operations.FromVector4Destructive(Configuration.Default, associatedNativeSource, actualFromAssociatedNative, PixelConversionModifiers.Premultiply); + operations.FromVector4Destructive(Configuration.Default, unassociatedScaledSource, actualFromUnassociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + operations.FromVector4Destructive(Configuration.Default, associatedScaledSource, actualFromAssociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + + Assert.Equal(expectedFromUnassociatedNative, actualFromUnassociatedNative); + Assert.Equal(expectedFromAssociatedNative, actualFromAssociatedNative); + Assert.Equal(expectedFromUnassociatedScaled, actualFromUnassociatedScaled); + Assert.Equal(expectedFromAssociatedScaled, actualFromAssociatedScaled); + + NormalizedByte4P[] expectedFromNative = new NormalizedByte4P[length]; + NormalizedByte4P[] expectedFromScaled = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromNative = new NormalizedByte4P[length]; + NormalizedByte4P[] actualFromScaled = new NormalizedByte4P[length]; + + for (int i = 0; i < length; i++) + { + expectedFromNative[i] = NormalizedByte4P.FromVector4(expectedNative[i]); + expectedFromScaled[i] = NormalizedByte4P.FromScaledVector4(expectedAssociatedScaled[i]); + } + + Vector4[] actualNativeSource = [.. expectedNative]; + Vector4[] actualScaledSource = [.. expectedAssociatedScaled]; + + operations.FromVector4Destructive(Configuration.Default, actualNativeSource, actualFromNative, PixelConversionModifiers.None); + operations.FromVector4Destructive(Configuration.Default, actualScaledSource, actualFromScaled, PixelConversionModifiers.Scale); + + Assert.Equal(expectedFromNative, actualFromNative); + Assert.Equal(expectedFromScaled, actualFromScaled); + } + } +} + +/// +/// Tests the pixel format. +/// +public class HalfVector4PTests : AssociatedAlphaPixelTests +{ + [Fact] + public void PackedValueMatchesHalfVector4ForAssociatedVector() + { + Vector4 associated = new(.1F, -.3F, .5F, -.7F); + + Assert.Equal(new HalfVector4(associated).PackedValue, new HalfVector4P(associated).PackedValue); + } + + [Fact] + public void ColorRoundTripDoesNotIntroduceAdditionalAssociationLoss() + { + HalfVector4P source = HalfVector4P.FromAssociatedScaledVector4(new Vector4(.125F, .25F, .375F, .5F)); + HalfVector4P expected = HalfVector4P.FromScaledVector4(source.ToScaledVector4()); + Color color = Color.FromPixel(source); + Color[] bulkColors = new Color[1]; + + Color.FromPixel(new[] { source }, bulkColors); + + Assert.Equal(source.ToScaledVector4(), color.ToScaledVector4()); + Assert.Equal(expected, color.ToPixel()); + Assert.Equal(source.ToScaledVector4(), bulkColors[0].ToScaledVector4()); + Assert.Equal(expected, bulkColors[0].ToPixel()); + } + + [Fact] + public void BulkConversionsMatchScalarAcrossHardwareWidths() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertBulkConversionsMatchScalar, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertBulkConversionsMatchScalar() + { + AssertEveryHalfBitPatternUnpacksExactly(); + AssertHalfPackingMatchesScalar(); + AssertAlphaConversionsMatchScalar(); + } + + private static void AssertEveryHalfBitPatternUnpacksExactly() + { + HalfVector4P[] pixels = new HalfVector4P[(ushort.MaxValue + 1) / 4]; + Vector4[] expectedNative = new Vector4[pixels.Length]; + Vector4[] expectedScaled = new Vector4[pixels.Length]; + Vector4[] actualNative = new Vector4[pixels.Length]; + Vector4[] actualScaled = new Vector4[pixels.Length]; + + for (int i = 0; i < pixels.Length; i++) + { + ulong x = (ushort)(i * 4); + ulong y = (ushort)((i * 4) + 1); + ulong z = (ushort)((i * 4) + 2); + ulong w = (ushort)((i * 4) + 3); + pixels[i].PackedValue = x | (y << 16) | (z << 32) | (w << 48); + expectedNative[i] = pixels[i].ToVector4(); + expectedScaled[i] = pixels[i].ToScaledVector4(); + } + + PixelOperations.Instance.ToVector4(Configuration.Default, pixels, actualNative, PixelConversionModifiers.None); + PixelOperations.Instance.ToVector4(Configuration.Default, pixels, actualScaled, PixelConversionModifiers.Scale); + + AssertVectorBitsEqual(expectedNative, actualNative); + AssertVectorBitsEqual(expectedScaled, actualScaled); + } + + private static void AssertHalfPackingMatchesScalar() + { + const int finitePositiveHalfCount = 0x7BFF; + const int distributedFloatCount = ushort.MaxValue + 1; + const int componentCount = (ushort.MaxValue + 1) + (finitePositiveHalfCount * 2) + distributedFloatCount + 2; + Vector4[] source = new Vector4[componentCount / 4]; + Span components = MemoryMarshal.Cast(source.AsSpan()); + int index = 0; + + for (int bits = 0; bits <= ushort.MaxValue; bits++) + { + components[index++] = (float)BitConverter.UInt16BitsToHalf((ushort)bits); + } + + for (int bits = 0; bits < finitePositiveHalfCount; bits++) + { + float lower = (float)BitConverter.UInt16BitsToHalf((ushort)bits); + float upper = (float)BitConverter.UInt16BitsToHalf((ushort)(bits + 1)); + + // Every midpoint exercises binary16 round-to-nearest-even; negating it covers the symmetric sign path. + float midpoint = (lower + upper) * .5F; + components[index++] = midpoint; + components[index++] = -midpoint; + } + + // Equal high and low words distribute samples across the complete binary32 sign, exponent, and fraction space. + for (uint bits = 0; bits <= ushort.MaxValue; bits++) + { + components[index++] = BitConverter.UInt32BitsToSingle(bits * 0x0001_0001U); + } + + // The two padding components also verify finite overflow in both directions. + components[index++] = float.MaxValue; + components[index] = float.MinValue; + + HalfVector4P[] expected = new HalfVector4P[source.Length]; + HalfVector4P[] actual = new HalfVector4P[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + expected[i] = HalfVector4P.FromVector4(source[i]); + } + + Vector4[] destructiveSource = [.. source]; + + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actual, PixelConversionModifiers.None); + + for (int i = 0; i < expected.Length; i++) + { + for (int component = 0; component < 4; component++) + { + int shift = component * 16; + ushort expectedBits = (ushort)(expected[i].PackedValue >> shift); + ushort actualBits = (ushort)(actual[i].PackedValue >> shift); + + // Association performs floating-point arithmetic before packing. IEEE 754 requires a NaN result but does not specify which operand's payload survives that arithmetic. + if (Half.IsNaN(BitConverter.UInt16BitsToHalf(expectedBits))) + { + Assert.True(Half.IsNaN(BitConverter.UInt16BitsToHalf(actualBits)), $"Pixel {i}, component {component} should be NaN."); + } + else + { + Assert.Equal(expectedBits, actualBits); + } + } + } + } + + private static void AssertAlphaConversionsMatchScalar() + { + // Values immediately below, at, and above each vector width force every hardware regime through its vector body and scalar tail. + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + foreach (int length in lengths) + { + Vector4[] unassociatedScaled = new Vector4[length]; + Vector4[] associatedScaled = new Vector4[length]; + Vector4[] unassociatedNative = new Vector4[length]; + Vector4[] associatedNative = new Vector4[length]; + HalfVector4P[] expectedFromUnassociatedNative = new HalfVector4P[length]; + HalfVector4P[] expectedFromAssociatedNative = new HalfVector4P[length]; + HalfVector4P[] expectedFromUnassociatedScaled = new HalfVector4P[length]; + HalfVector4P[] expectedFromAssociatedScaled = new HalfVector4P[length]; + HalfVector4P[] actualFromUnassociatedNative = new HalfVector4P[length]; + HalfVector4P[] actualFromAssociatedNative = new HalfVector4P[length]; + HalfVector4P[] actualFromUnassociatedScaled = new HalfVector4P[length]; + HalfVector4P[] actualFromAssociatedScaled = new HalfVector4P[length]; + + for (int i = 0; i < length; i++) + { + float alpha = ((i * 151) % 4093) / 4092F; + unassociatedScaled[i] = new Vector4(((i * 37) % 4093) / 4092F, ((i * 73) % 4093) / 4092F, ((i * 109) % 4093) / 4092F, alpha); + associatedScaled[i] = new Vector4(unassociatedScaled[i].X * alpha, unassociatedScaled[i].Y * alpha, unassociatedScaled[i].Z * alpha, alpha); + + unassociatedNative[i] = (unassociatedScaled[i] * 131008F) - new Vector4(65504F); + associatedNative[i] = (associatedScaled[i] * 131008F) - new Vector4(65504F); + expectedFromUnassociatedNative[i] = HalfVector4P.FromUnassociatedVector4(unassociatedNative[i]); + expectedFromAssociatedNative[i] = HalfVector4P.FromAssociatedVector4(associatedNative[i]); + expectedFromUnassociatedScaled[i] = HalfVector4P.FromUnassociatedScaledVector4(unassociatedScaled[i]); + expectedFromAssociatedScaled[i] = HalfVector4P.FromAssociatedScaledVector4(associatedScaled[i]); + } + + Vector4[] unassociatedNativeSource = [.. unassociatedNative]; + Vector4[] associatedNativeSource = [.. associatedNative]; + Vector4[] unassociatedScaledSource = [.. unassociatedScaled]; + Vector4[] associatedScaledSource = [.. associatedScaled]; + + operations.FromVector4Destructive(Configuration.Default, unassociatedNativeSource, actualFromUnassociatedNative, PixelConversionModifiers.UnPremultiply); + operations.FromVector4Destructive(Configuration.Default, associatedNativeSource, actualFromAssociatedNative, PixelConversionModifiers.Premultiply); + operations.FromVector4Destructive(Configuration.Default, unassociatedScaledSource, actualFromUnassociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + operations.FromVector4Destructive(Configuration.Default, associatedScaledSource, actualFromAssociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + + Assert.Equal(expectedFromUnassociatedNative, actualFromUnassociatedNative); + Assert.Equal(expectedFromAssociatedNative, actualFromAssociatedNative); + Assert.Equal(expectedFromUnassociatedScaled, actualFromUnassociatedScaled); + Assert.Equal(expectedFromAssociatedScaled, actualFromAssociatedScaled); + + Vector4[] expectedAssociatedNative = new Vector4[length]; + Vector4[] expectedAssociatedScaled = new Vector4[length]; + Vector4[] expectedUnassociatedNative = new Vector4[length]; + Vector4[] expectedUnassociatedScaled = new Vector4[length]; + Vector4[] actualAssociatedNative = new Vector4[length]; + Vector4[] actualAssociatedScaled = new Vector4[length]; + Vector4[] actualUnassociatedNative = new Vector4[length]; + Vector4[] actualUnassociatedScaled = new Vector4[length]; + + for (int i = 0; i < length; i++) + { + expectedAssociatedNative[i] = expectedFromAssociatedScaled[i].ToAssociatedVector4(); + expectedAssociatedScaled[i] = expectedFromAssociatedScaled[i].ToAssociatedScaledVector4(); + expectedUnassociatedNative[i] = expectedFromAssociatedScaled[i].ToUnassociatedVector4(); + expectedUnassociatedScaled[i] = expectedFromAssociatedScaled[i].ToUnassociatedScaledVector4(); + } + + operations.ToVector4(Configuration.Default, expectedFromAssociatedScaled, actualAssociatedNative, PixelConversionModifiers.Premultiply); + operations.ToVector4(Configuration.Default, expectedFromAssociatedScaled, actualAssociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + operations.ToVector4(Configuration.Default, expectedFromAssociatedScaled, actualUnassociatedNative, PixelConversionModifiers.UnPremultiply); + operations.ToVector4(Configuration.Default, expectedFromAssociatedScaled, actualUnassociatedScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + AssertVectorBitsEqual(expectedAssociatedNative, actualAssociatedNative); + AssertVectorBitsEqual(expectedAssociatedScaled, actualAssociatedScaled); + AssertVectorBitsEqual(expectedUnassociatedNative, actualUnassociatedNative); + AssertVectorBitsEqual(expectedUnassociatedScaled, actualUnassociatedScaled); + } + } + + private static void AssertVectorBitsEqual(ReadOnlySpan expected, ReadOnlySpan actual) + { + ReadOnlySpan expectedBits = MemoryMarshal.Cast(expected); + ReadOnlySpan actualBits = MemoryMarshal.Cast(actual); + + Assert.Equal(expectedBits.ToArray(), actualBits.ToArray()); + } +} + +/// +/// Tests conversion between associated-alpha packed byte layouts. +/// +public class AssociatedAlphaPackedPixelConversionTests +{ + [Fact] + public void Rgba32PToBgra32PRoundTripIsLossless() => AssertLosslessRoundTrip(); + + [Fact] + public void Rgba32PToArgb32PRoundTripIsLossless() => AssertLosslessRoundTrip(); + + [Fact] + public void Rgba32PToAbgr32PRoundTripIsLossless() => AssertLosslessRoundTrip(); + + [Fact] + public void Rgba32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Rgba32P(r, g, b, a)); + + [Fact] + public void Bgra32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Bgra32P(r, g, b, a)); + + [Fact] + public void Argb32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Argb32P(r, g, b, a)); + + [Fact] + public void Abgr32PScalarAndBulkAssociatedVectorsAreEqual() => AssertScalarAndBulkAssociatedVectorsAreEqual((r, g, b, a) => new Abgr32P(r, g, b, a)); + + [Fact] + public void Rgba32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual(); + + [Fact] + public void Bgra32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual(); + + [Fact] + public void Argb32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual(); + + [Fact] + public void Abgr32PScalarAndBulkFromAssociatedVectorsAreEqual() => AssertScalarAndBulkFromAssociatedVectorsAreEqual(); + + [Fact] + public void Rgba32PScalarAndBulkUnassociatedVectorsAreEqual() => AssertScalarAndBulkUnassociatedVectorsAreEqual((r, g, b, a) => new Rgba32P(r, g, b, a)); + + [Fact] + public void Bgra32PScalarAndBulkUnassociatedVectorsAreEqual() => AssertScalarAndBulkUnassociatedVectorsAreEqual((r, g, b, a) => new Bgra32P(r, g, b, a)); + + [Fact] + public void Argb32PScalarAndBulkUnassociatedVectorsAreEqual() => AssertScalarAndBulkUnassociatedVectorsAreEqual((r, g, b, a) => new Argb32P(r, g, b, a)); + + [Fact] + public void Abgr32PScalarAndBulkUnassociatedVectorsAreEqual() => AssertScalarAndBulkUnassociatedVectorsAreEqual((r, g, b, a) => new Abgr32P(r, g, b, a)); + + [Fact] + public void Rgba32PScalarAndBulkFromUnassociatedVectorsAreEqual() => AssertScalarAndBulkFromUnassociatedVectorsAreEqual(); + + [Fact] + public void Bgra32PScalarAndBulkFromUnassociatedVectorsAreEqual() => AssertScalarAndBulkFromUnassociatedVectorsAreEqual(); + + [Fact] + public void Argb32PScalarAndBulkFromUnassociatedVectorsAreEqual() => AssertScalarAndBulkFromUnassociatedVectorsAreEqual(); + + [Fact] + public void Abgr32PScalarAndBulkFromUnassociatedVectorsAreEqual() => AssertScalarAndBulkFromUnassociatedVectorsAreEqual(); + + [Fact] + public void BulkConversionsMatchScalarAcrossHardwareWidths() + { + // RemoteExecutor resolves the delegate by method name alone, so its entry point must not share the generic worker's name. + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertPackedBulkConversionsMatchScalar, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + } + + private static void AssertPackedBulkConversionsMatchScalar() + { + AssertBulkConversionsMatchScalar((r, g, b, a) => new Rgba32P(r, g, b, a)); + AssertBulkConversionsMatchScalar((r, g, b, a) => new Bgra32P(r, g, b, a)); + AssertBulkConversionsMatchScalar((r, g, b, a) => new Argb32P(r, g, b, a)); + AssertBulkConversionsMatchScalar((r, g, b, a) => new Abgr32P(r, g, b, a)); + } + + private static void AssertBulkConversionsMatchScalar(Func createPixel) + where TPixel : unmanaged, IPixel + { + AssertScalarAndBulkAssociatedVectorsAreEqual(createPixel); + AssertScalarAndBulkFromAssociatedVectorsAreEqual(); + AssertScalarAndBulkUnassociatedVectorsAreEqual(createPixel); + AssertScalarAndBulkFromUnassociatedVectorsAreEqual(); + } + + private static void AssertLosslessRoundTrip() + where TIntermediate : unmanaged, IPixel + { + const int componentAlphaPairCount = ((byte.MaxValue + 1) * (byte.MaxValue + 2)) / 2; + const int colorChannelCount = 3; + Rgba32P[] expected = new Rgba32P[componentAlphaPairCount * colorChannelCount]; + TIntermediate[] intermediate = new TIntermediate[expected.Length]; + Rgba32P[] actual = new Rgba32P[expected.Length]; + int index = 0; + + // Every component/alpha pair in the valid associated-byte domain must survive a layout + // conversion exactly. Exercising each color lane also catches an incorrect channel map. + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + for (int component = 0; component <= alpha; component++) + { + expected[index++] = new Rgba32P((byte)component, 0, 0, (byte)alpha); + expected[index++] = new Rgba32P(0, (byte)component, 0, (byte)alpha); + expected[index++] = new Rgba32P(0, 0, (byte)component, (byte)alpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, expected, intermediate); + PixelOperations.Instance.From(Configuration.Default, intermediate, actual); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i].ToScaledVector4(), intermediate[i].ToScaledVector4()); + } + + Assert.Equal(expected, actual); + } + + private static void AssertScalarAndBulkAssociatedVectorsAreEqual(Func createPixel) + where TPixel : unmanaged, IPixel + { + // Prefixes straddle every vector width; the final length also preserves exhaustive byte-component coverage and adds a scalar tail. + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259, byte.MaxValue * 4 + 5]; + TPixel[] source = new TPixel[lengths[^1]]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + for (int component = 0; component <= byte.MaxValue; component++) + { + int index = component * 4; + source[index] = createPixel((byte)component, 0, 0, byte.MaxValue); + source[index + 1] = createPixel(0, (byte)component, 0, byte.MaxValue); + source[index + 2] = createPixel(0, 0, (byte)component, byte.MaxValue); + source[index + 3] = createPixel(0, 0, 0, (byte)component); + } + + source[^1] = createPixel(37, 73, 109, 151); + + foreach (int length in lengths) + { + ReadOnlySpan pixels = source.AsSpan(0, length); + Vector4[] expectedNative = new Vector4[length]; + Vector4[] expectedScaled = new Vector4[length]; + Vector4[] actualNative = new Vector4[length]; + Vector4[] actualScaled = new Vector4[length]; + + for (int i = 0; i < pixels.Length; i++) + { + expectedNative[i] = pixels[i].ToAssociatedVector4(); + expectedScaled[i] = pixels[i].ToAssociatedScaledVector4(); + } + + operations.ToVector4(Configuration.Default, pixels, actualNative, PixelConversionModifiers.Premultiply); + operations.ToVector4(Configuration.Default, pixels, actualScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + + Assert.Equal(expectedNative, actualNative); + Assert.Equal(expectedScaled, actualScaled); + } + } + + private static void AssertScalarAndBulkFromAssociatedVectorsAreEqual() + where TPixel : unmanaged, IPixel + { + // Values immediately below, at, and above each vector width force every hardware regime through its vector body and scalar tail. + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + Vector4[] source = new Vector4[lengths[^1]]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + for (int i = 0; i < source.Length; i++) + { + // Alternating exact byte alpha values with fractional values covers both reassociation branches. + float alpha = (i & 1) == 0 ? (i % 256) / 255F : ((i % 255) + .375F) / 255F; + source[i] = new Vector4(((i * 37) % 256) / 255F, ((i * 73) % 256) / 255F, ((i * 109) % 256) / 255F, 1F) * alpha; + source[i].W = alpha; + } + + foreach (int length in lengths) + { + Vector4[] nativeSource = [.. source.AsSpan(0, length)]; + Vector4[] scaledSource = [.. source.AsSpan(0, length)]; + TPixel[] expectedNative = new TPixel[length]; + TPixel[] expectedScaled = new TPixel[length]; + TPixel[] actualNative = new TPixel[length]; + TPixel[] actualScaled = new TPixel[length]; + + for (int i = 0; i < length; i++) + { + expectedNative[i] = TPixel.FromAssociatedVector4(nativeSource[i]); + expectedScaled[i] = TPixel.FromAssociatedScaledVector4(scaledSource[i]); + } + + operations.FromVector4Destructive(Configuration.Default, nativeSource, actualNative, PixelConversionModifiers.Premultiply); + operations.FromVector4Destructive(Configuration.Default, scaledSource, actualScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply); + + Assert.Equal(expectedNative, actualNative); + Assert.Equal(expectedScaled, actualScaled); + } + } + + private static void AssertScalarAndBulkUnassociatedVectorsAreEqual(Func createPixel) + where TPixel : unmanaged, IPixel + { + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + foreach (int length in lengths) + { + TPixel[] pixels = new TPixel[length]; + Vector4[] expectedNative = new Vector4[length]; + Vector4[] expectedScaled = new Vector4[length]; + Vector4[] actualNative = new Vector4[length]; + Vector4[] actualScaled = new Vector4[length]; + + for (int i = 0; i < pixels.Length; i++) + { + // Independent component sequences exercise every packed layout across each SIMD width and remainder. + pixels[i] = createPixel((byte)((i * 37) % 256), (byte)((i * 73) % 256), (byte)((i * 109) % 256), (byte)((i * 151) % 256)); + expectedNative[i] = pixels[i].ToUnassociatedVector4(); + expectedScaled[i] = pixels[i].ToUnassociatedScaledVector4(); + } + + if (length > 0) + { + // A zero-alpha pixel with stored RGB verifies the converter's defined zero-divisor behavior outside the lossless straight-color round-trip domain. + pixels[0] = createPixel(37, 73, 109, 0); + expectedNative[0] = pixels[0].ToUnassociatedVector4(); + expectedScaled[0] = pixels[0].ToUnassociatedScaledVector4(); + } + + operations.ToVector4(Configuration.Default, pixels, actualNative, PixelConversionModifiers.UnPremultiply); + operations.ToVector4(Configuration.Default, pixels, actualScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + Assert.Equal(expectedNative, actualNative); + Assert.Equal(expectedScaled, actualScaled); + } + } + + private static void AssertScalarAndBulkFromUnassociatedVectorsAreEqual() + where TPixel : unmanaged, IPixel + { + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + AssociatedAlphaPixelOperations operations = (AssociatedAlphaPixelOperations)PixelOperations.Instance; + + foreach (int length in lengths) + { + Vector4[] vectors = new Vector4[length]; + TPixel[] expectedNative = new TPixel[length]; + TPixel[] expectedScaled = new TPixel[length]; + TPixel[] actualNative = new TPixel[length]; + TPixel[] actualScaled = new TPixel[length]; + + for (int i = 0; i < vectors.Length; i++) + { + // Fractional alpha values verify destination-alpha quantization across each SIMD width and remainder. + vectors[i] = new Vector4(((i * 37) % 1021) / 1020F, ((i * 73) % 1021) / 1020F, ((i * 109) % 1021) / 1020F, ((i * 151) % 1021) / 1020F); + expectedNative[i] = TPixel.FromUnassociatedVector4(vectors[i]); + expectedScaled[i] = TPixel.FromUnassociatedScaledVector4(vectors[i]); + } + + Vector4[] nativeSource = [.. vectors]; + Vector4[] scaledSource = [.. vectors]; + + operations.FromVector4Destructive(Configuration.Default, nativeSource, actualNative, PixelConversionModifiers.UnPremultiply); + operations.FromVector4Destructive(Configuration.Default, scaledSource, actualScaled, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + Assert.Equal(expectedNative, actualNative); + Assert.Equal(expectedScaled, actualScaled); + } + } +} + +/// +/// Tests conversion between associated and unassociated packed byte formats. +/// +public class AssociatedToUnassociatedPackedPixelConversionTests +{ + [Fact] + public void Rgba32ToRgba32PMatchesExactAssociationForEveryComponentAndAlpha() + => AssertUnsignedByteAssociation((red, green, blue, alpha) => new Rgba32P(red, green, blue, alpha)); + + [Fact] + public void Rgba32ToBgra32PMatchesExactAssociationForEveryComponentAndAlpha() + => AssertUnsignedByteAssociation((red, green, blue, alpha) => new Bgra32P(red, green, blue, alpha)); + + [Fact] + public void Rgba32ToArgb32PMatchesExactAssociationForEveryComponentAndAlpha() + => AssertUnsignedByteAssociation((red, green, blue, alpha) => new Argb32P(red, green, blue, alpha)); + + [Fact] + public void Rgba32ToAbgr32PMatchesExactAssociationForEveryComponentAndAlpha() + => AssertUnsignedByteAssociation((red, green, blue, alpha) => new Abgr32P(red, green, blue, alpha)); + + [Fact] + public void Rgba32ToNormalizedByte4PMatchesExactAssociationForEveryComponentAndAlpha() + { + const int pairCount = 65536; + const int channelCount = 3; + Rgba32[] source = new Rgba32[pairCount * channelCount]; + NormalizedByte4P[] expected = new NormalizedByte4P[source.Length]; + NormalizedByte4P[] actualBulk = new NormalizedByte4P[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + // NormalizedByte4 has 254 intervals from -1 through 1, so alpha is first rounded to that destination grid. + int destinationAlpha = ((alpha * 254) + 127) / byte.MaxValue; + + for (int unassociated = 0; unassociated <= byte.MaxValue; unassociated++) + { + // Association uses the alpha representable by the destination, then rounds the result to the same 254-interval grid. + int associated = ((unassociated * destinationAlpha) + 127) / byte.MaxValue; + + source[index] = new Rgba32((byte)unassociated, 0, 0, (byte)alpha); + expected[index++] = CreateNormalizedByte4P(associated, 0, 0, destinationAlpha); + source[index] = new Rgba32(0, (byte)unassociated, 0, (byte)alpha); + expected[index++] = CreateNormalizedByte4P(0, associated, 0, destinationAlpha); + source[index] = new Rgba32(0, 0, (byte)unassociated, (byte)alpha); + expected[index++] = CreateNormalizedByte4P(0, 0, associated, destinationAlpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualBulk); + + Assert.Equal(expected, actualBulk); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(expected[i], NormalizedByte4P.FromRgba32(source[i])); + Assert.Equal(expected[i], Color.FromPixel(source[i]).ToPixel()); + } + } + + [Fact] + public void Rgba32ToHalfVector4PMatchesExactAssociationForEveryComponentAndAlpha() + { + const int pairCount = 65536; + const int channelCount = 3; + + const float finiteMinimum = -65504F; + const float finiteRange = 131008F; + const float inverseFiniteRange = (float)(1D / finiteRange); + const ulong zeroComponentBits = 0xFBFF; + Rgba32[] source = new Rgba32[pairCount * channelCount]; + HalfVector4P[] expected = new HalfVector4P[source.Length]; + HalfVector4P[] actualBulk = new HalfVector4P[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + // Association must use the alpha value recovered from the destination half, rather than the higher-precision source alpha. + float normalizedAlpha = (float)(alpha / (double)byte.MaxValue); + ushort alphaBits = BitConverter.HalfToUInt16Bits((Half)((normalizedAlpha * finiteRange) + finiteMinimum)); + float destinationAlpha = ((float)BitConverter.UInt16BitsToHalf(alphaBits) * inverseFiniteRange) + .5F; + + for (int unassociated = 0; unassociated <= byte.MaxValue; unassociated++) + { + float normalizedComponent = (float)(unassociated / (double)byte.MaxValue); + float associated = normalizedComponent * destinationAlpha; + ushort associatedBits = BitConverter.HalfToUInt16Bits((Half)((associated * finiteRange) + finiteMinimum)); + ulong alphaPacked = (ulong)alphaBits << 48; + + source[index] = new Rgba32((byte)unassociated, 0, 0, (byte)alpha); + expected[index++] = new HalfVector4P { PackedValue = associatedBits | (zeroComponentBits << 16) | (zeroComponentBits << 32) | alphaPacked }; + source[index] = new Rgba32(0, (byte)unassociated, 0, (byte)alpha); + expected[index++] = new HalfVector4P { PackedValue = zeroComponentBits | ((ulong)associatedBits << 16) | (zeroComponentBits << 32) | alphaPacked }; + source[index] = new Rgba32(0, 0, (byte)unassociated, (byte)alpha); + expected[index++] = new HalfVector4P { PackedValue = zeroComponentBits | (zeroComponentBits << 16) | ((ulong)associatedBits << 32) | alphaPacked }; + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualBulk); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(expected[i], HalfVector4P.FromRgba32(source[i])); + Assert.Equal(expected[i], Color.FromPixel(source[i]).ToPixel()); + Assert.Equal(expected[i], actualBulk[i]); + } + } + + [Fact] + public void Rgba32PToRgba32ScalarRoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + { + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + // Components greater than alpha are valid for additive blending but cannot round-trip losslessly through an 8-bit unassociated pixel. + // The exhaustive lossless domain is therefore triangular rather than 256 squared. + for (int associated = 0; associated <= alpha; associated++) + { + // This integer expression is the exact nearest 8-bit unassociated value for associated * 255 / alpha. + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + Rgba32P source = new((byte)associated, 0, 0, (byte)alpha); + Rgba32 expectedUnassociated = new(unassociated, 0, 0, (byte)alpha); + + Rgba32 actualUnassociated = source.ToRgba32(); + Rgba32P actualRoundTrip = Rgba32P.FromRgba32(actualUnassociated); + + Assert.Equal(expectedUnassociated, actualUnassociated); + Assert.Equal(source, actualRoundTrip); + } + } + } + + [Fact] + public void ColorFromRgba32PPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + { + const int pairCount = 32896; + Rgba32P[] sourcePixels = new Rgba32P[pairCount]; + Color[] bulkColors = new Color[pairCount]; + int index = 0; + + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + for (int associated = 0; associated <= alpha; associated++) + { + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + Rgba32P source = new((byte)associated, 0, 0, (byte)alpha); + Color color = Color.FromPixel(source); + sourcePixels[index++] = source; + + Assert.Equal(new Rgba32(unassociated, 0, 0, (byte)alpha), color.ToPixel()); + Assert.Equal(source, color.ToPixel()); + } + } + + Color.FromPixel(sourcePixels, bulkColors); + + for (int i = 0; i < sourcePixels.Length; i++) + { + Assert.Equal(sourcePixels[i], bulkColors[i].ToPixel()); + } + } + + [Fact] + public void Rgba32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Rgba32P(red, green, blue, alpha)); + + [Fact] + public void Bgra32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Bgra32P(red, green, blue, alpha)); + + [Fact] + public void Argb32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Argb32P(red, green, blue, alpha)); + + [Fact] + public void Abgr32PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + => AssertUnsignedByteBulkRoundTrip((red, green, blue, alpha) => new Abgr32P(red, green, blue, alpha)); + + [Fact] + public void NormalizedByte4PToRgba32ScalarRoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + { + for (int alpha = 0; alpha < byte.MaxValue; alpha++) + { + for (int associated = 0; associated <= alpha; associated++) + { + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254); + NormalizedByte4P source = CreateNormalizedByte4P(associated, 0, 0, alpha); + + Rgba32 actualUnassociated = source.ToRgba32(); + NormalizedByte4P actualRoundTrip = NormalizedByte4P.FromRgba32(actualUnassociated); + + Assert.Equal(new Rgba32(unassociated, 0, 0, unassociatedAlpha), actualUnassociated); + Assert.Equal(source, actualRoundTrip); + } + } + } + + [Fact] + public void ColorFromNormalizedByte4PPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + { + const int pairCount = 32640; + NormalizedByte4P[] sourcePixels = new NormalizedByte4P[pairCount]; + Color[] bulkColors = new Color[pairCount]; + int index = 0; + + for (int alpha = 0; alpha < byte.MaxValue; alpha++) + { + for (int associated = 0; associated <= alpha; associated++) + { + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254); + NormalizedByte4P source = CreateNormalizedByte4P(associated, 0, 0, alpha); + Color color = Color.FromPixel(source); + sourcePixels[index++] = source; + + Assert.Equal(new Rgba32(unassociated, 0, 0, unassociatedAlpha), color.ToPixel()); + Assert.Equal(source, color.ToPixel()); + } + } + + Color.FromPixel(sourcePixels, bulkColors); + + for (int i = 0; i < sourcePixels.Length; i++) + { + Assert.Equal(sourcePixels[i], bulkColors[i].ToPixel()); + } + } + + [Fact] + public void NormalizedByte4PToBgra32RoundTripPreservesEveryLosslesslyRoundTrippableAssociatedComponent() + { + const int pairCount = 32640; + const int channelCount = 3; + NormalizedByte4P[] source = new NormalizedByte4P[pairCount * channelCount]; + Bgra32[] expectedUnassociated = new Bgra32[source.Length]; + Bgra32[] actualUnassociated = new Bgra32[source.Length]; + NormalizedByte4P[] actualRoundTrip = new NormalizedByte4P[source.Length]; + int index = 0; + + for (int alpha = 0; alpha < byte.MaxValue; alpha++) + { + for (int associated = 0; associated <= alpha; associated++) + { + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + byte unassociatedAlpha = (byte)(((alpha * byte.MaxValue) + 127) / 254); + + source[index] = CreateNormalizedByte4P(associated, 0, 0, alpha); + expectedUnassociated[index++] = new Bgra32(unassociated, 0, 0, unassociatedAlpha); + source[index] = CreateNormalizedByte4P(0, associated, 0, alpha); + expectedUnassociated[index++] = new Bgra32(0, unassociated, 0, unassociatedAlpha); + source[index] = CreateNormalizedByte4P(0, 0, associated, alpha); + expectedUnassociated[index++] = new Bgra32(0, 0, unassociated, unassociatedAlpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualUnassociated); + PixelOperations.Instance.From(Configuration.Default, actualUnassociated, actualRoundTrip); + + Assert.Equal(expectedUnassociated, actualUnassociated); + Assert.Equal(source, actualRoundTrip); + + for (int i = 0; i < source.Length; i++) + { + Bgra32 expected = expectedUnassociated[i]; + + // Scalar conversion and Color must preserve the same exact canonical value proven for the bulk path. + Assert.Equal(new Rgba32(expected.R, expected.G, expected.B, expected.A), source[i].ToRgba32()); + Assert.Equal(expected, Color.FromPixel(source[i]).ToPixel()); + } + } + + private static void AssertUnsignedByteBulkRoundTrip(Func createPixel) + where TPixel : unmanaged, IPixel + { + const int pairCount = 32896; + const int channelCount = 3; + TPixel[] source = new TPixel[pairCount * channelCount]; + Bgra32[] expectedUnassociated = new Bgra32[source.Length]; + Bgra32[] actualUnassociated = new Bgra32[source.Length]; + TPixel[] actualRoundTrip = new TPixel[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + // Components greater than alpha are valid for additive blending but cannot round-trip losslessly through an 8-bit unassociated pixel. + // The exhaustive lossless domain is therefore triangular rather than 256 squared. + for (int associated = 0; associated <= alpha; associated++) + { + // This integer expression is the exact nearest 8-bit unassociated value for associated * 255 / alpha. + byte unassociated = alpha == 0 ? (byte)0 : (byte)(((associated * byte.MaxValue) + (alpha / 2)) / alpha); + + source[index] = createPixel((byte)associated, 0, 0, (byte)alpha); + expectedUnassociated[index++] = new Bgra32(unassociated, 0, 0, (byte)alpha); + source[index] = createPixel(0, (byte)associated, 0, (byte)alpha); + expectedUnassociated[index++] = new Bgra32(0, unassociated, 0, (byte)alpha); + source[index] = createPixel(0, 0, (byte)associated, (byte)alpha); + expectedUnassociated[index++] = new Bgra32(0, 0, unassociated, (byte)alpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualUnassociated); + PixelOperations.Instance.From(Configuration.Default, actualUnassociated, actualRoundTrip); + + Assert.Equal(expectedUnassociated, actualUnassociated); + Assert.Equal(source, actualRoundTrip); + + for (int i = 0; i < source.Length; i++) + { + Bgra32 expected = expectedUnassociated[i]; + + // Scalar conversion and Color must use the same exact canonical byte as the independently calculated bulk oracle. + Assert.Equal(new Rgba32(expected.R, expected.G, expected.B, expected.A), source[i].ToRgba32()); + Assert.Equal(expected, Color.FromPixel(source[i]).ToPixel()); + } + } + + private static void AssertUnsignedByteAssociation(Func createPixel) + where TPixel : unmanaged, IPixel + { + const int pairCount = 65536; + const int channelCount = 3; + Rgba32[] source = new Rgba32[pairCount * channelCount]; + TPixel[] expected = new TPixel[source.Length]; + TPixel[] actualBulk = new TPixel[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= byte.MaxValue; alpha++) + { + for (int unassociated = 0; unassociated <= byte.MaxValue; unassociated++) + { + // The destination stores round(unassociated * alpha / 255). The integer numerator adds half + // the divisor so the expected value is independent of the floating-point implementation. + byte associated = (byte)(((unassociated * alpha) + 127) / byte.MaxValue); + + source[index] = new Rgba32((byte)unassociated, 0, 0, (byte)alpha); + expected[index++] = createPixel(associated, 0, 0, (byte)alpha); + source[index] = new Rgba32(0, (byte)unassociated, 0, (byte)alpha); + expected[index++] = createPixel(0, associated, 0, (byte)alpha); + source[index] = new Rgba32(0, 0, (byte)unassociated, (byte)alpha); + expected[index++] = createPixel(0, 0, associated, (byte)alpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualBulk); + + Assert.Equal(expected, actualBulk); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(expected[i], TPixel.FromRgba32(source[i])); + Assert.Equal(expected[i], Color.FromPixel(source[i]).ToPixel()); + } + } + + private static NormalizedByte4P CreateNormalizedByte4P(int red, int green, int blue, int alpha) + { + uint packed = (byte)(red - 127) + | ((uint)(byte)(green - 127) << 8) + | ((uint)(byte)(blue - 127) << 16) + | ((uint)(byte)(alpha - 127) << 24); + + return new NormalizedByte4P { PackedValue = packed }; + } +} + +/// +/// Tests that associated destinations use their stored alpha value when associating color components. +/// +public class AssociatedDestinationAlphaQuantizationTests +{ + [Fact] + public void Rgba32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation(); + + [Fact] + public void Bgra32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation(); + + [Fact] + public void Argb32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation(); + + [Fact] + public void Abgr32PQuantizesDestinationAlphaBeforeAssociation() => AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation(); + + [Fact] + public void NormalizedByte4PQuantizesDestinationAlphaBeforeAssociation() + { + ReadOnlySpan components = [64, 127, 191]; + Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length]; + NormalizedByte4P[] actualBulk = new NormalizedByte4P[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + foreach (byte component in components) + { + source[index++] = new Rgba64((ushort)(component * 257), 0, 0, (ushort)alpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualBulk); + index = 0; + + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + int expectedAlpha = ((alpha * 254) + 32767) / ushort.MaxValue; + + foreach (byte component in components) + { + int expectedRed = ((component * expectedAlpha) + 127) / byte.MaxValue; + NormalizedByte4P actualScalar = NormalizedByte4P.FromRgba64(source[index]); + int scalarRed = (sbyte)actualScalar.PackedValue + 127; + int scalarAlpha = (sbyte)(actualScalar.PackedValue >> 24) + 127; + int bulkRed = (sbyte)actualBulk[index].PackedValue + 127; + int bulkAlpha = (sbyte)(actualBulk[index].PackedValue >> 24) + 127; + + Assert.Equal(expectedRed, scalarRed); + Assert.Equal(expectedAlpha, scalarAlpha); + Assert.Equal(expectedRed, bulkRed); + Assert.Equal(expectedAlpha, bulkAlpha); + index++; + } + } + } + + [Fact] + public void HalfVector4PQuantizesDestinationAlphaBeforeAssociation() + { + const float finiteMinimum = -65504F; + const float finiteRange = 131008F; + const float inverseFiniteRange = (float)(1D / finiteRange); + ReadOnlySpan components = [64, 127, 191]; + Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length]; + HalfVector4P[] actualBulk = new HalfVector4P[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + foreach (byte component in components) + { + source[index++] = new Rgba64((ushort)(component * 257), 0, 0, (ushort)alpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualBulk); + index = 0; + + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + float normalizedAlpha = alpha / (float)ushort.MaxValue; + ushort expectedAlpha = BitConverter.HalfToUInt16Bits((Half)((normalizedAlpha * finiteRange) + finiteMinimum)); + float storedAlpha = ((float)BitConverter.UInt16BitsToHalf(expectedAlpha) * inverseFiniteRange) + .5F; + + foreach (byte component in components) + { + float associatedRed = (component / (float)byte.MaxValue) * storedAlpha; + ushort expectedRed = BitConverter.HalfToUInt16Bits((Half)((associatedRed * finiteRange) + finiteMinimum)); + HalfVector4P actualScalar = HalfVector4P.FromRgba64(source[index]); + + Assert.Equal(expectedRed, (ushort)actualScalar.PackedValue); + Assert.Equal(expectedAlpha, (ushort)(actualScalar.PackedValue >> 48)); + Assert.Equal(expectedRed, (ushort)actualBulk[index].PackedValue); + Assert.Equal(expectedAlpha, (ushort)(actualBulk[index].PackedValue >> 48)); + index++; + } + } + } + + [Fact] + public void RgbaHalfPQuantizesDestinationAlphaBeforeAssociation() + { + ReadOnlySpan components = [64, 127, 191]; + Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length]; + RgbaHalfP[] actualBulk = new RgbaHalfP[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + foreach (byte component in components) + { + source[index++] = new Rgba64((ushort)(component * 257), 0, 0, (ushort)alpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualBulk); + index = 0; + + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + float normalizedAlpha = alpha / (float)ushort.MaxValue; + Half expectedAlpha = (Half)normalizedAlpha; + float storedAlpha = (float)expectedAlpha; + + foreach (byte component in components) + { + Half expectedRed = (Half)((component / (float)byte.MaxValue) * storedAlpha); + RgbaHalfP actualScalar = RgbaHalfP.FromRgba64(source[index]); + + Assert.Equal(expectedRed, actualScalar.R); + Assert.Equal(expectedAlpha, actualScalar.A); + Assert.Equal(expectedRed, actualBulk[index].R); + Assert.Equal(expectedAlpha, actualBulk[index].A); + index++; + } + } + } + + [Fact] + public void ColorToRgba32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToBgra32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToArgb32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToAbgr32PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToNormalizedByte4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToHalfVector4PUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + [Fact] + public void ColorToRgbaHalfPUsesDestinationAlphaRepresentation() => AssertColorUsesDestinationAlphaRepresentation(); + + /// + /// Verifies the unsigned-byte destination grid through scalar and bulk conversion entry points. + /// + /// The associated unsigned-byte pixel format. + private static void AssertUnsignedByteDestinationQuantizesAlphaBeforeAssociation() + where TPixel : unmanaged, IPixel + { + ReadOnlySpan components = [64, 127, 191]; + Rgba64[] source = new Rgba64[(ushort.MaxValue + 1) * components.Length]; + TPixel[] actualBulk = new TPixel[source.Length]; + int index = 0; + + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + foreach (byte component in components) + { + source[index++] = new Rgba64((ushort)(component * 257), 0, 0, (ushort)alpha); + } + } + + PixelOperations.Instance.From(Configuration.Default, source, actualBulk); + index = 0; + + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + int expectedAlpha = ((alpha * byte.MaxValue) + 32767) / ushort.MaxValue; + + foreach (byte component in components) + { + int expectedRed = ((component * expectedAlpha) + 127) / byte.MaxValue; + Vector4 expected = new Vector4(expectedRed, 0, 0, expectedAlpha) / byte.MaxValue; + + Assert.Equal(expected, TPixel.FromRgba64(source[index]).ToScaledVector4()); + Assert.Equal(expected, actualBulk[index].ToScaledVector4()); + index++; + } + } + } + + /// + /// Verifies that delegates association to the destination pixel operations. + /// + /// The associated destination pixel format. + private static void AssertColorUsesDestinationAlphaRepresentation() + where TPixel : unmanaged, IPixel + { + for (int alpha = 0; alpha <= ushort.MaxValue; alpha++) + { + ushort component = (ushort)((byte)alpha * 257); + Rgba64 source = new(component, 0, 0, (ushort)alpha); + TPixel expected = TPixel.FromRgba64(source); + TPixel actual = Color.FromPixel(source).ToPixel(); + + Assert.Equal(expected, actual); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs index 9366c51c9..3e06f0471 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfSingleTests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -26,45 +27,62 @@ public class HalfSingleTests public void HalfSingle_ToVector4() { // arrange - HalfSingle pixel = new(0.5f); - Vector4 expected = new(0.5f, 0, 0, 1); + HalfSingle pixel = new(-2F); + Vector4 expected = new(-2F, 0, 0, 1); // act Vector4 actual = pixel.ToVector4(); // assert Assert.Equal(expected, actual); + Assert.Equal((float)Half.MinValue, new HalfSingle((float)Half.MinValue).ToSingle()); + Assert.Equal((float)Half.MaxValue, new HalfSingle((float)Half.MaxValue).ToSingle()); } [Fact] public void HalfSingle_ToScaledVector4() { - // arrange - HalfSingle pixel = new(-1F); - - // act - Vector4 actual = pixel.ToScaledVector4(); - - // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); + Assert.Equal(new Vector4(0F, 0F, 0F, 1F), new HalfSingle((float)Half.MinValue).ToScaledVector4()); + Assert.Equal(new Vector4(.5F, 0F, 0F, 1F), new HalfSingle(0F).ToScaledVector4()); + Assert.Equal(new Vector4(1F, 0F, 0F, 1F), new HalfSingle((float)Half.MaxValue).ToScaledVector4()); } [Fact] public void HalfSingle_FromScaledVector4() { - // arrange - Vector4 scaled = new HalfSingle(-1F).ToScaledVector4(); - const int expected = 48128; + Assert.Equal((ushort)0xFBFF, HalfSingle.FromScaledVector4(new Vector4(0F, 0F, 0F, 1F)).PackedValue); + Assert.Equal((ushort)0, HalfSingle.FromScaledVector4(new Vector4(.5F, 0F, 0F, 1F)).PackedValue); + Assert.Equal((ushort)0x7BFF, HalfSingle.FromScaledVector4(new Vector4(1F, 0F, 0F, 1F)).PackedValue); + } - // act - HalfSingle pixel = HalfSingle.FromScaledVector4(scaled); - ushort actual = pixel.PackedValue; + [Fact] + public void HalfSingle_BulkScaledConversionsCoverFiniteRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertHalfSingleBulkScaledConversionsCoverFiniteRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); - // assert - Assert.Equal(expected, actual); + private static void AssertHalfSingleBulkScaledConversionsCoverFiniteRange() + { + ushort[] packedValues = [0xFBFF, 0, 0x7BFF]; + Vector4[] scaledValues = [new(0F, 0F, 0F, 1F), new(.5F, 0F, 0F, 1F), new(1F, 0F, 0F, 1F)]; + HalfSingle[] source = new HalfSingle[17]; + Vector4[] expectedVectors = new Vector4[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + int valueIndex = i % packedValues.Length; + source[i].PackedValue = packedValues[valueIndex]; + expectedVectors[i] = scaledValues[valueIndex]; + } + + Vector4[] actualVectors = new Vector4[source.Length]; + PixelOperations.Instance.ToVector4(Configuration.Default, source, actualVectors, PixelConversionModifiers.Scale); + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] destructiveSource = [.. expectedVectors]; + HalfSingle[] actualPixels = new HalfSingle[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.Equal(source, actualPixels); } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs index c5a89df1e..1fde34a82 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector2Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -25,19 +26,21 @@ public class HalfVector2Tests Assert.Equal(Vector2.One, new HalfVector2(Vector2.One).ToVector2()); Assert.Equal(Vector2.UnitX, new HalfVector2(Vector2.UnitX).ToVector2()); Assert.Equal(Vector2.UnitY, new HalfVector2(Vector2.UnitY).ToVector2()); + Assert.Equal(new Vector2(-2F, 4F), new HalfVector2(-2F, 4F).ToVector2()); + Assert.Equal(new Vector2((float)Half.MinValue, (float)Half.MaxValue), new HalfVector2((float)Half.MinValue, (float)Half.MaxValue).ToVector2()); } [Fact] public void HalfVector2_ToScaledVector4() { // arrange - HalfVector2 halfVector = new(Vector2.One); + HalfVector2 halfVector = new((float)Half.MinValue, (float)Half.MaxValue); // act Vector4 actual = halfVector.ToScaledVector4(); // assert - Assert.Equal(1F, actual.X); + Assert.Equal(0F, actual.X); Assert.Equal(1F, actual.Y); Assert.Equal(0, actual.Z); Assert.Equal(1, actual.W); @@ -47,8 +50,8 @@ public class HalfVector2Tests public void HalfVector2_FromScaledVector4() { // arrange - Vector4 scaled = new HalfVector2(Vector2.One).ToScaledVector4(); - const uint expected = 1006648320u; + Vector4 scaled = new(0F, 1F, 0F, 1F); + const uint expected = 0x7BFF_FBFF; // act HalfVector2 halfVector = HalfVector2.FromScaledVector4(scaled); @@ -58,6 +61,30 @@ public class HalfVector2Tests Assert.Equal(expected, actual); } + [Fact] + public void HalfVector2_BulkScaledConversionsCoverFiniteRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertHalfVector2BulkScaledConversionsCoverFiniteRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertHalfVector2BulkScaledConversionsCoverFiniteRange() + { + HalfVector2 pixel = new() { PackedValue = 0x7BFF_FBFF }; + HalfVector2[] source = new HalfVector2[17]; + Vector4[] expectedVectors = new Vector4[source.Length]; + Array.Fill(source, pixel); + Array.Fill(expectedVectors, new Vector4(0F, 1F, 0F, 1F)); + + Vector4[] actualVectors = new Vector4[source.Length]; + PixelOperations.Instance.ToVector4(Configuration.Default, source, actualVectors, PixelConversionModifiers.Scale); + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] destructiveSource = [.. expectedVectors]; + HalfVector2[] actualPixels = new HalfVector2[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.Equal(source, actualPixels); + } + [Fact] public void HalfVector2_ToVector4() { diff --git a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs index 16c78a23d..75130c144 100644 --- a/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/HalfVector4Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -33,30 +34,32 @@ public class HalfVector4Tests Assert.Equal(Vector4.UnitY, new HalfVector4(Vector4.UnitY).ToVector4()); Assert.Equal(Vector4.UnitZ, new HalfVector4(Vector4.UnitZ).ToVector4()); Assert.Equal(Vector4.UnitW, new HalfVector4(Vector4.UnitW).ToVector4()); + Assert.Equal(new Vector4(-2F, 4F, .5F, 8F), new HalfVector4(-2F, 4F, .5F, 8F).ToVector4()); + Assert.Equal( + new Vector4((float)Half.MinValue, (float)Half.MaxValue, (float)Half.MinValue, (float)Half.MaxValue), + new HalfVector4((float)Half.MinValue, (float)Half.MaxValue, (float)Half.MinValue, (float)Half.MaxValue).ToVector4()); } [Fact] public void HalfVector4_ToScaledVector4() { // arrange - HalfVector4 pixel = new(-Vector4.One); + Vector4 expected = new(0F, .5F, 1F, 0F); + HalfVector4 pixel = new((float)Half.MinValue, 0F, (float)Half.MaxValue, (float)Half.MinValue); // act Vector4 actual = pixel.ToScaledVector4(); // assert - Assert.Equal(0, actual.X); - Assert.Equal(0, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(0, actual.W); + Assert.Equal(expected, actual); } [Fact] public void HalfVector4_FromScaledVector4() { // arrange - Vector4 scaled = new HalfVector4(-Vector4.One).ToScaledVector4(); - const ulong expected = 13547034390470638592uL; + Vector4 scaled = new(0F, .25F, .5F, 1F); + ulong expected = new HalfVector4((float)Half.MinValue, -32752F, 0F, (float)Half.MaxValue).PackedValue; // act HalfVector4 pixel = HalfVector4.FromScaledVector4(scaled); @@ -66,6 +69,31 @@ public class HalfVector4Tests Assert.Equal(expected, actual); } + [Fact] + public void HalfVector4_BulkScaledConversionsCoverFiniteRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertHalfVector4BulkScaledConversionsCoverFiniteRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertHalfVector4BulkScaledConversionsCoverFiniteRange() + { + const ulong packedValue = 0xFBFF_7BFF_0000_FBFF; + HalfVector4 pixel = new() { PackedValue = packedValue }; + HalfVector4[] source = new HalfVector4[17]; + Vector4[] expectedVectors = new Vector4[source.Length]; + Array.Fill(source, pixel); + Array.Fill(expectedVectors, new Vector4(0F, .5F, 1F, 0F)); + + Vector4[] actualVectors = new Vector4[source.Length]; + PixelOperations.Instance.ToVector4(Configuration.Default, source, actualVectors, PixelConversionModifiers.Scale); + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] destructiveSource = [.. expectedVectors]; + HalfVector4[] actualPixels = new HalfVector4[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.Equal(source, actualPixels); + } + [Fact] public void HalfVector4_FromBgra5551() { diff --git a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs index 1ca865ef4..58f226f50 100644 --- a/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/L8Tests.cs @@ -12,7 +12,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats; public class L8Tests { public static readonly TheoryData LuminanceData - = new() { 0, 1, 2, 3, 5, 13, 31, 71, 73, 79, 83, 109, 127, 128, 131, 199, 250, 251, 254, 255 }; + = + [ + (byte)0, (byte)1, (byte)2, (byte)3, (byte)5, (byte)13, (byte)31, (byte)71, (byte)73, (byte)79, (byte)83, + (byte)109, (byte)127, (byte)128, (byte)131, (byte)199, (byte)250, (byte)251, (byte)254, (byte)255 + ]; [Theory] [InlineData(0)] diff --git a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs index f6cbfc442..d37d125e1 100644 --- a/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/La16Tests.cs @@ -12,7 +12,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats; public class La16Tests { public static readonly TheoryData LuminanceData - = new() { 0, 1, 2, 3, 5, 13, 31, 71, 73, 79, 83, 109, 127, 128, 131, 199, 250, 251, 254, 255 }; + = + [ + (byte)0, (byte)1, (byte)2, (byte)3, (byte)5, (byte)13, (byte)31, (byte)71, (byte)73, (byte)79, (byte)83, + (byte)109, (byte)127, (byte)128, (byte)131, (byte)199, (byte)250, (byte)251, (byte)254, (byte)255 + ]; [Theory] [InlineData(0, 0)] diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs index ffbddb139..e6be539c9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte2Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -29,6 +30,42 @@ public class NormalizedByte2Tests Assert.Equal(-Vector2.One, new NormalizedByte2(Vector2.One * -1234.0f).ToVector2()); } + [Fact] + public void NormalizedByte2_MinimumStorageCodeDecodesAsNegativeOne() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertNormalizedByte2MinimumStorageCodeDecodesAsNegativeOne, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertNormalizedByte2MinimumStorageCodeDecodesAsNegativeOne() + { + NormalizedByte2 pixel = new() { PackedValue = 0x8080 }; + Vector4 expectedNative = new(-1F, -1F, 0F, 1F); + Vector4 expectedScaled = new(0F, 0F, 0F, 1F); + + Assert.Equal(expectedNative, pixel.ToVector4()); + Assert.Equal(expectedScaled, pixel.ToScaledVector4()); + + NormalizedByte2[] source = new NormalizedByte2[17]; + Vector4[] native = new Vector4[source.Length]; + Vector4[] scaled = new Vector4[source.Length]; + Array.Fill(source, pixel); + + PixelOperations.Instance.ToVector4(Configuration.Default, source, native); + PixelOperations.Instance.ToVector4(Configuration.Default, source, scaled, PixelConversionModifiers.Scale); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(expectedNative, native[i]); + Assert.Equal(expectedScaled, scaled[i]); + } + + Vector4[] destructiveSource = new Vector4[source.Length]; + Array.Fill(destructiveSource, expectedScaled); + NormalizedByte2[] actualPixels = new NormalizedByte2[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.All(actualPixels, actual => Assert.Equal((ushort)0x8181, actual.PackedValue)); + } + [Fact] public void NormalizedByte2_ToVector4() { diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs index 5a025f6c4..c2a0047d2 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedByte4Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -60,6 +61,39 @@ public class NormalizedByte4Tests Assert.Equal(-Vector4.One, new NormalizedByte4(Vector4.One * -1234.0f).ToVector4()); } + [Fact] + public void NormalizedByte4_MinimumStorageCodeDecodesAsNegativeOne() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertNormalizedByte4MinimumStorageCodeDecodesAsNegativeOne, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertNormalizedByte4MinimumStorageCodeDecodesAsNegativeOne() + { + NormalizedByte4 pixel = new() { PackedValue = 0x80808080 }; + + Assert.Equal(-Vector4.One, pixel.ToVector4()); + Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); + + NormalizedByte4[] source = new NormalizedByte4[17]; + Vector4[] native = new Vector4[source.Length]; + Vector4[] scaled = new Vector4[source.Length]; + Array.Fill(source, pixel); + + PixelOperations.Instance.ToVector4(Configuration.Default, source, native); + PixelOperations.Instance.ToVector4(Configuration.Default, source, scaled, PixelConversionModifiers.Scale); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(-Vector4.One, native[i]); + Assert.Equal(Vector4.Zero, scaled[i]); + } + + Vector4[] destructiveSource = new Vector4[source.Length]; + NormalizedByte4[] actualPixels = new NormalizedByte4[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.All(actualPixels, actual => Assert.Equal(0x81818181U, actual.PackedValue)); + } + [Fact] public void NormalizedByte4_ToScaledVector4() { diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs index be7b39052..9cb3aee50 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort2Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -33,6 +34,42 @@ public class NormalizedShort2Tests Assert.Equal(-Vector2.One, new NormalizedShort2(Vector2.One * -1234.0f).ToVector2()); } + [Fact] + public void NormalizedShort2_MinimumStorageCodeDecodesAsNegativeOne() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertNormalizedShort2MinimumStorageCodeDecodesAsNegativeOne, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertNormalizedShort2MinimumStorageCodeDecodesAsNegativeOne() + { + NormalizedShort2 pixel = new() { PackedValue = 0x80008000 }; + Vector4 expectedNative = new(-1F, -1F, 0F, 1F); + Vector4 expectedScaled = new(0F, 0F, 0F, 1F); + + Assert.Equal(expectedNative, pixel.ToVector4()); + Assert.Equal(expectedScaled, pixel.ToScaledVector4()); + + NormalizedShort2[] source = new NormalizedShort2[17]; + Vector4[] native = new Vector4[source.Length]; + Vector4[] scaled = new Vector4[source.Length]; + Array.Fill(source, pixel); + + PixelOperations.Instance.ToVector4(Configuration.Default, source, native); + PixelOperations.Instance.ToVector4(Configuration.Default, source, scaled, PixelConversionModifiers.Scale); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(expectedNative, native[i]); + Assert.Equal(expectedScaled, scaled[i]); + } + + Vector4[] destructiveSource = new Vector4[source.Length]; + Array.Fill(destructiveSource, expectedScaled); + NormalizedShort2[] actualPixels = new NormalizedShort2[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.All(actualPixels, actual => Assert.Equal(0x80018001U, actual.PackedValue)); + } + [Fact] public void NormalizedShort2_ToVector4() { diff --git a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs index 281ae7ee5..c317586e4 100644 --- a/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/NormalizedShort4Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -61,6 +62,39 @@ public class NormalizedShort4Tests Assert.Equal(-Vector4.One, new NormalizedShort4(Vector4.One * -1234.0f).ToVector4()); } + [Fact] + public void NormalizedShort4_MinimumStorageCodeDecodesAsNegativeOne() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertNormalizedShort4MinimumStorageCodeDecodesAsNegativeOne, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertNormalizedShort4MinimumStorageCodeDecodesAsNegativeOne() + { + NormalizedShort4 pixel = new() { PackedValue = 0x8000800080008000 }; + + Assert.Equal(-Vector4.One, pixel.ToVector4()); + Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); + + NormalizedShort4[] source = new NormalizedShort4[17]; + Vector4[] native = new Vector4[source.Length]; + Vector4[] scaled = new Vector4[source.Length]; + Array.Fill(source, pixel); + + PixelOperations.Instance.ToVector4(Configuration.Default, source, native); + PixelOperations.Instance.ToVector4(Configuration.Default, source, scaled, PixelConversionModifiers.Scale); + + for (int i = 0; i < source.Length; i++) + { + Assert.Equal(-Vector4.One, native[i]); + Assert.Equal(Vector4.Zero, scaled[i]); + } + + Vector4[] destructiveSource = new Vector4[source.Length]; + NormalizedShort4[] actualPixels = new NormalizedShort4[source.Length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.All(actualPixels, actual => Assert.Equal(0x8001800180018001UL, actual.PackedValue)); + } + [Fact] public void NormalizedShort4_ToScaledVector4() { diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs new file mode 100644 index 000000000..2cc26e76c --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelAlphaRepresentationTests.cs @@ -0,0 +1,548 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.ColorProfiles.Companding; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using SixLabors.ImageSharp.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// Verifies scalar and bulk alpha-representation conversions for a pixel format. +/// +/// The pixel format. +[Trait("Category", "PixelFormats")] +public abstract class PixelAlphaRepresentationTests + where TPixel : unmanaged, IPixel +{ + private static readonly Vector4[] UnassociatedScaledVectors = + [ + new(.8F, .4F, .2F, .5F), + new(.15F, .65F, .35F, .25F), + new(.9F, .1F, .7F, 1F), + Vector4.Zero + ]; + + private static readonly Vector4[] OutOfRangeUnassociatedScaledVectors = + [ + new(-.25F, 1.25F, .5F, .75F), + new(1.5F, -.5F, 2F, 1F) + ]; + + [Fact] + public void ScalarScaledConversionsDescribeTheSameLogicalColors() + { + foreach (Vector4 unassociated in UnassociatedScaledVectors) + { + Vector4 associated = Associate(unassociated); + TPixel fromUnassociated = TPixel.FromUnassociatedScaledVector4(unassociated); + TPixel fromAssociated = TPixel.FromAssociatedScaledVector4(associated); + + Assert.Equal(fromUnassociated, fromAssociated); + } + } + + [Fact] + public void ScalarAssociatedVectorsContainPremultipliedRgb() + { + foreach (Vector4 source in UnassociatedScaledVectors) + { + TPixel pixel = TPixel.FromUnassociatedScaledVector4(source); + Vector4 unassociatedScaled = pixel.ToUnassociatedScaledVector4(); + Vector4 expectedAssociatedScaled = Associate(unassociatedScaled); + Vector4 actualAssociatedScaled = pixel.ToAssociatedScaledVector4(); + + if (typeof(TPixel) == typeof(NormalizedByte4P)) + { + // Exhaustive valid-component coverage proves that signed-byte unassociation followed by reassociation can differ by at most two ULP. + AlphaRepresentationTestAssertions.EqualWithinTwoUlps(expectedAssociatedScaled, actualAssociatedScaled); + } + else + { + // Reversing association can introduce one final float rounding step, but this still rejects an unassociated no-op by several orders of magnitude. + AlphaRepresentationTestAssertions.EqualWithinOneUlp(expectedAssociatedScaled, actualAssociatedScaled); + } + + Assert.Equal(BitConverter.SingleToInt32Bits(expectedAssociatedScaled.W), BitConverter.SingleToInt32Bits(actualAssociatedScaled.W)); + + Vector4 unassociatedNative = pixel.ToUnassociatedVector4(); + + if (unassociatedNative == unassociatedScaled) + { + // Native-equals-scaled formats use the same independent oracle; affine-native formats are verified by AffineNativeAlphaRepresentationTests. + AlphaRepresentationTestAssertions.EqualWithinOneUlp(expectedAssociatedScaled, pixel.ToAssociatedVector4()); + } + } + } + + [Fact] + public void ScalarScaledConversionsClampHighAlphaConsistently() + { + Vector4 unassociated = new(.8F, .4F, .2F, 1.5F); + Vector4 associated = Associate(unassociated); + + // Both entry points describe the same logical color, so clamping opacity at the storage boundary must not change RGB differently between them. + Assert.Equal(TPixel.FromUnassociatedScaledVector4(unassociated), TPixel.FromAssociatedScaledVector4(associated)); + } + + [Fact] + public void ScalarNativeConversionsRoundTripTheirDeclaredRepresentations() + { + foreach (Vector4 source in UnassociatedScaledVectors) + { + TPixel pixel = TPixel.FromUnassociatedScaledVector4(source); + TPixel fromUnassociated = TPixel.FromUnassociatedVector4(pixel.ToUnassociatedVector4()); + TPixel fromAssociated = TPixel.FromAssociatedVector4(pixel.ToAssociatedVector4()); + + Assert.Equal(pixel, fromUnassociated); + + if (pixel.ToUnassociatedScaledVector4().W == 0F) + { + // Associated color cannot encode hidden RGB at zero alpha. The round trip must therefore produce the format's transparent black value. + Assert.Equal(TPixel.FromUnassociatedScaledVector4(Vector4.Zero), fromAssociated); + } + else + { + Assert.Equal(pixel, fromAssociated); + } + } + } + + [Fact] + public void ParameterlessVectorConversionsUseTheDeclaredAlphaRepresentation() + { + PixelAlphaRepresentation alphaRepresentation = TPixel.GetPixelTypeInfo().AlphaRepresentation; + + foreach (Vector4 source in UnassociatedScaledVectors) + { + TPixel pixel = TPixel.FromUnassociatedScaledVector4(source); + Vector4 expectedNative = alphaRepresentation == PixelAlphaRepresentation.Associated + ? pixel.ToAssociatedVector4() + : pixel.ToUnassociatedVector4(); + + Vector4 expectedScaled = alphaRepresentation == PixelAlphaRepresentation.Associated + ? pixel.ToAssociatedScaledVector4() + : pixel.ToUnassociatedScaledVector4(); + + TPixel expectedFromNative = alphaRepresentation == PixelAlphaRepresentation.Associated + ? TPixel.FromAssociatedVector4(expectedNative) + : TPixel.FromUnassociatedVector4(expectedNative); + + TPixel expectedFromScaled = alphaRepresentation == PixelAlphaRepresentation.Associated + ? TPixel.FromAssociatedScaledVector4(expectedScaled) + : TPixel.FromUnassociatedScaledVector4(expectedScaled); + + Assert.Equal(expectedNative, pixel.ToVector4()); + Assert.Equal(expectedScaled, pixel.ToScaledVector4()); + Assert.Equal(expectedFromNative, TPixel.FromVector4(expectedNative)); + Assert.Equal(expectedFromScaled, TPixel.FromScaledVector4(expectedScaled)); + } + } + + [Fact] + public void BulkConversionsMatchScalarConversions() + { + const int length = 259; + TPixel[] pixels = new TPixel[length]; + + for (int i = 0; i < pixels.Length; i++) + { + Vector4 source = UnassociatedScaledVectors[i % UnassociatedScaledVectors.Length]; + pixels[i] = TPixel.FromUnassociatedScaledVector4(source); + } + + AssertBulkToMatchesScalar(pixels, static pixel => pixel.ToUnassociatedVector4(), static (operations, source, destination) => operations.ToVector4(Configuration.Default, source, destination, PixelConversionModifiers.UnPremultiply)); + AssertBulkToMatchesScalar(pixels, static pixel => pixel.ToAssociatedVector4(), static (operations, source, destination) => operations.ToVector4(Configuration.Default, source, destination, PixelConversionModifiers.Premultiply)); + AssertBulkToMatchesScalar(pixels, static pixel => pixel.ToUnassociatedScaledVector4(), static (operations, source, destination) => operations.ToVector4(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply)); + AssertBulkToMatchesScalar(pixels, static pixel => pixel.ToAssociatedScaledVector4(), static (operations, source, destination) => operations.ToVector4(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + + AssertBulkFromMatchesScalar(pixels, static pixel => pixel.ToUnassociatedVector4(), static vector => TPixel.FromUnassociatedVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.UnPremultiply)); + AssertBulkFromMatchesScalar(pixels, static pixel => pixel.ToAssociatedVector4(), static vector => TPixel.FromAssociatedVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Premultiply)); + AssertBulkFromMatchesScalar(pixels, static pixel => pixel.ToUnassociatedScaledVector4(), static vector => TPixel.FromUnassociatedScaledVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply)); + AssertBulkFromMatchesScalar(pixels, static pixel => pixel.ToAssociatedScaledVector4(), static vector => TPixel.FromAssociatedScaledVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } + + [Fact] + public void BulkScaledConversionsClampLikeScalarConversions() + { + Vector4[] associated = new Vector4[OutOfRangeUnassociatedScaledVectors.Length]; + + for (int i = 0; i < associated.Length; i++) + { + associated[i] = Associate(OutOfRangeUnassociatedScaledVectors[i]); + } + + AssertBulkFromMatchesScalar(OutOfRangeUnassociatedScaledVectors, static vector => TPixel.FromUnassociatedScaledVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply)); + AssertBulkFromMatchesScalar(associated, static vector => TPixel.FromAssociatedScaledVector4(vector), static (operations, source, destination) => operations.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void BulkCompandingHonorsRequestedAlphaRepresentationAndRange(bool scaled, bool associated) + { + const int length = 259; + TPixel[] pixels = new TPixel[length]; + + for (int i = 0; i < pixels.Length; i++) + { + pixels[i] = TPixel.FromUnassociatedScaledVector4(UnassociatedScaledVectors[i % UnassociatedScaledVectors.Length]); + } + + PixelConversionModifiers modifiers = PixelConversionModifiers.SRgbCompand + | (scaled ? PixelConversionModifiers.Scale : PixelConversionModifiers.None) + | (associated ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.UnPremultiply); + + Vector4[] expectedVectors = new Vector4[length]; + + for (int i = 0; i < pixels.Length; i++) + { + expectedVectors[i] = scaled ? pixels[i].ToUnassociatedScaledVector4() : pixels[i].ToUnassociatedVector4(); + } + + // Transfer functions operate on straight RGB. Association is therefore applied after expansion on the outbound path. + SRgbCompanding.Expand(expectedVectors); + + if (associated) + { + Numerics.Premultiply(expectedVectors); + } + + Vector4[] actualVectors = new Vector4[length]; + PixelOperations.Instance.ToVector4(Configuration.Default, pixels, actualVectors, modifiers); + + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] expectedPixelSource = [.. expectedVectors]; + + if (associated) + { + Numerics.UnPremultiply(expectedPixelSource); + } + + // Reverse the transfer only after restoring straight RGB, matching the observable modifier order for inbound vectors. + SRgbCompanding.Compress(expectedPixelSource); + + TPixel[] expectedPixels = new TPixel[length]; + + for (int i = 0; i < expectedPixels.Length; i++) + { + expectedPixels[i] = scaled + ? TPixel.FromUnassociatedScaledVector4(expectedPixelSource[i]) + : TPixel.FromUnassociatedVector4(expectedPixelSource[i]); + } + + Vector4[] actualPixelSource = [.. expectedVectors]; + TPixel[] actualPixels = new TPixel[length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, actualPixelSource, actualPixels, modifiers); + + Assert.Equal(expectedPixels, actualPixels); + } + + private static void AssertBulkToMatchesScalar(TPixel[] pixels, Func scalar, BulkToVector4 bulk) + { + Vector4[] expected = new Vector4[pixels.Length]; + Vector4[] actual = new Vector4[pixels.Length + 3]; + Vector4 sentinel = new(.125F, .25F, .5F, .75F); + + for (int i = 0; i < pixels.Length; i++) + { + expected[i] = scalar(pixels[i]); + } + + actual.AsSpan(pixels.Length).Fill(sentinel); + bulk(PixelOperations.Instance, pixels, actual); + + // Bulk conversion is source-length driven and must leave excess destination capacity untouched. + Assert.Equal(expected, actual[..pixels.Length]); + Assert.All(actual[pixels.Length..], vector => Assert.Equal(sentinel, vector)); + } + + private static void AssertBulkFromMatchesScalar(TPixel[] pixels, Func createSource, Func scalar, BulkFromVector4 bulk) + { + Vector4[] source = new Vector4[pixels.Length]; + + for (int i = 0; i < pixels.Length; i++) + { + source[i] = createSource(pixels[i]); + } + + AssertBulkFromMatchesScalar(source, scalar, bulk); + } + + private static void AssertBulkFromMatchesScalar(Vector4[] source, Func scalar, BulkFromVector4 bulk) + { + TPixel[] expected = new TPixel[source.Length]; + TPixel[] actual = new TPixel[source.Length + 3]; + TPixel sentinel = TPixel.FromUnassociatedScaledVector4(new Vector4(.125F, .25F, .5F, .75F)); + + for (int i = 0; i < source.Length; i++) + { + expected[i] = scalar(source[i]); + } + + // From-vector bulk operations are destructive, so preserve the scalar source for diagnostics and future assertions. + Vector4[] destructiveSource = [.. source]; + actual.AsSpan(source.Length).Fill(sentinel); + bulk(PixelOperations.Instance, destructiveSource, actual); + + // A destination may expose spare capacity; the source span still defines how many pixels are written. + Assert.Equal(expected, actual[..source.Length]); + Assert.All(actual[source.Length..], pixel => Assert.Equal(sentinel, pixel)); + } + + private static Vector4 Associate(Vector4 vector) + { + vector.X *= vector.W; + vector.Y *= vector.W; + vector.Z *= vector.W; + return vector; + } + + private delegate void BulkToVector4(PixelOperations operations, ReadOnlySpan source, Span destination); + + private delegate void BulkFromVector4(PixelOperations operations, Span source, Span destination); +} + +/// +/// Verifies the shared bulk vector conversion used by RGB byte formats. +/// +[Trait("Category", "PixelFormats")] +public class RgbaCompatiblePixelOperationsTests +{ + [Fact] + public void BulkConversionsMatchScalarAcrossHardwareWidths() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertBulkConversionsMatchScalar, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void AssertBulkConversionsMatchScalar() + { + AssertBulkConversionsMatchScalarForPixel(); + AssertBulkConversionsMatchScalarForPixel(); + } + + private static void AssertBulkConversionsMatchScalarForPixel() + where TPixel : unmanaged, IPixel + { + const int length = 259; + Vector4 sourceVector = new(.5F, .25F, .125F, 1F); + TPixel expectedPixel = TPixel.FromUnassociatedScaledVector4(sourceVector); + TPixel[] pixels = new TPixel[length]; + Array.Fill(pixels, expectedPixel); + + Vector4 expectedVector = expectedPixel.ToUnassociatedScaledVector4(); + Vector4 vectorSentinel = new(.125F, .5F, .75F, 1F); + Vector4[] vectors = new Vector4[length + 3]; + vectors.AsSpan(length).Fill(vectorSentinel); + + PixelOperations.Instance.ToVector4(Configuration.Default, pixels, vectors, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + Assert.All(vectors[..length], vector => Assert.Equal(expectedVector, vector)); + Assert.All(vectors[length..], vector => Assert.Equal(vectorSentinel, vector)); + + Vector4[] source = new Vector4[length]; + source.AsSpan().Fill(sourceVector); + + TPixel pixelSentinel = TPixel.FromUnassociatedScaledVector4(vectorSentinel); + TPixel[] destination = new TPixel[length + 3]; + destination.AsSpan(length).Fill(pixelSentinel); + + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, source, destination, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + + // A 259-pixel source crosses the 128- and 256-bit optimized thresholds while leaving spare destination capacity to detect writes beyond the source length. + Assert.All(destination[..length], pixel => Assert.Equal(expectedPixel, pixel)); + Assert.All(destination[length..], pixel => Assert.Equal(pixelSentinel, pixel)); + } +} + +/// +/// Verifies alpha conversion for formats with distinct native vector contracts. +/// +[Trait("Category", "PixelFormats")] +public class AffineNativeAlphaRepresentationTests +{ + private static readonly Vector4 UnassociatedScaled = new(.8F, .4F, .2F, .5F); + + [Fact] + public void Byte4NativeConversionsUseScaledAlpha() + => AssertNativeConversions(static vector => vector * byte.MaxValue); + + [Fact] + public void Short4NativeConversionsUseScaledAlpha() + => AssertNativeConversions(static vector => (vector * ushort.MaxValue) - new Vector4(32768F)); + + [Fact] + public void NormalizedByte4NativeConversionsUseScaledAlpha() + => AssertSignedNormalizedNativeConversions(static vector => (vector * 2F) - Vector4.One); + + [Fact] + public void NormalizedByte4PNativeConversionsUseScaledAlpha() + => AssertSignedNormalizedNativeConversions(static vector => (vector * 2F) - Vector4.One); + + [Fact] + public void NormalizedShort4NativeConversionsUseScaledAlpha() + => AssertSignedNormalizedNativeConversions(static vector => (vector * 2F) - Vector4.One); + + [Fact] + public void HalfVector4NativeConversionsUseScaledAlpha() + => AssertNativeConversions(static vector => (vector * 131008F) - new Vector4(65504F)); + + [Fact] + public void HalfVector4PNativeConversionsUseScaledAlpha() + => AssertNativeConversions(static vector => (vector * 131008F) - new Vector4(65504F)); + + [Fact] + public void HalfSingleFromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 131008F) - 65504F, 0F, 0F, vector.W)); + + [Fact] + public void HalfVector2FromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 131008F) - 65504F, (vector.Y * 131008F) - 65504F, 0F, vector.W)); + + [Fact] + public void NormalizedByte2FromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 2F) - 1F, (vector.Y * 2F) - 1F, 0F, vector.W)); + + [Fact] + public void NormalizedShort2FromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * 2F) - 1F, (vector.Y * 2F) - 1F, 0F, vector.W)); + + [Fact] + public void Short2FromAssociatedNativeVectorUsesScaledAlpha() + => AssertAlphaLessNativeFrom(static vector => new Vector4((vector.X * ushort.MaxValue) - 32768F, (vector.Y * ushort.MaxValue) - 32768F, 0F, vector.W)); + + private static void AssertNativeConversions(Func encodeNative) + where TPixel : unmanaged, IPixel + { + TPixel pixel = TPixel.FromUnassociatedScaledVector4(UnassociatedScaled); + Vector4 unassociatedScaled = pixel.ToUnassociatedScaledVector4(); + Vector4 associatedScaled = Associate(unassociatedScaled); + + // Native alpha can be signed or use an integer component range, so multiplying native RGB by native W would not represent opacity. + AlphaRepresentationTestAssertions.EqualWithinOneUlp(encodeNative(unassociatedScaled), pixel.ToUnassociatedVector4()); + AlphaRepresentationTestAssertions.EqualWithinOneUlp(encodeNative(associatedScaled), pixel.ToAssociatedVector4()); + Assert.Equal(pixel, TPixel.FromUnassociatedVector4(encodeNative(unassociatedScaled))); + Assert.Equal(pixel, TPixel.FromAssociatedVector4(encodeNative(associatedScaled))); + } + + private static void AssertSignedNormalizedNativeConversions(Func encodeNative) + where TPixel : unmanaged, IPixel + { + TPixel pixel = TPixel.FromUnassociatedScaledVector4(UnassociatedScaled); + Vector4 unassociatedScaled = pixel.ToUnassociatedScaledVector4(); + Vector4 associatedScaled = Associate(unassociatedScaled); + + // The 2*x-1 affine map is ill-conditioned around native zero, so a native-space ULP tolerance would not measure conversion accuracy. + // Verify the stored native representation exactly, then verify both inverse paths using independently encoded scaled vectors. + if (TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Associated) + { + Assert.Equal(pixel.ToVector4(), pixel.ToAssociatedVector4()); + } + else + { + Assert.Equal(pixel.ToVector4(), pixel.ToUnassociatedVector4()); + } + + Assert.Equal(pixel, TPixel.FromUnassociatedVector4(encodeNative(unassociatedScaled))); + Assert.Equal(pixel, TPixel.FromAssociatedVector4(encodeNative(associatedScaled))); + } + + private static void AssertAlphaLessNativeFrom(Func encodeNative) + where TPixel : unmanaged, IPixel + { + TPixel expected = TPixel.FromUnassociatedScaledVector4(UnassociatedScaled); + Vector4 associatedScaled = Associate(UnassociatedScaled); + + // Formats with implicit alpha still need to unassociate an associated source before discarding its W component. + Assert.Equal(expected, TPixel.FromAssociatedVector4(encodeNative(associatedScaled))); + } + + private static Vector4 Associate(Vector4 vector) + { + vector.X *= vector.W; + vector.Y *= vector.W; + vector.Z *= vector.W; + return vector; + } +} + +/// +/// Provides exact floating-point bounds for independently ordered alpha-association calculations. +/// +internal static class AlphaRepresentationTestAssertions +{ + /// + /// Verifies that corresponding components differ by no more than one binary32 value. + /// + /// The independently calculated expected vector. + /// The vector produced by the pixel implementation. + internal static void EqualWithinOneUlp(Vector4 expected, Vector4 actual) + { + // One neighboring value is the strict bound for a single independently ordered, correctly rounded binary32 operation. + Assert.InRange(actual.X, MathF.BitDecrement(expected.X), MathF.BitIncrement(expected.X)); + Assert.InRange(actual.Y, MathF.BitDecrement(expected.Y), MathF.BitIncrement(expected.Y)); + Assert.InRange(actual.Z, MathF.BitDecrement(expected.Z), MathF.BitIncrement(expected.Z)); + Assert.InRange(actual.W, MathF.BitDecrement(expected.W), MathF.BitIncrement(expected.W)); + } + + /// + /// Verifies that corresponding components differ by no more than two binary32 values. + /// + /// The independently calculated expected vector. + /// The vector produced by the pixel implementation. + internal static void EqualWithinTwoUlps(Vector4 expected, Vector4 actual) + { + // Two neighboring values are the exhaustive bound for NormalizedByte4P unassociation followed by reassociation. + // Bit increments express that exact bound without introducing a loose decimal tolerance. + Assert.InRange(actual.X, MathF.BitDecrement(MathF.BitDecrement(expected.X)), MathF.BitIncrement(MathF.BitIncrement(expected.X))); + Assert.InRange(actual.Y, MathF.BitDecrement(MathF.BitDecrement(expected.Y)), MathF.BitIncrement(MathF.BitIncrement(expected.Y))); + Assert.InRange(actual.Z, MathF.BitDecrement(MathF.BitDecrement(expected.Z)), MathF.BitIncrement(MathF.BitIncrement(expected.Z))); + Assert.InRange(actual.W, MathF.BitDecrement(MathF.BitDecrement(expected.W)), MathF.BitIncrement(MathF.BitIncrement(expected.W))); + } +} + +public class A8AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Abgr32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Abgr32PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Argb32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Argb32PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgr24AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgr565AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgra32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgra32PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgra4444AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Bgra5551AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Byte4AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class HalfSingleAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class HalfVector2AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class HalfVector4AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class HalfVector4PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class L16AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class L8AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class La16AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class La32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class NormalizedByte2AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class NormalizedByte4AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class NormalizedByte4PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class NormalizedShort2AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class NormalizedShort4AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rg32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgb24AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgb48AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgb96AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgba1010102AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgba128AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgba32AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgba32PAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class RgbaHalfAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class RgbaHalfPAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Rgba64AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class RgbaVectorAlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Short2AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class Short4AlphaRepresentationTests : PixelAlphaRepresentationTests { } +public class RgbaDoubleAlphaRepresentationTests : PixelAlphaRepresentationTests { } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs index 924e94d92..69bc96ece 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -12,26 +13,322 @@ public class PixelBlenderTests { public static TheoryData BlenderMappings = new() { - { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, - { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, - { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, - { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, - { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, - { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, - { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, - { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, - { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, - { new TestPixel(), typeof(DefaultPixelBlenders.NormalSrcOver), PixelColorBlendingMode.Normal }, - { new TestPixel(), typeof(DefaultPixelBlenders.ScreenSrcOver), PixelColorBlendingMode.Screen }, - { new TestPixel(), typeof(DefaultPixelBlenders.HardLightSrcOver), PixelColorBlendingMode.HardLight }, - { new TestPixel(), typeof(DefaultPixelBlenders.OverlaySrcOver), PixelColorBlendingMode.Overlay }, - { new TestPixel(), typeof(DefaultPixelBlenders.DarkenSrcOver), PixelColorBlendingMode.Darken }, - { new TestPixel(), typeof(DefaultPixelBlenders.LightenSrcOver), PixelColorBlendingMode.Lighten }, - { new TestPixel(), typeof(DefaultPixelBlenders.AddSrcOver), PixelColorBlendingMode.Add }, - { new TestPixel(), typeof(DefaultPixelBlenders.SubtractSrcOver), PixelColorBlendingMode.Subtract }, - { new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply }, + { new TestPixel(), DefaultPixelBlenders.NormalSrcOver.GetType(), PixelColorBlendingMode.Normal }, + { new TestPixel(), DefaultPixelBlenders.ScreenSrcOver.GetType(), PixelColorBlendingMode.Screen }, + { new TestPixel(), DefaultPixelBlenders.HardLightSrcOver.GetType(), PixelColorBlendingMode.HardLight }, + { new TestPixel(), DefaultPixelBlenders.OverlaySrcOver.GetType(), PixelColorBlendingMode.Overlay }, + { new TestPixel(), DefaultPixelBlenders.DarkenSrcOver.GetType(), PixelColorBlendingMode.Darken }, + { new TestPixel(), DefaultPixelBlenders.LightenSrcOver.GetType(), PixelColorBlendingMode.Lighten }, + { new TestPixel(), DefaultPixelBlenders.AddSrcOver.GetType(), PixelColorBlendingMode.Add }, + { new TestPixel(), DefaultPixelBlenders.SubtractSrcOver.GetType(), PixelColorBlendingMode.Subtract }, + { new TestPixel(), DefaultPixelBlenders.MultiplySrcOver.GetType(), PixelColorBlendingMode.Multiply }, + { new TestPixel(), DefaultPixelBlenders.NormalSrcOver.GetType(), PixelColorBlendingMode.Normal }, + { new TestPixel(), DefaultPixelBlenders.ScreenSrcOver.GetType(), PixelColorBlendingMode.Screen }, + { new TestPixel(), DefaultPixelBlenders.HardLightSrcOver.GetType(), PixelColorBlendingMode.HardLight }, + { new TestPixel(), DefaultPixelBlenders.OverlaySrcOver.GetType(), PixelColorBlendingMode.Overlay }, + { new TestPixel(), DefaultPixelBlenders.DarkenSrcOver.GetType(), PixelColorBlendingMode.Darken }, + { new TestPixel(), DefaultPixelBlenders.LightenSrcOver.GetType(), PixelColorBlendingMode.Lighten }, + { new TestPixel(), DefaultPixelBlenders.AddSrcOver.GetType(), PixelColorBlendingMode.Add }, + { new TestPixel(), DefaultPixelBlenders.SubtractSrcOver.GetType(), PixelColorBlendingMode.Subtract }, + { new TestPixel(), DefaultPixelBlenders.MultiplySrcOver.GetType(), PixelColorBlendingMode.Multiply }, }; + public static TheoryData BlenderModeMappings + { + get + { + TheoryData data = new(); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.Clear, + DefaultPixelBlenders.NormalClear.GetType(), + DefaultPixelBlenders.MultiplyClear.GetType(), + DefaultPixelBlenders.AddClear.GetType(), + DefaultPixelBlenders.SubtractClear.GetType(), + DefaultPixelBlenders.ScreenClear.GetType(), + DefaultPixelBlenders.DarkenClear.GetType(), + DefaultPixelBlenders.LightenClear.GetType(), + DefaultPixelBlenders.OverlayClear.GetType(), + DefaultPixelBlenders.HardLightClear.GetType(), + DefaultPixelBlenders.ColorDodgeClear.GetType(), + DefaultPixelBlenders.ColorBurnClear.GetType(), + DefaultPixelBlenders.SoftLightClear.GetType(), + DefaultPixelBlenders.DifferenceClear.GetType(), + DefaultPixelBlenders.ExclusionClear.GetType(), + DefaultPixelBlenders.HueClear.GetType(), + DefaultPixelBlenders.SaturationClear.GetType(), + DefaultPixelBlenders.ColorClear.GetType(), + DefaultPixelBlenders.LuminosityClear.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.Xor, + DefaultPixelBlenders.NormalXor.GetType(), + DefaultPixelBlenders.MultiplyXor.GetType(), + DefaultPixelBlenders.AddXor.GetType(), + DefaultPixelBlenders.SubtractXor.GetType(), + DefaultPixelBlenders.ScreenXor.GetType(), + DefaultPixelBlenders.DarkenXor.GetType(), + DefaultPixelBlenders.LightenXor.GetType(), + DefaultPixelBlenders.OverlayXor.GetType(), + DefaultPixelBlenders.HardLightXor.GetType(), + DefaultPixelBlenders.ColorDodgeXor.GetType(), + DefaultPixelBlenders.ColorBurnXor.GetType(), + DefaultPixelBlenders.SoftLightXor.GetType(), + DefaultPixelBlenders.DifferenceXor.GetType(), + DefaultPixelBlenders.ExclusionXor.GetType(), + DefaultPixelBlenders.HueXor.GetType(), + DefaultPixelBlenders.SaturationXor.GetType(), + DefaultPixelBlenders.ColorXor.GetType(), + DefaultPixelBlenders.LuminosityXor.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.Plus, + DefaultPixelBlenders.NormalPlus.GetType(), + DefaultPixelBlenders.MultiplyPlus.GetType(), + DefaultPixelBlenders.AddPlus.GetType(), + DefaultPixelBlenders.SubtractPlus.GetType(), + DefaultPixelBlenders.ScreenPlus.GetType(), + DefaultPixelBlenders.DarkenPlus.GetType(), + DefaultPixelBlenders.LightenPlus.GetType(), + DefaultPixelBlenders.OverlayPlus.GetType(), + DefaultPixelBlenders.HardLightPlus.GetType(), + DefaultPixelBlenders.ColorDodgePlus.GetType(), + DefaultPixelBlenders.ColorBurnPlus.GetType(), + DefaultPixelBlenders.SoftLightPlus.GetType(), + DefaultPixelBlenders.DifferencePlus.GetType(), + DefaultPixelBlenders.ExclusionPlus.GetType(), + DefaultPixelBlenders.HuePlus.GetType(), + DefaultPixelBlenders.SaturationPlus.GetType(), + DefaultPixelBlenders.ColorPlus.GetType(), + DefaultPixelBlenders.LuminosityPlus.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.Src, + DefaultPixelBlenders.NormalSrc.GetType(), + DefaultPixelBlenders.MultiplySrc.GetType(), + DefaultPixelBlenders.AddSrc.GetType(), + DefaultPixelBlenders.SubtractSrc.GetType(), + DefaultPixelBlenders.ScreenSrc.GetType(), + DefaultPixelBlenders.DarkenSrc.GetType(), + DefaultPixelBlenders.LightenSrc.GetType(), + DefaultPixelBlenders.OverlaySrc.GetType(), + DefaultPixelBlenders.HardLightSrc.GetType(), + DefaultPixelBlenders.ColorDodgeSrc.GetType(), + DefaultPixelBlenders.ColorBurnSrc.GetType(), + DefaultPixelBlenders.SoftLightSrc.GetType(), + DefaultPixelBlenders.DifferenceSrc.GetType(), + DefaultPixelBlenders.ExclusionSrc.GetType(), + DefaultPixelBlenders.HueSrc.GetType(), + DefaultPixelBlenders.SaturationSrc.GetType(), + DefaultPixelBlenders.ColorSrc.GetType(), + DefaultPixelBlenders.LuminositySrc.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.SrcAtop, + DefaultPixelBlenders.NormalSrcAtop.GetType(), + DefaultPixelBlenders.MultiplySrcAtop.GetType(), + DefaultPixelBlenders.AddSrcAtop.GetType(), + DefaultPixelBlenders.SubtractSrcAtop.GetType(), + DefaultPixelBlenders.ScreenSrcAtop.GetType(), + DefaultPixelBlenders.DarkenSrcAtop.GetType(), + DefaultPixelBlenders.LightenSrcAtop.GetType(), + DefaultPixelBlenders.OverlaySrcAtop.GetType(), + DefaultPixelBlenders.HardLightSrcAtop.GetType(), + DefaultPixelBlenders.ColorDodgeSrcAtop.GetType(), + DefaultPixelBlenders.ColorBurnSrcAtop.GetType(), + DefaultPixelBlenders.SoftLightSrcAtop.GetType(), + DefaultPixelBlenders.DifferenceSrcAtop.GetType(), + DefaultPixelBlenders.ExclusionSrcAtop.GetType(), + DefaultPixelBlenders.HueSrcAtop.GetType(), + DefaultPixelBlenders.SaturationSrcAtop.GetType(), + DefaultPixelBlenders.ColorSrcAtop.GetType(), + DefaultPixelBlenders.LuminositySrcAtop.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.SrcIn, + DefaultPixelBlenders.NormalSrcIn.GetType(), + DefaultPixelBlenders.MultiplySrcIn.GetType(), + DefaultPixelBlenders.AddSrcIn.GetType(), + DefaultPixelBlenders.SubtractSrcIn.GetType(), + DefaultPixelBlenders.ScreenSrcIn.GetType(), + DefaultPixelBlenders.DarkenSrcIn.GetType(), + DefaultPixelBlenders.LightenSrcIn.GetType(), + DefaultPixelBlenders.OverlaySrcIn.GetType(), + DefaultPixelBlenders.HardLightSrcIn.GetType(), + DefaultPixelBlenders.ColorDodgeSrcIn.GetType(), + DefaultPixelBlenders.ColorBurnSrcIn.GetType(), + DefaultPixelBlenders.SoftLightSrcIn.GetType(), + DefaultPixelBlenders.DifferenceSrcIn.GetType(), + DefaultPixelBlenders.ExclusionSrcIn.GetType(), + DefaultPixelBlenders.HueSrcIn.GetType(), + DefaultPixelBlenders.SaturationSrcIn.GetType(), + DefaultPixelBlenders.ColorSrcIn.GetType(), + DefaultPixelBlenders.LuminositySrcIn.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.SrcOut, + DefaultPixelBlenders.NormalSrcOut.GetType(), + DefaultPixelBlenders.MultiplySrcOut.GetType(), + DefaultPixelBlenders.AddSrcOut.GetType(), + DefaultPixelBlenders.SubtractSrcOut.GetType(), + DefaultPixelBlenders.ScreenSrcOut.GetType(), + DefaultPixelBlenders.DarkenSrcOut.GetType(), + DefaultPixelBlenders.LightenSrcOut.GetType(), + DefaultPixelBlenders.OverlaySrcOut.GetType(), + DefaultPixelBlenders.HardLightSrcOut.GetType(), + DefaultPixelBlenders.ColorDodgeSrcOut.GetType(), + DefaultPixelBlenders.ColorBurnSrcOut.GetType(), + DefaultPixelBlenders.SoftLightSrcOut.GetType(), + DefaultPixelBlenders.DifferenceSrcOut.GetType(), + DefaultPixelBlenders.ExclusionSrcOut.GetType(), + DefaultPixelBlenders.HueSrcOut.GetType(), + DefaultPixelBlenders.SaturationSrcOut.GetType(), + DefaultPixelBlenders.ColorSrcOut.GetType(), + DefaultPixelBlenders.LuminositySrcOut.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.Dest, + DefaultPixelBlenders.NormalDest.GetType(), + DefaultPixelBlenders.MultiplyDest.GetType(), + DefaultPixelBlenders.AddDest.GetType(), + DefaultPixelBlenders.SubtractDest.GetType(), + DefaultPixelBlenders.ScreenDest.GetType(), + DefaultPixelBlenders.DarkenDest.GetType(), + DefaultPixelBlenders.LightenDest.GetType(), + DefaultPixelBlenders.OverlayDest.GetType(), + DefaultPixelBlenders.HardLightDest.GetType(), + DefaultPixelBlenders.ColorDodgeDest.GetType(), + DefaultPixelBlenders.ColorBurnDest.GetType(), + DefaultPixelBlenders.SoftLightDest.GetType(), + DefaultPixelBlenders.DifferenceDest.GetType(), + DefaultPixelBlenders.ExclusionDest.GetType(), + DefaultPixelBlenders.HueDest.GetType(), + DefaultPixelBlenders.SaturationDest.GetType(), + DefaultPixelBlenders.ColorDest.GetType(), + DefaultPixelBlenders.LuminosityDest.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.DestAtop, + DefaultPixelBlenders.NormalDestAtop.GetType(), + DefaultPixelBlenders.MultiplyDestAtop.GetType(), + DefaultPixelBlenders.AddDestAtop.GetType(), + DefaultPixelBlenders.SubtractDestAtop.GetType(), + DefaultPixelBlenders.ScreenDestAtop.GetType(), + DefaultPixelBlenders.DarkenDestAtop.GetType(), + DefaultPixelBlenders.LightenDestAtop.GetType(), + DefaultPixelBlenders.OverlayDestAtop.GetType(), + DefaultPixelBlenders.HardLightDestAtop.GetType(), + DefaultPixelBlenders.ColorDodgeDestAtop.GetType(), + DefaultPixelBlenders.ColorBurnDestAtop.GetType(), + DefaultPixelBlenders.SoftLightDestAtop.GetType(), + DefaultPixelBlenders.DifferenceDestAtop.GetType(), + DefaultPixelBlenders.ExclusionDestAtop.GetType(), + DefaultPixelBlenders.HueDestAtop.GetType(), + DefaultPixelBlenders.SaturationDestAtop.GetType(), + DefaultPixelBlenders.ColorDestAtop.GetType(), + DefaultPixelBlenders.LuminosityDestAtop.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.DestIn, + DefaultPixelBlenders.NormalDestIn.GetType(), + DefaultPixelBlenders.MultiplyDestIn.GetType(), + DefaultPixelBlenders.AddDestIn.GetType(), + DefaultPixelBlenders.SubtractDestIn.GetType(), + DefaultPixelBlenders.ScreenDestIn.GetType(), + DefaultPixelBlenders.DarkenDestIn.GetType(), + DefaultPixelBlenders.LightenDestIn.GetType(), + DefaultPixelBlenders.OverlayDestIn.GetType(), + DefaultPixelBlenders.HardLightDestIn.GetType(), + DefaultPixelBlenders.ColorDodgeDestIn.GetType(), + DefaultPixelBlenders.ColorBurnDestIn.GetType(), + DefaultPixelBlenders.SoftLightDestIn.GetType(), + DefaultPixelBlenders.DifferenceDestIn.GetType(), + DefaultPixelBlenders.ExclusionDestIn.GetType(), + DefaultPixelBlenders.HueDestIn.GetType(), + DefaultPixelBlenders.SaturationDestIn.GetType(), + DefaultPixelBlenders.ColorDestIn.GetType(), + DefaultPixelBlenders.LuminosityDestIn.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.DestOut, + DefaultPixelBlenders.NormalDestOut.GetType(), + DefaultPixelBlenders.MultiplyDestOut.GetType(), + DefaultPixelBlenders.AddDestOut.GetType(), + DefaultPixelBlenders.SubtractDestOut.GetType(), + DefaultPixelBlenders.ScreenDestOut.GetType(), + DefaultPixelBlenders.DarkenDestOut.GetType(), + DefaultPixelBlenders.LightenDestOut.GetType(), + DefaultPixelBlenders.OverlayDestOut.GetType(), + DefaultPixelBlenders.HardLightDestOut.GetType(), + DefaultPixelBlenders.ColorDodgeDestOut.GetType(), + DefaultPixelBlenders.ColorBurnDestOut.GetType(), + DefaultPixelBlenders.SoftLightDestOut.GetType(), + DefaultPixelBlenders.DifferenceDestOut.GetType(), + DefaultPixelBlenders.ExclusionDestOut.GetType(), + DefaultPixelBlenders.HueDestOut.GetType(), + DefaultPixelBlenders.SaturationDestOut.GetType(), + DefaultPixelBlenders.ColorDestOut.GetType(), + DefaultPixelBlenders.LuminosityDestOut.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.DestOver, + DefaultPixelBlenders.NormalDestOver.GetType(), + DefaultPixelBlenders.MultiplyDestOver.GetType(), + DefaultPixelBlenders.AddDestOver.GetType(), + DefaultPixelBlenders.SubtractDestOver.GetType(), + DefaultPixelBlenders.ScreenDestOver.GetType(), + DefaultPixelBlenders.DarkenDestOver.GetType(), + DefaultPixelBlenders.LightenDestOver.GetType(), + DefaultPixelBlenders.OverlayDestOver.GetType(), + DefaultPixelBlenders.HardLightDestOver.GetType(), + DefaultPixelBlenders.ColorDodgeDestOver.GetType(), + DefaultPixelBlenders.ColorBurnDestOver.GetType(), + DefaultPixelBlenders.SoftLightDestOver.GetType(), + DefaultPixelBlenders.DifferenceDestOver.GetType(), + DefaultPixelBlenders.ExclusionDestOver.GetType(), + DefaultPixelBlenders.HueDestOver.GetType(), + DefaultPixelBlenders.SaturationDestOver.GetType(), + DefaultPixelBlenders.ColorDestOver.GetType(), + DefaultPixelBlenders.LuminosityDestOver.GetType()); + + AddBlenderModeMappings( + data, + PixelAlphaCompositionMode.SrcOver, + DefaultPixelBlenders.NormalSrcOver.GetType(), + DefaultPixelBlenders.MultiplySrcOver.GetType(), + DefaultPixelBlenders.AddSrcOver.GetType(), + DefaultPixelBlenders.SubtractSrcOver.GetType(), + DefaultPixelBlenders.ScreenSrcOver.GetType(), + DefaultPixelBlenders.DarkenSrcOver.GetType(), + DefaultPixelBlenders.LightenSrcOver.GetType(), + DefaultPixelBlenders.OverlaySrcOver.GetType(), + DefaultPixelBlenders.HardLightSrcOver.GetType(), + DefaultPixelBlenders.ColorDodgeSrcOver.GetType(), + DefaultPixelBlenders.ColorBurnSrcOver.GetType(), + DefaultPixelBlenders.SoftLightSrcOver.GetType(), + DefaultPixelBlenders.DifferenceSrcOver.GetType(), + DefaultPixelBlenders.ExclusionSrcOver.GetType(), + DefaultPixelBlenders.HueSrcOver.GetType(), + DefaultPixelBlenders.SaturationSrcOver.GetType(), + DefaultPixelBlenders.ColorSrcOver.GetType(), + DefaultPixelBlenders.LuminositySrcOver.GetType()); + + return data; + } + } + [Theory] [MemberData(nameof(BlenderMappings))] public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelColorBlendingMode mode) @@ -41,6 +338,496 @@ public class PixelBlenderTests Assert.IsType(type, blender); } + [Theory] + [MemberData(nameof(BlenderModeMappings))] + public void ReturnsCorrectBlenderForAllModeCombinations(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode, Type type) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(colorMode, alphaMode); + Assert.IsType(type, blender); + } + + [Fact] + public void BlendFunctionsAreCalledForAllModeCombinations() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + ExerciseAllBlenderModeCombinations, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void AssociatedAlphaBlendFunctionsAreCalledForAllModeCombinations() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + ExerciseAllAssociatedAlphaBlenderModeCombinations, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + [Theory] + [InlineData(PixelColorBlendingMode.ColorDodge)] + [InlineData(PixelColorBlendingMode.ColorBurn)] + [InlineData(PixelColorBlendingMode.SoftLight)] + [InlineData(PixelColorBlendingMode.Difference)] + [InlineData(PixelColorBlendingMode.Exclusion)] + [InlineData(PixelColorBlendingMode.Hue)] + [InlineData(PixelColorBlendingMode.Saturation)] + [InlineData(PixelColorBlendingMode.Color)] + [InlineData(PixelColorBlendingMode.Luminosity)] + public void NewSrcOverModesMatchAcrossAlphaRepresentations(PixelColorBlendingMode colorMode) + { + Rgba32 background = new(220, 80, 40, 160); + Rgba32 source = new(20, 180, 120, 96); + PixelBlender straightBlender = PixelOperations.Instance.GetPixelBlender(colorMode, PixelAlphaCompositionMode.SrcOver); + PixelBlender associatedBlender = PixelOperations.Instance.GetPixelBlender(colorMode, PixelAlphaCompositionMode.SrcOver); + + Rgba32 straightResult = straightBlender.Blend(background, source, .625F); + Rgba32P expected = Rgba32P.FromRgba32(straightResult); + Rgba32P actual = associatedBlender.Blend(Rgba32P.FromRgba32(background), Rgba32P.FromRgba32(source), .625F); + + // Associated storage introduces one extra byte quantization, but both paths must describe the same composite. + Assert.True(Math.Abs(expected.R - actual.R) <= 1, $"{colorMode}: expected {expected}, actual {actual}"); + Assert.True(Math.Abs(expected.G - actual.G) <= 1, $"{colorMode}: expected {expected}, actual {actual}"); + Assert.True(Math.Abs(expected.B - actual.B) <= 1, $"{colorMode}: expected {expected}, actual {actual}"); + Assert.Equal(expected.A, actual.A); + } + + [Fact] + public void PlusMatchesAcrossAlphaRepresentations() + { + Rgba32 background = new(220, 80, 40, 160); + Rgba32 source = new(20, 180, 120, 96); + PixelBlender straightBlender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Plus); + PixelBlender associatedBlender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.Plus); + + Rgba32P expected = Rgba32P.FromRgba32(straightBlender.Blend(background, source, .625F)); + Rgba32P actual = associatedBlender.Blend(Rgba32P.FromRgba32(background), Rgba32P.FromRgba32(source), .625F); + + Assert.True(Math.Abs(expected.R - actual.R) <= 1, $"expected {expected}, actual {actual}"); + Assert.True(Math.Abs(expected.G - actual.G) <= 1, $"expected {expected}, actual {actual}"); + Assert.True(Math.Abs(expected.B - actual.B) <= 1, $"expected {expected}, actual {actual}"); + Assert.Equal(expected.A, actual.A); + } + + [Fact] + public void AssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunAssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void RunAssociatedHardLightDestAtopRoundsExactMidpointAwayFromZero() + { + Rgba32P background = Rgba32P.FromRgba32(new Rgba32(220, 80, 40, 160)); + Rgba32P source = Rgba32P.FromRgba32(new Rgba32(20, 180, 120, 96)); + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.HardLight, PixelAlphaCompositionMode.DestAtop); + + Assert.Equal(new Rgba32P(30, 33, 16, 60), blender.Blend(background, source, .625F)); + } + + [Fact] + public void AssociatedBlendWithCoverageAppliesCoverageToColorAndAlpha() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunAssociatedBlendWithCoverageAppliesCoverageToColorAndAlpha, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void BlendWithCoverageMatchesAcrossAlphaRepresentations() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunBlendWithCoverageMatchesAcrossAlphaRepresentations, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + private static void RunAssociatedBlendWithCoverageAppliesCoverageToColorAndAlpha() + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); + Rgba32P source = Rgba32P.FromRgba32(new Rgba32(37, 211, 89, 173)); + Vector4 sourceScaled = source.ToScaledVector4(); + + // Seven pixels exercise one AVX-512 batch plus three tails, or three AVX2 batches plus one tail. + float[] coverage = [1F, .8F, .6F, .4F, .2F, 0F, .5F]; + float[] amounts = [1F, 1F, 1F, 1F, 1F, 1F, 1F]; + Rgba32P[] background = new Rgba32P[7]; + Rgba32P[] sourceSpan = [source, source, source, source, source, source, source]; + Rgba32P[] destination = new Rgba32P[7]; + Rgba32P[] expected = new Rgba32P[7]; + Vector4[] sourceSpanBuffer = new Vector4[destination.Length * 3]; + Vector4[] constantSourceBuffer = new Vector4[destination.Length * 2]; + + // Compositing over transparency reduces source-over to the source itself, so coverage must + // scale the associated color and alpha lanes together: the premultiplied source times coverage. + // An unassociated coverage lerp instead premultiplies and unassociates around the mix, which + // cancels coverage out of the color lanes entirely at zero backdrop alpha. + for (int i = 0; i < expected.Length; i++) + { + expected[i] = Rgba32P.FromScaledVector4(sourceScaled * coverage[i]); + } + + blender.BlendWithCoverage(Configuration.Default, destination, background, sourceSpan, 1F, coverage, sourceSpanBuffer); + Assert.Equal(expected, destination); + + blender.BlendWithCoverage(Configuration.Default, destination, background, source, 1F, coverage, constantSourceBuffer); + Assert.Equal(expected, destination); + + blender.BlendWithCoverage(Configuration.Default, destination, background, sourceSpan, amounts, coverage, sourceSpanBuffer); + Assert.Equal(expected, destination); + + blender.BlendWithCoverage(Configuration.Default, destination, background, source, amounts, coverage, constantSourceBuffer); + Assert.Equal(expected, destination); + } + + private static void RunBlendWithCoverageMatchesAcrossAlphaRepresentations() + { + PixelBlender straightBlender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); + PixelBlender associatedBlender = PixelOperations.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.SrcOver); + + Rgba32[] background = + [ + new(0, 0, 0, 0), + new(29, 83, 137, 107), + new(255, 255, 255, 255), + new(220, 80, 40, 160), + new(10, 20, 30, 40), + new(40, 200, 100, 192), + new(180, 160, 20, 128), + ]; + + Rgba32 source = new(37, 211, 89, 173); + float[] coverage = [1F, .8F, .6F, .4F, .2F, 0F, .5F]; + + Rgba32[] straightDestination = new Rgba32[background.Length]; + Rgba32P[] associatedDestination = new Rgba32P[background.Length]; + Rgba32P[] associatedBackground = new Rgba32P[background.Length]; + Vector4[] workingBuffer = new Vector4[background.Length * 2]; + + for (int i = 0; i < background.Length; i++) + { + associatedBackground[i] = Rgba32P.FromRgba32(background[i]); + } + + straightBlender.BlendWithCoverage(Configuration.Default, straightDestination, background, source, 1F, coverage, workingBuffer); + associatedBlender.BlendWithCoverage(Configuration.Default, associatedDestination, associatedBackground, Rgba32P.FromRgba32(source), 1F, coverage, workingBuffer); + + // Both destinations must describe the same composite: canonical associated bytes may differ + // by one quantization step per channel while alpha is representation-independent. + for (int i = 0; i < background.Length; i++) + { + Rgba32P expected = Rgba32P.FromRgba32(straightDestination[i]); + Rgba32P actual = associatedDestination[i]; + + Assert.True(Math.Abs(expected.R - actual.R) <= 1, $"[{i}] expected {expected}, actual {actual}"); + Assert.True(Math.Abs(expected.G - actual.G) <= 1, $"[{i}] expected {expected}, actual {actual}"); + Assert.True(Math.Abs(expected.B - actual.B) <= 1, $"[{i}] expected {expected}, actual {actual}"); + Assert.Equal(expected.A, actual.A); + } + } + + [Fact] + public void Blend_WithConstantSourceAndSingleAmount() + { + PixelBlender blender = DefaultPixelBlenders.NormalSrcOver; + Rgba32[] destination = new Rgba32[2]; + Rgba32[] background = + [ + Color.Red.ToPixel(), + Color.Green.ToPixel() + ]; + + Rgba32 source = Color.Blue.ToPixel(); + + blender.Blend(Configuration.Default, destination, background, source, 1F); + + Assert.Equal(source, destination[0]); + Assert.Equal(source, destination[1]); + } + + [Fact] + public void Blend_WithConstantSourceSingleAmountAndWorkingBuffer() + { + PixelBlender blender = DefaultPixelBlenders.NormalSrcOver; + Rgba32[] destination = new Rgba32[2]; + Rgba32[] background = + [ + Color.Red.ToPixel(), + Color.Green.ToPixel() + ]; + + Rgba32 source = Color.Blue.ToPixel(); + Vector4[] workingBuffer = new Vector4[destination.Length * 2]; + + blender.Blend(Configuration.Default, destination, background, source, 1F, workingBuffer); + + Assert.Equal(source, destination[0]); + Assert.Equal(source, destination[1]); + } + + [Fact] + public void Blend_WithConstantSourceAndAmountSpan() + { + PixelBlender blender = DefaultPixelBlenders.NormalSrcOver; + Rgba32[] destination = new Rgba32[2]; + Rgba32[] background = + [ + Color.Red.ToPixel(), + Color.Green.ToPixel() + ]; + + Rgba32 source = Color.Blue.ToPixel(); + float[] amount = [1F, 1F]; + + blender.Blend(Configuration.Default, destination, background, source, amount); + + Assert.Equal(source, destination[0]); + Assert.Equal(source, destination[1]); + } + + [Fact] + public void Blend_WithConstantSourceAmountSpanAndWorkingBuffer() + { + PixelBlender blender = DefaultPixelBlenders.NormalSrcOver; + Rgba32[] destination = new Rgba32[2]; + Rgba32[] background = + [ + Color.Red.ToPixel(), + Color.Green.ToPixel() + ]; + + Rgba32 source = Color.Blue.ToPixel(); + float[] amount = [1F, 1F]; + Vector4[] workingBuffer = new Vector4[destination.Length * 2]; + + blender.Blend(Configuration.Default, destination, background, source, amount, workingBuffer); + + Assert.Equal(source, destination[0]); + Assert.Equal(source, destination[1]); + } + + [Fact] + public void Blend_WithSourceSpanAmountSpanAndWorkingBuffer() + { + PixelBlender blender = DefaultPixelBlenders.NormalSrcOver; + Rgba32[] destination = new Rgba32[2]; + Rgba32[] background = + [ + Color.Red.ToPixel(), + Color.Green.ToPixel() + ]; + + Rgba32[] source = + [ + Color.Blue.ToPixel(), + Color.Yellow.ToPixel() + ]; + + float[] amount = [1F, 1F]; + Vector4[] workingBuffer = new Vector4[destination.Length * 3]; + + blender.Blend(Configuration.Default, destination, background, source, amount, workingBuffer); + + Assert.Equal(source[0], destination[0]); + Assert.Equal(source[1], destination[1]); + } + + [Fact] + public void BlendWithCoverage_WithConstantSourceAndSingleAmount() + { + PixelBlender blender = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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() }, @@ -79,6 +866,8 @@ public class PixelBlenderTests { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.SrcIn, Color.MidnightBlue.ToPixel() }, { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) }, { Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelAlphaCompositionMode.SrcOver, Color.MidnightBlue.ToPixel() }, + { new Rgba32(51, 102, 153, 77), new Rgba32(204, 51, 26, 102), .5F, PixelAlphaCompositionMode.Plus, new Rgba32(112, 82, 102, 128) }, + { new Rgba32(230, 51, 26, 191), new Rgba32(204, 230, 77, 191), 1F, PixelAlphaCompositionMode.Plus, new Rgba32(255, 210, 77, 255) }, }; [Theory] @@ -92,4 +881,293 @@ public class PixelBlenderTests // var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4()); } + + private static void AddBlenderModeMappings( + TheoryData data, + PixelAlphaCompositionMode alphaMode, + Type normal, + Type multiply, + Type add, + Type subtract, + Type screen, + Type darken, + Type lighten, + Type overlay, + Type hardLight, + Type colorDodge, + Type colorBurn, + Type softLight, + Type difference, + Type exclusion, + Type hue, + Type saturation, + Type color, + Type luminosity) + { + data.Add(PixelColorBlendingMode.Normal, alphaMode, normal); + data.Add(PixelColorBlendingMode.Multiply, alphaMode, multiply); + data.Add(PixelColorBlendingMode.Add, alphaMode, add); + data.Add(PixelColorBlendingMode.Subtract, alphaMode, subtract); + data.Add(PixelColorBlendingMode.Screen, alphaMode, screen); + data.Add(PixelColorBlendingMode.Darken, alphaMode, darken); + data.Add(PixelColorBlendingMode.Lighten, alphaMode, lighten); + data.Add(PixelColorBlendingMode.Overlay, alphaMode, overlay); + data.Add(PixelColorBlendingMode.HardLight, alphaMode, hardLight); + data.Add(PixelColorBlendingMode.ColorDodge, alphaMode, colorDodge); + data.Add(PixelColorBlendingMode.ColorBurn, alphaMode, colorBurn); + data.Add(PixelColorBlendingMode.SoftLight, alphaMode, softLight); + data.Add(PixelColorBlendingMode.Difference, alphaMode, difference); + data.Add(PixelColorBlendingMode.Exclusion, alphaMode, exclusion); + data.Add(PixelColorBlendingMode.Hue, alphaMode, hue); + data.Add(PixelColorBlendingMode.Saturation, alphaMode, saturation); + data.Add(PixelColorBlendingMode.Color, alphaMode, color); + data.Add(PixelColorBlendingMode.Luminosity, alphaMode, luminosity); + } + + private static void ExerciseAllBlenderModeCombinations() + { + foreach (PixelAlphaCompositionMode alphaMode in Enum.GetValues()) + { + foreach (PixelColorBlendingMode colorMode in Enum.GetValues()) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(colorMode, alphaMode); + ExerciseBlender(blender); + } + } + } + + private static void ExerciseAllAssociatedAlphaBlenderModeCombinations() + { + Rgba32P[] background = + [ + Rgba32P.FromRgba32(new Rgba32(220, 80, 40, 160)), + Rgba32P.FromRgba32(new Rgba32(40, 200, 100, 192)), + Rgba32P.FromRgba32(new Rgba32(120, 60, 230, 224)), + Rgba32P.FromRgba32(new Rgba32(180, 160, 20, 128)), + Rgba32P.FromRgba32(new Rgba32(30, 140, 210, 96)), + ]; + + Rgba32P[] source = + [ + Rgba32P.FromRgba32(new Rgba32(20, 180, 120, 96)), + Rgba32P.FromRgba32(new Rgba32(210, 30, 150, 144)), + Rgba32P.FromRgba32(new Rgba32(80, 220, 40, 176)), + Rgba32P.FromRgba32(new Rgba32(240, 110, 60, 208)), + Rgba32P.FromRgba32(new Rgba32(100, 50, 200, 112)), + ]; + + foreach (PixelAlphaCompositionMode alphaMode in Enum.GetValues()) + { + foreach (PixelColorBlendingMode colorMode in Enum.GetValues()) + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(colorMode, alphaMode); + AssertAssociatedBlenderMatchesScalar(blender, background, source, colorMode, alphaMode); + } + } + } + + private static void AssertAssociatedBlenderMatchesScalar( + PixelBlender blender, + Rgba32P[] associatedBackground, + Rgba32P[] associatedSource, + PixelColorBlendingMode colorMode, + PixelAlphaCompositionMode alphaMode) + { + const float amount = .625F; + float[] amounts = [.125F, .375F, .625F, .875F, 1F]; + float[] coverage = [.2F, .4F, .6F, .8F, 1F]; + Rgba32P constantAssociatedSource = associatedSource[2]; + + Rgba32P[] expected = new Rgba32P[associatedBackground.Length]; + Rgba32P[] actual = new Rgba32P[associatedBackground.Length]; + Vector4[] actualSourceSpanBuffer = new Vector4[associatedBackground.Length * 3]; + Vector4[] actualConstantSourceBuffer = new Vector4[associatedBackground.Length * 2]; + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = blender.Blend(associatedBackground[i], associatedSource[i], amount); + } + + blender.Blend(Configuration.Default, actual, associatedBackground, associatedSource, amount, actualSourceSpanBuffer); + AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanSingleAmount"); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = blender.Blend(associatedBackground[i], constantAssociatedSource, amount); + } + + blender.Blend(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amount, actualConstantSourceBuffer); + AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceSingleAmount"); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = blender.Blend(associatedBackground[i], associatedSource[i], amounts[i]); + } + + blender.Blend(Configuration.Default, actual, associatedBackground, associatedSource, amounts, actualSourceSpanBuffer); + AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanAmountSpan"); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = blender.Blend(associatedBackground[i], constantAssociatedSource, amounts[i]); + } + + blender.Blend(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amounts, actualConstantSourceBuffer); + AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceAmountSpan"); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], associatedSource[i], amount, coverage[i]); + } + + blender.BlendWithCoverage(Configuration.Default, actual, associatedBackground, associatedSource, amount, coverage, actualSourceSpanBuffer); + AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanSingleAmountCoverage"); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], constantAssociatedSource, amount, coverage[i]); + } + + blender.BlendWithCoverage(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amount, coverage, actualConstantSourceBuffer); + AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceSingleAmountCoverage"); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], associatedSource[i], amounts[i], coverage[i]); + } + + blender.BlendWithCoverage(Configuration.Default, actual, associatedBackground, associatedSource, amounts, coverage, actualSourceSpanBuffer); + AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "SourceSpanAmountSpanCoverage"); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = BlendWithCoverageScalar(blender, associatedBackground[i], constantAssociatedSource, amounts[i], coverage[i]); + } + + blender.BlendWithCoverage(Configuration.Default, actual, associatedBackground, constantAssociatedSource, amounts, coverage, actualConstantSourceBuffer); + AssertRgba32PEqual(expected, actual, colorMode, alphaMode, "ConstantSourceAmountSpanCoverage"); + } + + private static Rgba32P BlendWithCoverageScalar(PixelBlender blender, Rgba32P background, Rgba32P source, float amount, float coverage) + { + Span destination = stackalloc Rgba32P[1]; + Span backgroundSpan = [background]; + Span sourceSpan = [source]; + Span coverageSpan = [coverage]; + Span buffer = stackalloc Vector4[3]; + + // A one-pixel span takes the scalar remainder path, providing an exact oracle for each SIMD coverage result. + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverageSpan, buffer); + return destination[0]; + } + + private static void AssertRgba32PEqual( + ReadOnlySpan expected, + ReadOnlySpan actual, + PixelColorBlendingMode colorMode, + PixelAlphaCompositionMode alphaMode, + string scenario) + { + for (int i = 0; i < expected.Length; i++) + { + Assert.True(expected[i] == actual[i], $"{colorMode}/{alphaMode}/{scenario}[{i}]: expected {expected[i]}, actual {actual[i]}"); + } + } + + private static void ExerciseBlender(PixelBlender blender) + { + Rgba32 background = Color.MistyRose.ToPixel(); + Rgba32 source = Color.MidnightBlue.ToPixel(); + + ExerciseBlender(blender, background, source); + } + + /// + /// Exercises every bulk overload across vector-width boundaries and compares it with single-pixel composition. + /// + /// The pixel format. + /// The blender under test. + /// The background pixel. + /// The source pixel. + private static void ExerciseBlender(PixelBlender blender, TPixel background, TPixel source) + where TPixel : unmanaged, IPixel + { + const float amount = .625F; + + float[] amounts = [0F, .125F, .375F, .625F, .875F, 1F, .5F]; + float[] coverage = [1F, .8F, .6F, .4F, .2F, 0F, .5F]; + + // Seven pixels exercise one AVX-512 batch plus three tails, or three AVX2 batches plus one tail. + TPixel[] destination = new TPixel[7]; + TPixel[] expected = new TPixel[7]; + TPixel[] backgroundSpan = [background, background, background, background, background, background, background]; + TPixel[] sourceSpan = [source, source, source, source, source, source, source]; + Vector4[] sourceSpanBuffer = new Vector4[destination.Length * 3]; + Vector4[] constantSourceBuffer = new Vector4[destination.Length * 2]; + + Array.Fill(expected, blender.Blend(background, source, amount)); + + blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, sourceSpanBuffer); + Assert.Equal(expected, destination); + + blender.Blend(Configuration.Default, destination, backgroundSpan, source, amount, constantSourceBuffer); + Assert.Equal(expected, destination); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = blender.Blend(background, source, amounts[i]); + } + + blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, amounts, sourceSpanBuffer); + Assert.Equal(expected, destination); + + blender.Blend(Configuration.Default, destination, backgroundSpan, source, amounts, constantSourceBuffer); + Assert.Equal(expected, destination); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = BlendWithCoverageScalar(blender, background, source, amount, coverage[i]); + } + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverage, sourceSpanBuffer); + Assert.Equal(expected, destination); + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amount, coverage, constantSourceBuffer); + Assert.Equal(expected, destination); + + for (int i = 0; i < expected.Length; i++) + { + expected[i] = BlendWithCoverageScalar(blender, background, source, amounts[i], coverage[i]); + } + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amounts, coverage, sourceSpanBuffer); + Assert.Equal(expected, destination); + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, source, amounts, coverage, constantSourceBuffer); + Assert.Equal(expected, destination); + } + + /// + /// Computes a one-pixel coverage result through the scalar remainder path. + /// + /// The pixel format. + /// The blender under test. + /// The background pixel. + /// The source pixel. + /// The source opacity. + /// The pixel coverage. + /// The blended pixel. + private static TPixel BlendWithCoverageScalar(PixelBlender blender, TPixel background, TPixel source, float amount, float coverage) + where TPixel : unmanaged, IPixel + { + Span destination = stackalloc TPixel[1]; + Span backgroundSpan = [background]; + Span sourceSpan = [source]; + Span coverageSpan = [coverage]; + Span buffer = stackalloc Vector4[3]; + + blender.BlendWithCoverage(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, coverageSpan, buffer); + + return destination[0]; + } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs index 1086afe76..f8c296846 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs @@ -59,7 +59,7 @@ public class PorterDuffCompositorTests FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX, provider, mode.ToString()); } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs index 976a272eb..d2ca3f12c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -4,7 +4,7 @@ using System.Numerics; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -using Castle.Components.DictionaryAdapter; +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -45,6 +45,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.NormalSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData MultiplyFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -77,6 +93,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.MultiplySrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData AddFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -109,6 +141,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.AddSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData SubtractFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(0, 0, 0, 1) }, @@ -141,6 +189,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(SubtractFunctionData))] + public void SubtractFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.SubtractSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData ScreenFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -173,6 +237,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.ScreenSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData DarkenFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -205,6 +285,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.DarkenSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData LightenFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -237,6 +333,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.LightenSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData OverlayFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -269,6 +381,22 @@ public class PorterDuffFunctionsTests Assert.Equal(expected256, actual, FloatComparer); } + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.OverlaySrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + public static TheoryData HardLightFunctionData { get; } = new() { { new TestVector4(1, 1, 1, 1), new TestVector4(1, 1, 1, 1), 1, new TestVector4(1, 1, 1, 1) }, @@ -300,4 +428,154 @@ public class PorterDuffFunctionsTests Vector256 actual = PorterDuffFunctions.HardLightSrcOver(back256, source256, Vector256.Create(amount)); Assert.Equal(expected256, actual, FloatComparer); } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction512(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 back512 = CreateVector512(back); + Vector512 source512 = CreateVector512(source); + Vector512 expected512 = CreateVector512(expected); + Vector512 actual = PorterDuffFunctions.HardLightSrcOver(back512, source512, Vector512.Create(amount)); + Assert.Equal(expected512, actual, FloatComparer); + } + + public static TheoryData ExtendedBlendFunctionData { get; } = new() + { + { PixelColorBlendingMode.ColorDodge, CreateTestVector(0F, .25F, .8F, 1F), CreateTestVector(1F, .5F, .5F, 1F), CreateTestVector(0F, .5F, 1F, 1F) }, + { PixelColorBlendingMode.ColorBurn, CreateTestVector(1F, .4F, .75F, 1F), CreateTestVector(0F, 0F, .5F, 1F), CreateTestVector(1F, 0F, .5F, 1F) }, + { PixelColorBlendingMode.SoftLight, CreateTestVector(.25F, .64F, .16F, 1F), CreateTestVector(.75F, .75F, .25F, 1F), CreateTestVector(.375F, .72F, .0928F, 1F) }, + { PixelColorBlendingMode.SoftLight, CreateTestVector(.25F, .36F, .81F, 1F), CreateTestVector(.5F, .5F, .5F, 1F), CreateTestVector(.25F, .36F, .81F, 1F) }, + { PixelColorBlendingMode.Difference, CreateTestVector(.1F, .8F, .4F, 1F), CreateTestVector(.7F, .2F, .4F, 1F), CreateTestVector(.6F, .6F, 0F, 1F) }, + { PixelColorBlendingMode.Exclusion, CreateTestVector(.1F, .8F, .4F, 1F), CreateTestVector(.7F, .2F, .4F, 1F), CreateTestVector(.66F, .68F, .48F, 1F) }, + { PixelColorBlendingMode.Hue, CreateTestVector(.2F, .7F, .4F, 1F), CreateTestVector(.9F, .1F, .6F, 1F), CreateTestVector(.832625F, .332625F, .645125F, 1F) }, + { PixelColorBlendingMode.Saturation, CreateTestVector(.2F, .7F, .4F, 1F), CreateTestVector(.9F, .1F, .6F, 1F), CreateTestVector(.0098F, .8098F, .3298F, 1F) }, + { PixelColorBlendingMode.Color, CreateTestVector(.2F, .7F, .4F, 1F), CreateTestVector(.9F, .1F, .6F, 1F), CreateTestVector(1F, .234851485F, .713069307F, 1F) }, + { PixelColorBlendingMode.Luminosity, CreateTestVector(.2F, .7F, .4F, 1F), CreateTestVector(.9F, .1F, .6F, 1F), CreateTestVector(.078F, .578F, .278F, 1F) }, + { PixelColorBlendingMode.Hue, CreateTestVector(.5F, .5F, .5F, 1F), CreateTestVector(.9F, .2F, .6F, 1F), CreateTestVector(.5F, .5F, .5F, 1F) }, + { PixelColorBlendingMode.Saturation, CreateTestVector(.5F, .5F, .5F, 1F), CreateTestVector(.9F, .2F, .6F, 1F), CreateTestVector(.5F, .5F, .5F, 1F) }, + }; + + [Theory] + [MemberData(nameof(ExtendedBlendFunctionData))] + public void ExtendedBlendFunction(PixelColorBlendingMode mode, TestVector4 back, TestVector4 source, TestVector4 expected) + { + Vector4 actual = InvokeExtendedBlend(mode, back, source, 1F); + VectorAssert.Equal(expected, actual, 4); + } + + [Theory] + [MemberData(nameof(ExtendedBlendFunctionData))] + public void ExtendedBlendFunction256(PixelColorBlendingMode mode, TestVector4 back, TestVector4 source, TestVector4 expected) + { + if (!Avx.IsSupported) + { + return; + } + + Vector256 actual = InvokeExtendedBlend(mode, Vector256.Create(back.AsVector().AsVector128(), back.AsVector().AsVector128()), Vector256.Create(source.AsVector().AsVector128(), source.AsVector().AsVector128()), Vector256.Create(1F)); + Vector256 expectedVector = Vector256.Create(expected.AsVector().AsVector128(), expected.AsVector().AsVector128()); + Assert.Equal(expectedVector, actual, FloatComparer); + } + + [Theory] + [MemberData(nameof(ExtendedBlendFunctionData))] + public void ExtendedBlendFunction512(PixelColorBlendingMode mode, TestVector4 back, TestVector4 source, TestVector4 expected) + { + if (!Avx512F.IsSupported) + { + return; + } + + Vector512 actual = InvokeExtendedBlend(mode, CreateVector512(back), CreateVector512(source), Vector512.Create(1F)); + Assert.Equal(CreateVector512(expected), actual, FloatComparer); + } + + /// + /// Creates serializable vector test data without relying on , + /// which assigns Z from X and can hide channel-specific regressions. + /// + /// The X component. + /// The Y component. + /// The Z component. + /// The W component. + /// The test vector. + private static TestVector4 CreateTestVector(float x, float y, float z, float w) + => new() { X = x, Y = y, Z = z, W = w }; + + /// + /// Invokes a new blend mode through its straight-alpha source-over function. + /// + /// The color blending mode. + /// The backdrop vector. + /// The source vector. + /// The source amount. + /// The composition result. + private static Vector4 InvokeExtendedBlend(PixelColorBlendingMode mode, Vector4 backdrop, Vector4 source, float amount) + => mode switch + { + PixelColorBlendingMode.ColorDodge => PorterDuffFunctions.ColorDodgeSrcOver(backdrop, source, amount), + PixelColorBlendingMode.ColorBurn => PorterDuffFunctions.ColorBurnSrcOver(backdrop, source, amount), + PixelColorBlendingMode.SoftLight => PorterDuffFunctions.SoftLightSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Difference => PorterDuffFunctions.DifferenceSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Exclusion => PorterDuffFunctions.ExclusionSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Hue => PorterDuffFunctions.HueSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Saturation => PorterDuffFunctions.SaturationSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Color => PorterDuffFunctions.ColorSrcOver(backdrop, source, amount), + _ => PorterDuffFunctions.LuminositySrcOver(backdrop, source, amount), + }; + + /// + private static Vector256 InvokeExtendedBlend(PixelColorBlendingMode mode, Vector256 backdrop, Vector256 source, Vector256 amount) + => mode switch + { + PixelColorBlendingMode.ColorDodge => PorterDuffFunctions.ColorDodgeSrcOver(backdrop, source, amount), + PixelColorBlendingMode.ColorBurn => PorterDuffFunctions.ColorBurnSrcOver(backdrop, source, amount), + PixelColorBlendingMode.SoftLight => PorterDuffFunctions.SoftLightSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Difference => PorterDuffFunctions.DifferenceSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Exclusion => PorterDuffFunctions.ExclusionSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Hue => PorterDuffFunctions.HueSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Saturation => PorterDuffFunctions.SaturationSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Color => PorterDuffFunctions.ColorSrcOver(backdrop, source, amount), + _ => PorterDuffFunctions.LuminositySrcOver(backdrop, source, amount), + }; + + /// + private static Vector512 InvokeExtendedBlend(PixelColorBlendingMode mode, Vector512 backdrop, Vector512 source, Vector512 amount) + => mode switch + { + PixelColorBlendingMode.ColorDodge => PorterDuffFunctions.ColorDodgeSrcOver(backdrop, source, amount), + PixelColorBlendingMode.ColorBurn => PorterDuffFunctions.ColorBurnSrcOver(backdrop, source, amount), + PixelColorBlendingMode.SoftLight => PorterDuffFunctions.SoftLightSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Difference => PorterDuffFunctions.DifferenceSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Exclusion => PorterDuffFunctions.ExclusionSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Hue => PorterDuffFunctions.HueSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Saturation => PorterDuffFunctions.SaturationSrcOver(backdrop, source, amount), + PixelColorBlendingMode.Color => PorterDuffFunctions.ColorSrcOver(backdrop, source, amount), + _ => PorterDuffFunctions.LuminositySrcOver(backdrop, source, amount), + }; + + private static Vector512 CreateVector512(TestVector4 vector) + => Vector512.Create( + vector.X, + vector.Y, + vector.Z, + vector.W, + vector.X, + vector.Y, + vector.Z, + vector.W, + vector.X, + vector.Y, + vector.Z, + vector.W, + vector.X, + vector.Y, + vector.Z, + vector.W); } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs index 2c97cbde0..00b86b743 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTestsTPixel.cs @@ -9,10 +9,19 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders; public class PorterDuffFunctionsTestsTPixel { + private const int BulkBlendCount = 4; + private static Span AsSpan(T value) where T : struct { - return new Span(new[] { value }); + return new Span([value]); + } + + private static T[] CreateFilledArray(T value) + { + T[] values = new T[BulkBlendCount]; + values.AsSpan().Fill(value); + return values; } public static TheoryData NormalBlendFunctionData = new() @@ -37,7 +46,7 @@ public class PorterDuffFunctionsTestsTPixel public void NormalBlendFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.NormalSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.NormalSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -46,9 +55,14 @@ public class PorterDuffFunctionsTestsTPixel public void NormalBlendFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.NormalSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + DefaultPixelBlenders.NormalSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData MultiplyFunctionData = new() @@ -77,7 +91,7 @@ public class PorterDuffFunctionsTestsTPixel public void MultiplyFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.MultiplySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.MultiplySrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -86,9 +100,14 @@ public class PorterDuffFunctionsTestsTPixel public void MultiplyFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.MultiplySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + DefaultPixelBlenders.MultiplySrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData AddFunctionData = new() @@ -127,7 +146,7 @@ public class PorterDuffFunctionsTestsTPixel public void AddFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.AddSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.AddSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -136,9 +155,14 @@ public class PorterDuffFunctionsTestsTPixel public void AddFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.AddSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + DefaultPixelBlenders.AddSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData SubtractFunctionData = new() @@ -167,7 +191,7 @@ public class PorterDuffFunctionsTestsTPixel public void SubtractFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.SubtractSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.SubtractSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -176,9 +200,14 @@ public class PorterDuffFunctionsTestsTPixel public void SubtractFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.SubtractSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + DefaultPixelBlenders.SubtractSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData ScreenFunctionData = new() @@ -207,7 +236,7 @@ public class PorterDuffFunctionsTestsTPixel public void ScreenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.ScreenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.ScreenSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -216,9 +245,14 @@ public class PorterDuffFunctionsTestsTPixel public void ScreenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.ScreenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + DefaultPixelBlenders.ScreenSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData DarkenFunctionData = new() @@ -247,7 +281,7 @@ public class PorterDuffFunctionsTestsTPixel public void DarkenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.DarkenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.DarkenSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -256,9 +290,14 @@ public class PorterDuffFunctionsTestsTPixel public void DarkenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.DarkenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + DefaultPixelBlenders.DarkenSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData LightenFunctionData = new() @@ -287,7 +326,7 @@ public class PorterDuffFunctionsTestsTPixel public void LightenFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.LightenSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.LightenSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -296,9 +335,14 @@ public class PorterDuffFunctionsTestsTPixel public void LightenFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.LightenSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + DefaultPixelBlenders.LightenSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData OverlayFunctionData = new() @@ -327,7 +371,7 @@ public class PorterDuffFunctionsTestsTPixel public void OverlayFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.OverlaySrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.OverlaySrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -336,9 +380,14 @@ public class PorterDuffFunctionsTestsTPixel public void OverlayFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.OverlaySrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + DefaultPixelBlenders.OverlaySrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } public static TheoryData HardLightFunctionData = new() @@ -367,7 +416,7 @@ public class PorterDuffFunctionsTestsTPixel public void HardLightFunctionBlender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - TPixel actual = new DefaultPixelBlenders.HardLightSrcOver().Blend(back.AsPixel(), source.AsPixel(), amount); + TPixel actual = DefaultPixelBlenders.HardLightSrcOver.Blend(back.AsPixel(), source.AsPixel(), amount); VectorAssert.Equal(expected.AsPixel(), actual, 2); } @@ -376,8 +425,13 @@ public class PorterDuffFunctionsTestsTPixel public void HardLightFunctionBlenderBulk(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : unmanaged, IPixel { - Span dest = new(new TPixel[1]); - new DefaultPixelBlenders.HardLightSrcOver().Blend(this.Configuration, dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); - VectorAssert.Equal(expected.AsPixel(), dest[0], 2); + TPixel[] dest = new TPixel[BulkBlendCount]; + DefaultPixelBlenders.HardLightSrcOver.Blend(this.Configuration, dest, CreateFilledArray(back.AsPixel()), CreateFilledArray(source.AsPixel()), CreateFilledArray(amount)); + + TPixel expectedPixel = expected.AsPixel(); + foreach (TPixel pixel in dest) + { + VectorAssert.Equal(expectedPixel, pixel, 2); + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs index 2a5c5765a..dbff54140 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.PixelFormats; @@ -73,13 +74,11 @@ public abstract partial class PixelConverterTests } internal static void To( - Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) where TSourcePixel : unmanaged, IPixel where TDestinationPixel : unmanaged, IPixel { - Guard.NotNull(configuration, nameof(configuration)); Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); int count = sourcePixels.Length; @@ -92,13 +91,14 @@ public abstract partial class PixelConverterTests return; } - // Normal conversion + // Scalar source and destination boundaries are the reference for the bulk operation: each format owns any representation conversion and destination quantization it requires. ref TDestinationPixel destRef = ref MemoryMarshal.GetReference(destinationPixels); for (int i = 0; i < count; i++) { ref TSourcePixel sp = ref Unsafe.Add(ref sourceRef, i); ref TDestinationPixel dp = ref Unsafe.Add(ref destRef, i); - dp = TDestinationPixel.FromScaledVector4(sp.ToScaledVector4()); + Vector4 vector = sp.ToUnassociatedScaledVector4(); + dp = TDestinationPixel.FromUnassociatedScaledVector4(vector); } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs index b37282927..306c2e628 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs @@ -1,18 +1,16 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. // using SixLabors.ImageSharp.PixelFormats; using Xunit; -using Xunit.Abstractions; - namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; public partial class PixelOperationsTests { - + public partial class A8_OperationsTests : PixelOperationsTests { public A8_OperationsTests(ITestOutputHelper output) @@ -43,6 +41,21 @@ public partial class PixelOperationsTests } } + public partial class Argb32P_OperationsTests : PixelOperationsTests + { + public Argb32P_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = Argb32P.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation); + } + } + public partial class Abgr32_OperationsTests : PixelOperationsTests { public Abgr32_OperationsTests(ITestOutputHelper output) @@ -58,6 +71,21 @@ public partial class PixelOperationsTests } } + public partial class Abgr32P_OperationsTests : PixelOperationsTests + { + public Abgr32P_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = Abgr32P.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation); + } + } + public partial class Bgr24_OperationsTests : PixelOperationsTests { public Bgr24_OperationsTests(ITestOutputHelper output) @@ -103,6 +131,21 @@ public partial class PixelOperationsTests } } + public partial class Bgra32P_OperationsTests : PixelOperationsTests + { + public Bgra32P_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = Bgra32P.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation); + } + } + public partial class Bgra4444_OperationsTests : PixelOperationsTests { public Bgra4444_OperationsTests(ITestOutputHelper output) @@ -193,6 +236,21 @@ public partial class PixelOperationsTests } } + public partial class HalfVector4P_OperationsTests : PixelOperationsTests + { + public HalfVector4P_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = HalfVector4P.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation); + } + } + public partial class L16_OperationsTests : PixelOperationsTests { public L16_OperationsTests(ITestOutputHelper output) @@ -283,6 +341,21 @@ public partial class PixelOperationsTests } } + public partial class NormalizedByte4P_OperationsTests : PixelOperationsTests + { + public NormalizedByte4P_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = NormalizedByte4P.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation); + } + } + public partial class NormalizedShort2_OperationsTests : PixelOperationsTests { public NormalizedShort2_OperationsTests(ITestOutputHelper output) @@ -388,6 +461,21 @@ public partial class PixelOperationsTests } } + public partial class Rgba32P_OperationsTests : PixelOperationsTests + { + public Rgba32P_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = Rgba32P.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation); + } + } + public partial class Rgba64_OperationsTests : PixelOperationsTests { public Rgba64_OperationsTests(ITestOutputHelper output) @@ -403,6 +491,36 @@ public partial class PixelOperationsTests } } + public partial class RgbaHalf_OperationsTests : PixelOperationsTests + { + public RgbaHalf_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = RgbaHalf.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); + } + } + + public partial class RgbaHalfP_OperationsTests : PixelOperationsTests + { + public RgbaHalfP_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = RgbaHalfP.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Associated, alphaRepresentation); + } + } + public partial class RgbaVector_OperationsTests : PixelOperationsTests { public RgbaVector_OperationsTests(ITestOutputHelper output) diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude index ccf90fe40..90c706733 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude @@ -10,7 +10,6 @@ using SixLabors.ImageSharp.PixelFormats; using Xunit; -using Xunit.Abstractions; <#+ private static readonly string[] UnassociatedAlphaPixelTypes = [ @@ -29,32 +28,47 @@ using Xunit.Abstractions; "Rgba1010102", "Rgba32", "Rgba64", + "RgbaHalf", "RgbaVector", "Short4" ]; - private static readonly string[] AssociatedAlphaPixelTypes = []; + private static readonly string[] AssociatedAlphaPixelTypes = + [ + "Argb32P", + "Abgr32P", + "Bgra32P", + "HalfVector4P", + "NormalizedByte4P", + "Rgba32P", + "RgbaHalfP" + ]; private static readonly string[] CommonPixelTypes = [ "A8", "Argb32", + "Argb32P", "Abgr32", + "Abgr32P", "Bgr24", "Bgr565", "Bgra32", + "Bgra32P", "Bgra4444", "Bgra5551", "Byte4", "HalfSingle", "HalfVector2", "HalfVector4", + "HalfVector4P", "L16", "L8", "La16", "La32", "NormalizedByte2", "NormalizedByte4", + "NormalizedByte4P", "NormalizedShort2", "NormalizedShort4", "Rg32", @@ -62,7 +76,10 @@ using Xunit.Abstractions; "Rgb48", "Rgba1010102", "Rgba32", + "Rgba32P", "Rgba64", + "RgbaHalf", + "RgbaHalfP", "RgbaVector", "Short2", "Short4" diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 32b62fc03..0fa34af39 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -10,7 +10,6 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Common; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; @@ -70,7 +69,7 @@ public abstract class PixelOperationsTests : MeasureFixture protected virtual PixelOperations Operations { get; } = PixelOperations.Instance; - protected bool HasUnassociatedAlpha => TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Unassociated; + protected bool HasAssociatedAlpha => TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Associated; internal static TPixel[] CreateExpectedPixelData(Vector4[] source, RefAction vectorModifier = null) { @@ -102,6 +101,35 @@ public abstract class PixelOperationsTests : MeasureFixture return expected; } + /// + /// Creates the scalar oracle for companded scaled input. + /// + /// The source vectors. + /// Whether the source vectors use associated alpha. + /// The converted pixels. + internal static TPixel[] CreateCompandedExpectedPixelData(Vector4[] source, bool associated) + { + TPixel[] expected = new TPixel[source.Length]; + + for (int i = 0; i < source.Length; i++) + { + Vector4 vector = source[i]; + + if (associated) + { + Numerics.UnPremultiply(ref vector); + } + + vector = SRgbCompanding.Compress(vector); + + // Transfer functions operate on straight RGB. Let the destination pixel perform any required association so its stored + // alpha quantization participates exactly once, matching the public bulk conversion contract. + expected[i] = TPixel.FromUnassociatedScaledVector4(vector); + } + + return expected; + } + [Fact] public void PixelTypeInfoHasCorrectBitsPerPixel() { @@ -161,12 +189,18 @@ public abstract class PixelOperationsTests : MeasureFixture [MemberData(nameof(ArraySizesData))] public void FromCompandedScaledVector4(int count) { - void SourceAction(ref Vector4 v) => v = SRgbCompanding.Expand(v); + void SourceAction(ref Vector4 v) + { + v = SRgbCompanding.Expand(v); - void ExpectedAction(ref Vector4 v) => v = SRgbCompanding.Compress(v); + if (this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } + } Vector4[] source = CreateVector4TestData(count, SourceAction); - TPixel[] expected = CreateScaledExpectedPixelData(source, ExpectedAction); + TPixel[] expected = CreateCompandedExpectedPixelData(source, this.HasAssociatedAlpha); TestOperation( source, @@ -183,35 +217,27 @@ public abstract class PixelOperationsTests : MeasureFixture [MemberData(nameof(ArraySizesData))] public void FromPremultipliedVector4(int count) { - void SourceAction(ref Vector4 v) - { - if (this.HasUnassociatedAlpha) - { - Numerics.Premultiply(ref v); - } - } + Vector4[] source = CreateVector4TestData(count); + TPixel[] expected = new TPixel[count]; - void ExpectedAction(ref Vector4 v) + for (int i = 0; i < source.Length; i++) { - if (this.HasUnassociatedAlpha) - { - Numerics.UnPremultiply(ref v); - } + // Native W is not necessarily opacity. Use the per-pixel representation contract to create and consume associated-native vectors. + TPixel pixel = TPixel.FromUnassociatedVector4(source[i]); + source[i] = pixel.ToAssociatedVector4(); + expected[i] = TPixel.FromAssociatedVector4(source[i]); } - Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); - TPixel[] expected = CreateExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); - TestOperation( source, expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; - - this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers); + this.Operations.FromVector4Destructive( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply); }); } @@ -221,7 +247,7 @@ public abstract class PixelOperationsTests : MeasureFixture { void SourceAction(ref Vector4 v) { - if (this.HasUnassociatedAlpha) + if (!this.HasAssociatedAlpha) { Numerics.Premultiply(ref v); } @@ -229,7 +255,7 @@ public abstract class PixelOperationsTests : MeasureFixture void ExpectedAction(ref Vector4 v) { - if (this.HasUnassociatedAlpha) + if (!this.HasAssociatedAlpha) { Numerics.UnPremultiply(ref v); } @@ -243,15 +269,11 @@ public abstract class PixelOperationsTests : MeasureFixture expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; - this.Operations.FromVector4Destructive( - this.Configuration, - s, - d.GetSpan(), - modifiers | PixelConversionModifiers.Scale); + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale); }); } @@ -262,40 +284,22 @@ public abstract class PixelOperationsTests : MeasureFixture void SourceAction(ref Vector4 v) { v = SRgbCompanding.Expand(v); - - if (this.HasUnassociatedAlpha) - { - Numerics.Premultiply(ref v); - } - } - - void ExpectedAction(ref Vector4 v) - { - if (this.HasUnassociatedAlpha) - { - Numerics.UnPremultiply(ref v); - } - - v = SRgbCompanding.Compress(v); + Numerics.Premultiply(ref v); } Vector4[] source = CreateVector4TestData(count, SourceAction); - TPixel[] expected = CreateScaledExpectedPixelData(source, ExpectedAction); + TPixel[] expected = CreateCompandedExpectedPixelData(source, true); TestOperation( source, expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha - ? PixelConversionModifiers.Premultiply - : PixelConversionModifiers.None; - this.Operations.FromVector4Destructive( - this.Configuration, - s, - d.GetSpan(), - modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale); + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Premultiply | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale); }, false); } @@ -313,38 +317,47 @@ public abstract class PixelOperationsTests : MeasureFixture (s, d) => this.Operations.ToVector4(this.Configuration, s, d.GetSpan())); } - public static readonly TheoryData Generic_To_Data = new() - { - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - new TestPixel(), - }; + public static readonly TheoryData Generic_To_Data = new( + new object[] + { + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + new TestPixel(), + }); [Theory] [MemberData(nameof(Generic_To_Data))] @@ -355,7 +368,7 @@ public abstract class PixelOperationsTests : MeasureFixture TPixel[] source = CreatePixelTestData(count); TDestPixel[] expected = new TDestPixel[count]; - PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); + PixelConverterTests.ReferenceImplementations.To(source, expected); TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()), false); } @@ -383,9 +396,26 @@ public abstract class PixelOperationsTests : MeasureFixture { void SourceAction(ref Vector4 v) { + if (this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } } - void ExpectedAction(ref Vector4 v) => v = SRgbCompanding.Expand(v); + void ExpectedAction(ref Vector4 v) + { + if (this.HasAssociatedAlpha) + { + Numerics.UnPremultiply(ref v); + } + + v = SRgbCompanding.Expand(v); + + if (this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } + } TPixel[] source = CreateScaledPixelTestData(count, SourceAction); Vector4[] expected = CreateExpectedScaledVector4Data(source, ExpectedAction); @@ -404,15 +434,15 @@ public abstract class PixelOperationsTests : MeasureFixture [MemberData(nameof(ArraySizesData))] public void ToPremultipliedVector4(int count) { - void SourceAction(ref Vector4 v) + TPixel[] source = CreatePixelTestData(count); + Vector4[] expected = new Vector4[count]; + + for (int i = 0; i < source.Length; i++) { + // Association is defined by the pixel format because native W can be affine or integer-valued rather than normalized opacity. + expected[i] = source[i].ToAssociatedVector4(); } - void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); - - TPixel[] source = CreatePixelTestData(count, SourceAction); - Vector4[] expected = CreateExpectedVector4Data(source, ExpectedAction); - TestOperation( source, expected, @@ -427,7 +457,13 @@ public abstract class PixelOperationsTests : MeasureFixture { } - void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); + void ExpectedAction(ref Vector4 v) + { + if (!this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } + } TPixel[] source = CreateScaledPixelTestData(count, SourceAction); Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -448,10 +484,19 @@ public abstract class PixelOperationsTests : MeasureFixture { void SourceAction(ref Vector4 v) { + if (this.HasAssociatedAlpha) + { + Numerics.Premultiply(ref v); + } } void ExpectedAction(ref Vector4 v) { + if (this.HasAssociatedAlpha) + { + Numerics.UnPremultiply(ref v); + } + v = SRgbCompanding.Expand(v); Numerics.Premultiply(ref v); } @@ -499,7 +544,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i4 = i * 4; - Argb32 argb = Argb32.FromScaledVector4(source[i].ToScaledVector4()); + Argb32 argb = Argb32.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i4] = argb.A; expected[i4 + 1] = argb.R; @@ -543,7 +588,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i3 = i * 3; - Bgr24 bgr = Bgr24.FromScaledVector4(source[i].ToScaledVector4()); + Bgr24 bgr = Bgr24.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i3] = bgr.B; expected[i3 + 1] = bgr.G; expected[i3 + 2] = bgr.R; @@ -585,7 +630,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i4 = i * 4; - Bgra32 bgra = Bgra32.FromScaledVector4(source[i].ToScaledVector4()); + Bgra32 bgra = Bgra32.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i4] = bgra.B; expected[i4 + 1] = bgra.G; expected[i4 + 2] = bgra.R; @@ -628,7 +673,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i4 = i * 4; - Abgr32 abgr = Abgr32.FromScaledVector4(source[i].ToScaledVector4()); + Abgr32 abgr = Abgr32.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i4] = abgr.A; expected[i4 + 1] = abgr.B; expected[i4 + 2] = abgr.G; @@ -674,7 +719,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int offset = i * size; - Bgra5551 bgra = Bgra5551.FromScaledVector4(source[i].ToScaledVector4()); + Bgra5551 bgra = Bgra5551.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); OctetBytes bytes = Unsafe.As(ref bgra); expected[offset] = bytes[0]; expected[offset + 1] = bytes[1]; @@ -691,7 +736,7 @@ public abstract class PixelOperationsTests : MeasureFixture public void FromL8(int count) { byte[] sourceBytes = CreateByteTestData(count); - L8[] source = sourceBytes.Select(b => new L8(b)).ToArray(); + L8[] source = [.. sourceBytes.Select(b => new L8(b))]; TPixel[] expected = new TPixel[count]; for (int i = 0; i < count; i++) @@ -714,7 +759,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { - expected[i] = L8.FromScaledVector4(source[i].ToScaledVector4()); + expected[i] = L8.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); } TestOperation( @@ -727,7 +772,7 @@ public abstract class PixelOperationsTests : MeasureFixture [MemberData(nameof(ArraySizesData))] public void FromL16(int count) { - L16[] source = CreateVector4TestData(count).Select(L16.FromVector4).ToArray(); + L16[] source = [.. CreateVector4TestData(count).Select(L16.FromVector4)]; TPixel[] expected = new TPixel[count]; @@ -751,7 +796,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { - expected[i] = L16.FromScaledVector4(source[i].ToScaledVector4()); + expected[i] = L16.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); } TestOperation( @@ -793,7 +838,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int offset = i * size; - La16 la = La16.FromScaledVector4(source[i].ToScaledVector4()); + La16 la = La16.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); OctetBytes bytes = Unsafe.As(ref la); expected[offset] = bytes[0]; expected[offset + 1] = bytes[1]; @@ -838,7 +883,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int offset = i * size; - La32 la = La32.FromScaledVector4(source[i].ToScaledVector4()); + La32 la = La32.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); OctetBytes bytes = Unsafe.As(ref la); expected[offset] = bytes[0]; expected[offset + 1] = bytes[1]; @@ -882,7 +927,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i3 = i * 3; - Rgb24 rgb = Rgb24.FromScaledVector4(source[i].ToScaledVector4()); + Rgb24 rgb = Rgb24.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i3] = rgb.R; expected[i3 + 1] = rgb.G; expected[i3 + 2] = rgb.B; @@ -924,7 +969,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i4 = i * 4; - Rgba32 rgba = Rgba32.FromScaledVector4(source[i].ToScaledVector4()); + Rgba32 rgba = Rgba32.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); expected[i4] = rgba.R; expected[i4 + 1] = rgba.G; expected[i4 + 2] = rgba.B; @@ -967,7 +1012,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i6 = i * 6; - Rgb48 rgb = Rgb48.FromScaledVector4(source[i].ToScaledVector4()); + Rgb48 rgb = Rgb48.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); OctetBytes rgb48Bytes = Unsafe.As(ref rgb); expected[i6] = rgb48Bytes[0]; expected[i6 + 1] = rgb48Bytes[1]; @@ -1013,7 +1058,7 @@ public abstract class PixelOperationsTests : MeasureFixture for (int i = 0; i < count; i++) { int i8 = i * 8; - Rgba64 rgba = Rgba64.FromScaledVector4(source[i].ToScaledVector4()); + Rgba64 rgba = Rgba64.FromScaledVector4(ToUnassociatedScaledVector4(source[i])); OctetBytes rgba64Bytes = Unsafe.As(ref rgba); expected[i8] = rgba64Bytes[0]; expected[i8 + 1] = rgba64Bytes[1]; @@ -1072,6 +1117,9 @@ public abstract class PixelOperationsTests : MeasureFixture return expected; } + // The scalar conversion boundary owns representation-specific rounding. Byte-export tests compare each bulk operation with that scalar result. + private static Vector4 ToUnassociatedScaledVector4(TPixel source) => source.ToUnassociatedScaledVector4(); + internal static void TestOperation( TSource[] source, TDest[] expected, @@ -1208,15 +1256,34 @@ public abstract class PixelOperationsTests : MeasureFixture Assert.Equal(expected[i], actual[i], comparer); } } - else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)) && IsComplexPixel()) + else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest))) { Span expected = this.ExpectedDestBuffer.AsSpan(); Span actual = this.ActualDestBuffer.GetSpan(); - ApproximateFloatComparer comparer = new(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); - for (int i = 0; i < count; i++) + if (IsComplexPixel()) + { + ApproximateFloatComparer comparer = new(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); + + for (int i = 0; i < count; i++) + { + Assert.Equal(((IPixel)expected[i]).ToScaledVector4(), ((IPixel)actual[i]).ToScaledVector4(), comparer); + } + } + else { - Assert.Equal(((IPixel)expected[i]).ToScaledVector4(), ((IPixel)actual[i]).ToScaledVector4(), comparer); + // SIMD and scalar conversion can select adjacent packed values at a quantization boundary. + int tolerance = Unsafe.SizeOf() <= sizeof(ushort) ? 17 : 1; + + for (int i = 0; i < count; i++) + { + Rgba32 expectedPixel = ((IPixel)expected[i]).ToRgba32(); + Rgba32 actualPixel = ((IPixel)actual[i]).ToRgba32(); + Assert.InRange(Math.Abs(expectedPixel.R - actualPixel.R), 0, tolerance); + Assert.InRange(Math.Abs(expectedPixel.G - actualPixel.G), 0, tolerance); + Assert.InRange(Math.Abs(expectedPixel.B - actualPixel.B), 0, tolerance); + Assert.InRange(Math.Abs(expectedPixel.A - actualPixel.A), 0, tolerance); + } } } else diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs new file mode 100644 index 000000000..e70e2fa0f --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Rgb96Tests.cs @@ -0,0 +1,311 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Rgb96Tests +{ + [Theory] + [InlineData(1, 2, 3)] + [InlineData(50, 70, 80)] + [InlineData(1000, 2000, 3000)] + public void Constructor(uint r, uint g, uint b) + { + Rgb96 p = new(r, g, b); + + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + } + + [Fact] + public void Rgb96_ToVector4() + { + Assert.Equal(Vector4.UnitW, new Rgb96(0, 0, 0).ToVector4()); + Assert.Equal(Vector4.One, new Rgb96(uint.MaxValue, uint.MaxValue, uint.MaxValue).ToVector4()); + Assert.Equal(new Vector4(0.5F, 0.5F, 0.5F, 1.0F), new Rgb96(uint.MaxValue / 2, uint.MaxValue / 2, uint.MaxValue / 2).ToVector4()); + } + + [Theory] + [InlineData(uint.MaxValue, uint.MaxValue, uint.MaxValue)] + [InlineData(0, 0, 0)] + [InlineData(uint.MaxValue / 2, 100, 2222)] + public void Rgb96_ToScaledVector4(uint r, uint g, uint b) + { + // arrange + Rgb96 rgb = new(r, g, b); + + float max = uint.MaxValue; + float rr = r / max; + float gg = g / max; + float bb = b / max; + + // act + Vector4 actual = rgb.ToScaledVector4(); + + // assert + Assert.Equal(rr, actual.X); + Assert.Equal(gg, actual.Y); + Assert.Equal(bb, actual.Z); + Assert.Equal(1.0F, actual.W); + } + + [Theory] + [InlineData(0, 0, 0)] + [InlineData(5000, 100, 2222)] + public void Rgb96_FromScaledVector4(uint r, uint g, uint b) + { + // arrange + Rgb96 source = new(r, g, b); + Vector4 scaled = source.ToScaledVector4(); + + // act + Rgb96 actual = Rgb96.FromScaledVector4(scaled); + + // assert + Assert.Equal(source, actual); + } + + [Fact] + public void Rgb96_ToRgba32() + { + // arrange + Rgb96 rgb96 = new((uint)(uint.MaxValue * 0.1), uint.MaxValue / 2, uint.MaxValue); + Rgba32 expected = new(25, 127, 255, 255); + + // act + Rgba32 actual = rgb96.ToRgba32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Equality_WhenTrue() + { + Rgb96 c1 = new(100, 2000, 3000); + Rgb96 c2 = new(100, 2000, 3000); + + Assert.True(c1.Equals(c2)); + Assert.True(c1.GetHashCode() == c2.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + Rgb96 c1 = new(100, 2000, 3000); + Rgb96 c2 = new(101, 2000, 3000); + Rgb96 c3 = new(100, 2000, 3001); + + Assert.False(c1.Equals(c2)); + Assert.False(c2.Equals(c3)); + Assert.False(c3.Equals(c1)); + } + + [Fact] + public void Rgb96_FromRgba32() + { + // arrange + Rgba32 source = new(25, 127, 255, 255); + Rgb96 expected = new(421075225, 2139062143, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromRgba32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromRgb24() + { + // arrange + Rgb24 source = new(25, 127, 255); + Rgb96 expected = new(421075225, 2139062143, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromRgb24(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromRgb48() + { + // arrange + Rgb48 source = new(0, 32767, ushort.MaxValue); + Rgb96 expected = new(0, 2147450879, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromRgb48(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromRgba64() + { + // arrange + Rgba64 source = new(0, 32767, ushort.MaxValue, ushort.MaxValue); + Rgb96 expected = new(0, 2147450879, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromRgba64(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromLa32() + { + // arrange + La32 source = new(32767, ushort.MaxValue); + Rgb96 expected = new(2147450879, 2147450879, 2147450879); + + // act + Rgb96 actual = Rgb96.FromLa32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromLa16() + { + // arrange + La16 source = new(127, byte.MaxValue); + Rgb96 expected = new(2139062143, 2139062143, 2139062143); + + // act + Rgb96 actual = Rgb96.FromLa16(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromL16() + { + // arrange + L16 source = new(32767); + Rgb96 expected = new(2147450879, 2147450879, 2147450879); + + // act + Rgb96 actual = Rgb96.FromL16(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromL8() + { + // arrange + L8 source = new(127); + Rgb96 expected = new(2139062143, 2139062143, 2139062143); + + // act + Rgb96 actual = Rgb96.FromL8(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromBgra32() + { + // arrange + Bgra32 source = new(127, 25, 255); + Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromBgra32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromBgr24() + { + // arrange + Bgr24 source = new(127, 25, 255); + Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromBgr24(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromBgra5551() + { + // arrange + Bgra5551 source = new(1.0f, 1.0f, 1.0f, 1.0f); + Rgb96 expected = new(uint.MaxValue, uint.MaxValue, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromBgra5551(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromArgb32() + { + // arrange + Argb32 source = new(127, 25, 255); + Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromArgb32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_FromAbgr32() + { + // arrange + Abgr32 source = new(127, 25, 255); + Rgb96 expected = new(2139062143, 421075225, uint.MaxValue); + + // act + Rgb96 actual = Rgb96.FromAbgr32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgb96_PixelInformation() + { + PixelTypeInfo info = Rgb96.GetPixelTypeInfo(); + Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); + Assert.Equal(PixelAlphaRepresentation.None, info.AlphaRepresentation); + Assert.Equal(PixelColorType.RGB, info.ColorType); + + PixelComponentInfo? maybeComponentInfo = info.ComponentInfo; + Assert.NotNull(maybeComponentInfo); + PixelComponentInfo componentInfo = maybeComponentInfo.Value; + + Assert.Equal(3, componentInfo.ComponentCount); + Assert.Equal(0, componentInfo.Padding); + Assert.Equal(32, componentInfo.GetComponentPrecision(0)); + Assert.Equal(32, componentInfo.GetComponentPrecision(1)); + Assert.Equal(32, componentInfo.GetComponentPrecision(2)); + Assert.Equal(32, componentInfo.GetMaximumComponentPrecision()); + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs new file mode 100644 index 000000000..339d57ed4 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba128Tests.cs @@ -0,0 +1,313 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +[Trait("Category", "PixelFormats")] +public class Rgba128Tests +{ + [Theory] + [InlineData(1, 2, 3, 4)] + [InlineData(50, 70, 80, 90)] + [InlineData(1000, 2000, 3000, 4000)] + public void Constructor(uint r, uint g, uint b, uint a) + { + Rgba128 p = new(r, g, b, a); + + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + } + + [Fact] + public void Rgba128_ToVector4() + { + Assert.Equal(Vector4.Zero, new Rgba128(0, 0, 0, 0).ToVector4()); + Assert.Equal(Vector4.One, new Rgba128(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue).ToVector4()); + Assert.Equal(new Vector4(0.5F, 0.5F, 0.5F, 0.5F), new Rgba128(uint.MaxValue / 2, uint.MaxValue / 2, uint.MaxValue / 2, uint.MaxValue / 2).ToVector4()); + } + + [Theory] + [InlineData(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue)] + [InlineData(0, 0, 0, 0)] + [InlineData(uint.MaxValue / 2, 100, 2222, 3333)] + public void Rgba128_ToScaledVector4(uint r, uint g, uint b, uint a) + { + // arrange + Rgba128 rgb = new(r, g, b, a); + + float max = uint.MaxValue; + float rr = r / max; + float gg = g / max; + float bb = b / max; + float aa = a / max; + + // act + Vector4 actual = rgb.ToScaledVector4(); + + // assert + Assert.Equal(rr, actual.X); + Assert.Equal(gg, actual.Y); + Assert.Equal(bb, actual.Z); + Assert.Equal(aa, actual.W); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(5000, 100, 2222, 3333)] + public void Rgba128_FromScaledVector4(uint r, uint g, uint b, uint a) + { + // arrange + Rgba128 source = new(r, g, b, a); + Vector4 scaled = source.ToScaledVector4(); + + // act + Rgba128 actual = Rgba128.FromScaledVector4(scaled); + + // assert + Assert.Equal(source, actual); + } + + [Fact] + public void Rgba128_ToRgba32() + { + // arrange + Rgba128 rgb96 = new((uint)(uint.MaxValue * 0.1), uint.MaxValue / 2, uint.MaxValue, uint.MaxValue); + Rgba32 expected = new(25, 127, 255, 255); + + // act + Rgba32 actual = rgb96.ToRgba32(); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Equality_WhenTrue() + { + Rgba128 c1 = new(100, 2000, 3000, 4000); + Rgba128 c2 = new(100, 2000, 3000, 4000); + + Assert.True(c1.Equals(c2)); + Assert.True(c1.GetHashCode() == c2.GetHashCode()); + } + + [Fact] + public void Equality_WhenFalse() + { + Rgba128 c1 = new(100, 2000, 3000, 4000); + Rgba128 c2 = new(101, 2000, 3000, 4000); + Rgba128 c3 = new(100, 2000, 3001, 4000); + + Assert.False(c1.Equals(c2)); + Assert.False(c2.Equals(c3)); + Assert.False(c3.Equals(c1)); + } + + [Fact] + public void Rgba128_FromRgba32() + { + // arrange + Rgba32 source = new(25, 127, 255, 255); + Rgba128 expected = new(421075225, 2139062143, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromRgba32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromRgb24() + { + // arrange + Rgb24 source = new(25, 127, 255); + Rgba128 expected = new(421075225, 2139062143, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromRgb24(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromRgb48() + { + // arrange + Rgb48 source = new(0, 32767, ushort.MaxValue); + Rgba128 expected = new(0, 2147450879, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromRgb48(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromRgba64() + { + // arrange + Rgba64 source = new(0, 32767, ushort.MaxValue, ushort.MaxValue); + Rgba128 expected = new(0, 2147450879, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromRgba64(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromLa32() + { + // arrange + La32 source = new(32767, ushort.MaxValue); + Rgba128 expected = new(2147450879, 2147450879, 2147450879, 2147450879); + + // act + Rgba128 actual = Rgba128.FromLa32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromLa16() + { + // arrange + La16 source = new(127, byte.MaxValue); + Rgba128 expected = new(2139062143, 2139062143, 2139062143, 2139062143); + + // act + Rgba128 actual = Rgba128.FromLa16(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromL16() + { + // arrange + L16 source = new(32767); + Rgba128 expected = new(2147450879, 2147450879, 2147450879, 2147450879); + + // act + Rgba128 actual = Rgba128.FromL16(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromL8() + { + // arrange + L8 source = new(127); + Rgba128 expected = new(2139062143, 2139062143, 2139062143, 2139062143); + + // act + Rgba128 actual = Rgba128.FromL8(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromBgra32() + { + // arrange + Bgra32 source = new(127, 25, 255); + Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromBgra32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromBgr24() + { + // arrange + Bgr24 source = new(127, 25, 255); + Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromBgr24(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromBgra5551() + { + // arrange + Bgra5551 source = new(1.0f, 1.0f, 1.0f, 1.0f); + Rgba128 expected = new(uint.MaxValue, uint.MaxValue, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromBgra5551(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromArgb32() + { + // arrange + Argb32 source = new(127, 25, 255); + Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromArgb32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_FromAbgr32() + { + // arrange + Abgr32 source = new(127, 25, 255); + Rgba128 expected = new(2139062143, 421075225, uint.MaxValue, uint.MaxValue); + + // act + Rgba128 actual = Rgba128.FromAbgr32(source); + + // assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Rgba128_PixelInformation() + { + PixelTypeInfo info = Rgba128.GetPixelTypeInfo(); + Assert.Equal(Unsafe.SizeOf() * 8, info.BitsPerPixel); + Assert.Equal(PixelAlphaRepresentation.Unassociated, info.AlphaRepresentation); + Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); + + PixelComponentInfo? maybeComponentInfo = info.ComponentInfo; + Assert.NotNull(maybeComponentInfo); + PixelComponentInfo componentInfo = maybeComponentInfo.Value; + + Assert.Equal(4, componentInfo.ComponentCount); + Assert.Equal(0, componentInfo.Padding); + Assert.Equal(32, componentInfo.GetComponentPrecision(0)); + Assert.Equal(32, componentInfo.GetComponentPrecision(1)); + Assert.Equal(32, componentInfo.GetComponentPrecision(2)); + Assert.Equal(32, componentInfo.GetComponentPrecision(3)); + Assert.Equal(32, componentInfo.GetMaximumComponentPrecision()); + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/RgbaHalfTests.cs b/tests/ImageSharp.Tests/PixelFormats/RgbaHalfTests.cs new file mode 100644 index 000000000..9b22a9f8c --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/RgbaHalfTests.cs @@ -0,0 +1,283 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// Tests the unit-range binary16 RGBA pixel formats. +/// +[Trait("Category", "PixelFormats")] +public class RgbaHalfTests +{ + /// + /// Verifies that the unassociated format has the native layout required by RGBA binary16 surfaces. + /// + [Fact] + public void RgbaHalfHasRgbaBinary16Layout() + { + RgbaHalf pixel = new(.25F, .5F, .75F, 1F); + ulong expected = BitConverter.HalfToUInt16Bits((Half).25F) + | ((ulong)BitConverter.HalfToUInt16Bits((Half).5F) << 16) + | ((ulong)BitConverter.HalfToUInt16Bits((Half).75F) << 32) + | ((ulong)BitConverter.HalfToUInt16Bits((Half)1F) << 48); + + Assert.Equal(8, Unsafe.SizeOf()); + Assert.Equal(expected, pixel.PackedValue); + Assert.Equal(new Vector4(.25F, .5F, .75F, 1F), pixel.ToVector4()); + Assert.Equal(pixel.ToVector4(), pixel.ToScaledVector4()); + } + + /// + /// Verifies that zero-filled binary16 storage represents transparent black without affine remapping. + /// + [Fact] + public void RgbaHalfDefaultIsTransparentBlack() + { + RgbaHalf pixel = default; + + Assert.Equal(Vector4.Zero, pixel.ToVector4()); + Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); + } + + /// + /// Verifies that scaled input is clamped to the pixel format's unit color range. + /// + [Fact] + public void RgbaHalfFromScaledVector4ClampsToUnitRange() + { + RgbaHalf pixel = RgbaHalf.FromScaledVector4(new Vector4(-1F, .5F, 2F, 1F)); + + Assert.Equal(new Vector4(0F, .5F, 1F, 1F), pixel.ToScaledVector4()); + } + + /// + /// Verifies that the associated format stores associated binary16 components in the same RGBA order. + /// + [Fact] + public void RgbaHalfPHasAssociatedRgbaBinary16Layout() + { + RgbaHalfP pixel = new(.125F, .25F, .375F, .5F); + ulong expected = BitConverter.HalfToUInt16Bits((Half).125F) + | ((ulong)BitConverter.HalfToUInt16Bits((Half).25F) << 16) + | ((ulong)BitConverter.HalfToUInt16Bits((Half).375F) << 32) + | ((ulong)BitConverter.HalfToUInt16Bits((Half).5F) << 48); + + Assert.Equal(8, Unsafe.SizeOf()); + Assert.Equal(expected, pixel.PackedValue); + Assert.Equal(new Vector4(.125F, .25F, .375F, .5F), pixel.ToAssociatedScaledVector4()); + } + + /// + /// Verifies that zero-filled associated binary16 storage represents transparent black. + /// + [Fact] + public void RgbaHalfPDefaultIsTransparentBlack() + { + RgbaHalfP pixel = default; + + Assert.Equal(Vector4.Zero, pixel.ToVector4()); + Assert.Equal(Vector4.Zero, pixel.ToScaledVector4()); + Assert.Equal(Vector4.Zero, pixel.ToUnassociatedScaledVector4()); + } + + /// + /// Verifies that association uses the alpha value that survives binary16 quantization. + /// + [Fact] + public void RgbaHalfPQuantizesAlphaBeforeAssociation() + { + Vector4 source = new(.75F, .5F, .25F, 1F / 3F); + float storedAlpha = (float)(Half)source.W; + RgbaHalfP pixel = RgbaHalfP.FromUnassociatedScaledVector4(source); + + Assert.Equal((Half)(source.X * storedAlpha), pixel.R); + Assert.Equal((Half)(source.Y * storedAlpha), pixel.G); + Assert.Equal((Half)(source.Z * storedAlpha), pixel.B); + Assert.Equal((Half)storedAlpha, pixel.A); + } + + /// + /// Verifies every bulk representation path against its scalar pixel contract at each SIMD width and tail boundary. + /// + [Fact] + public void RgbaHalfBulkConversionsMatchScalarAcrossHardwareWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + static () => AssertBulkConversionsMatchScalar(), + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Verifies every associated bulk representation path against its scalar pixel contract at each SIMD width and tail boundary. + /// + [Fact] + public void RgbaHalfPBulkConversionsMatchScalarAcrossHardwareWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + static () => AssertBulkConversionsMatchScalar(), + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Verifies the declared component precision and alpha representation for the unassociated format. + /// + [Fact] + public void RgbaHalfPixelInformationIsCorrect() => AssertPixelInformation(PixelAlphaRepresentation.Unassociated); + + /// + /// Verifies the declared component precision and alpha representation for the associated format. + /// + [Fact] + public void RgbaHalfPPixelInformationIsCorrect() => AssertPixelInformation(PixelAlphaRepresentation.Associated); + + /// + /// Compares bulk conversions with the corresponding scalar pixel operations for representative vector widths and remainders. + /// + /// The binary16 pixel format to test. + private static void AssertBulkConversionsMatchScalar() + where TPixel : unmanaged, IPixel + { + int[] lengths = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 259]; + PixelConversionModifiers[] modifiers = + [ + PixelConversionModifiers.None, + PixelConversionModifiers.Scale, + PixelConversionModifiers.Premultiply, + PixelConversionModifiers.Scale | PixelConversionModifiers.Premultiply, + PixelConversionModifiers.UnPremultiply, + PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply + ]; + + foreach (int length in lengths) + { + TPixel[] pixels = new TPixel[length]; + + for (int i = 0; i < length; i++) + { + pixels[i] = TPixel.FromUnassociatedScaledVector4(CreateUnassociatedVector(i)); + } + + foreach (PixelConversionModifiers modifier in modifiers) + { + Vector4[] expectedVectors = new Vector4[length]; + Vector4[] actualVectors = new Vector4[length]; + Vector4[] sourceVectors = new Vector4[length]; + TPixel[] expectedPixels = new TPixel[length]; + TPixel[] actualPixels = new TPixel[length]; + bool associated = RequestsAssociated(modifier); + bool scaled = modifier.IsDefined(PixelConversionModifiers.Scale); + + for (int i = 0; i < length; i++) + { + expectedVectors[i] = ToScalarVector(pixels[i], associated, scaled); + sourceVectors[i] = CreateUnassociatedVector(i + 19); + + if (associated) + { + Numerics.Premultiply(ref sourceVectors[i]); + } + + expectedPixels[i] = FromScalarVector(sourceVectors[i], associated, scaled); + } + + PixelOperations.Instance.ToVector4(Configuration.Default, pixels, actualVectors, modifier); + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, sourceVectors.AsSpan(), actualPixels, modifier); + + Assert.Equal(expectedVectors, actualVectors); + Assert.Equal(expectedPixels, actualPixels); + } + } + } + + /// + /// Creates a deterministic unassociated unit-range vector for bulk conversion tests. + /// + /// The source index used to vary the component values. + /// The generated vector. + private static Vector4 CreateUnassociatedVector(int index) + => new( + ((index * 37) % 101) / 100F, + ((index * 53) % 101) / 100F, + ((index * 71) % 101) / 100F, + ((index * 89) % 101) / 100F); + + /// + /// Resolves whether a modifier combination requests associated output under the pixel format's native representation. + /// + /// The pixel format whose native representation participates in modifier resolution. + /// The conversion modifiers. + /// when the resulting vector representation is associated. + private static bool RequestsAssociated(PixelConversionModifiers modifiers) + where TPixel : unmanaged, IPixel + => modifiers.IsDefined(PixelConversionModifiers.Premultiply) + || (TPixel.GetPixelTypeInfo().AlphaRepresentation == PixelAlphaRepresentation.Associated + && !modifiers.IsDefined(PixelConversionModifiers.UnPremultiply)); + + /// + /// Converts one pixel through the scalar API selected by the requested representation and range. + /// + /// The source pixel format. + /// The source pixel. + /// Whether the result should use associated alpha. + /// Whether the result should use the scaled range. + /// The converted vector. + private static Vector4 ToScalarVector(TPixel pixel, bool associated, bool scaled) + where TPixel : unmanaged, IPixel + => (associated, scaled) switch + { + (true, true) => pixel.ToAssociatedScaledVector4(), + (true, false) => pixel.ToAssociatedVector4(), + (false, true) => pixel.ToUnassociatedScaledVector4(), + _ => pixel.ToUnassociatedVector4() + }; + + /// + /// Converts one vector through the scalar API selected by its representation and range. + /// + /// The destination pixel format. + /// The source vector. + /// Whether the source uses associated alpha. + /// Whether the source uses the scaled range. + /// The converted pixel. + private static TPixel FromScalarVector(Vector4 vector, bool associated, bool scaled) + where TPixel : unmanaged, IPixel + => (associated, scaled) switch + { + (true, true) => TPixel.FromAssociatedScaledVector4(vector), + (true, false) => TPixel.FromAssociatedVector4(vector), + (false, true) => TPixel.FromUnassociatedScaledVector4(vector), + _ => TPixel.FromUnassociatedVector4(vector) + }; + + /// + /// Verifies the component layout and alpha metadata exposed by a binary16 pixel format. + /// + /// The pixel format to inspect. + /// The expected alpha representation. + private static void AssertPixelInformation(PixelAlphaRepresentation alphaRepresentation) + where TPixel : unmanaged, IPixel + { + PixelTypeInfo info = TPixel.GetPixelTypeInfo(); + PixelComponentInfo componentInfo = info.ComponentInfo.Value; + + Assert.Equal(64, info.BitsPerPixel); + Assert.Equal(alphaRepresentation, info.AlphaRepresentation); + Assert.Equal(PixelColorType.RGB | PixelColorType.Alpha, info.ColorType); + Assert.Equal(4, componentInfo.ComponentCount); + Assert.Equal(0, componentInfo.Padding); + + for (int i = 0; i < componentInfo.ComponentCount; i++) + { + Assert.Equal(16, componentInfo.GetComponentPrecision(i)); + } + } +} + +/// +/// Applies the shared associated-alpha contract to . +/// +public class RgbaHalfPAssociatedAlphaTests : AssociatedAlphaPixelTests +{ +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs index f23da0c7a..86fc82830 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short2Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -51,33 +52,44 @@ public class Short2Tests [Fact] public void Short2_ToScaledVector4() { - // arrange - Short2 short2 = new(Vector2.One * 0x7FFF); - - // act - Vector4 actual = short2.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(0, actual.Z); - Assert.Equal(1, actual.W); + Assert.Equal(new Vector4(1F, 1F, 0F, 1F), new Short2(Vector2.One * short.MaxValue).ToScaledVector4()); + Assert.Equal(new Vector4(0F, 0F, 0F, 1F), new Short2(Vector2.One * short.MinValue).ToScaledVector4()); } [Fact] public void Short2_FromScaledVector4() { - // arrange - Short2 short2 = new(Vector2.One * 0x7FFF); - const ulong expected = 0x7FFF7FFF; + Assert.Equal(0x80008000U, Short2.FromScaledVector4(new Vector4(0F, 0F, 0F, 1F)).PackedValue); + Assert.Equal(0x7FFF7FFFU, Short2.FromScaledVector4(Vector4.One).PackedValue); + } - // act - Vector4 scaled = short2.ToScaledVector4(); - Short2 pixel = Short2.FromScaledVector4(scaled); - uint actual = pixel.PackedValue; + [Fact] + public void Short2_BulkScaledConversionsCoverFullSignedRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertShort2BulkScaledConversionsCoverFullSignedRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); - // assert - Assert.Equal(expected, actual); + private static void AssertShort2BulkScaledConversionsCoverFullSignedRange() + { + const int length = 17; + Short2[] source = new Short2[length]; + Vector4[] expectedVectors = new Vector4[length]; + + for (int i = 0; i < length; i++) + { + bool minimum = (i & 1) == 0; + source[i].PackedValue = minimum ? 0x80008000U : 0x7FFF7FFFU; + expectedVectors[i] = minimum ? new Vector4(0F, 0F, 0F, 1F) : new Vector4(1F, 1F, 0F, 1F); + } + + Vector4[] actualVectors = new Vector4[length]; + PixelOperations.Instance.ToVector4(Configuration.Default, source, actualVectors, PixelConversionModifiers.Scale); + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] destructiveSource = [.. expectedVectors]; + Short2[] actualPixels = new Short2[length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.Equal(source, actualPixels); } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs index 819ff0e1e..c8502462d 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; namespace SixLabors.ImageSharp.Tests.PixelFormats; @@ -38,32 +39,44 @@ public class Short4Tests [Fact] public void Short4_ToScaledVector4() { - // arrange - Short4 short4 = new(Vector4.One * 0x7FFF); - - // act - Vector4 actual = short4.ToScaledVector4(); - - // assert - Assert.Equal(1, actual.X); - Assert.Equal(1, actual.Y); - Assert.Equal(1, actual.Z); - Assert.Equal(1, actual.W); + Assert.Equal(Vector4.One, new Short4(Vector4.One * short.MaxValue).ToScaledVector4()); + Assert.Equal(Vector4.Zero, new Short4(Vector4.One * short.MinValue).ToScaledVector4()); } [Fact] public void Short4_FromScaledVector4() { - // arrange - Short4 short4 = new(Vector4.One * 0x7FFF); - Vector4 scaled = short4.ToScaledVector4(); - const long expected = 0x7FFF7FFF7FFF7FFF; + Assert.Equal(0x8000800080008000UL, Short4.FromScaledVector4(Vector4.Zero).PackedValue); + Assert.Equal(0x7FFF7FFF7FFF7FFFUL, Short4.FromScaledVector4(Vector4.One).PackedValue); + } - // act - Short4 pixel = Short4.FromScaledVector4(scaled); + [Fact] + public void Short4_BulkScaledConversionsCoverFullSignedRange() => + FeatureTestRunner.RunWithHwIntrinsicsFeature( + AssertShort4BulkScaledConversionsCoverFullSignedRange, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); - // assert - Assert.Equal((ulong)expected, pixel.PackedValue); + private static void AssertShort4BulkScaledConversionsCoverFullSignedRange() + { + const int length = 17; + Short4[] source = new Short4[length]; + Vector4[] expectedVectors = new Vector4[length]; + + for (int i = 0; i < length; i++) + { + bool minimum = (i & 1) == 0; + source[i].PackedValue = minimum ? 0x8000800080008000UL : 0x7FFF7FFF7FFF7FFFUL; + expectedVectors[i] = minimum ? Vector4.Zero : Vector4.One; + } + + Vector4[] actualVectors = new Vector4[length]; + PixelOperations.Instance.ToVector4(Configuration.Default, source, actualVectors, PixelConversionModifiers.Scale); + Assert.Equal(expectedVectors, actualVectors); + + Vector4[] destructiveSource = [.. expectedVectors]; + Short4[] actualPixels = new Short4[length]; + PixelOperations.Instance.FromVector4Destructive(Configuration.Default, destructiveSource, actualPixels, PixelConversionModifiers.Scale); + Assert.Equal(source, actualPixels); } [Fact] diff --git a/tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs b/tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs new file mode 100644 index 000000000..837d807f0 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Vector4ConvertersTests.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats.Utils; +using SixLabors.ImageSharp.Tests.TestUtilities; + +namespace SixLabors.ImageSharp.Tests.PixelFormats; + +/// +/// Verifies the shared stateful traversal used by affine pixel-vector conversions. +/// +[Trait("Category", "PixelFormats")] +public class Vector4ConvertersTests +{ + private static readonly int[] Lengths = [0, 1, 2, 3, 4, 5, 7, 8, 15, 16, 17, 257]; + + /// + /// Verifies multiply-then-add behavior for every SIMD boundary and the software fallback. + /// + [Fact] + public void MultiplyThenAddMatchesComponentArithmeticAcrossHardwareWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertMultiplyThenAddMatchesComponentArithmetic, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Verifies add-then-divide behavior for every SIMD boundary and the software fallback. + /// + [Fact] + public void AddThenDivideMatchesComponentArithmeticAcrossHardwareWidths() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertAddThenDivideMatchesComponentArithmetic, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + + /// + /// Compares the multiply-then-add traversal with independently evaluated component expressions. + /// + private static void AssertMultiplyThenAddMatchesComponentArithmetic() + { + Vector4 multiplier = new(2F, -3F, .5F, 4F); + Vector4 offset = new(-7F, 11F, 13F, -17F); + + foreach (int length in Lengths) + { + Vector4[] actual = CreateSource(length); + Vector4[] expected = new Vector4[length]; + + for (int i = 0; i < expected.Length; i++) + { + Vector4 value = actual[i]; + + expected[i] = new Vector4((value.X * multiplier.X) + offset.X, (value.Y * multiplier.Y) + offset.Y, (value.Z * multiplier.Z) + offset.Z, (value.W * multiplier.W) + offset.W); + } + + Vector4Converters.MultiplyThenAdd(actual, multiplier, offset); + + Assert.Equal(expected, actual); + } + } + + /// + /// Compares the add-then-divide traversal with independently evaluated component expressions. + /// + private static void AssertAddThenDivideMatchesComponentArithmetic() + { + Vector4 offset = new(-7F, 11F, 13F, -17F); + Vector4 divisor = new(2F, -3F, .5F, 4F); + + foreach (int length in Lengths) + { + Vector4[] actual = CreateSource(length); + Vector4[] expected = new Vector4[length]; + + for (int i = 0; i < expected.Length; i++) + { + Vector4 value = actual[i]; + + expected[i] = new Vector4((value.X + offset.X) / divisor.X, (value.Y + offset.Y) / divisor.Y, (value.Z + offset.Z) / divisor.Z, (value.W + offset.W) / divisor.W); + } + + Vector4Converters.AddThenDivide(actual, offset, divisor); + + Assert.Equal(expected, actual); + } + } + + /// + /// Creates non-uniform values that expose component ordering and traversal overlap errors. + /// + /// The number of vectors to create. + /// The populated vector buffer. + private static Vector4[] CreateSource(int length) + { + Vector4[] result = new Vector4[length]; + + for (int i = 0; i < result.Length; i++) + { + result[i] = new Vector4((i * 17F) - 31F, (i * -23F) + 37F, (i * .25F) - 41F, (i * 3F) + 43F); + } + + return result; + } +} diff --git a/tests/ImageSharp.Tests/Primitives/PointFTests.cs b/tests/ImageSharp.Tests/Primitives/PointFTests.cs index 8b574daf0..bea0f802f 100644 --- a/tests/ImageSharp.Tests/Primitives/PointFTests.cs +++ b/tests/ImageSharp.Tests/Primitives/PointFTests.cs @@ -133,6 +133,69 @@ public class PointFTests Assert.Equal(new PointF(30, 30), pout); } + [Fact] + public void TransformMatrix4x4_AffineMatchesMatrix3x2() + { + PointF p = new(13, 17); + Matrix3x2 m3 = Matrix3x2Extensions.CreateRotationDegrees(45, PointF.Empty); + Matrix4x4 m4 = new(m3); + + PointF r3 = PointF.Transform(p, m3); + PointF r4 = PointF.Transform(p, m4); + + Assert.Equal(r3.X, r4.X, ApproximateFloatComparer); + Assert.Equal(r3.Y, r4.Y, ApproximateFloatComparer); + } + + [Fact] + public void TransformMatrix4x4_Identity() + { + PointF p = new(42.5F, -17.3F); + PointF result = PointF.Transform(p, Matrix4x4.Identity); + + Assert.Equal(p.X, result.X, ApproximateFloatComparer); + Assert.Equal(p.Y, result.Y, ApproximateFloatComparer); + } + + [Fact] + public void TransformMatrix4x4_Translation() + { + PointF p = new(10, 20); + Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0); + PointF result = PointF.Transform(p, m); + + Assert.Equal(15F, result.X, ApproximateFloatComparer); + Assert.Equal(17F, result.Y, ApproximateFloatComparer); + } + + [Fact] + public void TransformMatrix4x4_Scale() + { + PointF p = new(10, 20); + Matrix4x4 m = Matrix4x4.CreateScale(2, 3, 1); + PointF result = PointF.Transform(p, m); + + Assert.Equal(20F, result.X, ApproximateFloatComparer); + Assert.Equal(60F, result.Y, ApproximateFloatComparer); + } + + [Fact] + public void TransformMatrix4x4_Projective() + { + // A taper matrix with M14 != 0 produces W != 1, requiring perspective divide. + PointF p = new(100, 50); + Matrix4x4 m = Matrix4x4.Identity; + m.M14 = 0.005F; // perspective component + + PointF result = PointF.Transform(p, m); + + // W = x*M14 + M44 = 100*0.005 + 1 = 1.5 + // X = x*M11 + M41 = 100, Y = y*M22 + M42 = 50 + // result = (100/1.5, 50/1.5) + Assert.Equal(100F / 1.5F, result.X, ApproximateFloatComparer); + Assert.Equal(50F / 1.5F, result.Y, ApproximateFloatComparer); + } + [Theory] [InlineData(float.MaxValue, float.MinValue)] [InlineData(float.MinValue, float.MaxValue)] diff --git a/tests/ImageSharp.Tests/Primitives/PointTests.cs b/tests/ImageSharp.Tests/Primitives/PointTests.cs index 34e4f4d8f..b740cbb28 100644 --- a/tests/ImageSharp.Tests/Primitives/PointTests.cs +++ b/tests/ImageSharp.Tests/Primitives/PointTests.cs @@ -180,9 +180,10 @@ public class PointTests Point p = new(13, 17); Matrix3x2 matrix = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); - Point pout = Point.Transform(p, matrix); + PointF pout = Point.Transform(p, matrix); - Assert.Equal(new Point(-3, 21), pout); + Assert.Equal(-2.828427F, pout.X, 4); + Assert.Equal(21.213203F, pout.Y, 4); } [Fact] @@ -191,8 +192,56 @@ public class PointTests Point p = new(13, 17); Matrix3x2 matrix = Matrix3x2Extensions.CreateSkewDegrees(45, 45, Point.Empty); - Point pout = Point.Transform(p, matrix); - Assert.Equal(new Point(30, 30), pout); + PointF pout = Point.Transform(p, matrix); + Assert.Equal(30F, pout.X, 4); + Assert.Equal(30F, pout.Y, 4); + } + + [Fact] + public void TransformMatrix4x4_AffineMatchesMatrix3x2() + { + Point p = new(13, 17); + Matrix3x2 m3 = Matrix3x2Extensions.CreateRotationDegrees(45, Point.Empty); + Matrix4x4 m4 = new(m3); + + PointF r3 = Point.Transform(p, m3); + PointF r4 = Point.Transform(p, m4); + + Assert.Equal(r3, r4); + } + + [Fact] + public void TransformMatrix4x4_Identity() + { + Point p = new(42, -17); + PointF result = Point.Transform(p, Matrix4x4.Identity); + + Assert.Equal((PointF)p, result); + } + + [Fact] + public void TransformMatrix4x4_Translation() + { + Point p = new(10, 20); + Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0); + PointF result = Point.Transform(p, m); + + Assert.Equal(15F, result.X, 4); + Assert.Equal(17F, result.Y, 4); + } + + [Fact] + public void TransformMatrix4x4_Projective() + { + Point p = new(100, 50); + Matrix4x4 m = Matrix4x4.Identity; + m.M14 = 0.005F; + + PointF result = Point.Transform(p, m); + + // W = 100*0.005 + 1 = 1.5 => (100/1.5, 50/1.5) + Assert.Equal(100F / 1.5F, result.X, 4); + Assert.Equal(50F / 1.5F, result.Y, 4); } [Theory] diff --git a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs index 4122daaa5..9a6ff0957 100644 --- a/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs +++ b/tests/ImageSharp.Tests/Primitives/RectangleFTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Globalization; +using System.Numerics; namespace SixLabors.ImageSharp.Tests; @@ -243,6 +244,61 @@ public class RectangleFTests Assert.Equal(expectedRect, r1); } + [Fact] + public void TransformMatrix4x4_AffineMatchesMatrix3x2() + { + RectangleF rect = new(10, 20, 100, 50); + Matrix3x2 m3 = Matrix3x2.CreateTranslation(5, -3); + Matrix4x4 m4 = new(m3); + + RectangleF r3 = RectangleF.Transform(rect, m3); + RectangleF r4 = RectangleF.Transform(rect, m4); + + Assert.Equal(r3, r4); + } + + [Fact] + public void TransformMatrix4x4_Identity() + { + RectangleF rect = new(10, 20, 100, 50); + RectangleF result = RectangleF.Transform(rect, Matrix4x4.Identity); + + Assert.Equal(rect, result); + } + + [Fact] + public void TransformMatrix4x4_Translation() + { + RectangleF rect = new(10, 20, 100, 50); + Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0); + RectangleF result = RectangleF.Transform(rect, m); + + Assert.Equal(new RectangleF(15, 17, 100, 50), result); + } + + [Fact] + public void TransformMatrix4x4_Scale() + { + RectangleF rect = new(10, 20, 100, 50); + Matrix4x4 m = Matrix4x4.CreateScale(2, 3, 1); + RectangleF result = RectangleF.Transform(rect, m); + + Assert.Equal(new RectangleF(20, 60, 200, 150), result); + } + + [Fact] + public void TransformMatrix4x4_RotationReturnsBoundingBox() + { + RectangleF rect = new(10, 20, 100, 50); + Matrix4x4 m = Matrix4x4.CreateRotationZ(MathF.PI / 2F); + RectangleF result = RectangleF.Transform(rect, m); + + Assert.Equal(-70F, result.X, 4); + Assert.Equal(10F, result.Y, 4); + Assert.Equal(50F, result.Width, 4); + Assert.Equal(100F, result.Height, 4); + } + [Fact] public void ToStringTest() { diff --git a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs index 2800852af..a8ce99078 100644 --- a/tests/ImageSharp.Tests/Primitives/RectangleTests.cs +++ b/tests/ImageSharp.Tests/Primitives/RectangleTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Globalization; +using System.Numerics; namespace SixLabors.ImageSharp.Tests; @@ -294,6 +295,51 @@ public class RectangleTests Assert.Equal(expectedRect, r1); } + [Fact] + public void TransformMatrix4x4_AffineMatchesMatrix3x2() + { + Rectangle rect = new(10, 20, 100, 50); + Matrix3x2 m3 = Matrix3x2.CreateTranslation(5, -3); + Matrix4x4 m4 = new(m3); + + RectangleF r3 = Rectangle.Transform(rect, m3); + RectangleF r4 = Rectangle.Transform(rect, m4); + + Assert.Equal(r3, r4); + } + + [Fact] + public void TransformMatrix4x4_Identity() + { + Rectangle rect = new(10, 20, 100, 50); + RectangleF result = Rectangle.Transform(rect, Matrix4x4.Identity); + + Assert.Equal(new RectangleF(10, 20, 100, 50), result); + } + + [Fact] + public void TransformMatrix4x4_Translation() + { + Rectangle rect = new(10, 20, 100, 50); + Matrix4x4 m = Matrix4x4.CreateTranslation(5, -3, 0); + RectangleF result = Rectangle.Transform(rect, m); + + Assert.Equal(new RectangleF(15, 17, 100, 50), result); + } + + [Fact] + public void TransformMatrix4x4_RotationReturnsBoundingBox() + { + Rectangle rect = new(10, 20, 100, 50); + Matrix4x4 m = Matrix4x4.CreateRotationZ(MathF.PI / 2F); + RectangleF result = Rectangle.Transform(rect, m); + + Assert.Equal(-70F, result.X, 4); + Assert.Equal(10F, result.Y, 4); + Assert.Equal(50F, result.Width, 4); + Assert.Equal(100F, result.Height, 4); + } + [Fact] public void ToStringTest() { diff --git a/tests/ImageSharp.Tests/Processing/AlphaAssociationProcessorTests.cs b/tests/ImageSharp.Tests/Processing/AlphaAssociationProcessorTests.cs new file mode 100644 index 000000000..485f64174 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/AlphaAssociationProcessorTests.cs @@ -0,0 +1,441 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Extensions.Convolution; +using SixLabors.ImageSharp.Processing.Processors.Convolution; +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.ImageSharp.Processing.Processors.Filters; +using SixLabors.ImageSharp.Processing.Processors.Normalization; + +namespace SixLabors.ImageSharp.Tests.Processing; + +[Trait("Category", "Processors")] +public class AlphaAssociationProcessorTests +{ + [Theory] + [InlineData(ProcessorCase.Opaque)] + [InlineData(ProcessorCase.Invert)] + [InlineData(ProcessorCase.ColorMatrix)] + [InlineData(ProcessorCase.Convolution)] + [InlineData(ProcessorCase.ConvolutionPreserveAlpha)] + [InlineData(ProcessorCase.Convolution2D)] + [InlineData(ProcessorCase.Convolution2DPreserveAlpha)] + [InlineData(ProcessorCase.Convolution2Pass)] + [InlineData(ProcessorCase.Convolution2PassPreserveAlpha)] + [InlineData(ProcessorCase.Median)] + [InlineData(ProcessorCase.MedianPreserveAlpha)] + [InlineData(ProcessorCase.BokehBlur)] + [InlineData(ProcessorCase.OilPaint)] + [InlineData(ProcessorCase.HistogramGlobal)] + [InlineData(ProcessorCase.HistogramAdaptiveSlidingWindow)] + [InlineData(ProcessorCase.HistogramAdaptiveTileInterpolation)] + [InlineData(ProcessorCase.HistogramAutoLevel)] + [InlineData(ProcessorCase.HistogramAutoLevelSeparateChannels)] + [InlineData(ProcessorCase.ResizeCompanded)] + [InlineData(ProcessorCase.ResizeUnassociated)] + [InlineData(ProcessorCase.AffineTransform)] + [InlineData(ProcessorCase.ProjectiveTransform)] + [InlineData(ProcessorCase.OrderedDither)] + [InlineData(ProcessorCase.EntropyCrop)] + public void EquivalentAlphaRepresentationsProduceEquivalentResults(ProcessorCase processor) + { + using Image unassociated = processor == ProcessorCase.EntropyCrop ? CreateEntropyCropImage() : CreateTestImage(); + using Image associated = CreateAssociatedImage(unassociated); + + ApplyProcessor(unassociated, processor); + ApplyProcessor(associated, processor); + + AssertEquivalent(unassociated, associated); + } + + /// + /// Verifies the native-range bulk hooks required by the public associated-alpha operation contract. + /// + [Fact] + public void ScaledAssociatedPixelNativeBulkConversionsMatchScalarConversions() + { + const int length = 259; + ScaledRgbaVectorP pixel = ScaledRgbaVectorP.FromUnassociatedScaledVector4(new Vector4(.8F, .4F, .2F, .5F)); + ScaledRgbaVectorP[] pixels = new ScaledRgbaVectorP[length]; + Vector4[] expectedVectors = new Vector4[length]; + Vector4[] actualVectors = new Vector4[length]; + ScaledRgbaVectorP[] expectedPixels = new ScaledRgbaVectorP[length]; + ScaledRgbaVectorP[] actualPixels = new ScaledRgbaVectorP[length]; + PixelOperations operations = PixelOperations.Instance; + + Array.Fill(pixels, pixel); + + for (int i = 0; i < length; i++) + { + expectedVectors[i] = pixels[i].ToUnassociatedVector4(); + } + + operations.ToVector4(Configuration.Default, pixels, actualVectors, PixelConversionModifiers.UnPremultiply); + Assert.Equal(expectedVectors, actualVectors); + + for (int i = 0; i < length; i++) + { + expectedVectors[i] = pixels[i].ToAssociatedVector4(); + } + + operations.ToVector4(Configuration.Default, pixels, actualVectors, PixelConversionModifiers.Premultiply); + Assert.Equal(expectedVectors, actualVectors); + + for (int i = 0; i < length; i++) + { + expectedVectors[i] = pixels[i].ToUnassociatedVector4(); + expectedPixels[i] = ScaledRgbaVectorP.FromUnassociatedVector4(expectedVectors[i]); + } + + operations.FromVector4Destructive(Configuration.Default, expectedVectors, actualPixels, PixelConversionModifiers.UnPremultiply); + Assert.Equal(expectedPixels, actualPixels); + + for (int i = 0; i < length; i++) + { + expectedVectors[i] = pixels[i].ToAssociatedVector4(); + expectedPixels[i] = ScaledRgbaVectorP.FromAssociatedVector4(expectedVectors[i]); + } + + operations.FromVector4Destructive(Configuration.Default, expectedVectors, actualPixels, PixelConversionModifiers.Premultiply); + Assert.Equal(expectedPixels, actualPixels); + } + + [Fact] + public void ErrorDiffusionUsesLogicalUnassociatedError() + { + Vector4 background = new(.25F, .25F, .25F, .5F); + using Image unassociated = new(4, 4, RgbaVector.FromScaledVector4(background)); + using Image associated = CreateAssociatedImage(unassociated); + + Vector4 source = new(.5F, .5F, .5F, .5F); + Vector4 transformed = new(.25F, .25F, .25F, .5F); + RgbaVector unassociatedSource = RgbaVector.FromScaledVector4(source); + RgbaVector unassociatedTransformed = RgbaVector.FromScaledVector4(transformed); + ScaledRgbaVectorP associatedSource = ScaledRgbaVectorP.FromUnassociatedScaledVector4(source); + ScaledRgbaVectorP associatedTransformed = ScaledRgbaVectorP.FromUnassociatedScaledVector4(transformed); + + ErrorDither.FloydSteinberg.Dither(unassociated.Frames.RootFrame, unassociated.Bounds, unassociatedSource, unassociatedTransformed, 1, 1, 1F); + ErrorDither.FloydSteinberg.Dither(associated.Frames.RootFrame, associated.Bounds, associatedSource, associatedTransformed, 1, 1, 1F); + + AssertEquivalent(unassociated, associated); + } + + [Fact] + public void AffineTransformFractionalEdgesMatchNormalPixelConversion() + { + using Image rgba = new(4, 4, new Rgba32(255, 64, 16, 255)); + using Image bgra = rgba.CloneAs(); + using Image rgb = rgba.CloneAs(); + + ApplyProcessor(rgba, ProcessorCase.AffineTransform); + ApplyProcessor(bgra, ProcessorCase.AffineTransform); + ApplyProcessor(rgb, ProcessorCase.AffineTransform); + + // Alpha-less output drops fractional coverage after unassociation, just like a normal pixel-format conversion. + using Image expectedBgra = rgba.CloneAs(); + using Image expectedRgb = rgba.CloneAs(); + + Assert.Equal(rgba.Size, bgra.Size); + Assert.Equal(rgba.Size, rgb.Size); + + bool foundFractionalCoverage = false; + for (int y = 0; y < rgba.Height; y++) + { + for (int x = 0; x < rgba.Width; x++) + { + Rgba32 pixel = rgba[x, y]; + + if (pixel.A is > 0 and < 255) + { + foundFractionalCoverage = true; + Assert.Equal(new Rgba32(255, 64, 16, pixel.A), pixel); + } + + Assert.Equal(expectedBgra[x, y], bgra[x, y]); + Assert.Equal(expectedRgb[x, y], rgb[x, y]); + } + } + + Assert.True(foundFractionalCoverage); + } + + [Fact] + public void ConvolutionDoesNotObserveColorBehindZeroAlpha() + { + using Image hiddenColor = new(3, 1); + using Image transparentBlack = new(3, 1); + + hiddenColor[0, 0] = new RgbaVector(1, 0, 0, 0); + hiddenColor[1, 0] = new RgbaVector(0, 0, 1, 1); + hiddenColor[2, 0] = new RgbaVector(1, 0, 0, 0); + transparentBlack[1, 0] = hiddenColor[1, 0]; + + ApplyProcessor(hiddenColor, ProcessorCase.Convolution); + ApplyProcessor(transparentBlack, ProcessorCase.Convolution); + + // Associated filtering makes fully transparent RGB unobservable before the kernel is evaluated. + for (int x = 0; x < hiddenColor.Width; x++) + { + Assert.Equal(transparentBlack[x, 0], hiddenColor[x, 0]); + } + } + + private static Image CreateTestImage() + { + Image image = new(8, 8); + + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + float alpha = ((x + (2 * y)) % 3) switch + { + 0 => .25F, + 1 => .5F, + _ => 1F + }; + + // Dyadic components and alpha make the initial association round trip exact in binary floating point. + float red = ((x + y) & 7) / 8F; + float green = (((3 * x) + y + 1) & 7) / 8F; + float blue = ((x + (5 * y) + 2) & 7) / 8F; + image[x, y] = new RgbaVector(red, green, blue, alpha); + } + } + + return image; + } + + private static Image CreateEntropyCropImage() + { + Image image = new(8, 8, new RgbaVector(0, 0, 0, 1)); + + for (int y = 2; y < 6; y++) + { + for (int x = 2; x < 6; x++) + { + image[x, y] = new RgbaVector(1, 1, 1, 1); + } + } + + return image; + } + + private static Image CreateAssociatedImage(Image source) + { + Image image = new(source.Width, source.Height); + + for (int y = 0; y < source.Height; y++) + { + for (int x = 0; x < source.Width; x++) + { + Vector4 vector = source[x, y].ToScaledVector4(); + Numerics.Premultiply(ref vector); + image[x, y] = ScaledRgbaVectorP.FromScaledVector4(vector); + } + } + + return image; + } + + private static void ApplyProcessor(Image image, ProcessorCase processor) + where TPixel : unmanaged, IPixel + { + if (processor == ProcessorCase.Opaque) + { + using OpaqueProcessor opaque = new(image.Configuration, image, image.Bounds); + opaque.Apply(image.Frames.RootFrame); + return; + } + + if (processor is ProcessorCase.Convolution2D or ProcessorCase.Convolution2DPreserveAlpha) + { + DenseMatrix kernelX = new float[,] + { + { -.25F, 0, .25F }, + { -.5F, 0, .5F }, + { -.25F, 0, .25F } + }; + DenseMatrix kernelY = new float[,] + { + { -.25F, -.5F, -.25F }, + { 0, 0, 0 }, + { .25F, .5F, .25F } + }; + + using Convolution2DProcessor convolution = new( + image.Configuration, + kernelX, + kernelY, + processor == ProcessorCase.Convolution2DPreserveAlpha, + image, + image.Bounds); + + convolution.Apply(image.Frames.RootFrame); + return; + } + + if (processor is ProcessorCase.Convolution2Pass or ProcessorCase.Convolution2PassPreserveAlpha) + { + float[] kernel = [.25F, .5F, .25F]; + using Convolution2PassProcessor convolution = new( + image.Configuration, + kernel, + processor == ProcessorCase.Convolution2PassPreserveAlpha, + image, + image.Bounds, + BorderWrappingMode.Repeat, + BorderWrappingMode.Repeat); + + convolution.Apply(image.Frames.RootFrame); + return; + } + + image.Mutate(context => + { + switch (processor) + { + case ProcessorCase.Invert: + context.Invert(); + break; + case ProcessorCase.ColorMatrix: + ColorMatrix matrix = new() + { + M11 = .5F, + M22 = .5F, + M33 = .5F, + M41 = .25F, + M42 = .25F, + M43 = .25F, + M14 = .125F, + M24 = .125F, + M34 = .125F, + M44 = .5F + }; + + context.Filter(matrix); + break; + case ProcessorCase.Convolution: + context.Convolve(new float[,] { { .25F, .5F, .25F } }); + break; + case ProcessorCase.ConvolutionPreserveAlpha: + context.Convolve(new float[,] { { .25F, .5F, .25F } }, true); + break; + case ProcessorCase.Median: + context.MedianBlur(1, false); + break; + case ProcessorCase.MedianPreserveAlpha: + context.MedianBlur(1, true); + break; + case ProcessorCase.BokehBlur: + context.BokehBlur(2, 2, 2F); + break; + case ProcessorCase.OilPaint: + context.OilPaint(4, 3); + break; + case ProcessorCase.HistogramGlobal: + context.HistogramEqualization(CreateHistogramOptions(HistogramEqualizationMethod.Global)); + break; + case ProcessorCase.HistogramAdaptiveSlidingWindow: + context.HistogramEqualization(CreateHistogramOptions(HistogramEqualizationMethod.AdaptiveSlidingWindow)); + break; + case ProcessorCase.HistogramAdaptiveTileInterpolation: + context.HistogramEqualization(CreateHistogramOptions(HistogramEqualizationMethod.AdaptiveTileInterpolation)); + break; + case ProcessorCase.HistogramAutoLevel: + context.HistogramEqualization(CreateHistogramOptions(HistogramEqualizationMethod.AutoLevel)); + break; + case ProcessorCase.HistogramAutoLevelSeparateChannels: + HistogramEqualizationOptions options = CreateHistogramOptions(HistogramEqualizationMethod.AutoLevel); + options.SyncChannels = false; + context.HistogramEqualization(options); + break; + case ProcessorCase.ResizeCompanded: + context.Resize(new ResizeOptions + { + Size = new Size(5, 5), + Sampler = KnownResamplers.Box, + Compand = true, + PremultiplyAlpha = true + }); + break; + case ProcessorCase.ResizeUnassociated: + context.Resize(new ResizeOptions + { + Size = new Size(5, 5), + Sampler = KnownResamplers.Box, + PremultiplyAlpha = false + }); + break; + case ProcessorCase.AffineTransform: + context.Transform(image.Bounds, Matrix3x2.CreateTranslation(.5F, .5F), image.Size, KnownResamplers.Bicubic); + break; + case ProcessorCase.ProjectiveTransform: + context.Transform(image.Bounds, Matrix4x4.CreateTranslation(.5F, .5F, 0), image.Size, KnownResamplers.Bicubic); + break; + case ProcessorCase.OrderedDither: + context.Dither(KnownDitherings.Bayer4x4); + break; + case ProcessorCase.EntropyCrop: + context.EntropyCrop(.5F); + break; + } + }); + } + + private static HistogramEqualizationOptions CreateHistogramOptions(HistogramEqualizationMethod method) + => new() + { + Method = method, + LuminanceLevels = 16, + NumberOfTiles = 2 + }; + + private static void AssertEquivalent(Image unassociated, Image associated) + { + // Materialize both representations through the same 16-bit logical-color boundary. Their floating-point storage paths can differ by one rounding operation when associated results are unassociated for RgbaVector storage. + using Image expected = unassociated.CloneAs(); + using Image actual = associated.CloneAs(); + + Assert.Equal(expected.Size, actual.Size); + + for (int y = 0; y < expected.Height; y++) + { + for (int x = 0; x < expected.Width; x++) + { + Assert.Equal(expected[x, y], actual[x, y]); + } + } + } + + public enum ProcessorCase + { + Opaque, + Invert, + ColorMatrix, + Convolution, + ConvolutionPreserveAlpha, + Convolution2D, + Convolution2DPreserveAlpha, + Convolution2Pass, + Convolution2PassPreserveAlpha, + Median, + MedianPreserveAlpha, + BokehBlur, + OilPaint, + HistogramGlobal, + HistogramAdaptiveSlidingWindow, + HistogramAdaptiveTileInterpolation, + HistogramAutoLevel, + HistogramAutoLevelSeparateChannels, + ResizeCompanded, + ResizeUnassociated, + AffineTransform, + ProjectiveTransform, + OrderedDither, + EntropyCrop + } +} diff --git a/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs index eb1d3031a..d5dbb1b11 100644 --- a/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs @@ -411,9 +411,9 @@ public class KernelSamplingMapTest map.BuildSamplingOffsetMap(kernelSize.Height, kernelSize.Width, bounds, xBorderMode, yBorderMode); // Assert - int[] xOffsets = map.GetColumnOffsetSpan().ToArray(); + int[] xOffsets = [.. map.GetColumnOffsetSpan()]; Assert.Equal(xExpected, xOffsets); - int[] yOffsets = map.GetRowOffsetSpan().ToArray(); + int[] yOffsets = [.. map.GetRowOffsetSpan()]; Assert.Equal(yExpected, yOffsets); } } diff --git a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs index 135212400..d5f7f69d9 100644 --- a/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs +++ b/tests/ImageSharp.Tests/Processing/Filters/BrightnessTest.cs @@ -43,13 +43,14 @@ public class BrightnessTest : BaseImageOperationsExtensionTest Assert.Equal(new Rgb24(20, 20, 20), rgbImage[0, 0]); - Image halfSingleImage = new(Configuration.Default, 100, 100, new HalfSingle(-1)); + // HalfSingle normalizes the complete finite binary16 interval, making -65504 logical zero and -32752 logical .25. + Image halfSingleImage = new(Configuration.Default, 100, 100, new HalfSingle((float)Half.MinValue)); halfSingleImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); - Assert.Equal(new HalfSingle(-1), halfSingleImage[0, 0]); + Assert.Equal(new HalfSingle((float)Half.MinValue), halfSingleImage[0, 0]); - halfSingleImage = new Image(Configuration.Default, 100, 100, new HalfSingle(-0.5f)); + halfSingleImage = new Image(Configuration.Default, 100, 100, new HalfSingle(-32752F)); halfSingleImage.Mutate(x => x.ApplyProcessor(new BrightnessProcessor(2))); diff --git a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs index 81ba1693d..a0ea99a9d 100644 --- a/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs +++ b/tests/ImageSharp.Tests/Processing/IntegralImageTests.cs @@ -76,7 +76,7 @@ public class IntegralImageTests : BaseImageOperationsExtensionTest Buffer2D integralBuffer, Func getPixel) where TPixel : unmanaged, IPixel - => VerifySumValues(provider, integralBuffer, integralBuffer.Bounds(), getPixel); + => VerifySumValues(provider, integralBuffer, integralBuffer.Bounds, getPixel); private static void VerifySumValues( TestImageProvider provider, diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs index f045c981e..430e3ceef 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; @@ -47,18 +47,20 @@ public class BokehBlurTest public void VerifyComplexComponents() { // Get the saved components - List components = new(); + List components = []; foreach (Match match in Regex.Matches(Components10x2, @"\[\[(.*?)\]\]", RegexOptions.Singleline)) { - string[] values = match.Groups[1].Value.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - Complex64[] component = values.Select( - value => - { - Match pair = Regex.Match(value, @"([+-]?\d+\.\d+)([+-]?\d+\.\d+)j"); - return new Complex64( - float.Parse(pair.Groups[1].Value, CultureInfo.InvariantCulture), - float.Parse(pair.Groups[2].Value, CultureInfo.InvariantCulture)); - }).ToArray(); + string[] values = match.Groups[1].Value.Trim().Split([' '], StringSplitOptions.RemoveEmptyEntries); + Complex64[] component = + [ + .. values.Select(value => + { + Match pair = Regex.Match(value, @"([+-]?\d+\.\d+)([+-]?\d+\.\d+)j"); + return new Complex64( + float.Parse(pair.Groups[1].Value, CultureInfo.InvariantCulture), + float.Parse(pair.Groups[2].Value, CultureInfo.InvariantCulture)); + }) + ]; components.Add(component); } @@ -114,12 +116,12 @@ public class BokehBlurTest }; public static readonly string[] TestFiles = - { - TestImages.Png.CalliphoraPartial, + [ + TestImages.Png.CalliphoraPartial, TestImages.Png.Bike, TestImages.Png.BikeGrayscale, - TestImages.Png.Cross, - }; + TestImages.Png.Cross + ]; [Theory] [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] @@ -148,7 +150,7 @@ public class BokehBlurTest [Theory] [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.AllowAll)] - [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.DisableSSE41)] + [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.DisableHWIntrinsic)] public void BokehBlurFilterProcessor_Bounded(TestImageProvider provider, BokehBlurInfo value, HwIntrinsics intrinsicsFilter) { static void RunTest(string arg1, string arg2) diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs index 574c98371..38c635004 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/ConvolutionProcessorHelpersTest.cs @@ -41,6 +41,41 @@ public class ConvolutionProcessorHelpersTest } } + /// + /// Verifies that Gaussian sharpening preserves the scalar kernel formula across scalar and SIMD lengths. + /// + /// The kernel radius. + [Theory] + [InlineData(1)] + [InlineData(3)] + [InlineData(9)] + [InlineData(32)] + [InlineData(80)] + public void VerifyGaussianSharpenKernel(int radius) + { + int kernelSize = (radius * 2) + 1; + float sigma = radius / 3F; + float[] expected = new float[kernelSize]; + float sum = 0F; + + for (int i = 0; i < kernelSize; i++) + { + float value = Numerics.Gaussian(i - radius, sigma); + expected[i] = value; + sum += value; + } + + for (int i = 0; i < kernelSize; i++) + { + expected[i] = i == radius ? (2F * sum) - expected[i] : -expected[i]; + expected[i] /= sum; + } + + float[] actual = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, sigma); + + Assert.Equal(expected, actual); + } + [Fact] public void VerifyNonSeparableMatrix() { diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs index 0728bb5d8..f9f10cb70 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/GaussianBlurTest.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Convolution; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; @@ -13,4 +15,32 @@ public class GaussianBlurTest : Basic1ParameterConvolutionTests protected override void Apply(IImageProcessingContext ctx, int value, Rectangle bounds) => ctx.GaussianBlur(bounds, value); + + [Theory] + [InlineData(BorderWrappingMode.Repeat)] + [InlineData(BorderWrappingMode.Mirror)] + [InlineData(BorderWrappingMode.Bounce)] + [InlineData(BorderWrappingMode.Wrap)] + public void OnImageSmallerThanKernelRadius_LeavesSolidColorUnchanged(BorderWrappingMode mode) + { + // 8 rows against sigma 10 (61-tap kernel, radius 30). A normalized blur of a solid + // image must remain that color no matter how the border sampling folds. + Rgba32 red = new(255, 0, 0); + using Image image = new(130, 8, red); + image.Mutate(x => x.GaussianBlur(image.Bounds, 10F, mode, mode)); + + image.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + foreach (Rgba32 pixel in accessor.GetRowSpan(y)) + { + Assert.InRange(pixel.R, 254, 255); + Assert.InRange(pixel.G, 0, 1); + Assert.InRange(pixel.B, 0, 1); + Assert.Equal(255, pixel.A); + } + } + }); + } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/KernelSamplingMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/KernelSamplingMapTests.cs new file mode 100644 index 000000000..5194fa001 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/KernelSamplingMapTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Processing.Processors.Convolution; + +namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution; + +[Trait("Category", "Processors")] +[GroupOutput("Convolution")] +public class KernelSamplingMapTests +{ + public static readonly TheoryData BoundsSmallerThanKernel = new() + { + { BorderWrappingMode.Repeat, 8, 5 }, + { BorderWrappingMode.Mirror, 8, 5 }, + { BorderWrappingMode.Bounce, 8, 5 }, + { BorderWrappingMode.Wrap, 8, 5 }, + { BorderWrappingMode.Repeat, 1, 1 }, + { BorderWrappingMode.Mirror, 1, 1 }, + { BorderWrappingMode.Bounce, 1, 1 }, + { BorderWrappingMode.Wrap, 1, 1 }, + { BorderWrappingMode.Repeat, 30, 30 }, + { BorderWrappingMode.Mirror, 30, 30 }, + { BorderWrappingMode.Bounce, 30, 30 }, + { BorderWrappingMode.Wrap, 30, 30 }, + }; + + [Theory] + [MemberData(nameof(BoundsSmallerThanKernel))] + public void BuildSamplingOffsetMap_BoundsSmallerThanKernelRadius_OffsetsStayInBounds(BorderWrappingMode mode, int width, int height) + { + // A 61-tap kernel has radius 30, so these bounds are covered entirely by the border + // regions and offsets can overshoot the bounds by more than one sampling extent. + const int kernelSize = 61; + Rectangle bounds = new(100, 200, width, height); + + using KernelSamplingMap map = new(Configuration.Default.MemoryAllocator); + map.BuildSamplingOffsetMap(kernelSize, kernelSize, bounds, mode, mode); + + foreach (int x in map.GetColumnOffsetSpan()) + { + Assert.InRange(x, bounds.Left, bounds.Right - 1); + } + + foreach (int y in map.GetRowOffsetSpan()) + { + Assert.InRange(y, bounds.Top, bounds.Bottom - 1); + } + } +} diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 25acd3aa6..3c2e5e0d5 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -43,8 +43,8 @@ public class DitherTests public static readonly TheoryData DefaultInstanceDitherers = new() { - default(ErrorDither), - default(OrderedDither) + (IDither)default(ErrorDither), + (IDither)default(OrderedDither) }; private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05f); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs similarity index 65% rename from tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs rename to tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs index c9f3daf0f..b154551e2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/OctreeQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/HexadecatreeQuantizerTests.cs @@ -8,37 +8,37 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Quantization; [Trait("Category", "Processors")] -public class OctreeQuantizerTests +public class HexadecatreeQuantizerTests { [Fact] - public void OctreeQuantizerConstructor() + public void HexadecatreeQuantizerConstructor() { QuantizerOptions expected = new() { MaxColors = 128 }; - OctreeQuantizer quantizer = new(expected); + HexadecatreeQuantizer quantizer = new(expected); Assert.Equal(expected.MaxColors, quantizer.Options.MaxColors); Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); expected = new QuantizerOptions { Dither = null }; - quantizer = new OctreeQuantizer(expected); + quantizer = new HexadecatreeQuantizer(expected); Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); Assert.Null(quantizer.Options.Dither); expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson }; - quantizer = new OctreeQuantizer(expected); + quantizer = new HexadecatreeQuantizer(expected); Assert.Equal(QuantizerConstants.MaxColors, quantizer.Options.MaxColors); Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); expected = new QuantizerOptions { Dither = KnownDitherings.Atkinson, MaxColors = 0 }; - quantizer = new OctreeQuantizer(expected); + quantizer = new HexadecatreeQuantizer(expected); Assert.Equal(QuantizerConstants.MinColors, quantizer.Options.MaxColors); Assert.Equal(KnownDitherings.Atkinson, quantizer.Options.Dither); } [Fact] - public void OctreeQuantizerCanCreateFrameQuantizer() + public void HexadecatreeQuantizerCanCreateFrameQuantizer() { - OctreeQuantizer quantizer = new(); + HexadecatreeQuantizer quantizer = new(); IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); @@ -46,17 +46,31 @@ public class OctreeQuantizerTests Assert.Equal(QuantizerConstants.DefaultDither, frameQuantizer.Options.Dither); frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }); + quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = null }); frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); Assert.Null(frameQuantizer.Options.Dither); frameQuantizer.Dispose(); - quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); + quantizer = new HexadecatreeQuantizer(new QuantizerOptions { Dither = KnownDitherings.Atkinson }); frameQuantizer = quantizer.CreatePixelSpecificQuantizer(Configuration.Default); Assert.NotNull(frameQuantizer); Assert.Equal(KnownDitherings.Atkinson, frameQuantizer.Options.Dither); frameQuantizer.Dispose(); } + + [Fact] + public void PaletteUsesNormalizedTransparencyThreshold() + { + using HexadecatreeQuantizer.Hexadecatree tree = new(Configuration.Default, 8, 2, .5F); + tree.AddColors([new Rgba32(255, 255, 255, 127)]); + + Span palette = stackalloc Rgba32[2]; + short paletteIndex = 0; + tree.Palettize(palette, ref paletteIndex); + + Assert.Equal(1, paletteIndex); + Assert.Equal(default, palette[0]); + } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs index f2a4b079b..9c1985840 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/PaletteQuantizerTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -75,4 +76,161 @@ public class PaletteQuantizerTests IQuantizer quantizer = KnownQuantizers.Werner; Assert.Equal(QuantizerConstants.DefaultDither, quantizer.Options.Dither); } + + [Fact] + public void ExactColorMatchingMatchesUncachedAfterCacheOverflow() + { + Rgba32[] palette = + [ + new(0, 0, 0), + new(7, 0, 0) + ]; + + using PixelMap exact = CreatePixelMap(palette); + using PixelMap cachedExact = CreatePixelMap(palette); + + for (int i = 0; i < AccurateCache.Capacity; i++) + { + cachedExact.GetClosestColor(CreateOverflowFillerColor(i), out _); + } + + Rgba32 first = new(1, 0, 0); + Rgba32 second = new(6, 0, 0); + + AssertMatchesUncached(exact, cachedExact, first); + AssertMatchesUncached(exact, cachedExact, second); + } + + [Fact] + public void ExactColorMatchingMatchesUncachedAcrossManyProbeBinsAfterRepeatedEviction() + { + Rgba32[] palette = CreateGrayscalePalette(256); + + using PixelMap exact = CreatePixelMap(palette); + using PixelMap cachedExact = CreatePixelMap(palette); + + for (int i = 0; i < AccurateCache.Capacity * 2; i++) + { + cachedExact.GetClosestColor(CreateEvictionFillerColor(i), out _); + } + + for (int i = 0; i < AccurateCache.Capacity; i++) + { + AssertMatchesUncached(exact, cachedExact, CreateEvictionProbeColor(i)); + } + } + + [Fact] + public void ExactColorMatchingMatchesUncachedForDitherStressColorSequence() + { + Rgba32[] palette = CreateGrayscalePalette(16); + + using Image source = CreateDitherStressImage(); + using PixelMap exact = CreatePixelMap(palette); + using PixelMap cachedExact = CreatePixelMap(palette); + + for (int y = 0; y < source.Height; y++) + { + for (int x = 0; x < source.Width; x++) + { + AssertMatchesUncached(exact, cachedExact, source[x, y]); + } + } + } + + // Split the first 512 integers across R and G so the warmup loop produces 512 distinct exact colors: + // the low 8 bits go into R, and the ninth bit spills into G once R wraps after 255. + // Keeping B fixed and G offset away from zero also avoids accidentally probing the red-axis test colors below. + private static Rgba32 CreateOverflowFillerColor(int i) + => new((byte)i, (byte)(16 + (i >> 8)), 32); + + // Treat i as three packed 5-bit coordinates and expand each coordinate back to an 8-bit channel by + // shifting left by 3. That lands on the lower edge of each 5-bit coarse bucket, giving the test a + // deterministic way to fill many distinct coarse buckets before probing nearby exact colors. + private static Rgba32 CreateEvictionFillerColor(int i) + { + byte r = (byte)((i & 31) << 3); + byte g = (byte)(((i >> 5) & 31) << 3); + byte b = (byte)(((i >> 10) & 31) << 3); + return new Rgba32(r, g, b); + } + + // Reconstruct the same 5-bit RGB bucket coordinates used by CreateEvictionFillerColor, then set the + // low 3 bits in each channel to 0b111. That keeps the probe inside the same coarse bucket while making + // it a different exact color, which is the shape that used to expose coarse-fallback false hits. + private static Rgba32 CreateEvictionProbeColor(int i) + { + byte r = (byte)(((i & 31) << 3) | 0x07); + byte g = (byte)((((i >> 5) & 31) << 3) | 0x07); + byte b = (byte)((((i >> 10) & 31) << 3) | 0x07); + return new Rgba32(r, g, b); + } + + private static PixelMap CreatePixelMap(Rgba32[] palette) + where TCache : struct, IColorIndexCache + => new EuclideanPixelMap(Configuration.Default, palette); + + private static void AssertMatchesUncached(PixelMap exact, PixelMap cachedExact, Rgba32 color) + { + int exactIndex = exact.GetClosestColor(color, out Rgba32 exactMatch); + int cachedIndex = cachedExact.GetClosestColor(color, out Rgba32 cachedMatch); + + Assert.Equal(exactIndex, cachedIndex); + Assert.Equal(exactMatch, cachedMatch); + } + + private static Rgba32[] CreateGrayscalePalette(int count) + { + Rgba32[] palette = new Rgba32[count]; + for (int i = 0; i < count; i++) + { + byte value = count == 1 ? (byte)0 : (byte)((i * 255) / (count - 1)); + palette[i] = new Rgba32(value, value, value); + } + + return palette; + } + + // Generate a deterministic pseudo-image where each channel uses a different x/y slope. + // Neighboring pixels stay correlated, like real image content, but the combined RGB values + // churn heavily enough that exact repeats are rare. That makes this a useful stress input + // for verifying cached exact matching against an uncached baseline under dither-like access. + private static Image CreateDitherStressImage() + { + Image image = new(192, 96); + + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + image[x, y] = new Rgba32( + (byte)((x * 17) + (y * 13)), + (byte)((x * 29) + (y * 7)), + (byte)((x * 11) + (y * 23))); + } + } + + return image; + } + + private readonly struct UncachedCache : IColorIndexCache + { + public static UncachedCache Create(MemoryAllocator allocator) => default; + + public bool TryAdd(Rgba32 color, short value) => true; + + public bool TryGetValue(Rgba32 color, out short value) + { + value = -1; + return false; + } + + public void Clear() + { + } + + public void Dispose() + { + } + } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs index 2ba757c11..4668d0c29 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Quantization/QuantizerTests.cs @@ -74,79 +74,79 @@ public class QuantizerTests = new() { // Known uses error diffusion by default. - KnownQuantizers.Octree, + KnownQuantizers.Hexadecatree, KnownQuantizers.WebSafe, KnownQuantizers.Werner, KnownQuantizers.Wu, - new OctreeQuantizer(NoDitherOptions), - new WebSafePaletteQuantizer(NoDitherOptions), - new WernerPaletteQuantizer(NoDitherOptions), - new WuQuantizer(NoDitherOptions), - new OctreeQuantizer(OrderedDitherOptions), - new WebSafePaletteQuantizer(OrderedDitherOptions), - new WernerPaletteQuantizer(OrderedDitherOptions), - new WuQuantizer(OrderedDitherOptions) + (IQuantizer)new HexadecatreeQuantizer(NoDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(NoDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(NoDitherOptions), + (IQuantizer)new WuQuantizer(NoDitherOptions), + (IQuantizer)new HexadecatreeQuantizer(OrderedDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(OrderedDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(OrderedDitherOptions), + (IQuantizer)new WuQuantizer(OrderedDitherOptions) }; public static readonly TheoryData DitherScaleQuantizers = new() { - new OctreeQuantizer(Diffuser0_ScaleDitherOptions), - new WebSafePaletteQuantizer(Diffuser0_ScaleDitherOptions), - new WernerPaletteQuantizer(Diffuser0_ScaleDitherOptions), - new WuQuantizer(Diffuser0_ScaleDitherOptions), - - new OctreeQuantizer(Diffuser0_25_ScaleDitherOptions), - new WebSafePaletteQuantizer(Diffuser0_25_ScaleDitherOptions), - new WernerPaletteQuantizer(Diffuser0_25_ScaleDitherOptions), - new WuQuantizer(Diffuser0_25_ScaleDitherOptions), - - new OctreeQuantizer(Diffuser0_5_ScaleDitherOptions), - new WebSafePaletteQuantizer(Diffuser0_5_ScaleDitherOptions), - new WernerPaletteQuantizer(Diffuser0_5_ScaleDitherOptions), - new WuQuantizer(Diffuser0_5_ScaleDitherOptions), - - new OctreeQuantizer(Diffuser0_75_ScaleDitherOptions), - new WebSafePaletteQuantizer(Diffuser0_75_ScaleDitherOptions), - new WernerPaletteQuantizer(Diffuser0_75_ScaleDitherOptions), - new WuQuantizer(Diffuser0_75_ScaleDitherOptions), - - new OctreeQuantizer(DiffuserDitherOptions), - new WebSafePaletteQuantizer(DiffuserDitherOptions), - new WernerPaletteQuantizer(DiffuserDitherOptions), - new WuQuantizer(DiffuserDitherOptions), - - new OctreeQuantizer(Ordered0_ScaleDitherOptions), - new WebSafePaletteQuantizer(Ordered0_ScaleDitherOptions), - new WernerPaletteQuantizer(Ordered0_ScaleDitherOptions), - new WuQuantizer(Ordered0_ScaleDitherOptions), - - new OctreeQuantizer(Ordered0_25_ScaleDitherOptions), - new WebSafePaletteQuantizer(Ordered0_25_ScaleDitherOptions), - new WernerPaletteQuantizer(Ordered0_25_ScaleDitherOptions), - new WuQuantizer(Ordered0_25_ScaleDitherOptions), - - new OctreeQuantizer(Ordered0_5_ScaleDitherOptions), - new WebSafePaletteQuantizer(Ordered0_5_ScaleDitherOptions), - new WernerPaletteQuantizer(Ordered0_5_ScaleDitherOptions), - new WuQuantizer(Ordered0_5_ScaleDitherOptions), - - new OctreeQuantizer(Ordered0_75_ScaleDitherOptions), - new WebSafePaletteQuantizer(Ordered0_75_ScaleDitherOptions), - new WernerPaletteQuantizer(Ordered0_75_ScaleDitherOptions), - new WuQuantizer(Ordered0_75_ScaleDitherOptions), - - new OctreeQuantizer(OrderedDitherOptions), - new WebSafePaletteQuantizer(OrderedDitherOptions), - new WernerPaletteQuantizer(OrderedDitherOptions), - new WuQuantizer(OrderedDitherOptions), + (IQuantizer)new HexadecatreeQuantizer(Diffuser0_ScaleDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(Diffuser0_ScaleDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(Diffuser0_ScaleDitherOptions), + (IQuantizer)new WuQuantizer(Diffuser0_ScaleDitherOptions), + + (IQuantizer)new HexadecatreeQuantizer(Diffuser0_25_ScaleDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(Diffuser0_25_ScaleDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(Diffuser0_25_ScaleDitherOptions), + (IQuantizer)new WuQuantizer(Diffuser0_25_ScaleDitherOptions), + + (IQuantizer)new HexadecatreeQuantizer(Diffuser0_5_ScaleDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(Diffuser0_5_ScaleDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(Diffuser0_5_ScaleDitherOptions), + (IQuantizer)new WuQuantizer(Diffuser0_5_ScaleDitherOptions), + + (IQuantizer)new HexadecatreeQuantizer(Diffuser0_75_ScaleDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(Diffuser0_75_ScaleDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(Diffuser0_75_ScaleDitherOptions), + (IQuantizer)new WuQuantizer(Diffuser0_75_ScaleDitherOptions), + + (IQuantizer)new HexadecatreeQuantizer(DiffuserDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(DiffuserDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(DiffuserDitherOptions), + (IQuantizer)new WuQuantizer(DiffuserDitherOptions), + + (IQuantizer)new HexadecatreeQuantizer(Ordered0_ScaleDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(Ordered0_ScaleDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(Ordered0_ScaleDitherOptions), + (IQuantizer)new WuQuantizer(Ordered0_ScaleDitherOptions), + + (IQuantizer)new HexadecatreeQuantizer(Ordered0_25_ScaleDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(Ordered0_25_ScaleDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(Ordered0_25_ScaleDitherOptions), + (IQuantizer)new WuQuantizer(Ordered0_25_ScaleDitherOptions), + + (IQuantizer)new HexadecatreeQuantizer(Ordered0_5_ScaleDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(Ordered0_5_ScaleDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(Ordered0_5_ScaleDitherOptions), + (IQuantizer)new WuQuantizer(Ordered0_5_ScaleDitherOptions), + + (IQuantizer)new HexadecatreeQuantizer(Ordered0_75_ScaleDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(Ordered0_75_ScaleDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(Ordered0_75_ScaleDitherOptions), + (IQuantizer)new WuQuantizer(Ordered0_75_ScaleDitherOptions), + + (IQuantizer)new HexadecatreeQuantizer(OrderedDitherOptions), + (IQuantizer)new WebSafePaletteQuantizer(OrderedDitherOptions), + (IQuantizer)new WernerPaletteQuantizer(OrderedDitherOptions), + (IQuantizer)new WuQuantizer(OrderedDitherOptions), }; public static readonly TheoryData DefaultInstanceDitherers = new() { - default(ErrorDither), - default(OrderedDither) + (IDither)default(ErrorDither), + (IDither)default(OrderedDither) }; private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.05F); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs index a7855e23a..2697505c2 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -7,7 +7,6 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Transforms; @@ -234,7 +233,23 @@ public class AffineTransformTests image.DebugSave(provider); Assert.Equal(4, image.Width); - Assert.Equal(8, image.Height); + Assert.Equal(7, image.Height); + } + + [Theory] + [WithFile(TestImages.Png.Issue3000, PixelTypes.Rgba32, 3, 3)] + [WithFile(TestImages.Png.Issue3000, PixelTypes.Rgba32, 4, 4)] + public void Issue3000(TestImageProvider provider, float x, float y) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + image.Mutate(c => c + .Transform(new AffineTransformBuilder().AppendRotationDegrees(90, new Vector2(x, y)))); + + string details = $"p-{x}-{y}"; + image.DebugSave(provider, testOutputDetails: details); + image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: details); } [Theory] @@ -267,31 +282,41 @@ public class AffineTransformTests image.CompareToReferenceOutput(ValidatorComparer, provider, testOutputDetails: radians); } - [Fact] - public void TransformRotationDoesNotOffset() + [Theory] + [WithSolidFilledImages(100, 100, "DimGray", PixelTypes.Rgba32)] + public void TransformRotationDoesNotOffset(TestImageProvider provider) + where TPixel : unmanaged, IPixel { Rgba32 background = Color.DimGray.ToPixel(); - Rgba32 marker = Color.Aqua.ToPixel(); + TPixel marker = Color.Aqua.ToPixel(); + + using Image canvas = provider.GetImage(); - using Image img = new(100, 100, background); + using Image img = canvas.Clone(); img[0, 0] = marker; img.Mutate(c => c.Rotate(180)); Assert.Equal(marker, img[99, 99]); - using Image img2 = new(100, 100, background); + img.DebugSave(provider, "Rotate180"); + + using Image img2 = canvas.Clone(); img2[0, 0] = marker; img2.Mutate( c => c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180), KnownResamplers.NearestNeighbor)); - using Image img3 = new(100, 100, background); + img.DebugSave(provider, "AffineRotate180NN"); + + using Image img3 = canvas.Clone(); img3[0, 0] = marker; img3.Mutate(c => c.Transform(new AffineTransformBuilder().AppendRotationDegrees(180))); + img3.DebugSave(provider, "AffineRotate180Bicubic"); + ImageComparer.Exact.VerifySimilarity(img, img2); ImageComparer.Exact.VerifySimilarity(img, img3); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs index 56e9a5201..838760144 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs @@ -35,7 +35,7 @@ public class CropTest { using Image image = provider.GetImage(); - image.Metadata.ExifProfile = new(); + image.Metadata.ExifProfile = new ExifProfile(); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs index 9aa04e370..abb4443b9 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs @@ -50,7 +50,7 @@ public class FlipTests { using Image image = provider.GetImage(); - image.Metadata.ExifProfile = new(); + image.Metadata.ExifProfile = new ExifProfile(); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]); @@ -79,7 +79,7 @@ public class FlipTests { using Image image = provider.GetImage(); - image.Metadata.ExifProfile = new(); + image.Metadata.ExifProfile = new ExifProfile(); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ProjectiveTransformTests.cs index 2e580ea9f..5b4ae9468 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ProjectiveTransformTests.cs @@ -9,7 +9,6 @@ using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; @@ -239,7 +238,7 @@ public class ProjectiveTransformTests { using Image image = provider.GetImage(); - image.Metadata.ExifProfile = new(); + image.Metadata.ExifProfile = new ExifProfile(); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]); @@ -249,17 +248,17 @@ public class ProjectiveTransformTests image.Mutate(ctx => ctx.Transform(builder)); // A 180-degree rotation inverts both axes around the image center. - // The subject location (5, 15) becomes (imageWidth - 5 - 1, imageHeight - 15 - 1) = (94, 84) + // The subject location (5, 15) becomes (imageWidth - 5, imageHeight - 15) = (95, 85) Assert.Equal( - [94, 84], + [95, 85], image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value); // The subject area is also mirrored around the center. // New X = imageWidth - originalX - width // New Y = imageHeight - originalY - height - // (5, 15, 50, 50) becomes (44, 34, 50, 50) + // (5, 15, 50, 50) becomes (45, 35, 50, 50) Assert.Equal( - [44, 34, 50, 50], + [45, 35, 50, 50], image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value); } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs index 00ad71b4c..ccc6bf29e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.ReferenceKernelMap.cs @@ -16,9 +16,7 @@ public partial class ResizeKernelMapTests private readonly ReferenceKernel[] kernels; public ReferenceKernelMap(ReferenceKernel[] kernels) - { - this.kernels = kernels; - } + => this.kernels = kernels; public int DestinationSize => this.kernels.Length; @@ -28,16 +26,16 @@ public partial class ResizeKernelMapTests where TResampler : struct, IResampler { double ratio = (double)sourceSize / destinationSize; - double scale = ratio; + double scaleD = ratio; - if (scale < 1F) + if (scaleD < 1) { - scale = 1F; + scaleD = 1; } TolerantMath tolerantMath = TolerantMath.Default; - double radius = tolerantMath.Ceiling(scale * sampler.Radius); + double radius = tolerantMath.Ceiling(scaleD * sampler.Radius); List result = []; @@ -58,15 +56,14 @@ public partial class ResizeKernelMapTests right = sourceSize - 1; } - double sum = 0; + float sum = 0; - double[] values = new double[right - left + 1]; + float[] values = new float[right - left + 1]; for (int j = left; j <= right; j++) { - double weight = sampler.GetValue((float)((j - center) / scale)); + float weight = sampler.GetValue((float)((j - center) / scaleD)); sum += weight; - values[j - left] = weight; } @@ -78,16 +75,14 @@ public partial class ResizeKernelMapTests } } - float[] floatVals = values.Select(v => (float)v).ToArray(); - - result.Add(new ReferenceKernel(left, floatVals)); + result.Add(new ReferenceKernel(left, values)); } - return new ReferenceKernelMap(result.ToArray()); + return new ReferenceKernelMap([.. result]); } } - internal struct ReferenceKernel + internal readonly struct ReferenceKernel { public ReferenceKernel(int left, float[] values) { @@ -102,8 +97,6 @@ public partial class ResizeKernelMapTests public int Length => this.Values.Length; public static implicit operator ReferenceKernel(ResizeKernel orig) - { - return new ReferenceKernel(orig.StartIndex, orig.Values.ToArray()); - } + => new(orig.StartIndex, [.. orig.Values]); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs index 0357dda04..750c576ec 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs @@ -4,7 +4,6 @@ using System.Text; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Transforms; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; @@ -13,10 +12,7 @@ public partial class ResizeKernelMapTests { private ITestOutputHelper Output { get; } - public ResizeKernelMapTests(ITestOutputHelper output) - { - this.Output = output; - } + public ResizeKernelMapTests(ITestOutputHelper output) => this.Output = output; /// /// resamplerName, srcSize, destSize @@ -24,71 +20,70 @@ public partial class ResizeKernelMapTests public static readonly TheoryData KernelMapData = new() { - { KnownResamplers.Bicubic, 15, 10 }, - { KnownResamplers.Bicubic, 10, 15 }, - { KnownResamplers.Bicubic, 20, 20 }, - { KnownResamplers.Bicubic, 50, 40 }, - { KnownResamplers.Bicubic, 40, 50 }, - { KnownResamplers.Bicubic, 500, 200 }, - { KnownResamplers.Bicubic, 200, 500 }, - { KnownResamplers.Bicubic, 3032, 400 }, - { KnownResamplers.Bicubic, 10, 25 }, - { KnownResamplers.Lanczos3, 16, 12 }, - { KnownResamplers.Lanczos3, 12, 16 }, - { KnownResamplers.Lanczos3, 12, 9 }, - { KnownResamplers.Lanczos3, 9, 12 }, - { KnownResamplers.Lanczos3, 6, 8 }, - { KnownResamplers.Lanczos3, 8, 6 }, - { KnownResamplers.Lanczos3, 20, 12 }, - { KnownResamplers.Lanczos3, 5, 25 }, - { KnownResamplers.Lanczos3, 5, 50 }, - { KnownResamplers.Lanczos3, 25, 5 }, - { KnownResamplers.Lanczos3, 50, 5 }, - { KnownResamplers.Lanczos3, 49, 5 }, - { KnownResamplers.Lanczos3, 31, 5 }, - { KnownResamplers.Lanczos8, 500, 200 }, - { KnownResamplers.Lanczos8, 100, 10 }, - { KnownResamplers.Lanczos8, 100, 80 }, - { KnownResamplers.Lanczos8, 10, 100 }, - - // Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5: - { KnownResamplers.Box, 378, 149 }, - { KnownResamplers.Box, 349, 174 }, - - // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData - { KnownResamplers.Box, 201, 100 }, - { KnownResamplers.Box, 199, 99 }, - { KnownResamplers.Box, 10, 299 }, - { KnownResamplers.Box, 299, 10 }, - { KnownResamplers.Box, 301, 300 }, - { KnownResamplers.Box, 1180, 480 }, - { KnownResamplers.Lanczos2, 3264, 3032 }, - { KnownResamplers.Bicubic, 1280, 2240 }, - { KnownResamplers.Bicubic, 1920, 1680 }, - { KnownResamplers.Bicubic, 3072, 2240 }, - { KnownResamplers.Welch, 300, 2008 }, - - // ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData - { KnownResamplers.Bicubic, 10, 50 }, - { KnownResamplers.Bicubic, 49, 301 }, - { KnownResamplers.Bicubic, 301, 49 }, - { KnownResamplers.Bicubic, 1680, 1200 }, - { KnownResamplers.Box, 13, 299 }, - { KnownResamplers.Lanczos5, 3032, 600 }, - - // Large number. https://github.com/SixLabors/ImageSharp/issues/1616 - { KnownResamplers.Bicubic, 207773, 51943 } - }; - - public static TheoryData GeneratedImageResizeData = - GenerateImageResizeData(); + { KnownResamplers.Bicubic, 15, 10 }, + { KnownResamplers.Bicubic, 10, 15 }, + { KnownResamplers.Bicubic, 20, 20 }, + { KnownResamplers.Bicubic, 50, 40 }, + { KnownResamplers.Bicubic, 40, 50 }, + { KnownResamplers.Bicubic, 500, 200 }, + { KnownResamplers.Bicubic, 200, 500 }, + { KnownResamplers.Bicubic, 3032, 400 }, + { KnownResamplers.Bicubic, 10, 25 }, + { KnownResamplers.Lanczos3, 16, 12 }, + { KnownResamplers.Lanczos3, 12, 16 }, + { KnownResamplers.Lanczos3, 12, 9 }, + { KnownResamplers.Lanczos3, 9, 12 }, + { KnownResamplers.Lanczos3, 6, 8 }, + { KnownResamplers.Lanczos3, 8, 6 }, + { KnownResamplers.Lanczos3, 20, 12 }, + { KnownResamplers.Lanczos3, 5, 25 }, + { KnownResamplers.Lanczos3, 5, 50 }, + { KnownResamplers.Lanczos3, 25, 5 }, + { KnownResamplers.Lanczos3, 50, 5 }, + { KnownResamplers.Lanczos3, 49, 5 }, + { KnownResamplers.Lanczos3, 31, 5 }, + { KnownResamplers.Lanczos8, 500, 200 }, + { KnownResamplers.Lanczos8, 100, 10 }, + { KnownResamplers.Lanczos8, 100, 80 }, + { KnownResamplers.Lanczos8, 10, 100 }, + + // Resize_WorksWithAllResamplers_Rgba32_CalliphoraPartial_Box-0.5: + { KnownResamplers.Box, 378, 149 }, + { KnownResamplers.Box, 349, 174 }, + + // Accuracy-related regression-test cases cherry-picked from GeneratedImageResizeData + { KnownResamplers.Box, 201, 100 }, + { KnownResamplers.Box, 199, 99 }, + { KnownResamplers.Box, 10, 299 }, + { KnownResamplers.Box, 299, 10 }, + { KnownResamplers.Box, 301, 300 }, + { KnownResamplers.Box, 1180, 480 }, + { KnownResamplers.Lanczos2, 3264, 3032 }, + { KnownResamplers.Bicubic, 1280, 2240 }, + { KnownResamplers.Bicubic, 1920, 1680 }, + { KnownResamplers.Bicubic, 3072, 2240 }, + { KnownResamplers.Welch, 300, 2008 }, + + // ResizeKernel.Length -related regression tests cherry-picked from GeneratedImageResizeData + { KnownResamplers.Bicubic, 10, 50 }, + { KnownResamplers.Bicubic, 49, 301 }, + { KnownResamplers.Bicubic, 301, 49 }, + { KnownResamplers.Bicubic, 1680, 1200 }, + { KnownResamplers.Box, 13, 299 }, + { KnownResamplers.Lanczos5, 3032, 600 }, + + // Large number. https://github.com/SixLabors/ImageSharp/issues/1616 + { KnownResamplers.Bicubic, 207773, 51943 } + }; + + public static TheoryData GeneratedImageResizeData = GenerateImageResizeData(); [Theory(Skip = "Only for debugging and development")] [MemberData(nameof(KernelMapData))] public void PrintNonNormalizedKernelMap(TResampler resampler, int srcSize, int destSize) where TResampler : struct, IResampler { - ReferenceKernelMap kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false); + ReferenceKernelMap kernelMap = ReferenceKernelMap.Calculate(in resampler, destSize, srcSize, false); this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); } @@ -97,9 +92,7 @@ public partial class ResizeKernelMapTests [MemberData(nameof(KernelMapData))] public void KernelMapContentIsCorrect(TResampler resampler, int srcSize, int destSize) where TResampler : struct, IResampler - { - this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize); - } + => this.VerifyKernelMapContentIsCorrect(resampler, srcSize, destSize); // Comprehensive but expensive tests, for ResizeKernelMap. // Enabling them can kill you, but sometimes you have to wear the burden! @@ -124,8 +117,8 @@ public partial class ResizeKernelMapTests this.Output.WriteLine($"Expected KernelMap:\n{PrintKernelMap(referenceMap)}\n"); this.Output.WriteLine($"Actual KernelMap:\n{PrintKernelMap(kernelMap)}\n"); #endif - ApproximateFloatComparer comparer = new(1e-6f); + ApproximateFloatComparer comparer = new(1e-6f); for (int i = 0; i < kernelMap.DestinationLength; i++) { ResizeKernel kernel = kernelMap.GetKernel((uint)i); @@ -139,7 +132,23 @@ public partial class ResizeKernelMapTests referenceKernel.Left == kernel.StartIndex, $"referenceKernel.Left != kernel.Left: {referenceKernel.Left} != {kernel.StartIndex}"); float[] expectedValues = referenceKernel.Values; - Span actualValues = kernel.Values; + Span actualValues; + + if (ResizeKernel.IsHardwareAccelerated) + { + Assert.Equal(expectedValues.Length, kernel.Values.Length / 4); + + actualValues = new float[expectedValues.Length]; + + for (int j = 0; j < expectedValues.Length; j++) + { + actualValues[j] = kernel.Values[j * 4]; + } + } + else + { + actualValues = kernel.Values; + } Assert.Equal(expectedValues.Length, actualValues.Length); @@ -199,12 +208,13 @@ public partial class ResizeKernelMapTests int[] dimensionVals = [ + // Arbitrary, small dimensions: - 9, 10, 11, 13, 49, 50, 53, 99, 100, 199, 200, 201, 299, 300, 301, + 9, 10, 11, 13, 49, 50, 53, 99, 100, 199, 200, 201, 299, 300, 301, - // Typical image sizes: - 640, 480, 800, 600, 1024, 768, 1280, 960, 1536, 1180, 1600, 1200, 2048, 1536, 2240, 1680, 2560, - 1920, 3032, 2008, 3072, 2304, 3264, 2448 + // Typical image sizes: + 640, 480, 800, 600, 1024, 768, 1280, 960, 1536, 1180, 1600, 1200, 2048, 1536, 2240, 1680, 2560, + 1920, 3032, 2008, 3072, 2304, 3264, 2448 ]; IOrderedEnumerable<(int S, int D)> source2Dest = dimensionVals diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 1e0e66965..e1a254246 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -3,6 +3,7 @@ using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.ColorProfiles.Companding; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -228,6 +229,72 @@ public class ResizeTests appendSourceFileOrDescription: false); } + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Resize_AppliesAlphaOptionsForUnassociatedPixels(bool compand, bool premultiplyAlpha) + => AssertResizeAlphaOptions(compand, premultiplyAlpha); + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Resize_AppliesAlphaOptionsForAssociatedPixels(bool compand, bool premultiplyAlpha) + => AssertResizeAlphaOptions(compand, premultiplyAlpha); + + private static void AssertResizeAlphaOptions(bool compand, bool premultiplyAlpha) + where TPixel : unmanaged, IPixel + { + using Image image = new(2, 1); + image[0, 0] = TPixel.FromUnassociatedScaledVector4(new Vector4(0.9F, 0.2F, 0.1F, 0.8F)); + image[1, 0] = TPixel.FromUnassociatedScaledVector4(new Vector4(0.1F, 0.8F, 0.6F, 0.2F)); + + // Read the stored pixels back through the scalar contract so the oracle includes the source format's actual quantization. + Vector4 expectedVector = image[0, 0].ToUnassociatedScaledVector4(); + Vector4 second = image[1, 0].ToUnassociatedScaledVector4(); + + if (compand) + { + expectedVector = SRgbCompanding.Expand(expectedVector); + second = SRgbCompanding.Expand(second); + } + + if (premultiplyAlpha) + { + Numerics.Premultiply(ref expectedVector); + Numerics.Premultiply(ref second); + } + + expectedVector = (expectedVector + second) / 2F; + + if (premultiplyAlpha) + { + Numerics.UnPremultiply(ref expectedVector); + } + + if (compand) + { + expectedVector = SRgbCompanding.Compress(expectedVector); + } + + TPixel expected = TPixel.FromUnassociatedScaledVector4(expectedVector); + ResizeOptions options = new() + { + Size = new Size(1, 1), + Mode = ResizeMode.Stretch, + Sampler = KnownResamplers.Box, + Compand = compand, + PremultiplyAlpha = premultiplyAlpha + }; + + image.Mutate(context => context.Resize(options)); + + Assert.Equal(expected, image[0, 0]); + } + [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32)] public void Resize_IsAppliedToAllFrames(TestImageProvider provider) @@ -632,6 +699,20 @@ public class ResizeTests appendSourceFileOrDescription: false); } + [Theory] + [InlineData(1024)] + [InlineData(2048)] + public void Issue3156_CanResizeLargeSquareToSinglePixel(int length) + { + Rgba32 white = Color.White.ToPixel(); + using Image image = new(length, length, white); + + image.Mutate(x => x.Resize(1, 1)); + + Assert.Equal(new Size(1, 1), image.Size); + Assert.Equal(white, image[0, 0]); + } + [Theory] [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void ResizeUpdatesSubject(TestImageProvider provider) @@ -639,7 +720,7 @@ public class ResizeTests { using Image image = provider.GetImage(); - image.Metadata.ExifProfile = new(); + image.Metadata.ExifProfile = new ExifProfile(); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 20, 20]); @@ -652,7 +733,7 @@ public class ResizeTests image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value); Assert.Equal( - [2, 7, 11, 11], + [2, 7, 10, 10], image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs index dfa263fec..f0a79abdf 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs @@ -12,20 +12,16 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms; [GroupOutput("Transforms")] public class RotateTests { - public static readonly TheoryData RotateAngles - = new() - { - 50, -50, 170, -170 - }; + public static readonly TheoryData RotateAngles = [50f, -50f, 170f, -170f]; public static readonly TheoryData RotateEnumValues - = new() - { + = + [ RotateMode.None, RotateMode.Rotate90, RotateMode.Rotate180, RotateMode.Rotate270 - }; + ]; [Theory] [WithTestPatternImages(nameof(RotateAngles), 100, 50, PixelTypes.Rgba32)] @@ -48,24 +44,24 @@ public class RotateTests { using Image image = provider.GetImage(); - image.Metadata.ExifProfile = new(); + image.Metadata.ExifProfile = new ExifProfile(); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]); image.Mutate(ctx => ctx.Rotate(180)); // A 180-degree rotation inverts both axes around the image center. - // The subject location (5, 15) becomes (imageWidth - 5 - 1, imageHeight - 15 - 1) = (94, 84) + // The subject location (5, 15) becomes (imageWidth - 5, imageHeight - 15) = (95, 85) Assert.Equal( - [94, 84], + [95, 85], image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value); // The subject area is also mirrored around the center. // New X = imageWidth - originalX - width // New Y = imageHeight - originalY - height - // (5, 15, 50, 50) becomes (44, 34, 50, 50) + // (5, 15, 50, 50) becomes (45, 35, 50, 50) Assert.Equal( - [44, 34, 50, 50], + [45, 35, 50, 50], image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value); } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs index 33e22d364..6b69d20b1 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs @@ -60,7 +60,7 @@ public class SwizzleTests { using Image image = provider.GetImage(); - image.Metadata.ExifProfile = new(); + image.Metadata.ExifProfile = new ExifProfile(); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]); image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 20, 20]); diff --git a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs index 232571d4d..d0e4cf566 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AffineTransformBuilderTests.cs @@ -58,6 +58,9 @@ public class AffineTransformBuilderTests : TransformBuilderTestBase builder.PrependTranslation(translate); + protected override void ClearBuilder(AffineTransformBuilder builder) + => builder.Clear(); + protected override Vector2 Execute( AffineTransformBuilder builder, Rectangle rectangle, diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs index a0380033f..dcc653316 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformBuilderTests.cs @@ -55,6 +55,9 @@ public class ProjectiveTransformBuilderTests : TransformBuilderTestBase builder.PrependRotationRadians(radians, origin); + protected override void ClearBuilder(ProjectiveTransformBuilder builder) + => builder.Clear(); + protected override Vector2 Execute( ProjectiveTransformBuilder builder, Rectangle rectangle, diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs index 1d445b980..3f2d8e059 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -102,6 +102,6 @@ public class ResizeTests : BaseImageOperationsExtensionTest FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableFMA); + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs index 5caf071ac..12f8326fc 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/TransformBuilderTestBase.cs @@ -98,7 +98,7 @@ public abstract class TransformBuilderTestBase this.AppendRotationDegrees(builder, degrees); // TODO: We should also test CreateRotationMatrixDegrees() (and all TransformUtils stuff!) for correctness - Matrix3x2 matrix = TransformUtils.CreateRotationTransformMatrixDegrees(degrees, size, TransformSpace.Pixel); + Matrix3x2 matrix = TransformUtilities.CreateRotationTransformMatrixDegrees(degrees, size); Vector2 position = new(x, y); Vector2 expected = Vector2.Transform(position, matrix); @@ -152,7 +152,7 @@ public abstract class TransformBuilderTestBase this.AppendSkewDegrees(builder, degreesX, degreesY); - Matrix3x2 matrix = TransformUtils.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size, TransformSpace.Pixel); + Matrix3x2 matrix = TransformUtilities.CreateSkewTransformMatrixDegrees(degreesX, degreesY, size); Vector2 position = new(x, y); Vector2 expected = Vector2.Transform(position, matrix); @@ -248,6 +248,48 @@ public abstract class TransformBuilderTestBase }); } + [Fact] + public void Clear_ResetsBuilderToIdentity() + { + Size size = new(100, 100); + Rectangle rectangle = new(Point.Empty, size); + Vector2 source = new(10, 20); + + TBuilder builder = this.CreateBuilder(); + + // Apply a transform that changes the point. + this.AppendScale(builder, new SizeF(2, 3)); + this.AppendTranslation(builder, new PointF(50, 50)); + Vector2 transformed = this.Execute(builder, rectangle, source); + Assert.NotEqual(source, transformed, Comparer); + + // Clear and verify the builder produces identity behavior. + this.ClearBuilder(builder); + Vector2 afterClear = this.Execute(builder, rectangle, source); + Assert.Equal(source, afterClear, Comparer); + } + + [Fact] + public void Clear_AllowsReuse() + { + Size size = new(100, 100); + Rectangle rectangle = new(Point.Empty, size); + Vector2 source = new(10, 20); + + TBuilder builder = this.CreateBuilder(); + + // First transform: scale by 2. + this.AppendScale(builder, new SizeF(2, 2)); + Vector2 scaled = this.Execute(builder, rectangle, source); + Assert.Equal(new Vector2(20, 40), scaled, Comparer); + + // Clear and apply a different transform: translate. + this.ClearBuilder(builder); + this.AppendTranslation(builder, new PointF(5, 10)); + Vector2 translated = this.Execute(builder, rectangle, source); + Assert.Equal(new Vector2(15, 30), translated, Comparer); + } + protected abstract TBuilder CreateBuilder(); protected abstract void AppendRotationDegrees(TBuilder builder, float degrees); @@ -282,5 +324,7 @@ public abstract class TransformBuilderTestBase protected abstract void PrependTranslation(TBuilder builder, PointF translate); + protected abstract void ClearBuilder(TBuilder builder); + protected abstract Vector2 Execute(TBuilder builder, Rectangle rectangle, Vector2 sourcePoint); } diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs index 209dd361e..e3a5922b1 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/LoadResizeSaveProfilingBenchmarks.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Processing; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks; diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs index bab11b0f9..145d0e58d 100644 --- a/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/ResizeProfilingBenchmarks.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks; diff --git a/tests/ImageSharp.Tests/Quantization/AlphaAssociationQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/AlphaAssociationQuantizerTests.cs new file mode 100644 index 000000000..df12e4762 --- /dev/null +++ b/tests/ImageSharp.Tests/Quantization/AlphaAssociationQuantizerTests.cs @@ -0,0 +1,118 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Dithering; +using SixLabors.ImageSharp.Processing.Processors.Quantization; + +namespace SixLabors.ImageSharp.Tests.Quantization; + +public class AlphaAssociationQuantizerTests +{ + public static TheoryData Dithers { get; } = new() + { + (IDither?)null, + KnownDitherings.Bayer4x4, + KnownDitherings.FloydSteinberg + }; + + [Theory] + [MemberData(nameof(Dithers))] + public void WuQuantizerProducesEquivalentResultsForAssociatedPixels(IDither? dither) + { + byte[] alphaValues = [0, 1, 63, 64, 65, 127, 128, 254, 255]; + Rgba32[] source = new Rgba32[alphaValues.Length * 4]; + + for (int i = 0; i < source.Length; i++) + { + byte alpha = alphaValues[i % alphaValues.Length]; + source[i] = alpha == 0 ? default : new Rgba32(255, 255, 255, alpha); + } + + QuantizerOptions options = new() + { + Dither = dither, + MaxColors = 8, + TransparencyThreshold = .5F + }; + + Rgba32[] expected = Quantize(source, options); + Rgba32[] actual = Quantize(source, options); + + Assert.Equal(expected, actual); + } + + [Fact] + public void PaletteQuantizerAppliesTransparencyThresholdToAssociatedPixels() + { + Configuration configuration = Configuration.Default; + Rgba32P source = new(127, 0, 0, 127); + Color[] palette = [Color.Transparent, Color.FromPixel(source)]; + QuantizerOptions options = new() { Dither = null, TransparencyThreshold = .5F }; + PaletteQuantizer quantizer = new(palette, options); + + using Image image = new(configuration, 1, 1, source); + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(configuration); + using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(image.Frames.RootFrame, image.Bounds); + + Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); + Assert.Equal(default, result.Palette.Span[0]); + } + + [Theory] + [InlineData(64 / 255F, (byte)63, (byte)64)] + [InlineData(.5F, (byte)127, (byte)128)] + [InlineData(1F, (byte)254, (byte)255)] + public void WuQuantizerAppliesNormalizedTransparencyThresholdToAssociatedPixels(float threshold, byte below, byte retained) + { + Rgba32[] source = + [ + new(255, 255, 255, below), + new(255, 255, 255, retained) + ]; + + QuantizerOptions options = new() + { + Dither = null, + MaxColors = 2, + TransparencyThreshold = threshold + }; + + Rgba32[] expected = Quantize(source, options); + Rgba32[] actual = Quantize(source, options); + + Assert.Equal(expected, actual); + Assert.Equal(default, actual[0]); + Assert.NotEqual(0, actual[1].A); + } + + private static Rgba32[] Quantize(Rgba32[] source, QuantizerOptions options) + where TPixel : unmanaged, IPixel + { + Configuration configuration = Configuration.Default; + using Image image = new(configuration, source.Length, 1); + + for (int x = 0; x < source.Length; x++) + { + image[x, 0] = TPixel.FromRgba32(source[x]); + } + + WuQuantizer quantizer = new(options); + ImageFrame frame = image.Frames.RootFrame; + + using IQuantizer frameQuantizer = quantizer.CreatePixelSpecificQuantizer(configuration); + using IndexedImageFrame result = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); + + ReadOnlySpan palette = result.Palette.Span; + ReadOnlySpan indices = result.DangerousGetRowSpan(0); + Rgba32[] colors = new Rgba32[source.Length]; + + for (int x = 0; x < colors.Length; x++) + { + colors[x] = palette[indices[x]].ToRgba32(); + } + + return colors; + } +} diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 75c096b1f..e94088652 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -15,12 +15,12 @@ public class QuantizedImageTests { WernerPaletteQuantizer werner = new(); WebSafePaletteQuantizer webSafe = new(); - OctreeQuantizer octree = new(); + HexadecatreeQuantizer hexadecatree = new(); WuQuantizer wu = new(); Assert.NotNull(werner.Options.Dither); Assert.NotNull(webSafe.Options.Dither); - Assert.NotNull(octree.Options.Dither); + Assert.NotNull(hexadecatree.Options.Dither); Assert.NotNull(wu.Options.Dither); using (IQuantizer quantizer = werner.CreatePixelSpecificQuantizer(this.Configuration)) @@ -33,7 +33,7 @@ public class QuantizedImageTests Assert.NotNull(quantizer.Options.Dither); } - using (IQuantizer quantizer = octree.CreatePixelSpecificQuantizer(this.Configuration)) + using (IQuantizer quantizer = hexadecatree.CreatePixelSpecificQuantizer(this.Configuration)) { Assert.NotNull(quantizer.Options.Dither); } @@ -47,13 +47,12 @@ public class QuantizedImageTests [Theory] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, true)] [WithFile(TestImages.Gif.Giphy, PixelTypes.Rgba32, false)] - public void OctreeQuantizerYieldsCorrectTransparentPixel( + public void HexadecatreeQuantizerYieldsCorrectTransparentPixel( TestImageProvider provider, bool dither) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - Assert.True(image[0, 0].Equals(default)); QuantizerOptions options = new(); if (!dither) @@ -61,7 +60,7 @@ public class QuantizedImageTests options.Dither = null; } - OctreeQuantizer quantizer = new(options); + HexadecatreeQuantizer quantizer = new(options); foreach (ImageFrame frame in image.Frames) { @@ -79,7 +78,6 @@ public class QuantizedImageTests where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - Assert.True(image[0, 0].Equals(default)); QuantizerOptions options = new(); if (!dither) @@ -105,8 +103,8 @@ public class QuantizedImageTests where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - OctreeQuantizer octreeQuantizer = new(); - IQuantizer quantizer = octreeQuantizer.CreatePixelSpecificQuantizer(Configuration.Default, new QuantizerOptions { MaxColors = 128 }); + HexadecatreeQuantizer hexadecatreeQuantizer = new(); + IQuantizer quantizer = hexadecatreeQuantizer.CreatePixelSpecificQuantizer(Configuration.Default, new QuantizerOptions { MaxColors = 128 }); ImageFrame frame = image.Frames[0]; quantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds); } diff --git a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMultiProcessElement.cs b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMultiProcessElement.cs index f79666e3e..5c077d5c3 100644 --- a/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMultiProcessElement.cs +++ b/tests/ImageSharp.Tests/TestDataIcc/Conversion/IccConversionDataMultiProcessElement.cs @@ -13,11 +13,10 @@ public class IccConversionDataMultiProcessElement { 2, 4, 6 }, { 3, 5, 7 }, }, - new float[] { 3, 4, 5 }); + [3, 4, 5]); private static readonly IccClut Clut = new( - new[] - { + [ 0.2f, 0.3f, 0.4f, 0.2f, @@ -28,9 +27,9 @@ public class IccConversionDataMultiProcessElement 0.42f, 0.52f, 0.23f, 0.33f, - 0.43f, 0.53f, - }, - new byte[] { 2, 2, 2 }, + 0.43f, 0.53f + ], + [2, 2, 2], IccClutDataType.Float, outputChannelCount: 2); @@ -42,39 +41,39 @@ public class IccConversionDataMultiProcessElement private static readonly IccCurveSetProcessElement CurveSet1DFormula2 = Create1DSingleCurveSet(FormulaCurveElement2); private static readonly IccCurveSetProcessElement CurveSet1DFormula3 = Create1DSingleCurveSet(FormulaCurveElement3); - private static readonly IccCurveSetProcessElement CurveSet1DFormula1And2 = Create1DMultiCurveSet(new[] { 0.5f }, FormulaCurveElement1, FormulaCurveElement2); + private static readonly IccCurveSetProcessElement CurveSet1DFormula1And2 = Create1DMultiCurveSet([0.5f], FormulaCurveElement1, FormulaCurveElement2); private static readonly IccClutProcessElement ClutElement = new(Clut); private static IccCurveSetProcessElement Create1DSingleCurveSet(IccCurveSegment segment) { - IccOneDimensionalCurve curve = new(new float[0], new[] { segment }); - return new IccCurveSetProcessElement(new[] { curve }); + IccOneDimensionalCurve curve = new([], [segment]); + return new IccCurveSetProcessElement([curve]); } private static IccCurveSetProcessElement Create1DMultiCurveSet(float[] breakPoints, params IccCurveSegment[] segments) { IccOneDimensionalCurve curve = new(breakPoints, segments); - return new IccCurveSetProcessElement(new[] { curve }); + return new IccCurveSetProcessElement([curve]); } public static object[][] MpeCurveConversionTestData = - { - new object[] { CurveSet1DFormula1, new[] { 0.51f }, new[] { 0.575982451f } }, - new object[] { CurveSet1DFormula2, new[] { 0.52f }, new[] { -0.4684991f } }, - new object[] { CurveSet1DFormula3, new[] { 0.53f }, new[] { 0.86126f } }, + [ + [CurveSet1DFormula1, new[] { 0.51f }, new[] { 0.575982451f }], + [CurveSet1DFormula2, new[] { 0.52f }, new[] { -0.4684991f }], + [CurveSet1DFormula3, new[] { 0.53f }, new[] { 0.86126f }], - new object[] { CurveSet1DFormula1And2, new[] { 0.31f }, new[] { 0.445982f } }, - new object[] { CurveSet1DFormula1And2, new[] { 0.61f }, new[] { -0.341274023f } }, - }; + [CurveSet1DFormula1And2, new[] { 0.31f }, new[] { 0.445982f }], + [CurveSet1DFormula1And2, new[] { 0.61f }, new[] { -0.341274023f }] + ]; public static object[][] MpeMatrixConversionTestData = - { - new object[] { Matrix, new float[] { 2, 4 }, new float[] { 19, 32, 45 } } - }; + [ + [Matrix, new float[] { 2, 4 }, new float[] { 19, 32, 45 }] + ]; public static object[][] MpeClutConversionTestData = - { - new object[] { ClutElement, new[] { 0.5f, 0.5f, 0.5f }, new[] { 0.5f, 0.5f } } - }; + [ + [ClutElement, new[] { 0.5f, 0.5f, 0.5f }, new[] { 0.5f, 0.5f }] + ]; } diff --git a/tests/ImageSharp.Tests/TestFontUtilities.cs b/tests/ImageSharp.Tests/TestFontUtilities.cs index a2c203267..d165ccf31 100644 --- a/tests/ImageSharp.Tests/TestFontUtilities.cs +++ b/tests/ImageSharp.Tests/TestFontUtilities.cs @@ -45,10 +45,10 @@ public static class TestFontUtilities "../../../../TestFonts/" ]; - directories = directories.SelectMany(x => new[] - { - Path.GetFullPath(x) - }).ToList(); + directories = + [ + .. directories.SelectMany(x => new[] { Path.GetFullPath(x) }) + ]; AddFormatsDirectoryFromTestAssemblyPath(directories); diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 28bfcfaa8..b178639f0 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -61,7 +61,7 @@ public class TestFormat : IImageFormatConfigurationModule, IImageFormat public void VerifySpecificDecodeCall(byte[] marker, Configuration config) where TPixel : unmanaged, IPixel { - DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TPixel))).ToArray(); + DecodeOperation[] discovered = [.. this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TPixel)))]; Assert.True(discovered.Length > 0, "No calls to decode on this format with the provided options happened"); @@ -73,7 +73,8 @@ public class TestFormat : IImageFormatConfigurationModule, IImageFormat public void VerifyAgnosticDecodeCall(byte[] marker, Configuration config) { - DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TestPixelForAgnosticDecode))).ToArray(); + DecodeOperation[] discovered = + [.. this.DecodeCalls.Where(x => x.IsMatch(marker, config, typeof(TestPixelForAgnosticDecode)))]; Assert.True(discovered.Length > 0, "No calls to decode on this format with the provided options happened"); @@ -219,7 +220,7 @@ public class TestFormat : IImageFormatConfigurationModule, IImageFormat Configuration configuration = options.GeneralOptions.Configuration; using MemoryStream ms = new(); stream.CopyTo(ms, configuration.StreamProcessingBufferSize); - byte[] marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); + byte[] marker = [.. ms.ToArray().Skip(this.testFormat.header.Length)]; this.testFormat.DecodeCalls.Add(new DecodeOperation { Marker = marker, @@ -271,6 +272,18 @@ public class TestFormat : IImageFormatConfigurationModule, IImageFormat public readonly Vector4 ToVector4() => default; + /// + public readonly Vector4 ToUnassociatedScaledVector4() => default; + + /// + public readonly Vector4 ToAssociatedScaledVector4() => default; + + /// + public readonly Vector4 ToUnassociatedVector4() => default; + + /// + public readonly Vector4 ToAssociatedVector4() => default; + public static PixelTypeInfo GetPixelTypeInfo() => PixelTypeInfo.Create( PixelComponentInfo.Create(2, 8, 8), @@ -283,6 +296,18 @@ public class TestFormat : IImageFormatConfigurationModule, IImageFormat public static TestPixelForAgnosticDecode FromVector4(Vector4 vector) => default; + /// + public static TestPixelForAgnosticDecode FromUnassociatedScaledVector4(Vector4 source) => default; + + /// + public static TestPixelForAgnosticDecode FromAssociatedScaledVector4(Vector4 source) => default; + + /// + public static TestPixelForAgnosticDecode FromUnassociatedVector4(Vector4 source) => default; + + /// + public static TestPixelForAgnosticDecode FromAssociatedVector4(Vector4 source) => default; + public static TestPixelForAgnosticDecode FromAbgr32(Abgr32 source) => default; public static TestPixelForAgnosticDecode FromArgb32(Argb32 source) => default; diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index c74e12b21..31d12cda3 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -62,6 +62,7 @@ public static class TestImages public const string TestPattern31x31HalfTransparent = "Png/testpattern31x31-halftransparent.png"; public const string XmpColorPalette = "Png/xmp-colorpalette.png"; public const string AdamHeadsHlg = "Png/adamHeadsHLG.png"; + public const string IptcMetadata = "Png/iptc-profile.png"; // Animated // https://philip.html5.org/tests/apng/tests.html @@ -75,8 +76,12 @@ public static class TestImages public const string BlendOverMultiple = "Png/animated/21-blend-over-multiple.png"; public const string FrameOffset = "Png/animated/frame-offset.png"; public const string DefaultNotAnimated = "Png/animated/default-not-animated.png"; + public const string AnimatedFrameCount = "Png/animated/issue-animated-frame-count.png"; public const string Issue2666 = "Png/issues/Issue_2666.png"; public const string Issue2882 = "Png/issues/Issue_2882.png"; + public const string Issue396Dragon = "Png/issues/Issue_396_dragon.png"; + public const string Issue396Hand1 = "Png/issues/Issue_396_hand_1.png"; + public const string Issue396Hand2 = "Png/issues/Issue_396_hand_2.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; @@ -163,6 +168,33 @@ public static class TestImages // Issue 2924: https://github.com/SixLabors/ImageSharp/issues/2924 public const string Issue2924 = "Png/issues/Issue_2924.png"; + // Issue 3000: https://github.com/SixLabors/ImageSharp/issues/3000 + public const string Issue3000 = "Png/issues/issue_3000.png"; + + public static class Icc + { + public const string SRgbGray = "Png/icc-profiles/sRGB_Gray.png"; + public const string SRgbGrayInterlacedRgba32 = "Png/icc-profiles/sRGB_Gray_Interlaced_Rgba32.png"; + public const string SRgbGrayInterlacedRgba64 = "Png/icc-profiles/sRGB_Gray_Interlaced_Rgba64.png"; + public const string Perceptual = "Png/icc-profiles/Perceptual.png"; + public const string PerceptualcLUTOnly = "Png/icc-profiles/Perceptual-cLUT-only.png"; + } + + public static class Cgbi + { + public const string Colors = "Png/cgbi/colors.png"; + public const string Clocks = "Png/cgbi/clocks.png"; + public const string Flecks = "Png/cgbi/flecks.png"; + public const string Screen = "Png/cgbi/screen.png"; + + // Issue 410: https://github.com/SixLabors/ImageSharp/issues/410 + public const string Issue410 = "Png/issues/Issue_410.png"; + + // Synthetic fixtures derived from colors.png to exercise CgBI validation. + public const string BitDepth16 = "Png/cgbi/colors-cgbi-bitdepth16.png"; + public const string Palette = "Png/cgbi/colors-cgbi-palette.png"; + } + public static class Bad { public const string MissingDataChunk = "Png/xdtn0g01.png"; @@ -185,9 +217,6 @@ public static class TestImages // Issue 1047: https://github.com/SixLabors/ImageSharp/issues/1047 public const string Issue1047_BadEndChunk = "Png/issues/Issue_1047.png"; - // Issue 410: https://github.com/SixLabors/ImageSharp/issues/410 - public const string Issue410_MalformedApplePng = "Png/issues/Issue_410.png"; - // Bad bit depth. public const string BitDepthZero = "Png/xd0n2c08.png"; public const string BitDepthThree = "Png/xd3n2c08.png"; @@ -216,6 +245,10 @@ public static class TestImages public const string AppleRGB = "Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg"; public const string CMYK = "Jpg/icc-profiles/issue-129.jpg"; public const string YCCK = "Jpg/icc-profiles/issue_2723.jpg"; + public const string SRgbGray = "Jpg/icc-profiles/sRGB_Gray.jpg"; + public const string Perceptual = "Jpg/icc-profiles/Perceptual.jpg"; + public const string PerceptualcLUTOnly = "Jpg/icc-profiles/Perceptual-cLUT-only.jpg"; + public const string Issue3064 = "Jpg/icc-profiles/issue-3064.jpg"; } public static class Progressive @@ -342,6 +375,8 @@ public static class TestImages public const string Issue2638 = "Jpg/issues/Issue2638.jpg"; public const string Issue2758 = "Jpg/issues/issue-2758.jpg"; public const string Issue2857 = "Jpg/issues/issue-2857-subsub-ifds.jpg"; + public const string Issue2948 = "Jpg/issues/issue-2948-sos.jpg"; + public const string Issue3118 = "Jpg/issues/issue3118-multiple-sof.jpg"; public static class Fuzz { @@ -374,7 +409,7 @@ public static class TestImages } } - public static readonly string[] All = Baseline.All.Concat(Progressive.All).ToArray(); + public static readonly string[] All = [.. Baseline.All, .. Progressive.All]; public static class BenchmarkSuite { @@ -582,6 +617,8 @@ public static class TestImages public const string Issue2859_A = "Gif/issues/issue_2859_A.gif"; public const string Issue2859_B = "Gif/issues/issue_2859_B.gif"; public const string Issue2953 = "Gif/issues/issue_2953.gif"; + public const string Issue2980 = "Gif/issues/issue_2980.gif"; + public const string Issue3142 = "Gif/issues/issue_3142.gif"; } public static readonly string[] Animated = @@ -603,7 +640,8 @@ public static class TestImages Issues.BadDescriptorWidth, Issues.Issue1530, Bit18RGBCube, - Global256NoTrans + Global256NoTrans, + Issues.Issue3142 ]; } @@ -883,6 +921,12 @@ public static class TestImages public const string AlphaBlend2 = "Webp/alpha-blend-2.webp"; public const string AlphaBlend3 = "Webp/alpha-blend-3.webp"; public const string AlphaBlend4 = "Webp/alpha-blend-4.webp"; + + public static class Icc + { + public const string Perceptual = "Webp/icc-profiles/Perceptual.webp"; + public const string PerceptualcLUTOnly = "Webp/icc-profiles/Perceptual-cLUT-only.webp"; + } } public static class Tiff @@ -1134,6 +1178,7 @@ public static class TestImages public const string Issues2435 = "Tiff/Issues/Issue2435.tiff"; public const string Issues2454_A = "Tiff/Issues/Issue2454_A.tif"; public const string Issues2454_B = "Tiff/Issues/Issue2454_B.tif"; + public const string Issues3031 = "Tiff/Issues/Issue3031.tiff"; public const string Issues2587 = "Tiff/Issues/Issue2587.tiff"; public const string Issues2679 = "Tiff/Issues/Issue2679.tiff"; public const string JpegCompressedGray0000539558 = "Tiff/Issues/JpegCompressedGray-0000539558.tiff"; @@ -1163,11 +1208,20 @@ public static class TestImages public const string IptcData = "Tiff/iptc.tiff"; public const string Issue2909 = "Tiff/Issues/Issue2909.tiff"; + public const string Issue2983 = "Tiff/Issues/Issue2983.tiff"; public static readonly string[] Multiframes = [MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ ]; public static readonly string[] Metadata = [SampleMetadata]; + + public static class Icc + { + public const string PerceptualCmyk = "Tiff/icc-profiles/Perceptual_CMYK.tiff"; + public const string PerceptualCieLab = "Tiff/icc-profiles/Perceptual_CIELAB.tiff"; + public const string PerceptualRgb8 = "Tiff/icc-profiles/Perceptual_RGB8.tiff"; + public const string PerceptualRgb16 = "Tiff/icc-profiles/Perceptual_RGB16.tiff"; + } } public static class BigTiff @@ -1203,6 +1257,7 @@ public static class TestImages public const string GrayscalePlainNormalized = "Pbm/grayscale_plain_normalized.pgm"; public const string GrayscalePlainMagick = "Pbm/grayscale_plain_magick.pgm"; public const string RgbBinary = "Pbm/00000_00000.ppm"; + public const string RgbBinaryWide = "Pbm/rgb_binary_wide.ppm"; public const string RgbBinaryPrematureEof = "Pbm/00000_00000_premature_eof.ppm"; public const string RgbPlain = "Pbm/rgb_plain.ppm"; public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm"; @@ -1365,4 +1420,30 @@ public static class TestImages public const string CurReal = "Icon/cur_real.cur"; public const string CurFake = "Icon/cur_fake.ico"; } + + public static class Ani + { + public const string Work = "Ani/Work.ani"; + public const string MultiFramesInEveryIconChunk = "Ani/aero_busy.ani"; + public const string Help = "Ani/Help.ani"; + } + + public static class Exr + { + public const string Benchmark = "Exr/Calliphora_benchmark.exr"; + public const string Uncompressed = "Exr/Calliphora_uncompressed.exr"; + public const string UncompressedRgba = "Exr/Calliphora_uncompressed_rgba.exr"; + public const string UncompressedFloatRgb = "Exr/Calliphora_float_uncompressed.exr"; + public const string UncompressedUintRgb = "Exr/Calliphora_uint32_uncompressed.exr"; + public const string UintRgba = "Exr/rgba_uint_uncompressed.exr"; + public const string Zip = "Exr/Calliphora_zip.exr"; + public const string Zips = "Exr/Calliphora_zips.exr"; + public const string Rle = "Exr/Calliphora_rle.exr"; + public const string B44 = "Exr/Calliphora_b44.exr"; + public const string Pxr24Half = "Exr/Calliphora_half_pxr24.exr"; + public const string Pxr24Float = "Exr/Calliphora_float_pxr24.exr"; + public const string Pxr24Uint = "Exr/Calliphora_uint_pxr24.exr"; + public const string Rgb = "Exr/Calliphora_rgb.exr"; + public const string Gray = "Exr/Calliphora_gray.exr"; + } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index 21ac6966b..7c45dd047 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -15,7 +15,8 @@ internal readonly struct ApproximateFloatComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, - IEqualityComparer> + IEqualityComparer>, + IEqualityComparer> { private readonly float epsilon; @@ -78,4 +79,19 @@ internal readonly struct ApproximateFloatComparer : && this.Equals(x.GetElement(7), y.GetElement(7)); public int GetHashCode([DisallowNull] Vector256 obj) => obj.GetHashCode(); + + public bool Equals(Vector512 x, Vector512 y) + { + for (int i = 0; i < Vector512.Count; i++) + { + if (!this.Equals(x.GetElement(i), y.GetElement(i))) + { + return false; + } + } + + return true; + } + + public int GetHashCode([DisallowNull] Vector512 obj) => obj.GetHashCode(); } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs index 7191f3ba7..11b8e5847 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs @@ -3,6 +3,7 @@ using System.Reflection; using Xunit.Sdk; +using Xunit.v3; namespace SixLabors.ImageSharp.Tests; @@ -42,11 +43,22 @@ public abstract class ImageDataAttributeBase : DataAttribute /// public Type MemberType { get; set; } + /// + public override bool SupportsDiscoveryEnumeration() => true; + + /// + public override ValueTask> GetData(MethodInfo testMethod, DisposalTracker disposalTracker) + { + IEnumerable rows = this.GetDataRows(testMethod); + IReadOnlyCollection result = rows.Select(row => ConvertDataRow(row)).ToList(); + return new ValueTask>(result); + } + /// Returns the data to be used to test the theory. /// The method that is being tested /// One or more sets of theory data. Each invocation of the test method /// is represented by a single object array. - public override IEnumerable GetData(MethodInfo testMethod) + private IEnumerable GetDataRows(MethodInfo testMethod) { IEnumerable addedRows = Enumerable.Empty().ToArray(); if (!string.IsNullOrWhiteSpace(this.MemberName)) @@ -59,11 +71,22 @@ public abstract class ImageDataAttributeBase : DataAttribute object obj = accessor(); if (obj is IEnumerable memberItems) { - addedRows = memberItems.Select(x => x as object[]); - if (addedRows.Any(x => x == null)) + // In xunit.v3, TheoryData yields ITheoryDataRow, not object[]. + // Call GetData to unpack the row's values. + addedRows = memberItems.Select(x => { - addedRows = memberItems.Select(x => new[] { x }); - } + if (x is ITheoryDataRow row) + { + return row.GetData(); + } + + if (x is object[] array) + { + return array; + } + + return [x]; + }); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs index 216ed95b8..b785558c8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/BasicSerializer.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using System.ComponentModel; -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests.TestUtilities; @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities; /// internal class BasicSerializer : IXunitSerializationInfo { - private readonly Dictionary map = []; + private readonly Dictionary map = []; public const char Separator = ':'; @@ -22,9 +22,10 @@ internal class BasicSerializer : IXunitSerializationInfo using MemoryStream ms = new(); using StreamWriter writer = new(ms); writer.WriteLine(type.FullName); - foreach (KeyValuePair kv in this.map) + foreach (KeyValuePair kv in this.map) { - writer.WriteLine($"{kv.Key}{Separator}{kv.Value}"); + // Format: key:TypeAssemblyQualifiedName:value + writer.WriteLine($"{kv.Key}{Separator}{kv.Value.Type.AssemblyQualifiedName}{Separator}{kv.Value.Str}"); } writer.Flush(); @@ -41,8 +42,13 @@ internal class BasicSerializer : IXunitSerializationInfo Type type = Type.GetType(reader.ReadLine()); for (string s = reader.ReadLine(); s != null; s = reader.ReadLine()) { - string[] kv = s.Split(Separator); - this.map[kv[0]] = kv[1]; + // Format: key:TypeAssemblyQualifiedName:value + string[] parts = s.Split(Separator, 3); + if (parts.Length == 3) + { + Type valueType = Type.GetType(parts[1]) ?? typeof(string); + this.map[parts[0]] = (parts[2], valueType); + } } return type; @@ -76,20 +82,18 @@ internal class BasicSerializer : IXunitSerializationInfo type ??= value.GetType(); - this.map[key] = TypeDescriptor.GetConverter(type).ConvertToInvariantString(value); + this.map[key] = (TypeDescriptor.GetConverter(type).ConvertToInvariantString(value), type); } - public object GetValue(string key, Type type) + public object GetValue(string key) { Guard.NotNull(key, nameof(key)); - if (!this.map.TryGetValue(key, out string str)) + if (!this.map.TryGetValue(key, out (string Str, Type Type) entry)) { - return type.IsValueType ? Activator.CreateInstance(type) : null; + return null; } - return TypeDescriptor.GetConverter(type).ConvertFromInvariantString(str); + return TypeDescriptor.GetConverter(entry.Type).ConvertFromInvariantString(entry.Str); } - - public T GetValue(string key) => (T)this.GetValue(key, typeof(T)); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs b/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs index 548354450..07d8db532 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; public class ByteBuffer { - private readonly List bytes = new(); + private readonly List bytes = []; private readonly bool isLittleEndian; public ByteBuffer(bool isLittleEndian) @@ -33,6 +33,6 @@ public class ByteBuffer public byte[] ToArray() { - return this.bytes.ToArray(); + return [.. this.bytes]; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/CorruptedMetadataImageFactory.cs b/tests/ImageSharp.Tests/TestUtilities/CorruptedMetadataImageFactory.cs new file mode 100644 index 000000000..c9d18510d --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/CorruptedMetadataImageFactory.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestDataIcc; + +namespace SixLabors.ImageSharp.Tests.TestUtilities; + +/// +/// Creates encoded images with malformed metadata payloads while keeping the surrounding container valid. +/// +internal static class CorruptedMetadataImageFactory +{ + /// + /// Creates an encoded single-pixel image with a malformed ICC profile. + /// + /// The encoder used to produce the image bytes. + /// The encoded image bytes with a malformed ICC profile payload. + public static byte[] CreateImageWithMalformedIccProfile(IImageEncoder encoder) + { + using Image image = new(1, 1); + image[0, 0] = new Rgba32(255, 0, 0, 255); + image.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.HeaderInvalidSizeSmallArray); + + using MemoryStream stream = new(); + image.Save(stream, encoder); + + return stream.ToArray(); + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs index 63126dcbc..d0b77882d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs +++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs @@ -4,7 +4,7 @@ using System.Diagnostics; using System.Globalization; using Microsoft.DotNet.RemoteExecutor; -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests.TestUtilities; @@ -414,7 +414,14 @@ public static class FeatureTestRunner // Not a COMPlus value. We filter in calling method. features.Add(key, nameof(HwIntrinsics.AllowAll)); break; +#if NET11_0_OR_GREATER + case nameof(HwIntrinsics.DisableSSE42): + // SSE3 through SSE4.2 and POPCNT are x86-64-v2 baseline in .NET 11+ and the + // switch is inert, so skip it entirely rather than spawn a child that tests nothing. + // https://learn.microsoft.com/en-us/dotnet/core/compatibility/jit/11/minimum-hardware-requirements + break; +#endif default: features.Add(key, intrinsic.Replace("Disable", "Enable")); break; @@ -436,37 +443,40 @@ public enum HwIntrinsics : long // Use flags so we can pass multiple values without using params. // Don't base on 0 or use inverse for All as that doesn't translate to string values. DisableHWIntrinsic = 1L << 0, - DisableSSE = 1L << 1, - DisableSSE2 = 1L << 2, - DisableAES = 1L << 3, - DisablePCLMULQDQ = 1L << 4, - DisableSSE3 = 1L << 5, - DisableSSSE3 = 1L << 6, - DisableSSE41 = 1L << 7, - DisableSSE42 = 1L << 8, - DisablePOPCNT = 1L << 9, - DisableAVX = 1L << 10, - DisableFMA = 1L << 11, - DisableAVX2 = 1L << 12, + DisableSSE42 = 1L << 1, + DisableAVX = 1L << 2, + DisableAVX2 = 1L << 3, + DisableAVX512F = 1L << 4, + DisableAVX512v2 = 1L << 5, + DisableAVX512v3 = 1L << 6, + DisableAVX10v1 = 1L << 7, + DisableAVX10v2 = 1L << 8, + DisableAPX = 1L << 9, + DisableAES = 1L << 10, + DisableAVX512VP2INTERSECT = 1L << 11, + DisableAVXIFMA = 1L << 12, DisableAVXVNNI = 1L << 13, - DisableAVX512BW = 1L << 14, - DisableAVX512BW_VL = 1L << 15, - DisableAVX512CD = 1L << 16, - DisableAVX512CD_VL = 1L << 17, - DisableAVX512DQ = 1L << 18, - DisableAVX512DQ_VL = 1L << 19, - DisableAVX512F = 1L << 20, - DisableAVX512F_VL = 1L << 21, - DisableAVX512VBMI = 1L << 22, - DisableAVX512VBMI_VL = 1L << 23, - DisableBMI1 = 1L << 24, - DisableBMI2 = 1L << 25, - DisableLZCNT = 1L << 26, - DisableArm64AdvSimd = 1L << 27, - DisableArm64Crc32 = 1L << 28, - DisableArm64Dp = 1L << 29, - DisableArm64Aes = 1L << 30, - DisableArm64Sha1 = 1L << 31, - DisableArm64Sha256 = 1L << 32, - AllowAll = 1L << 33 + DisableAVXVNNIINT = 1L << 14, + DisableGFNI = 1L << 15, + DisableSHA = 1L << 16, + DisableVAES = 1L << 17, + DisableWAITPKG = 1L << 18, + DisableX86Serialize = 1 << 19, + + // Arm64 + DisableArm64Aes = 1L << 20, + DisableArm64Atomics = 1L << 21, + DisableArm64Crc32 = 1L << 22, + DisableArm64Dczva = 1L << 23, + DisableArm64Dp = 1L << 24, + DisableArm64Rdm = 1L << 25, + DisableArm64Sha1 = 1L << 26, + DisableArm64Sha256 = 1L << 27, + DisableArm64Sve = 1L << 28, + DisableArm64Sve2 = 1L << 29, + + // RISC-V64 + DisableRiscV64Zba = 1L << 30, + DisableRiscV64Zbb = 1L << 31, + AllowAll = 1L << 32, } diff --git a/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs index 2a7b42f6b..deac649e4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs @@ -6,13 +6,10 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities; public class GraphicsOptionsComparer : IEqualityComparer { public bool Equals(GraphicsOptions x, GraphicsOptions y) - { - return x.AlphaCompositionMode == y.AlphaCompositionMode - && x.Antialias == y.Antialias - && x.AntialiasSubpixelDepth == y.AntialiasSubpixelDepth - && x.BlendPercentage == y.BlendPercentage - && x.ColorBlendingMode == y.ColorBlendingMode; - } + => x.AlphaCompositionMode == y.AlphaCompositionMode + && x.Antialias == y.Antialias + && x.BlendPercentage == y.BlendPercentage + && x.ColorBlendingMode == y.ColorBlendingMode; public int GetHashCode(GraphicsOptions obj) => obj.GetHashCode(); } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs index 8d6a1ffc7..95f983834 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs @@ -12,7 +12,7 @@ public class ImageDifferenceIsOverThresholdException : ImagesSimilarityException public ImageDifferenceIsOverThresholdException(IEnumerable reports) : base("Image difference is over threshold!" + StringifyReports(reports)) - => this.Reports = reports.ToArray(); + => this.Reports = [.. reports]; private static string StringifyReports(IEnumerable reports) { diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs index 1594258e0..6553dc510 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs @@ -19,7 +19,7 @@ public class ImageSimilarityReport this.ExpectedImage = expectedImage; this.ActualImage = actualImage; this.TotalNormalizedDifference = totalNormalizedDifference; - this.Differences = differences.ToArray(); + this.Differences = [.. differences]; } public int Index { get; } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index 813ed505d..676fcff7f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests; @@ -78,6 +78,6 @@ public abstract partial class TestImageProvider : IXunitSerializable return x < midX ? BottomLeftColor : BottomRightColor; } - private static TPixel GetBottomRightColor() => TPixel.FromScaledVector4(new Vector4(1f, 0f, 1f, 0.5f)); + private static TPixel GetBottomRightColor() => TPixel.FromUnassociatedScaledVector4(new Vector4(1f, 0f, 1f, 0.5f)); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index c8c3e09c3..35cd7832f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -2,8 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.PixelFormats; - -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 3f6e78a34..e1e86ca9a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -6,8 +6,7 @@ using System.Reflection; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; - -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs index 6da5b75f0..98c03b0f8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/MemberMethodProvider.cs @@ -3,7 +3,7 @@ using System.Reflection; using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index d78edf5bc..c83d3eba8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index ab624126b..6cfe97db2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -7,8 +7,7 @@ using Castle.Core.Internal; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; - -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs index 405d048fc..400ce733a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -4,7 +4,7 @@ using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests; @@ -158,15 +158,17 @@ public abstract partial class TestImageProvider : IXunitSerializable int bottom = pixels.Height; int height = (int)Math.Ceiling(pixels.Height / 6f); - Vector4 red = Color.Red.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern - Vector4 green = Color.Green.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern - Vector4 blue = Color.Blue.ToPixel().ToVector4(); // use real color so we can see how it translates in the test pattern + // Round-trip the primaries through TPixel so this fixture still demonstrates the format's color quantization, while + // keeping the working vectors unassociated because the loop replaces alpha independently of RGB. + Vector4 red = Color.Red.ToPixel().ToUnassociatedScaledVector4(); + Vector4 green = Color.Green.ToPixel().ToUnassociatedScaledVector4(); + Vector4 blue = Color.Blue.ToPixel().ToUnassociatedScaledVector4(); for (int x = left; x < right; x++) { blue.W = red.W = green.W = x / (float)right; - TPixel c = TPixel.FromVector4(red); + TPixel c = TPixel.FromUnassociatedScaledVector4(red); int topBand = top; for (int y = topBand; y < top + height; y++) { @@ -174,14 +176,14 @@ public abstract partial class TestImageProvider : IXunitSerializable } topBand += height; - c = TPixel.FromVector4(green); + c = TPixel.FromUnassociatedScaledVector4(green); for (int y = topBand; y < topBand + height; y++) { pixels[x, y] = c; } topBand += height; - c = TPixel.FromVector4(blue); + c = TPixel.FromUnassociatedScaledVector4(blue); for (int y = topBand; y < bottom; y++) { pixels[x, y] = c; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 73777ef60..376ff00fc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -217,12 +217,15 @@ public class ImagingTestCaseUtility { encoder ??= TestEnvironment.GetReferenceEncoder($"foo.{extension}"); - (int Index, string FileName)[] files = this.GetTestOutputFileNamesMultiFrame( - image.Frames.Count, - extension, - testOutputDetails, - appendPixelTypeToFileName, - predicate: predicate).ToArray(); + (int Index, string FileName)[] files = + [ + .. this.GetTestOutputFileNamesMultiFrame( + image.Frames.Count, + extension, + testOutputDetails, + appendPixelTypeToFileName, + predicate: predicate) + ]; foreach ((int Index, string FileName) file in files) { @@ -249,8 +252,12 @@ public class ImagingTestCaseUtility object testOutputDetails, bool appendPixelTypeToFileName = true, Func predicate = null) - => this.GetTestOutputFileNamesMultiFrame(frameCount, extension, testOutputDetails, appendPixelTypeToFileName, predicate: predicate) - .Select(x => (x.Index, TestEnvironment.GetReferenceOutputFileName(x.FileName))).ToArray(); + => + [ + .. this.GetTestOutputFileNamesMultiFrame(frameCount, extension, testOutputDetails, + appendPixelTypeToFileName, predicate: predicate) + .Select(x => (x.Index, TestEnvironment.GetReferenceOutputFileName(x.FileName))) + ]; internal void Init(string typeName, string methodName, string outputSubfolderName) { @@ -278,7 +285,7 @@ public class ImagingTestCaseUtility where TPixel : unmanaged, IPixel { TPixel pixel = img[x, y]; - Rgba64 rgbaPixel = Rgba64.FromScaledVector4(pixel.ToScaledVector4()); + Rgba64 rgbaPixel = Rgba64.FromUnassociatedScaledVector4(pixel.ToUnassociatedScaledVector4()); ushort change = (ushort)Math.Round((perChannelChange / 255F) * 65535F); if (rgbaPixel.R + perChannelChange <= 255) diff --git a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs index 81f16cf6b..2fd71e213 100644 --- a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs +++ b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 862d4b64d..d72556b0e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -6,6 +6,7 @@ using ImageMagick; using ImageMagick.Formats; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tiff; @@ -42,6 +43,8 @@ public class MagickReferenceDecoder : ImageDecoder public static MagickReferenceDecoder WebP { get; } = new(WebpFormat.Instance); + public static MagickReferenceDecoder Exr { get; } = new(ExrFormat.Instance); + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) { ImageMetadata metadata = new(); @@ -58,14 +61,14 @@ public class MagickReferenceDecoder : ImageDecoder MagickReadSettings settings = new() { - FrameCount = (int)options.MaxFrames + FrameCount = options.MaxFrames }; settings.SetDefines(bmpReadDefines); settings.SetDefines(pngReadDefines); using MagickImageCollection magickImageCollection = new(stream, settings); - int imageWidth = magickImageCollection.Max(x => x.Width); - int imageHeight = magickImageCollection.Max(x => x.Height); + int imageWidth = (int)magickImageCollection.Max(x => x.Width); + int imageHeight = (int)magickImageCollection.Max(x => x.Height); List> framesList = []; foreach (IMagickImage magicFrame in magickImageCollection) @@ -74,10 +77,10 @@ public class MagickReferenceDecoder : ImageDecoder framesList.Add(frame); Buffer2DRegion buffer = frame.PixelBuffer.GetRegion( - imageWidth - magicFrame.Width, - imageHeight - magicFrame.Height, - magicFrame.Width, - magicFrame.Height); + (int)(imageWidth - magicFrame.Width), + (int)(imageHeight - magicFrame.Height), + (int)magicFrame.Width, + (int)magicFrame.Height); using IUnsafePixelCollection pixels = magicFrame.GetPixelsUnsafe(); if (magicFrame.Depth is 12 or 10 or 8 or 6 or 5 or 4 or 3 or 2 or 1) @@ -118,6 +121,7 @@ public class MagickReferenceDecoder : ImageDecoder PixelType = metadata.GetDecodedPixelTypeInfo() }; } + private static void FromRgba32Bytes( Configuration configuration, Span rgbaBytes, diff --git a/tests/ImageSharp.Tests/TestUtilities/ScaledRgbaVectorP.cs b/tests/ImageSharp.Tests/TestUtilities/ScaledRgbaVectorP.cs new file mode 100644 index 000000000..afe320931 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ScaledRgbaVectorP.cs @@ -0,0 +1,271 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests; + +/// +/// A test pixel format that stores associated scaled components as 32-bit floating-point values. +/// +/// +/// Its native vector range differs from its scaled range so processor tests also detect missing +/// conversion boundaries without introducing packed-pixel quantization. +/// +[StructLayout(LayoutKind.Sequential)] +internal struct ScaledRgbaVectorP : IPixel +{ + private Vector4 scaled; + + public ScaledRgbaVectorP(Vector4 scaled) => this.scaled = Numerics.Clamp(scaled, Vector4.Zero, Vector4.One); + + public readonly Rgba32 ToRgba32() + { + Vector4 unassociated = this.scaled; + Numerics.UnPremultiply(ref unassociated); + return Rgba32.FromScaledVector4(unassociated); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToScaledVector4() => this.scaled; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToVector4() => (this.scaled * 2F) - Vector4.One; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedScaledVector4() + { + Vector4 vector = this.scaled; + Numerics.UnPremultiply(ref vector); + return vector; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedScaledVector4() => this.ToScaledVector4(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToUnassociatedVector4() + { + Vector4 vector = this.ToUnassociatedScaledVector4(); + + // The native vector is an affine view of the scaled data, so association changes occur before mapping the result. + return (vector * 2F) - Vector4.One; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Vector4 ToAssociatedVector4() => this.ToVector4(); + + public static PixelTypeInfo GetPixelTypeInfo() + => PixelTypeInfo.Create( + PixelComponentInfo.Create(4, 32, 32, 32, 32), + PixelColorType.RGB | PixelColorType.Alpha, + PixelAlphaRepresentation.Associated); + + public static PixelOperations CreatePixelOperations() => new ScaledPixelOperations(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromScaledVector4(Vector4 source) => new(source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromVector4(Vector4 source) => new((source + Vector4.One) * .5F); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromAssociatedScaledVector4(Vector4 source) + { + // Clamp logical color before restoring association so out-of-range alpha cannot permanently darken it. + Numerics.UnPremultiply(ref source); + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + Numerics.Premultiply(ref source); + return new ScaledRgbaVectorP(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromUnassociatedVector4(Vector4 source) + { + // The native vector is affine, so map it to scaled space before changing alpha representation. + source = (source + Vector4.One) * .5F; + return FromUnassociatedScaledVector4(source); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromAssociatedVector4(Vector4 source) + { + // The native vector is affine, so map it to scaled space before changing alpha representation. + source = (source + Vector4.One) * .5F; + return FromAssociatedScaledVector4(source); + } + + public static ScaledRgbaVectorP FromAbgr32(Abgr32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromArgb32(Argb32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromBgra5551(Bgra5551 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromBgr24(Bgr24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromBgra32(Bgra32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromL8(L8 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromL16(L16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromLa16(La16 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromLa32(La32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromRgb24(Rgb24 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromRgba32(Rgba32 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromRgb48(Rgb48 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public static ScaledRgbaVectorP FromRgba64(Rgba64 source) => FromUnassociatedScaledVector4(source.ToScaledVector4()); + + public override readonly bool Equals(object obj) => obj is ScaledRgbaVectorP other && this.Equals(other); + + public readonly bool Equals(ScaledRgbaVectorP other) => this.scaled.Equals(other.scaled); + + public override readonly int GetHashCode() => this.scaled.GetHashCode(); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ScaledRgbaVectorP FromUnassociatedScaledVector4(Vector4 source) + { + source = Numerics.Clamp(source, Vector4.Zero, Vector4.One); + Numerics.Premultiply(ref source); + return new ScaledRgbaVectorP(source); + } + + /// + /// Provides representation-aware bulk operations for the floating-point test pixel. + /// + private sealed class ScaledPixelOperations : AssociatedAlphaPixelOperations + { + /// + protected override void ToUnassociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Span vectors = destination[..source.Length]; + + // The test pixel contains one Vector4 field, so the cast copies its associated storage without changing representation. + MemoryMarshal.Cast(source).CopyTo(vectors); + Numerics.UnPremultiply(vectors); + + // Association is defined in scaled space, so map the unassociated result to the test format's native [-1, 1] range afterwards. + for (int i = 0; i < vectors.Length; i++) + { + vectors[i] = (vectors[i] * 2F) - Vector4.One; + } + } + + /// + protected override void ToAssociatedVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Span vectors = destination[..source.Length]; + MemoryMarshal.Cast(source).CopyTo(vectors); + + for (int i = 0; i < vectors.Length; i++) + { + vectors[i] = (vectors[i] * 2F) - Vector4.One; + } + } + + /// + protected override void ToUnassociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + Span vectors = destination[..source.Length]; + MemoryMarshal.Cast(source).CopyTo(vectors); + Numerics.UnPremultiply(vectors); + } + + /// + protected override void ToAssociatedScaledVector4(Configuration configuration, ReadOnlySpan source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + protected override void FromUnassociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + MapNativeToScaled(source); + Numerics.Clamp(MemoryMarshal.Cast(source), 0F, 1F); + Numerics.Premultiply(source); + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + protected override void FromAssociatedVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + MapNativeToScaled(source); + Numerics.UnPremultiply(source); + Numerics.Clamp(MemoryMarshal.Cast(source), 0F, 1F); + Numerics.Premultiply(source); + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + protected override void FromUnassociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // This test format stores floating-point alpha without quantization, so clamping and premultiplication can use the shared SIMD span operations directly. + Numerics.Clamp(MemoryMarshal.Cast(source), 0F, 1F); + Numerics.Premultiply(source); + + // Source now matches the pixel's sole associated Vector4 field and can be copied without a scalar conversion loop. + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + protected override void FromAssociatedScaledVector4Destructive(Configuration configuration, Span source, Span destination) + { + Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination)); + + destination = destination[..source.Length]; + + // Recovering the logical color before clamping preserves it when alpha lies outside the scaled range; premultiplication then restores the associated representation. + Numerics.UnPremultiply(source); + Numerics.Clamp(MemoryMarshal.Cast(source), 0F, 1F); + Numerics.Premultiply(source); + MemoryMarshal.Cast(source).CopyTo(destination); + } + + /// + /// Maps vectors from the test format's native numeric range to its scaled range. + /// + /// The vectors to convert in place. + private static void MapNativeToScaled(Span vectors) + { + // Native values use [-1, 1], so the affine inverse maps them to [0, 1] before changing alpha representation. + for (int i = 0; i < vectors.Length; i++) + { + vectors[i] = (vectors[i] + Vector4.One) * .5F; + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs b/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs index d8b0ff785..f3c19f117 100644 --- a/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs +++ b/tests/ImageSharp.Tests/TestUtilities/SixLaborsXunitTestFramework.cs @@ -3,22 +3,13 @@ using BenchmarkDotNet.Environments; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; -using Xunit.Sdk; +using Xunit.v3; -[assembly: Xunit.TestFramework(SixLaborsXunitTestFramework.Type, SixLaborsXunitTestFramework.Assembly)] +[assembly: Xunit.TestFramework(typeof(SixLaborsXunitTestFramework))] namespace SixLabors.ImageSharp.Tests.TestUtilities; public class SixLaborsXunitTestFramework : XunitTestFramework { - public const string Type = "SixLabors.ImageSharp.Tests.TestUtilities.SixLaborsXunitTestFramework"; - public const string Assembly = "SixLabors.ImageSharp.Tests"; - - public SixLaborsXunitTestFramework(IMessageSink messageSink) - : base(messageSink) - { - DiagnosticMessage message = new(HostEnvironmentInfo.GetInformation()); - messageSink.OnMessage(message); - } + public SixLaborsXunitTestFramework() => Console.Error.WriteLine(HostEnvironmentInfo.GetInformation()); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 01e9b0dd4..ba2a2f379 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; +using SixLabors.ImageSharp.Formats.Exr; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Jpeg; @@ -65,12 +66,13 @@ public static partial class TestEnvironment new TgaConfigurationModule(), new WebpConfigurationModule(), new TiffConfigurationModule(), + new ExrConfigurationModule(), new QoiConfigurationModule()); IImageEncoder pngEncoder = IsWindows ? SystemDrawingReferenceEncoder.Png : new ImageSharpPngEncoderWithDefaultConfiguration(); IImageEncoder bmpEncoder = IsWindows ? SystemDrawingReferenceEncoder.Bmp : new BmpEncoder(); - // Magick codecs should work on all platforms + // Magick codecs should work on all platforms. cfg.ConfigureCodecs( PngFormat.Instance, MagickReferenceDecoder.Png, diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 4a34529dd..b51e4e2d4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -505,7 +505,7 @@ public static class TestImageExtensions public static void CompareBuffers(Buffer2D expected, Buffer2D actual) where T : struct, IEquatable { - Assert.True(expected.Size() == actual.Size(), "Buffer sizes are not equal!"); + Assert.True(expected.Size == actual.Size, "Buffer sizes are not equal!"); for (int y = 0; y < expected.Height; y++) { @@ -826,14 +826,17 @@ public static class TestImageExtensions for (int y = rows.Min; y < rows.Max; y++) { Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); - PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); + PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); + for (int i = 0; i < span.Length; i++) { ref Vector4 v = ref span[i]; v.W = 1F; } - PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale); + // Changing opacity must operate on logical RGB. The representation-aware bulk conversion prevents associated + // formats from retaining the darker RGB values that belonged to the old alpha. + PixelOperations.Instance.FromVector4Destructive(this.configuration, span, rowSpan, PixelConversionModifiers.Scale | PixelConversionModifiers.UnPremultiply); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index a0ff4a466..667224912 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -33,11 +33,11 @@ internal class TestMemoryAllocator : MemoryAllocator public void EnableNonThreadSafeLogging() { - this.allocationLog = new List(); - this.returnLog = new List(); + this.allocationLog = []; + this.returnLog = []; } - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + protected override AllocationTrackedMemoryManager AllocateCore(int length, AllocationOptions options = AllocationOptions.None) { T[] array = this.AllocateArray(length, options); return new BasicArrayBuffer(array, length, this); @@ -110,7 +110,7 @@ internal class TestMemoryAllocator : MemoryAllocator /// /// Wraps an array as an instance. /// - private class BasicArrayBuffer : MemoryManager + private class BasicArrayBuffer : AllocationTrackedMemoryManager where T : struct { private readonly TestMemoryAllocator allocator; @@ -159,7 +159,7 @@ internal class TestMemoryAllocator : MemoryAllocator } /// - protected override void Dispose(bool disposing) + protected override void DisposeCore(bool disposing) { if (disposing) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs index f0344e2b9..c8383eacd 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -3,7 +3,7 @@ using System.Numerics; using SixLabors.ImageSharp.PixelFormats; -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests.TestUtilities; @@ -35,7 +35,7 @@ public class TestPixel : IXunitSerializable public float Alpha { get; set; } - public TPixel AsPixel() => TPixel.FromScaledVector4(new Vector4(this.Red, this.Green, this.Blue, this.Alpha)); + public TPixel AsPixel() => TPixel.FromUnassociatedScaledVector4(new Vector4(this.Red, this.Green, this.Blue, this.Alpha)); internal Span AsSpan() => new([this.AsPixel()]); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestType.cs b/tests/ImageSharp.Tests/TestUtilities/TestType.cs index 5b4f6a602..dbb267b33 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestType.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests.TestUtilities; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 2e2416952..910738327 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -25,9 +25,11 @@ public static class TestUtils private static readonly Dictionary PixelTypes2ClrTypes = new(); - private static readonly PixelTypes[] AllConcretePixelTypes = GetAllPixelTypes() - .Except([PixelTypes.Undefined, PixelTypes.All]) - .ToArray(); + private static readonly PixelTypes[] AllConcretePixelTypes = + [ + .. GetAllPixelTypes() + .Except([PixelTypes.Undefined, PixelTypes.All]) + ]; static TestUtils() { @@ -401,9 +403,11 @@ public static class TestUtils public static string[] GetAllResamplerNames(bool includeNearestNeighbour = true) { - return typeof(KnownResamplers).GetProperties(BindingFlags.Public | BindingFlags.Static) - .Select(p => p.Name) - .Where(name => includeNearestNeighbour || name != nameof(KnownResamplers.NearestNeighbor)) - .ToArray(); + return + [ + .. typeof(KnownResamplers).GetProperties(BindingFlags.Public | BindingFlags.Static) + .Select(p => p.Name) + .Where(name => includeNearestNeighbour || name != nameof(KnownResamplers.NearestNeighbor)) + ]; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs index 350333965..c4ad50437 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests.TestUtilities; diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs index 1b5b43f03..2f0e73f20 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/BasicSerializerTests.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; +using Xunit.Sdk; namespace SixLabors.ImageSharp.Tests; @@ -18,16 +18,16 @@ public class BasicSerializerTests public virtual void Deserialize(IXunitSerializationInfo info) { - info.AddValue(nameof(this.Length), this.Length); - info.AddValue(nameof(this.Name), this.Name); - info.AddValue(nameof(this.Lives), this.Lives); + this.Length = info.GetValue(nameof(this.Length)); + this.Name = info.GetValue(nameof(this.Name)); + this.Lives = info.GetValue(nameof(this.Lives)); } public virtual void Serialize(IXunitSerializationInfo info) { - this.Length = info.GetValue(nameof(this.Length)); - this.Name = info.GetValue(nameof(this.Name)); - this.Lives = info.GetValue(nameof(this.Lives)); + info.AddValue(nameof(this.Length), this.Length); + info.AddValue(nameof(this.Name), this.Name); + info.AddValue(nameof(this.Lives), this.Lives); } } @@ -37,8 +37,8 @@ public class BasicSerializerTests public override void Deserialize(IXunitSerializationInfo info) { - this.Strength = info.GetValue(nameof(this.Strength)); base.Deserialize(info); + this.Strength = info.GetValue(nameof(this.Strength)); } public override void Serialize(IXunitSerializationInfo info) @@ -51,7 +51,13 @@ public class BasicSerializerTests [Fact] public void SerializeDeserialize_ShouldPreserveValues() { - DerivedObj obj = new() { Length = 123.1, Name = "Lol123!", Lives = 7, Strength = 4.8 }; + DerivedObj obj = new() + { + Length = 123.1, + Name = "Lol123!", + Lives = 7, + Strength = 4.8, + }; string str = BasicSerializer.Serialize(obj); BaseObj mirrorBase = BasicSerializer.Deserialize(str); diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs index 82b247d9d..8b9210636 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs @@ -4,27 +4,32 @@ using System.Numerics; using System.Runtime.Intrinsics.Arm; using System.Runtime.Intrinsics.X86; -using Xunit.Abstractions; +using Xunit.Sdk; using Aes = System.Runtime.Intrinsics.X86.Aes; namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; public class FeatureTestRunnerTests { - public static TheoryData Intrinsics => + public static TheoryData Intrinsics => new() { - { HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, ["EnableAES", "AllowAll"] }, - { HwIntrinsics.DisableHWIntrinsic, ["EnableHWIntrinsic"] }, - { HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, ["EnableSSE42", "EnableAVX"] } + { HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, ["EnableAES", "AllowAll"] }, + { HwIntrinsics.DisableHWIntrinsic, HwIntrinsics.DisableHWIntrinsic, ["EnableHWIntrinsic"] }, +#if NET11_0_OR_GREATER + // ToFeatureKeyValueCollection filters DisableSSE42: SSE4.2 is x86-64-v2 baseline in .NET 11+ and cannot be disabled. + { HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, HwIntrinsics.DisableAVX, ["EnableAVX"] } +#else + { HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, ["EnableSSE42", "EnableAVX"] } +#endif }; [Theory] [MemberData(nameof(Intrinsics))] - public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics expectedIntrinsics, string[] expectedValues) + public void ToFeatureCollectionReturnsExpectedResult(HwIntrinsics intrinsics, HwIntrinsics expectedIntrinsics, string[] expectedValues) { - Dictionary features = expectedIntrinsics.ToFeatureKeyValueCollection(); - HwIntrinsics[] keys = features.Keys.ToArray(); + Dictionary features = intrinsics.ToFeatureKeyValueCollection(); + HwIntrinsics[] keys = [.. features.Keys]; HwIntrinsics actualIntrinsics = keys[0]; for (int i = 1; i < keys.Length; i++) @@ -47,7 +52,7 @@ public class FeatureTestRunnerTests } FeatureTestRunner.RunWithHwIntrinsicsFeature( - () => Assert.True(Vector.IsHardwareAccelerated), + () => Assert.True(Vector.IsHardwareAccelerated, "Vector hardware acceleration should be enabled when AllowAll is specified."), HwIntrinsics.AllowAll); } @@ -56,21 +61,21 @@ public class FeatureTestRunnerTests { static void AssertDisabled() { - Assert.False(Sse.IsSupported); - Assert.False(Sse2.IsSupported); - Assert.False(Aes.IsSupported); - Assert.False(Pclmulqdq.IsSupported); - Assert.False(Sse3.IsSupported); - Assert.False(Ssse3.IsSupported); - Assert.False(Sse41.IsSupported); - Assert.False(Sse42.IsSupported); - Assert.False(Popcnt.IsSupported); - Assert.False(Avx.IsSupported); - Assert.False(Fma.IsSupported); - Assert.False(Avx2.IsSupported); - Assert.False(Bmi1.IsSupported); - Assert.False(Bmi2.IsSupported); - Assert.False(Lzcnt.IsSupported); + Assert.False(Sse.IsSupported, "SSE should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse2.IsSupported, "SSE2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Aes.IsSupported, "AES (x86) should be disabled when DisableHWIntrinsic is set."); + Assert.False(Pclmulqdq.IsSupported, "PCLMULQDQ should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse3.IsSupported, "SSE3 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Ssse3.IsSupported, "SSSE3 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse41.IsSupported, "SSE4.1 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse42.IsSupported, "SSE4.2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Popcnt.IsSupported, "POPCNT should be disabled when DisableHWIntrinsic is set."); + Assert.False(Avx.IsSupported, "AVX should be disabled when DisableHWIntrinsic is set."); + Assert.False(Fma.IsSupported, "FMA should be disabled when DisableHWIntrinsic is set."); + Assert.False(Avx2.IsSupported, "AVX2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Bmi1.IsSupported, "BMI1 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Bmi2.IsSupported, "BMI2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Lzcnt.IsSupported, "LZCNT should be disabled when DisableHWIntrinsic is set."); } FeatureTestRunner.RunWithHwIntrinsicsFeature( @@ -88,90 +93,69 @@ public class FeatureTestRunnerTests switch (Enum.Parse(intrinsic)) { case HwIntrinsics.DisableHWIntrinsic: - Assert.False(Sse.IsSupported); - Assert.False(Sse2.IsSupported); - Assert.False(Aes.IsSupported); - Assert.False(Pclmulqdq.IsSupported); - Assert.False(Sse3.IsSupported); - Assert.False(Ssse3.IsSupported); - Assert.False(Sse41.IsSupported); - Assert.False(Sse42.IsSupported); - Assert.False(Popcnt.IsSupported); - Assert.False(Avx.IsSupported); - Assert.False(Fma.IsSupported); - Assert.False(Avx2.IsSupported); - Assert.False(Bmi1.IsSupported); - Assert.False(Bmi2.IsSupported); - Assert.False(Lzcnt.IsSupported); - Assert.False(AdvSimd.IsSupported); - Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported); - Assert.False(Crc32.IsSupported); - Assert.False(Dp.IsSupported); - Assert.False(Sha1.IsSupported); - Assert.False(Sha256.IsSupported); - break; - case HwIntrinsics.DisableSSE: - Assert.False(Sse.IsSupported); - break; - case HwIntrinsics.DisableSSE2: - Assert.False(Sse2.IsSupported); + Assert.False(Sse.IsSupported, "SSE should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse2.IsSupported, "SSE2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Aes.IsSupported, "AES (x86) should be disabled when DisableHWIntrinsic is set."); + Assert.False(Pclmulqdq.IsSupported, "PCLMULQDQ should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse3.IsSupported, "SSE3 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Ssse3.IsSupported, "SSSE3 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse41.IsSupported, "SSE4.1 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse42.IsSupported, "SSE4.2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Popcnt.IsSupported, "POPCNT should be disabled when DisableHWIntrinsic is set."); + Assert.False(Avx.IsSupported, "AVX should be disabled when DisableHWIntrinsic is set."); + Assert.False(Fma.IsSupported, "FMA should be disabled when DisableHWIntrinsic is set."); + Assert.False(Avx2.IsSupported, "AVX2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Bmi1.IsSupported, "BMI1 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Bmi2.IsSupported, "BMI2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Lzcnt.IsSupported, "LZCNT should be disabled when DisableHWIntrinsic is set."); + Assert.False(AdvSimd.IsSupported, "Arm64 AdvSimd should be disabled when DisableHWIntrinsic is set."); + Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported, "Arm64 AES should be disabled when DisableHWIntrinsic is set."); + Assert.False(Crc32.IsSupported, "Arm64 CRC32 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Dp.IsSupported, "Arm64 DotProd (DP) should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sha1.IsSupported, "Arm64 SHA1 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sha256.IsSupported, "Arm64 SHA256 should be disabled when DisableHWIntrinsic is set."); break; case HwIntrinsics.DisableAES: - Assert.False(Aes.IsSupported); - break; - case HwIntrinsics.DisablePCLMULQDQ: - Assert.False(Pclmulqdq.IsSupported); - break; - case HwIntrinsics.DisableSSE3: - Assert.False(Sse3.IsSupported); - break; - case HwIntrinsics.DisableSSSE3: - Assert.False(Ssse3.IsSupported); - break; - case HwIntrinsics.DisableSSE41: - Assert.False(Sse41.IsSupported); + Assert.False(Aes.IsSupported, "AES (x86) should be disabled when DisableAES is set."); +#if NET10_0_OR_GREATER + Assert.False(Pclmulqdq.IsSupported, "PCLMULQDQ should be disabled when DisableAES is set (paired disable)."); +#endif break; case HwIntrinsics.DisableSSE42: - Assert.False(Sse42.IsSupported); - break; - case HwIntrinsics.DisablePOPCNT: - Assert.False(Popcnt.IsSupported); +#if NET10_0_OR_GREATER + Assert.False(Sse3.IsSupported, "Sse3 should be disabled."); + Assert.False(Ssse3.IsSupported, "Ssse3 should be disabled."); + Assert.False(Sse41.IsSupported, "Sse41 should be disabled."); + Assert.False(Popcnt.IsSupported, "Popcnt should be disabled."); +#endif + Assert.False(Sse42.IsSupported, "Sse42 should be disabled when DisableSSE42 is set."); break; case HwIntrinsics.DisableAVX: - Assert.False(Avx.IsSupported); - break; - case HwIntrinsics.DisableFMA: - Assert.False(Fma.IsSupported); + Assert.False(Avx.IsSupported, "AVX should be disabled when DisableAVX is set."); break; case HwIntrinsics.DisableAVX2: - Assert.False(Avx2.IsSupported); - break; - case HwIntrinsics.DisableBMI1: - Assert.False(Bmi1.IsSupported); - break; - case HwIntrinsics.DisableBMI2: - Assert.False(Bmi2.IsSupported); - break; - case HwIntrinsics.DisableLZCNT: - Assert.False(Lzcnt.IsSupported); - break; - case HwIntrinsics.DisableArm64AdvSimd: - Assert.False(AdvSimd.IsSupported); + Assert.False(Avx2.IsSupported, "AVX2 should be disabled when DisableAVX2 is set."); +#if NET10_0_OR_GREATER + Assert.False(Fma.IsSupported, "FMA should be disabled when DisableAVX2 is set (paired disable)."); + Assert.False(Bmi1.IsSupported, "BMI1 should be disabled when DisableAVX2 is set (paired disable)."); + Assert.False(Bmi2.IsSupported, "BMI2 should be disabled when DisableAVX2 is set (paired disable)."); + Assert.False(Lzcnt.IsSupported, "LZCNT should be disabled when DisableAVX2 is set (paired disable)."); +#endif break; case HwIntrinsics.DisableArm64Aes: - Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported); + Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported, "Arm64 AES should be disabled when DisableArm64Aes is set."); break; case HwIntrinsics.DisableArm64Crc32: - Assert.False(Crc32.IsSupported); + Assert.False(Crc32.IsSupported, "Arm64 CRC32 should be disabled when DisableArm64Crc32 is set."); break; case HwIntrinsics.DisableArm64Dp: - Assert.False(Dp.IsSupported); + Assert.False(Dp.IsSupported, "Arm64 DotProd (DP) should be disabled when DisableArm64Dp is set."); break; case HwIntrinsics.DisableArm64Sha1: - Assert.False(Sha1.IsSupported); + Assert.False(Sha1.IsSupported, "Arm64 SHA1 should be disabled when DisableArm64Sha1 is set."); break; case HwIntrinsics.DisableArm64Sha256: - Assert.False(Sha256.IsSupported); + Assert.False(Sha256.IsSupported, "Arm64 SHA256 should be disabled when DisableArm64Sha256 is set."); break; } } @@ -189,12 +173,12 @@ public class FeatureTestRunnerTests { Assert.NotNull(serializable); Assert.NotNull(FeatureTestRunner.DeserializeForXunit(serializable)); - Assert.False(Sse.IsSupported); + Assert.False(Avx.IsSupported, "AVX should be disabled when DisableAVX is set (sanity check using serializable param overload)."); } FeatureTestRunner.RunWithHwIntrinsicsFeature( AssertHwIntrinsicsFeatureDisabled, - HwIntrinsics.DisableSSE, + HwIntrinsics.DisableAVX, new FakeSerializable()); } @@ -209,95 +193,74 @@ public class FeatureTestRunnerTests switch (Enum.Parse(intrinsic)) { case HwIntrinsics.DisableHWIntrinsic: - Assert.False(Sse.IsSupported); - Assert.False(Sse2.IsSupported); - Assert.False(Aes.IsSupported); - Assert.False(Pclmulqdq.IsSupported); - Assert.False(Sse3.IsSupported); - Assert.False(Ssse3.IsSupported); - Assert.False(Sse41.IsSupported); - Assert.False(Sse42.IsSupported); - Assert.False(Popcnt.IsSupported); - Assert.False(Avx.IsSupported); - Assert.False(Fma.IsSupported); - Assert.False(Avx2.IsSupported); - Assert.False(Bmi1.IsSupported); - Assert.False(Bmi2.IsSupported); - Assert.False(Lzcnt.IsSupported); - Assert.False(AdvSimd.IsSupported); - Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported); - Assert.False(Crc32.IsSupported); - Assert.False(Dp.IsSupported); - Assert.False(Sha1.IsSupported); - Assert.False(Sha256.IsSupported); - break; - case HwIntrinsics.DisableSSE: - Assert.False(Sse.IsSupported); - break; - case HwIntrinsics.DisableSSE2: - Assert.False(Sse2.IsSupported); + Assert.False(Sse.IsSupported, "SSE should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse2.IsSupported, "SSE2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Aes.IsSupported, "AES (x86) should be disabled when DisableHWIntrinsic is set."); + Assert.False(Pclmulqdq.IsSupported, "PCLMULQDQ should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse3.IsSupported, "SSE3 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Ssse3.IsSupported, "SSSE3 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse41.IsSupported, "SSE4.1 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sse42.IsSupported, "SSE4.2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Popcnt.IsSupported, "POPCNT should be disabled when DisableHWIntrinsic is set."); + Assert.False(Avx.IsSupported, "AVX should be disabled when DisableHWIntrinsic is set."); + Assert.False(Fma.IsSupported, "FMA should be disabled when DisableHWIntrinsic is set."); + Assert.False(Avx2.IsSupported, "AVX2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Bmi1.IsSupported, "BMI1 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Bmi2.IsSupported, "BMI2 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Lzcnt.IsSupported, "LZCNT should be disabled when DisableHWIntrinsic is set."); + Assert.False(AdvSimd.IsSupported, "Arm64 AdvSimd should be disabled when DisableHWIntrinsic is set."); + Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported, "Arm64 AES should be disabled when DisableHWIntrinsic is set."); + Assert.False(Crc32.IsSupported, "Arm64 CRC32 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Dp.IsSupported, "Arm64 DotProd (DP) should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sha1.IsSupported, "Arm64 SHA1 should be disabled when DisableHWIntrinsic is set."); + Assert.False(Sha256.IsSupported, "Arm64 SHA256 should be disabled when DisableHWIntrinsic is set."); break; case HwIntrinsics.DisableAES: - Assert.False(Aes.IsSupported); - break; - case HwIntrinsics.DisablePCLMULQDQ: - Assert.False(Pclmulqdq.IsSupported); - break; - case HwIntrinsics.DisableSSE3: - Assert.False(Sse3.IsSupported); - break; - case HwIntrinsics.DisableSSSE3: - Assert.False(Ssse3.IsSupported); - break; - case HwIntrinsics.DisableSSE41: - Assert.False(Sse41.IsSupported); + Assert.False(Aes.IsSupported, "AES (x86) should be disabled when DisableAES is set."); +#if NET10_0_OR_GREATER + Assert.False(Pclmulqdq.IsSupported, "PCLMULQDQ should be disabled when DisableAES is set (paired disable)."); +#endif break; case HwIntrinsics.DisableSSE42: - Assert.False(Sse42.IsSupported); - break; - case HwIntrinsics.DisablePOPCNT: - Assert.False(Popcnt.IsSupported); +#if NET10_0_OR_GREATER + Assert.False(Sse3.IsSupported, "Sse3 should be disabled."); + Assert.False(Ssse3.IsSupported, "Ssse3 should be disabled."); + Assert.False(Sse41.IsSupported, "Sse41 should be disabled."); + Assert.False(Popcnt.IsSupported, "Popcnt should be disabled."); +#endif + Assert.False(Sse42.IsSupported, "Sse42 should be disabled when DisableSSE42 is set."); break; case HwIntrinsics.DisableAVX: - Assert.False(Avx.IsSupported); - break; - case HwIntrinsics.DisableFMA: - Assert.False(Fma.IsSupported); + Assert.False(Avx.IsSupported, "AVX should be disabled when DisableAVX is set."); break; case HwIntrinsics.DisableAVX2: - Assert.False(Avx2.IsSupported); - break; - case HwIntrinsics.DisableBMI1: - Assert.False(Bmi1.IsSupported); - break; - case HwIntrinsics.DisableBMI2: - Assert.False(Bmi2.IsSupported); - break; - case HwIntrinsics.DisableLZCNT: - Assert.False(Lzcnt.IsSupported); - break; - case HwIntrinsics.DisableArm64AdvSimd: - Assert.False(AdvSimd.IsSupported); + Assert.False(Avx2.IsSupported, "AVX2 should be disabled when DisableAVX2 is set."); +#if NET10_0_OR_GREATER + Assert.False(Fma.IsSupported, "FMA should be disabled when DisableAVX2 is set (paired disable)."); + Assert.False(Bmi1.IsSupported, "BMI1 should be disabled when DisableAVX2 is set (paired disable)."); + Assert.False(Bmi2.IsSupported, "BMI2 should be disabled when DisableAVX2 is set (paired disable)."); + Assert.False(Lzcnt.IsSupported, "LZCNT should be disabled when DisableAVX2 is set (paired disable)."); +#endif break; case HwIntrinsics.DisableArm64Aes: - Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported); + Assert.False(System.Runtime.Intrinsics.Arm.Aes.IsSupported, "Arm64 AES should be disabled when DisableArm64Aes is set."); break; case HwIntrinsics.DisableArm64Crc32: - Assert.False(Crc32.IsSupported); + Assert.False(Crc32.IsSupported, "Arm64 CRC32 should be disabled when DisableArm64Crc32 is set."); break; case HwIntrinsics.DisableArm64Dp: - Assert.False(Dp.IsSupported); + Assert.False(Dp.IsSupported, "Arm64 DotProd (DP) should be disabled when DisableArm64Dp is set."); break; case HwIntrinsics.DisableArm64Sha1: - Assert.False(Sha1.IsSupported); + Assert.False(Sha1.IsSupported, "Arm64 SHA1 should be disabled when DisableArm64Sha1 is set."); break; case HwIntrinsics.DisableArm64Sha256: - Assert.False(Sha256.IsSupported); + Assert.False(Sha256.IsSupported, "Arm64 SHA256 should be disabled when DisableArm64Sha256 is set."); break; } } - foreach (HwIntrinsics intrinsic in (HwIntrinsics[])Enum.GetValues(typeof(HwIntrinsics))) + foreach (HwIntrinsics intrinsic in Enum.GetValues()) { FeatureTestRunner.RunWithHwIntrinsicsFeature(AssertHwIntrinsicsFeatureDisabled, intrinsic, new FakeSerializable()); } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs index 349dd258e..8d57ab58e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs @@ -5,7 +5,6 @@ using Moq; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs index b818e80b0..36b93b87b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/MagickReferenceCodecTests.cs @@ -5,7 +5,6 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs index a145e7365..801d2e481 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/ReferenceDecoderBenchmarks.cs @@ -4,7 +4,6 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs index 555041890..9b20244c4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/SystemDrawingReferenceCodecTests.cs @@ -8,7 +8,6 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs index 36c078dc0..45b9d774b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs @@ -9,7 +9,6 @@ using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests; diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 72ed27e8e..41df3544f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -8,24 +8,25 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit.Abstractions; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests; public class TestImageProviderTests { - public static readonly TheoryData BasicData = new() - { - TestImageProvider.Blank(10, 20), - TestImageProvider.Blank(10, 20), - }; + public static readonly TheoryData BasicData = new( + new object[] + { + TestImageProvider.Blank(10, 20), + TestImageProvider.Blank(10, 20), + }); - public static readonly TheoryData FileData = new() - { - TestImageProvider.File(TestImages.Bmp.Car), - TestImageProvider.File(TestImages.Bmp.F) - }; + public static readonly TheoryData FileData = new( + new object[] + { + TestImageProvider.File(TestImages.Bmp.Car), + TestImageProvider.File(TestImages.Bmp.F), + }); public static string[] AllBmpFiles = [TestImages.Bmp.F, TestImages.Bmp.Bit8]; diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 46fb7159e..219a77f4e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -5,7 +5,6 @@ using System.Numerics; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests; @@ -31,7 +30,7 @@ public class TestUtilityExtensionsTests Vector4 v = new(i, j, 0, 1); v /= 10; - pixels[i, j] = TPixel.FromVector4(v); + pixels[i, j] = TPixel.FromUnassociatedVector4(v); } } @@ -119,7 +118,7 @@ public class TestUtilityExtensionsTests [Fact] public void ToTypes_All() { - KeyValuePair[] expanded = PixelTypes.All.ExpandAllTypes().ToArray(); + KeyValuePair[] expanded = [.. PixelTypes.All.ExpandAllTypes()]; Assert.True(expanded.Length >= TestUtils.GetAllPixelTypes().Length - 2); AssertContainsPixelType(PixelTypes.Rgba32, expanded); diff --git a/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalDiscovererException.cs b/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalDiscovererException.cs new file mode 100644 index 000000000..930708cd8 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalDiscovererException.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.DotNet.XUnitExtensions; + +internal class ConditionalDiscovererException : Exception +{ + public ConditionalDiscovererException(string message) + : base(message) + { + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalFact.cs b/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalFact.cs new file mode 100644 index 000000000..2802f75d9 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalFact.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.DotNet.XUnitExtensions; +using XUnit; + +namespace Xunit; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +public sealed class ConditionalFactAttribute : FactAttribute +{ + [DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] + public Type CalleeType { get; private set; } + + public string[] ConditionMemberNames { get; private set; } + + public ConditionalFactAttribute( + [DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] + Type calleeType, + params string[] conditionMemberNames) + { + this.CalleeType = calleeType; + this.ConditionMemberNames = conditionMemberNames; + string skipReason = ConditionalTestDiscoverer.EvaluateSkipConditions(calleeType, conditionMemberNames); + if (skipReason != null) + { + this.Skip = skipReason; + } + } + + [Obsolete( + "Use the overload that takes a Type parameter: ConditionalFact(typeof(MyClass), nameof(MyCondition)).")] + public ConditionalFactAttribute(params string[] conditionMemberNames) => this.ConditionMemberNames = conditionMemberNames; +} diff --git a/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalTestDiscoverer.cs b/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalTestDiscoverer.cs new file mode 100644 index 000000000..3208b5a40 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalTestDiscoverer.cs @@ -0,0 +1,154 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using Xunit.Sdk; + +namespace Microsoft.DotNet.XUnitExtensions; + +// Internal helper class for code common to conditional test discovery through +// [ConditionalFact] and [ConditionalTheory] +internal static class ConditionalTestDiscoverer +{ + /// + /// Evaluates skip conditions given an explicit callee type and condition member names. + /// Used by attribute constructors in xunit v3 where discoverers are not needed. + /// + internal static string EvaluateSkipConditions([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type calleeType, string[] conditionMemberNames) + { + if ( + calleeType == null + || conditionMemberNames == null + || conditionMemberNames.Length == 0) + { + return null; + } + + List falseConditions = new(conditionMemberNames.Length); + foreach (string entry in conditionMemberNames) + { + if (string.IsNullOrWhiteSpace(entry)) + { + continue; + } + + Func conditionFunc = LookupConditionalMember(calleeType, entry); + if (conditionFunc == null) + { + throw new ConditionalDiscovererException( + GetFailedLookupString(entry, calleeType)); + } + + try + { + if (!conditionFunc()) + { + falseConditions.Add(entry); + } + } + catch (Exception exc) + { + falseConditions.Add($"{entry} ({exc.GetType().Name})"); + } + } + + return falseConditions.Count > 0 + ? $"Condition(s) not met: \"{string.Join("\", \"", falseConditions)}\"" + : null; + } + + internal static string GetFailedLookupString(string name, Type type) => + $"An appropriate member '{name}' could not be found. " + + $"The conditional method needs to be a static method, property, or field on the type {type} or any ancestor, " + + "of any visibility, accepting zero arguments, and having a return type of Boolean."; + + internal static Func LookupConditionalMember(Type t, string name) + { + if (t == null || name == null) + { + return null; + } + + TypeInfo ti = t.GetTypeInfo(); + + MethodInfo mi = ti.GetDeclaredMethod(name); + if ( + mi != null + && mi.IsStatic + && mi.GetParameters().Length == 0 + && mi.ReturnType == typeof(bool)) + { + return () => (bool)mi.Invoke(null, null); + } + + PropertyInfo pi = ti.GetDeclaredProperty(name); + if ( + pi != null + && pi.PropertyType == typeof(bool) + && pi.GetMethod != null + && pi.GetMethod.IsStatic + && pi.GetMethod.GetParameters().Length == 0) + { + return () => (bool)pi.GetValue(null); + } + + FieldInfo fi = ti.GetDeclaredField(name); + if (fi != null && fi.FieldType == typeof(bool) && fi.IsStatic) + { + return () => (bool)fi.GetValue(null); + } + + return LookupConditionalMember(ti.BaseType, name); + } + + internal static bool CheckInputToSkipExecution( + object[] conditionArguments, + ref Type calleeType, + ref string[] conditionMemberNames, + object testMethod = null) + { + // A null or empty list of conditionArguments is treated as "no conditions". + // and the test cases will be executed. + // Example: [ConditionalClass()] + if (conditionArguments == null || conditionArguments.Length == 0) + { + return true; + } + + calleeType = conditionArguments[0] as Type; + if (calleeType != null) + { + if (conditionArguments.Length < 2) + { + // [ConditionalFact(typeof(x))] no provided methods. + return true; + } + + // [ConditionalFact(typeof(x), "MethodName")] + conditionMemberNames = conditionArguments[1] as string[]; + } + else + { + // For [ConditionalClass], unable to get the Type info. All test cases will be executed. + if (testMethod == null) + { + return true; + } + + // [ConditionalFact("MethodName")] + conditionMemberNames = conditionArguments[0] as string[]; + } + + // [ConditionalFact((string[]) null)] + if (conditionMemberNames == null || conditionMemberNames.Count() == 0) + { + return true; + } + + return false; + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalTheory.cs b/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalTheory.cs new file mode 100644 index 000000000..c203b481e --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/XUnit/ConditionalTheory.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.DotNet.XUnitExtensions; + +namespace XUnit; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +public sealed class ConditionalTheoryAttribute: TheoryAttribute +{ + [DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] + public Type CalleeType { get; private set; } + + public string[] ConditionMemberNames { get; private set; } + + public ConditionalTheoryAttribute([DynamicallyAccessedMembers(StaticReflectionConstants.ConditionalMemberKinds)] Type calleeType, params string[] conditionMemberNames) + { + this.CalleeType = calleeType; + this.ConditionMemberNames = conditionMemberNames; + + string skipReason = ConditionalTestDiscoverer.EvaluateSkipConditions(calleeType, conditionMemberNames); + + if (skipReason != null) + { + this.Skip = skipReason; + } + } + + [Obsolete("Use the overload that takes a Type parameter: ConditionalTheory(typeof(MyClass), nameof(MyCondition)).")] + public ConditionalTheoryAttribute(params string[] conditionMemberNames) => this.ConditionMemberNames = conditionMemberNames; +} + diff --git a/tests/ImageSharp.Tests/TestUtilities/XUnit/StaticReflectionConstants.cs b/tests/ImageSharp.Tests/TestUtilities/XUnit/StaticReflectionConstants.cs new file mode 100644 index 000000000..8a641437f --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/XUnit/StaticReflectionConstants.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; + +namespace XUnit; + +internal static class StaticReflectionConstants +{ + // ConditionalTestDiscoverer looks at all fields/methods/properties recursively. + public const DynamicallyAccessedMemberTypes ConditionalMemberKinds = + DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.PublicProperties; +} diff --git a/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs b/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs index eca652900..07e05a759 100644 --- a/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs +++ b/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using System.Reflection; -using Xunit.Sdk; +using Xunit.v3; namespace SixLabors.ImageSharp.Tests; @@ -19,10 +19,10 @@ public class ValidateDisposedMemoryAllocationsAttribute : BeforeAfterTestAttribu public ValidateDisposedMemoryAllocationsAttribute(int expected) => this.expected = expected; - public override void Before(MethodInfo methodUnderTest) + public override void Before(MethodInfo methodUnderTest, IXunitTest test) => MemoryAllocatorValidator.MonitorAllocations(); - public override void After(MethodInfo methodUnderTest) + public override void After(MethodInfo methodUnderTest, IXunitTest test) { MemoryAllocatorValidator.ValidateAllocations(this.expected); MemoryAllocatorValidator.StopMonitoringAllocations(); diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Issue3000_Rgba32_issue_3000_p-3-3.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Issue3000_Rgba32_issue_3000_p-3-3.png new file mode 100644 index 000000000..385ac042c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Issue3000_Rgba32_issue_3000_p-3-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d75205909d532dc98da52389c804ff99cb3b796b5657afb521659fe221c2b8f0 +size 122 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Issue3000_Rgba32_issue_3000_p-4-4.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Issue3000_Rgba32_issue_3000_p-4-4.png new file mode 100644 index 000000000..ef4aa03b9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Issue3000_Rgba32_issue_3000_p-4-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a75beaec77378de4abb09317afa56b8e99ecba0d1c8571cad31aa790afb1a687 +size 123 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png index 53ac0ff89..7fad1fcba 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bbe1ffaf7b801fd92724438cc810fd0c5506e0a907b970c4f0bf5bec3627ca2a -size 551 +oid sha256:60b050406fda4ff347660e71cb28a9dfceb4b39532f62ee96cb61d2671d3cf00 +size 340 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48__original.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48__original.png new file mode 100644 index 000000000..0d610ae03 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle1_Rgba32_TestPattern96x48__original.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74bf3b8655c7548f28c25b1e467992f691dc429f4b06e85cfd04a3b541825811 +size 478 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png index 2480164d6..b22cbb019 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_FromSourceRectangle2_Rgba32_TestPattern96x48.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b45933471a1af1b6d4112240e1bc6b6187065a872043ddbf917200ce9e8cc84b -size 371 +oid sha256:fbfb3143d96070c58c949e8d1e8d9ddbcf1e7863514489ea2defc65863c84e73 +size 276 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png index da8e446c0..df64eea18 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(1,2)_T(0,0).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b66a5f9d8a7f3f2a78b868bec6c7d1deea927b82d81aa6d1677e0461a3920dc9 -size 3800 +oid sha256:120b661bef4adac64d362d8c987b3427cd8140ccac7404d09a16765ba1199434 +size 5191 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png index 4c45ba8c6..b1e8764c1 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(0)_S(2,1)_T(0,0).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d5fdc46ee866e088e0ec3221145a3d2d954a0bcb6d25cbb4d538978272f34949 -size 4871 +oid sha256:0d668ebe5f8857fd21d7eb9ae86860751a6f3061f6c9f76705ff49216dc07870 +size 6215 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png index 480c07da4..6067f0ccc 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(-20,-10).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ae57ca0658b1ffa7aca9031f4ec065ab5a9813fb8a9c5acd221526df6a4f729 -size 9747 +oid sha256:2fb676b3af585e7cbe2efdb893157d5f4e152cf810d0693cafb81596e941e121 +size 9697 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png index d1ea99cf9..69f862a5a 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(0,0).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fced9def2b41cbbf215a49ea6ef6baf4c3c041fd180671eb209db5c6e7177e5 -size 10470 +oid sha256:afe7ddbff155b918a4eff91af31e01100355c146cb9c8a12ab2496da8b22821d +size 10446 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png index 227f54651..5b88ba508 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1,1)_T(20,10).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e4cc16c2f1b439f8780dead04db01fed95f8e20b68270ae8e7a988af999e3db -size 10561 +oid sha256:ad76301984e5b54eae374adfe130693667053fbed181847b4c68688fb74c9818 +size 10518 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png index b93742a85..7f08d0dfd 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.1,1.3)_T(30,-20).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06e3966550f1c3ae72796e5522f7829cf1f86daca469c479acf49e6fae72e3d0 -size 13227 +oid sha256:fbd57af1fa982f9090f57d820a9b927f894914e5f54774e9cd6fdcfe14e5f761 +size 13139 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png index 57c3b02ba..2d4ad649f 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScaleTranslate_Rgba32_TestPattern100x50_R(50)_S(1.5,1.5)_T(0,0).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ce5fefe04cc2a036fddcfcf038901a7a09b4ea5d0621a1e0d3abc8430953ae3 -size 20778 +oid sha256:c4bbc28c203550baf885cefba95c48a3f91dfb5242c09acbf3a8509b7258048e +size 20768 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png index b3bfc7ee5..08182dcfb 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_RotateScale_ManuallyCentered_Rgba32_TestPattern96x96_R(50)_S(0.8).png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b653c0fe761d351cb15b09f35da578a954d103dea7507e2c1d7c4ebf3bdac49a -size 10943 +oid sha256:566e85b1a527f48c953bcc7bc6c58ebd1fe0b14972c38edd596b025e0dd48624 +size 10940 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png index a295c016d..392d3462f 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a17bb1653cc6d6ecc292ce0670c651bfea032f61c6a0e84636205bde53a86f8 -size 13536 +oid sha256:aa5b0d5de93f26c0a7a03b57a00d4a49cda62f4a4b98b6d374261467c03a8357 +size 13500 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png index 63214687d..56f2a3127 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8970378312c0d479d618e4d5b8da54175c127db517fbe54f9057188d02cc735 -size 4165 +oid sha256:62267d8d56af3e1452f0e25144f2cfe352b88def98af28e819a3a6982040a4ca +size 4102 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png index a295c016d..5919cce39 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a17bb1653cc6d6ecc292ce0670c651bfea032f61c6a0e84636205bde53a86f8 -size 13536 +oid sha256:878d5c53b84af4d133825206a327fd4cd02a43831ecabf5c61c5d89181c5a107 +size 13499 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png index ef37b3e2d..fa01f13cc 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9bbf7ef00f98b410f309b3bf70ce87d3c6455666a26e89cd004744145a10408a -size 12559 +oid sha256:f0aa3c19852632e603ec425aeecc5243d4c6c24a1ac6e3906d29913bf7ead2df +size 12535 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png index 93a0ce6c5..98ad27d78 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f9ab86abad276d58bb029bd8e2c2aaffac5618322788cb3619577c7643e10d2 -size 14223 +oid sha256:ec648c2e8006d478ace4a78d2434a4ef7f10d4a3502468cd8b9e2b1f154620b6 +size 14278 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png index c2ca6bf57..feb217ee9 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05c4dc9af1fef422fd5ada2fa1459c26253e0fb5e5a13226fa2e7445ece32272 -size 17927 +oid sha256:6cb06152d5a0765ad86e8005d6ddac469914ccced89d5ee37d77e7d030b97c9e +size 17281 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png index ade9a1ccd..fd1cf7e77 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:82b47e1cad2eea417b99a2e4b68a5ba1a6cd6703f360e8402f3dca8b92373ecc -size 18945 +oid sha256:38ea8596a682be0075bb31ed176b1fe04b518eb887235d551a574e338d45880b +size 18869 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png index cf04e2036..619483421 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b15ce5a201ee6b946de485a58d3d8e779b6841457e096b2bd7a92968a122f9af -size 20844 +oid sha256:965f42f021c63a0f2ccc691723c4ad7f92119294aec407c7ffd46a6238c8f268 +size 20792 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png index 6be0fc0ff..7201a5f15 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1622a48b3f4790d66b229ed29acd18504cedf68d0a548832665c28d47ea663b -size 13857 +oid sha256:86f1b9e8f1e38070ce862d87c927313904ceaa9e6080f5acead90e82d164738c +size 13879 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png index 0064e973f..77c61443b 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74df7b82e2148cfc8dae7e05c96009c0d70c09bf39cdc5ef9d727063d2a8cb3f -size 4154 +oid sha256:270f9c2bf5d15fcb21796b3b9eb393e0cc00d9c337036637295ad1efb56781b1 +size 4114 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png index 5dd0c5225..22dc949c3 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc740ccd76910e384ad84a780591652ac7ee0ea30abf7fd7f5b146f8ff380f07 -size 13991 +oid sha256:d413162a83c223124a2f29f8154e4bdc08d94bd3e15569ec6cffaa13bdda72c8 +size 13953 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png index a6e120e90..3d86b73ab 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccdc54e814604d4d339f6083091abf852aae65052ceb731af998208faddb5b0b -size 13744 +oid sha256:941ea7b4d1f2c187f58920546e2f19fc275505929439cc389edcc59e652e8787 +size 13777 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png index d32c11d44..060fdc809 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd24e0a52c7743ab7d3ed255e3757c2d5495b3f56198556a157df589b1fb67ca -size 14889 +oid sha256:cc5e6a607ef2343cb74c5227dbc7861840db956951f1fc4703fe53dbccda0974 +size 14808 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png index 72782b0b9..524072160 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:878f1aab39b0b2405498c24146b8f81248b37b974e5ea7882e96174a034b645f -size 12374 +oid sha256:2ac06a9ba2b2c8bef7e0117ac52fbb790101c0f89313dc49feb1f5a1d929ab02 +size 12381 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png index 6cedab729..669aeba9a 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dcc2bf4f7e0ab3d56ee71ac1e1855dababeb2e4ec167fd5dc264efdc9e727328 -size 17027 +oid sha256:ad0f483fa7fda620860858c4f330ba914480fba15d70b408fb1aa3fed52dbfc1 +size 16839 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png index 7368a3b00..438450e53 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_0.0001.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c733878f4c0cc6075a01fbe7cb471f8b3e91c2c5eaf89309ea3c073d9cc4921 -size 854 +oid sha256:ba501a7fc32a68f8989965aa6457b3860ec42947e2bcd4526c7570ff743f38fc +size 841 diff --git a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png index da66b2676..f7f2101d7 100644 --- a/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png +++ b/tests/Images/External/ReferenceOutput/AffineTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af872886136893938aee82b1ac73e7a1820666a9a5f4bbf34159c09b3283169a -size 5520 +oid sha256:8265c5b2e8edd5eaf0aeeccf86cac486e7beec581e696d3b4f4cfee8f4be9b2b +size 5554 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_issue735.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_issue735.png new file mode 100644 index 000000000..48281cebc --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_issue735.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15de93db72e2de0ad1a21b67931df57c1a129f062fe1ed3cb7f083761af4fe36 +size 61931 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565.png new file mode 100644 index 000000000..2e44b7ec5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a568f9f12adc5606e5b74d25a4bf083e2b565d73635704e205400d5a073fcd3 +size 9895 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565pal.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565pal.png new file mode 100644 index 000000000..2e44b7ec5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16-565pal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a568f9f12adc5606e5b74d25a4bf083e2b565d73635704e205400d5a073fcd3 +size 9895 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16bfdef.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16bfdef.png new file mode 100644 index 000000000..e91c987b0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb16bfdef.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c05e0b14a389d11f531d4238900106a81f25a741a55bbbcd160b8b67eb32adb6 +size 7019 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bf.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bf.png new file mode 100644 index 000000000..71e2079d0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bf.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d577ac117b8260ad08e6f051c949d9187f04699eed0f063476aaf42eada2366 +size 20640 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bfdef.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bfdef.png new file mode 100644 index 000000000..71e2079d0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeBitfields_Rgba32_rgb32bfdef.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d577ac117b8260ad08e6f051c949d9187f04699eed0f063476aaf42eada2366 +size 20640 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_16Bit_Inverted_Rgba32_test16-inverted.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_16Bit_Inverted_Rgba32_test16-inverted.png new file mode 100644 index 000000000..7cf41fb5b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_16Bit_Inverted_Rgba32_test16-inverted.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53a33da2c1a84c0e8426e1a2ba24e2fe2de17a7bc75760184fb10bf6dc96954a +size 7896 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rlecut.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rlecut.png new file mode 100644 index 000000000..db7f30b41 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rlecut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0c97446dec6af009423dcae431c44d23963179c3c3bfbeaf3f3b52022a33f68 +size 2414 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rletrns.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rletrns.png new file mode 100644 index 000000000..c2959addd --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_pal4rletrns.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a651b81764696cd4fe42113d5ac26006f54820d2df8b2103793e82c66597b2b0 +size 2837 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_rle4-delta-320x240.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_rle4-delta-320x240.png new file mode 100644 index 000000000..0e52f5332 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta_Rgba32_rle4-delta-320x240.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55672d405a3d085c72cfc9fb26bc3955bf858e525c54c6c4505f093f011f378d +size 3188 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_Inverted_Rgba32_RunLengthEncoded-inverted.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_Inverted_Rgba32_RunLengthEncoded-inverted.png new file mode 100644 index 000000000..1c8828256 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_Inverted_Rgba32_RunLengthEncoded-inverted.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e774cba4dda2fe9d3cdff141e7a8c1de7f3e9c8014093abf8697a34e6cc7144 +size 5379 diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDeltaAndEOL_MagickRefDecoder_Rgba32_pal8rlecut.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDeltaAndEOL_MagickRefDecoder_Rgba32_pal8rlecut.png new file mode 100644 index 000000000..86efdd207 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDeltaAndEOL_MagickRefDecoder_Rgba32_pal8rlecut.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44d281c31cd264dcb812df8cb5dc9d5042e915d64e8013af5577dbfea6cbb1cf +size 3955 diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp new file mode 100644 index 000000000..7f79c6f3a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithHexadecatreeQuantizer_rgb32.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fff91e78a87c2b2db661609dd70af2d7a74e6a0de18924ab2b6272e12c60977d +size 9270 diff --git a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp b/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp deleted file mode 100644 index f4ae3b9b6..000000000 --- a/tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a98b1ec707af066f77fad7d1a64b858d460986beb6d27682717dd5e221310fd4 -size 9270 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png index 564a76f90..3b4c12a24 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b3a6e47d880b7276702e6bf4b1ba1b4781abfa2cd99ee9321a56169440fc318c -size 49844 +oid sha256:0c047a274060b1bf73af9c922bff4f2ea627bd0fad023b3c3a5852f49d026c6b +size 50025 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png index 4f2dc77b0..7dad93fba 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18b4837dceb9a065c5b27133b67ef257a0f050fa1342d02a7e06d3501e068f47 -size 44966 +oid sha256:aa1b0b58ddb6a5c29ccd78f1bc2e773a2005493b92efe5c0deb8056af6fc0208 +size 45605 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png index 57ce8295e..a9b287c6b 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_BikeGrayscale_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e652706e1aec475c8eec08392d65be1bd888cc79eccc4019eb3826db71c6dac0 -size 54744 +oid sha256:ead63aaaf5b185342f83317c2400b7ada29a82223e35e9a7382f690101ae40f7 +size 54798 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png index a0e9c1b24..a4ac9718a 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffc82427285a2bfeb3226e3e87ac316e41a2a8f853bb406b88ba2ae7bfae099d -size 164923 +oid sha256:025751d22eeeac10878e79d2bf1987e0a055cf9f54903048673189a385036744 +size 157237 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png index caf152f9b..2c18a147d 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ea08065be7271e7ff75ed49e63544701bc14a03bd44d754fb056370559dd105 -size 149483 +oid sha256:77cae565842c0c8b0314d1c10c72f4b52483d002c614a33734b534f743d06b5d +size 146574 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png index 5ad60a4bf..0e31e223b 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Bike_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a61bca9de7e0b6bf6f88c4d01b9e3f1611f6866db10e4a1168550ab84804f42 -size 178531 +oid sha256:223027dcd3a8e5ded5f98b3aa47b0e18bc40b569e47f6bfdab1126f4420e5977 +size 171283 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png index d1b15eb8e..be4b535c7 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4e2f6c6c42ddd15c0730edcd402bf8fd05a1116cb72495e3156a545988ff0230 -size 85901 +oid sha256:db64cd4e0369fa2db33aa1f21a94466ca39a191b0e0adbbbfbcbca44284c61ab +size 86151 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png index 3acb240b3..5ddb7ce12 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8193ce8690dbb99517cd56e5a866268c0a5c971c992058afb818393658781b91 -size 77762 +oid sha256:afc3a020178a68715ab0dafa30ee1db81630b760fde3df3271048acc627f178c +size 77614 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png index 335375205..db03e0fb4 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_CalliphoraPartial_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:412c302f92500d855658d466aa1a686386ed998f6a3d5fe7326cd148a6f829ae -size 116229 +oid sha256:5ea7e0c3e16acf40491354191278a09af2db5ac6e0e7b92ba377f8fc9d52e305 +size 117067 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png index ffa9624d0..4d9777bd8 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d753a7dc732d6f01f7bce8c6de50d70158639aa5e0eb18d168e417fe36492731 -size 135 +oid sha256:ba9b046556b04556632f4b651dc9d8ebc4d27699e53567179ef15a850567fee9 +size 85 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png index ffa9624d0..4d9777bd8 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d753a7dc732d6f01f7bce8c6de50d70158639aa5e0eb18d168e417fe36492731 -size 135 +oid sha256:ba9b046556b04556632f4b651dc9d8ebc4d27699e53567179ef15a850567fee9 +size 85 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png index ffa9624d0..4d9777bd8 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_Solid50x50_(255,0,0,255)_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d753a7dc732d6f01f7bce8c6de50d70158639aa5e0eb18d168e417fe36492731 -size 135 +oid sha256:ba9b046556b04556632f4b651dc9d8ebc4d27699e53567179ef15a850567fee9 +size 85 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png index e155d618d..9edb930aa 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:570cb16b4026d38cbf586592c34d0fe303f2c1d99baeb531670c4ba25956f9c3 -size 15489 +oid sha256:20bb78a7539049ced92c936c2a78fdeb3809ffe0cb1dfae034e55e326de094a8 +size 14285 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png index 3f93014b6..ce093b38a 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27813f975640c7ac15e78af910efc796ada950cd7efcd9a4fe045d531de24ec8 -size 15197 +oid sha256:a55e413991f9817ce9cecaf9d3ed31f5cd7559f9d527ff2e03c8bea5be091887 +size 13943 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png index 198baadf7..a8d549dc6 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern200x100_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e10a91175585b3843b25b710dc45f734ba706b60fc56a21748da355f27b8dcf5 -size 12776 +oid sha256:2afdab1f7f430c3f38fa7a6cdac60fbeb03ebcdda00a1bc79b289680a6811388 +size 12056 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png index 9140cdb0b..5104bad4b 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c0aca87ea94ec94c4d9e458dd33d366e0166533ee3e3c39066ee9d8be63a74e -size 1393 +oid sha256:028cd8201f9e7cd4988d63cc80d9afc96d0c739c65d807f7c755947e49e65111 +size 1326 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png index 32778c0a7..f96d77928 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d486512f51a57c90de6c3126791fbd3b7e7dd756932b5bd716660ee9b72f010 -size 1168 +oid sha256:213e3bd008292b807e2a2ec216ec017a31cd165f4d2a8bc86b3deea3daffc81c +size 1109 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png index a60c7f0cb..6656f7564 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern23x31_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79f344abd9f1cfe6e8c2f0be52fd9498ee3ee6370cea94244d90fdaba7fa5e71 -size 1488 +oid sha256:2ceeb731111403f6354c460e403a30a2269ae1b2e2a9ef9b3819bdcadb50b859 +size 1424 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png index ae9b64afa..da9a0c2e3 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90a45e7212ce97754be0e1432fffdc62ef5d2bdc30fdff0477a1951754cc0929 -size 1113 +oid sha256:fa9d77a2f1b727a42323a8ac19cb4cabdef0e7f0257bc92bb0100cd280f87613 +size 1076 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png index 1e9dc61e6..e0ccdbb5d 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c61766f05dc48413c21baea8af1994895aa354732109fd5701a42f746f1f412 -size 1005 +oid sha256:fc9986cd6042d8cad709a5768dc464fc171a3cb5d3457406c11c46ef55388daf +size 956 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png index 99a249b1d..4f1d40e2c 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_TestPattern30x20_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be002593c2874eab803d0dc9b5646fb51b94ea92e17158797609219f46c2f723 -size 1518 +oid sha256:7e92efee845abd30705bb9a9cf61997092fca89b48e20d8383a0c08ab159a0af +size 1491 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png index d4ff4e16b..404dc83b1 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C1_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11edba36c7f73271d9575b16cc7663a1e75c3b34560df51b7430cfde3ce1ea08 -size 6576 +oid sha256:21e8dce4256aecb250e0079c8bd60deb8b2ca55848fb0fd647ed3115afcf82af +size 8401 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png index a02c2d670..525eedcd8 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R16_C2_G3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:75320c25e8977e5d56ff32f054e15773bd797e4ba745cd7bf3a2fd4c7000e3df -size 6400 +oid sha256:c4b1b7f904b23b6d7bd7b0fc4c533d3c213f38bcab689e9dac1936b05b71e9cf +size 8268 diff --git a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png index 5fbabed0a..2585ff479 100644 --- a/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png +++ b/tests/Images/External/ReferenceOutput/BokehBlurTest/BokehBlurFilterProcessor_cross_R8_C1_G1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bb334d24245359fda8b64a74cbd30267cbdd1238e0aeae674b57be5371cd7db -size 6266 +oid sha256:33ca697815ebf4c74343e0abc7a418af8ffdd8fa0830285770249a1f20929eae +size 8444 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/00.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/00.png new file mode 100644 index 000000000..9e3e48a52 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:003d5986b66f90e0841c3fdfa8c595563c8e237467b8218c6fd7fa283ba28b1d +size 21154 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/01.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/01.png new file mode 100644 index 000000000..7d8babf99 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1d148bd561f33c2435226c533dea539e1b21567d8f985a4059d501846e0bf30 +size 21761 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/02.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/02.png new file mode 100644 index 000000000..108ccd1f5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38519ad5d1d50548fada577d2bd4a8be7f76d1c3071f07bb98f1227c4bc5d303 +size 20522 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/03.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/03.png new file mode 100644 index 000000000..28c79f8c2 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e922f83fd719ba961b8720417befa119000f2c8f3956d3ac8df60c79ff56fe59 +size 21291 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/04.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/04.png new file mode 100644 index 000000000..401939430 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawImageAnimatedForegroundRepeatCount_Rgba32_giphy.gif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b7b904032ff6c142632e8c1efffe3d71c28a89d5824d9fe13dbf4ceeddcf5e8 +size 21367 diff --git a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png index 7e693a583..3692f1a1e 100644 --- a/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png +++ b/tests/Images/External/ReferenceOutput/Drawing/DrawImageTests/DrawTransformed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ba180567e820b145a13c9b26db9c777e95126adfe8e8cacec0ffe1060dcfe8d -size 184124 +oid sha256:7f8a4db4facce1d68b363a3b59ea40c9da9fa3c989c736d97a703c84d8230660 +size 184595 diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png new file mode 100644 index 000000000..8f31f0d8d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Pxr24Compressed_ExrPixelType_Uint_Rgba32_Calliphora_uint_pxr24.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f66bf45b5d80e412314341567b8cf240d56b5872f01ce9f3b0d6034d85e8e942 +size 163327 diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_Calliphora_uint32_uncompressed.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_Calliphora_uint32_uncompressed.png new file mode 100644 index 000000000..8f31f0d8d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgb_ExrPixelType_Uint_Rgba32_Calliphora_uint32_uncompressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f66bf45b5d80e412314341567b8cf240d56b5872f01ce9f3b0d6034d85e8e942 +size 163327 diff --git a/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_uncompressed.png b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_uncompressed.png new file mode 100644 index 000000000..96482b46e --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ExrDecoderTests/ExrDecoder_CanDecode_Uncompressed_Rgba_ExrPixelType_Uint_Rgba32_rgba_uint_uncompressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46ef9cd2f212651a5ddc78aa1707df87c18ce227466b7b6f00d215f11271e61a +size 24590 diff --git a/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png b/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png index 4f57775dc..aa561ace8 100644 --- a/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png +++ b/tests/Images/External/ReferenceOutput/Filters/BlackWhiteTest/ApplyBlackWhiteFilter_Rgba32_TestPattern48x48.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65d3a099dd24fcee2fc86c63b8664ebd81d7b3c530168da5f4e50ee0ca925496 -size 759 +oid sha256:d76ae5ac1a0aebb9d76b2fa5c6b1fc109e5fcbdc6c41d9681a76ade9c86fab79 +size 748 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png index 193cde24d..f9d43792c 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18b60d2066cb53d41988da37b8c521ddcb5355b995320a8413b95522a0492140 -size 687 +oid sha256:07b63781e5481a46955fc26e9023b243aeada231c4957332c80241e1ad119733 +size 273 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png index 4d2d25510..da7411347 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30ff7708250c5f02dc02d74238d398b319d8fc6c071178f32f82a17e3b637afd -size 542 +oid sha256:6bd0b25eafd2fb3f55f593a5243fa1e3b6a7ec43a70b8d0c3a6eddd56fe65ae6 +size 114 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png index 0654e49d4..5d2c89228 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/02.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d21f4576486692122b6ee719d75883849f65ddb07f632ea1c62b42651c289688 -size 591 +oid sha256:e2f7bb0aed90e52d7905014d790f0bcb5df3f05e5cb82b51dda88ac13dc5afcf +size 115 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png index 0c1090f66..a9db965de 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_firstframerestoreprev_loop.gif/03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88db68f2d59301b8ff9326143455a03c94cb616220f6e8e3832f13effe0c09bc -size 545 +oid sha256:b10d33fd285b8a200090bccc35541a608ed062edf1a055357c895935265d216b +size 116 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png index 193cde24d..f9d43792c 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18b60d2066cb53d41988da37b8c521ddcb5355b995320a8413b95522a0492140 -size 687 +oid sha256:07b63781e5481a46955fc26e9023b243aeada231c4957332c80241e1ad119733 +size 273 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png index f289fdca3..0d6140760 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:034b0b6b94c13fbef8c44d650daa07362f113aae6600d63230a3f96e29b16dec -size 790 +oid sha256:9296af767fc47ee67249de4f473633f308a323e9e82676dc00952e05cc23f761 +size 341 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png index 07537b9df..e4bfadab5 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/02.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4b3684db6e3df52a9eb520d562b51b54632e897e9e39bff5ce904ae00799f2f -size 924 +oid sha256:b2926b6f27314950ff21f1357636f933694f8424e391a10d980a1492ef7b3f07 +size 421 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png index e376be689..a2df6ead1 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_norestore_loop.gif/03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e33c564f55b653a693105612949401002014821abaecaf654c96d0f2b5d59b4 -size 962 +oid sha256:350624a4f22cbc47b07e5c8ffe0ea2e0f03687d53b77896c7701b04d7c93089d +size 431 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png index 193cde24d..f9d43792c 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18b60d2066cb53d41988da37b8c521ddcb5355b995320a8413b95522a0492140 -size 687 +oid sha256:07b63781e5481a46955fc26e9023b243aeada231c4957332c80241e1ad119733 +size 273 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png index f289fdca3..0d6140760 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:034b0b6b94c13fbef8c44d650daa07362f113aae6600d63230a3f96e29b16dec -size 790 +oid sha256:9296af767fc47ee67249de4f473633f308a323e9e82676dc00952e05cc23f761 +size 341 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png index 27f29acbb..2153fa5cf 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/02.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4f4eb12da36cd43c620aa5ad1c793bb6eb8431c61d2cc1b77c1118f35a741cc -size 876 +oid sha256:726aff614b67ea2ee9ffd53fff8e304130019de99d3ae641bef97e0a24f756be +size 343 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png index 684c2fa38..369f668fc 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_frame_restoreprev_loop.gif/03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15bf02e06c6819d74a0a79cbfc5c86913c248a8812ff0ec613c0e747a000241b -size 789 +oid sha256:a645f615d592a1c24b94ad3a4fc63503cb8b0504c9464ee5089d9831b23c28e1 +size 336 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png index 193cde24d..f9d43792c 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18b60d2066cb53d41988da37b8c521ddcb5355b995320a8413b95522a0492140 -size 687 +oid sha256:07b63781e5481a46955fc26e9023b243aeada231c4957332c80241e1ad119733 +size 273 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png index 7818cf380..4fa343103 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:01f389a2e93023f3132927a9565c04c8c1f827e36111ebe682177adecc3a27ee -size 774 +oid sha256:1e90013c0ea6e60ef68143914fdf4b14f83e869cf90aff42d837b1340d867f77 +size 276 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png index 18bc40863..cdd623a6a 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/02.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:00ef57db2fef89112ac7d1808afb6803612c6a20fc589166be6d6b7007c46400 -size 946 +oid sha256:69c0e3fb7365e09f6acd26e334f3cb65ba8657b2de7b1022256f5aac91f257ac +size 296 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png index bd2ea67ae..36f963ccc 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_Animated_WithTransparency_Rgba32_animated_transparent_loop.gif/03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:deafa4d4f8fd84489c061ca1042c2ad9e655fff3b6419248cfb35fa4ea40d9e6 -size 1000 +oid sha256:2be415d41972782f91ad513428796588b0040c4125d604f72d1288c5b3e3742f +size 282 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/00.png index c8b4db92c..06a7b52d6 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/00.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2cc0d653e6f3e06b1d8828ff5794fd5f81526a9e411137a2d1a78f9d8894100 -size 7168 +oid sha256:f212a6b0f4e2ce7d38a489dfe0e050adf807a33f8305367ce377f45a6d7c4619 +size 15016 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/01.png index afeab25c3..ae776f82f 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/01.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a7f721df04021f246e9df9f6b91c3654e8b40ded575473c05d646f7bc632b958 -size 7558 +oid sha256:8612f3caef51ce4f0daf2d37f3bf959382a63770391f2ec242c2af46eab2b3b5 +size 15748 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/02.png index a03761d56..82ba6c23d 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/02.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/02.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a40b7f8d2779e6fdf26e2720fdf24f8da03e9ef9d8b1ff68e9bb68f001814b79 -size 6956 +oid sha256:717938369a68299af27a2b42d58c97ddd5fcd2d8a93418af8f92b3d1c2e93064 +size 14485 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/03.png index 848c6be81..f83ea81d9 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/03.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fc7fdca7cfec1ae6d119dbccdc7ea78c19584076a197c54e494645b2c84e45e -size 7131 +oid sha256:6a0545794d1384f46acb32bbc4369a226d002eb8ae286e064855d55afd72f4c6 +size 15040 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/04.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/04.png index 11b93c1aa..20ff589c9 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/04.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_giphy.gif/04.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4b59fa394cc6205e00440428e9a141a627b88b5e2562ec6b1dd0d48651da77f -size 7104 +oid sha256:853a4f2749ff5ca67bd7c49c2e2f0323772a828a215fac0fd02e9b7eb9d63051 +size 15291 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/00.png index 9236bef85..7acd64ad9 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/00.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ac324bd9199113897bd986cffe8e7f3770fb2e68e6792d631071ff1db40075f -size 29351 +oid sha256:c41341baee0a461d811cbb8c6cd64d3276d7d4520e95732c5dd12f3834e0a6f3 +size 47309 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/01.png index 91b3c95dc..5822b7e96 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/01.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f9ec5a3228c71b54a2bccd8aaf929e11436d45cb56f43cd507cfd4e7bb288fb7 -size 30538 +oid sha256:7a0d6d652a7fc1bdf84bd5e1a785d97e1da08ba3397c3644973f221f3a75b59e +size 48583 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/02.png index d9c98dace..e4536355c 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/02.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/02.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa0c9f304a6afc62aba3b6567905160f7728f43e3b5ba3dc79bf743c9a5f49ab -size 30754 +oid sha256:2f8ed2197b5a9b9749c572096714251bf23be463b3bbd01329d47296078c5f23 +size 48824 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/03.png index 5573c1519..ca8f63548 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/03.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e61d8611c06c4f82c0000ebac4a1b3a53d1342e23f9efb10406ae06a510580c -size 31260 +oid sha256:6c75f616b6460d832ba7b6a0e0551f07d55c2cb0533e95ce16b9a1b9073f75f9 +size 49783 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/04.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/04.png index a595bb707..87d44ffc6 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/04.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/04.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:168bcccba4dffeaec2f2e3978405f802451089790b6377d8c653cfaed7bae833 -size 31741 +oid sha256:0bb07ef91e0f8a82d457ad2ebfed9ca6940d228a4e9954e75c3c1826d4ac5f53 +size 50381 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/05.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/05.png index 29537a2f5..a7b15ecf9 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/05.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/05.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c6675dcca4418fdc529e4de1bc0014a2463d6c6b197dd701b84044a00b31016 -size 30061 +oid sha256:7d80517e1d79f85a5a725d8678011ec5c05748ccc2eb7567573380b610183ea5 +size 47731 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/06.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/06.png index 380b5aad3..86299abfa 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/06.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/06.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7d01cfb14c66662f8972f48d62a7cbb70c2ffa174d74a5533be61a2565d923a0 -size 31139 +oid sha256:07a406840c321421e6aa07ad69d14d96e2bb728364c8fabb98151c5693efa2fa +size 49847 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/07.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/07.png index 9c815c41b..bfd4828ad 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/07.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/07.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6918449a54dea19a15f3f4ef9f5d0e890f7dc97e651d4c77bd60dfaa4f49d646 -size 31304 +oid sha256:72678c10f8cd852f87c19ec1c2a7acfb326291e1ae0f48588d5d7d999592bb32 +size 49850 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/08.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/08.png index 09521859c..c7c53dadb 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/08.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/08.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8927c2e34c9ab90c1fbc3d4f8be289f4a47fee5cfe9c4d72bc39eb78c94a3bf -size 32500 +oid sha256:7c9a61f0639bda2fd8690544ad41df7179ea29987b8827b4cae2e959998d27cf +size 51612 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/09.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/09.png index e38de41ab..accff0f9d 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/09.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/09.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2ffb21368cf6c3e4d567ca19cc40187bee254ea6ebea83c6d6f9c2df02b56eb -size 32374 +oid sha256:ef326f40bdc7f0a43ad1edb51b000388f4f5ca1b7c24633447c03d999bd088c6 +size 51724 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/10.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/10.png index 4d104e273..71c5577c3 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/10.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/10.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b903f04b431ed0d9474ef89cb48d2b7b7fa8c124b5fb1f2562d826eaa2cd3ba -size 32692 +oid sha256:a4e8f6a96ce47d36c778d7fa243d723bca725438fe9d9174b61afb638c1cd815 +size 52070 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/11.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/11.png index 141d2df42..58daa5067 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/11.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/11.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:898566396d44cb9c495ed6c40c56928fe642434ed4888331bb92b17fb0cd4847 -size 33243 +oid sha256:b1f96f15c1a46db19726b6ce0d5a78800b1a430c4323a60e628e7e08aa795189 +size 52829 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/12.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/12.png index 590132fca..164a07efb 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/12.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/12.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:18a1888eb6d79f857f8336e76e50b5402127036418183e03869e58021b8f9c47 -size 32797 +oid sha256:d98e70649b2a1971f62eb41d53e064f898b45da47f05e000e4dd80464f5ac290 +size 52329 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/13.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/13.png index 9ab3afd9e..1c1de607e 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/13.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/13.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8384afabdc9133435c61c0cec97fe599719f7cc601fc1444b1b182383dfcbd40 -size 31306 +oid sha256:54ae733a92eb63174fa02708aee26d28cf0d1069ae4514ff244ebea333d87af8 +size 49767 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/14.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/14.png index 33bdd55d4..061dda667 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/14.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/14.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ea0c2d692d1a5b645af16726b54cc54a0d0166292cc3b3619a96f062996f13f -size 30782 +oid sha256:6a3bc3c870891b220efd95d6da83a166595564e0610766f105496bc77b090e9c +size 48893 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/15.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/15.png index 1ca1ab28e..2958cfc36 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/15.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/15.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e4bb35f4484cb66bf63d46f4311617dc7e675ead33b8f978f564045491eea5bc -size 31875 +oid sha256:e9afd78075471066aa12aeaf33ddda780571faa14a61b842963c53b688432569 +size 50818 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/16.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/16.png index 9dced1b13..27964b46f 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/16.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/16.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:21aa4d98c2870022104e44de7c63e16d4a8f7ae139c7a996c862b1cbd55bc3d7 -size 32317 +oid sha256:0caefffa407c227ca906cc21e959d3480a0a35a5a1720a00076a8d2c7ca1afb4 +size 51462 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/17.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/17.png index 110ce0967..8664f2ed2 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/17.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/17.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7550515971c3af24efb51b4a81c21e3e3a642c53b654512bc6c6ad67d049748 -size 33265 +oid sha256:27e0c6a2204395b0da83c8fed89cf72168641b9318815db2e1d7a169317739d2 +size 52691 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/18.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/18.png index 68406f480..0c61aa82a 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/18.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/18.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:161d80a458dc2418eea4ab8caa851c0632eadd64538843a2c2a5cdd0a54ff66b -size 32545 +oid sha256:ff6af0ab7aabc30f57b1801045f27fcc327103db8f910dcd031cb68bd3e13df9 +size 51515 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/19.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/19.png index b0c387fb8..66a5f9c15 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/19.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/19.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8342d403ab99e900932b52b55f6c6583aac0567c4215c4e187eccf264f4e2c2c -size 32210 +oid sha256:eff399d98ab583c5c8dd26e78bc8920b43e5ee1c142fb9553080850e0506817d +size 51044 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/20.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/20.png index 7178b780d..6c18956a6 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/20.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/20.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a29b4ca76d42a067b3e4f27521e5a0b05c85668002e92a30d12f1a3784062ec -size 31574 +oid sha256:0bbd6daf4825780010d0da77e05ff505e594acef2588a804a4c98501501c8728 +size 50347 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/21.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/21.png index ffb8ced6e..f4f27b24c 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/21.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/21.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be2601503fadee016c5f88b137091b50c6a7451e577f253f166b212ee937b35a -size 31384 +oid sha256:e48f2f7c177ea47fa096d1fa37ef22c4cc818d43b18cdf64c26ed96c79f00b48 +size 49743 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/22.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/22.png index 1b0034ab5..8706c7cf0 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/22.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/22.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2800403642452bff9858db473777b93030dde29c04b0280665a36aa9b57fcb18 -size 31855 +oid sha256:40cbbf0c4d939b696e01302614aab6affe22775783bf1902a33644fbb6ec5bdc +size 50602 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/23.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/23.png index ddf9fbf7a..ed2a543c8 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/23.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/23.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0225e8395085728e2988ec148e33dc9df009746d5fe0860b12539776337372a -size 32035 +oid sha256:7e7901c37835974abd5163eede0a3049db5e936358089b3de67e99699019582e +size 50810 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/24.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/24.png index 302c97696..4ab5d089f 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/24.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/24.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f348830b798dff6f62a5c279105181efb4e692fe83f3e42cd3cae9b5ca0af7ce -size 32097 +oid sha256:80f0028de9c58117e8a4179ffa890ba0771f7135016fc018a2157bb2cabaa920 +size 50971 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/25.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/25.png index 3c3e74adb..6ac020e21 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/25.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd5fb5b83f44bbbc1b32690f54f995eb1709937de780ca35e31598c399c7d8ef -size 31750 +oid sha256:a01abb68b8be21b7531455dbd94b256cd826a52c170a62f88955e2b7a55376e0 +size 50531 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/26.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/26.png index 1b77c89bc..2119d2420 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/26.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Decode_VerifyAllFrames_Rgba32_kumin.gif/26.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a02cb3c82c90b05381126ff3f696e562200442aca34ba54830878701c51d932 -size 31647 +oid sha256:922fa84cc14f0795c8cb7459b6269dc5789018d1f0591560fba0a3c3e80b00b2 +size 50484 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png index 87c1fb728..2449c3efd 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_Decode_Resize_giphy_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cfd3afda359646aa3d46e1cffbfa7060395fda4c36c419ed3a2d8865284d090d -size 4031 +oid sha256:43e15b35bd282ecbf5f3b0b08b0f6fdba2e9144b0c17875d27bb8366b213c999 +size 4020 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Argb32_trans.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Argb32_trans.png index 1cfc3adb8..08b9fb724 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Argb32_trans.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Argb32_trans.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa4c441acc8fe614c9b375e8e609e735e1c767918ecc1f8f2f028665fdcfcf34 -size 12441 +oid sha256:49eeef6073a5ef2494ecfb138071c22157870e11c903ca81b837b32a813732ac +size 20256 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Rgba32_trans.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Rgba32_trans.png index 1cfc3adb8..08b9fb724 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Rgba32_trans.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_Rgba32_trans.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa4c441acc8fe614c9b375e8e609e735e1c767918ecc1f8f2f028665fdcfcf34 -size 12441 +oid sha256:49eeef6073a5ef2494ecfb138071c22157870e11c903ca81b837b32a813732ac +size 20256 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_RgbaVector_trans.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_RgbaVector_trans.png index 1cfc3adb8..08b9fb724 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_RgbaVector_trans.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/GifDecoder_IsNotBoundToSinglePixelType_RgbaVector_trans.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aa4c441acc8fe614c9b375e8e609e735e1c767918ecc1f8f2f028665fdcfcf34 -size 12441 +oid sha256:49eeef6073a5ef2494ecfb138071c22157870e11c903ca81b837b32a813732ac +size 20256 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png index 5d443b52a..bad4e3803 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2012BadMinCode_Rgba32_issue2012_drona1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a0e1677baade797de1eaec390f2e475865d24de8bd344edddbb3fce200d6bcb0 -size 135418 +oid sha256:05c72bee64dbf29fe16349d8dfdfbee565779241dbfa860f62a53b649be000e3 +size 1884 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png index f63cc98ad..75ffac608 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f39b23217f1d095eeb8eed5ccea36be813c307a60ef4b1942e9f74028451c38 -size 81944 +oid sha256:bc99ff5deb71c9caff1a645b4175b720edff792982d7c0d4189c769405386a90 +size 9474 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png index f63cc98ad..75ffac608 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2758_BadDescriptorDimensions_Rgba32_issue_2758.gif/01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f39b23217f1d095eeb8eed5ccea36be813c307a60ef4b1942e9f74028451c38 -size 81944 +oid sha256:bc99ff5deb71c9caff1a645b4175b720edff792982d7c0d4189c769405386a90 +size 9474 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/00.png index 36c368318..05e989247 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/00.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:731299281f942f277ce6803e0adda3b5dd0395eb79cae26cabc9d56905fae0fd +oid sha256:7d41ed74bdeaf5fb851935411dbe95e83c9ccbb8d5bad2d73c43fc5de4c5d798 size 1833 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/01.png index c03e5817f..9ac022ef4 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/01.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50ccac7739142578d99a76b6d39ba377099d4a7ac30cbb0a5aee44ef1e7c9c8c +oid sha256:052fc0cb71e18e6eb599f58bff8f4bfa822f536122f5dad77e6b8fa2c61bb207 size 1271 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/00.png new file mode 100644 index 000000000..7afb1c85b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f47b8bdb0159b3e9c6f3ba81a977fe198dec6cb0c47f33e8ba84203ef40dac9b +size 131 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/01.png new file mode 100644 index 000000000..d892d615a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e3c543efd04b63ee11d0e771f6b2dd2cba244f9b4a63f78a5555adfef13d60c +size 171 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/02.png new file mode 100644 index 000000000..cd8df1479 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ccae6eb76a1c3a8c7d75c78b7153964cbedc9223297bf67323dd2ec808ca96a +size 159 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/03.png new file mode 100644 index 000000000..5a42c4c6f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2980_Rgba32_issue_2980.gif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6df2819dfd822558f734b70adbcb8edd4afb63072610d5cb79579ca84958c324 +size 151 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png index 52f14e0f7..46f520cb0 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/00.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:473c5629d7a9f8b3d6c809e8ede40f8fd38e90beddf71851b352c726fc0570d6 -size 534 +oid sha256:4abf8935cd9ed76e3e2fe92d106928ecb7ede58498a550e52d52f0f7d6561c8e +size 466 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png index b47f34ba0..d4a1f1d91 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b9b9f856c0347b460f824d6b027b343c65c67a29360793181c9a29a76f9002b -size 538 +oid sha256:a5028c4c0250855b9f0f4ec81cb376d5ab6acd73c385c5fe5e6df6537aa95d32 +size 462 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png index 64869ca3c..c9f0f00c9 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/02.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74b8015c60d215808b1d663ae4af956d1454414206ba21326ad35b8952b0cab6 -size 534 +oid sha256:92e36c91cae2dbef6570792d25b9eb08080efc6be3f2e887c2da6d87411f784d +size 471 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png index ab52225f8..e44b6db71 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8dacb6a468d3cdc94613d56264ddf34d3649846edf33619fc13b9522fcf982d6 -size 539 +oid sha256:e9f6c6bee409938823b9a8ca106301142c44ec7479a72c3f6b3ea821ade30b72 +size 466 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png index 78988aa60..bdc59e93c 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/04.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8af74db6e01928ad54444fa122e4b87929741052c85abe9c0ffc998adffcbdfc -size 542 +oid sha256:c7c3b861dca59f386fdc88acd8849f71c68ff0af45b2dfa712a55d2d865605d7 +size 462 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png index ab52225f8..e44b6db71 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/05.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8dacb6a468d3cdc94613d56264ddf34d3649846edf33619fc13b9522fcf982d6 -size 539 +oid sha256:e9f6c6bee409938823b9a8ca106301142c44ec7479a72c3f6b3ea821ade30b72 +size 466 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png index 64869ca3c..c9f0f00c9 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/06.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:74b8015c60d215808b1d663ae4af956d1454414206ba21326ad35b8952b0cab6 -size 534 +oid sha256:92e36c91cae2dbef6570792d25b9eb08080efc6be3f2e887c2da6d87411f784d +size 471 diff --git a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png index 97610dbc0..fd9984855 100644 --- a/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png +++ b/tests/Images/External/ReferenceOutput/GifDecoderTests/IssueTooLargeLzwBits_Rgba32_issue_2743.gif/07.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c816ca1e58d14361b84ba47454e4cbf4d3e4d29dfb7827756eb52ef2604f297c -size 161 +oid sha256:ed091484d552b8c234ab75921e423e6d01172df61124b9b03dcdf2dadab34b85 +size 96 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/00.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/00.gif new file mode 100644 index 000000000..4a2dcec8d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/00.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba81598dc214845f18064f987a55eebdab0e53149df8fd67904733b42c760ad6 +size 7389 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/08.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/08.gif new file mode 100644 index 000000000..4a2dcec8d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/08.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba81598dc214845f18064f987a55eebdab0e53149df8fd67904733b42c760ad6 +size 7389 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/104.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/104.gif new file mode 100644 index 000000000..a28b1dd60 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/104.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6bd47e66b07bab7edf53a07631016f8334ba337243d6b8d61b0c8a3cf7f3f0d +size 7974 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/112.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/112.gif new file mode 100644 index 000000000..a28b1dd60 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/112.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6bd47e66b07bab7edf53a07631016f8334ba337243d6b8d61b0c8a3cf7f3f0d +size 7974 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/16.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/16.gif new file mode 100644 index 000000000..016f9f9b6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/16.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03d4e5cdaed2def942ca8480c9e45bb3d7914c6dbe3eac2b287afc8ff815b95d +size 8705 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/24.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/24.gif new file mode 100644 index 000000000..285514e23 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/24.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ad084d64942ea1dec0c1b22a88400b47f14145d761aa22a8e7deb42b5bd9fb0 +size 13011 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/32.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/32.gif new file mode 100644 index 000000000..34eff6c6c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/32.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:714cf4be4fc6c4164494ca1950796211577ab61cbd939f7cc53a36c3f7f742c5 +size 16457 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/40.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/40.gif new file mode 100644 index 000000000..7a732b4fe --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/40.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb04c45837b763abf2c326b3e831c5b772853d50c0bfc85fb7ac7ca62361292d +size 16872 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/48.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/48.gif new file mode 100644 index 000000000..7a732b4fe --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/48.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb04c45837b763abf2c326b3e831c5b772853d50c0bfc85fb7ac7ca62361292d +size 16872 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/56.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/56.gif new file mode 100644 index 000000000..6d60d4981 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/56.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c68bad5aebb65e86e4295956b9f0bc21e181a6d8dadd9dbfbd5febf0a5ba0d8 +size 17414 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/64.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/64.gif new file mode 100644 index 000000000..9af6451a6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/64.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d0672248b0e45ea700aa0c484ed0e342284ce18cad1cdc233700614636bed11 +size 16556 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/72.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/72.gif new file mode 100644 index 000000000..282ef1f8a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/72.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b12d34adf8c531c0d4ab02f7b57507a585bb1d31fb82d828c8c52732cef9f18 +size 15590 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/80.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/80.gif new file mode 100644 index 000000000..282ef1f8a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/80.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b12d34adf8c531c0d4ab02f7b57507a585bb1d31fb82d828c8c52732cef9f18 +size 15590 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/88.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/88.gif new file mode 100644 index 000000000..108da2ce3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/88.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a31b9aee61fbc7d5dd5f163f2c81cb36140a05d788a7ed2d15de550c0fe13232 +size 14561 diff --git a/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/96.gif b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/96.gif new file mode 100644 index 000000000..161443e4f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/GifEncoderTests/GifEncoder_CanDecode_AndEncode_Issue3142_Rgba32_issue_3142.gif/96.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bc49ba55ee33a3aff5069b7a6bf45cdb429ad691f8701093ad7fe04abe25ea1 +size 12156 diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png index de79ec729..84c7e64ee 100644 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png +++ b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SeparateChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aada4a2ccf45de24f2a591a18d9bc0260ceb3829e104fee6982061013ed87282 -size 14107709 +oid sha256:4a4d2a3a1f320d3fcb121bd8edf716354b7e8ceb251a9eaa2087f443a3adbbc1 +size 10896218 diff --git a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png index ff5b35a5f..679615d17 100644 --- a/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png +++ b/tests/Images/External/ReferenceOutput/HistogramEqualizationTests/AutoLevel_SynchronizedChannels_CompareToReferenceOutput_Rgba32_forest_bridge.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dca9b5b890d3a79b0002b7093d254d484ada4207e5010d1f0c6248d4dd6e22db -size 13909894 +oid sha256:0c3f30908a803ac5956dbd5959b225bceab1fc673a1b89d55d1096ba024c7bcc +size 12519351 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png index 07c29c097..95ea3d472 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Calliphora.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:362eddc5e06d672b4654bfe7a1ded995934a1c59719a3f909773b2e61931ffac -size 1332495 +oid sha256:9a6f484f06c0b466d16591db78fec3cd2c3f6ebc7578896075cd4af95d8ceaab +size 1411818 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png index 1f38ec542..828880162 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_Issue394-MultiHuffmanBaseline-Speakers.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7a1e89adcd70f792d678786a177bac927a15d6065001cd76aab0bf3cc1b7b4c -size 1034853 +oid sha256:88271ffea79e42914b8c37828da65afb691c7319b8e28fdd35942ecba4087baa +size 1060013 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png index 349ff6485..e72b1a041 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_MultiScanBaselineCMYK.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9dbb00a4be909d4b7fd605f83f786ff85545da54272a256c100afd80d687e8f -size 93253 +oid sha256:3b160cc44490303c23c0abaa0dda827d68e11bfa67127e19da974073085abbe4 +size 96042 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png index 830a95bd7..ccdb19e4d 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badeof.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24f637742951c438da5acbae6a93545830ea4d0065031572a3bdd1c96be15cce -size 48990 +oid sha256:7ce1a5d71cefd6527daecb8c1d9089f63189b833f1186971ddb89c6257d076ef +size 46745 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png index 0512251ab..4f2aae2ed 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_badrst.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0501e53f97ddb4252e15073dc75f4129428befe5594a07297c21ae9f89f075b2 -size 316278 +oid sha256:522b0a2f9a97689217af461ab2a63a41553c0ae6ca28cde046b01019c87e2393 +size 348228 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png index 27c22ecc6..e24cc5969 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_cmyk.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2253c83151238056caef2f3bca8800108a84360b2ff21979e4ff37fdddd2232 -size 419852 +oid sha256:5e81ac851f979c017082180a13ee4ddfe26c4151bfc082f7915252e57da44ec7 +size 416967 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png index 4032a32af..ae30676b3 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg420small.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b5e1d91fb6dc1ddb696fbee63331ba9c6ef3548b619c005887e60c5b01f4981 -size 27303 +oid sha256:79291e1eddee66548156fc40bb5c8b1209c7b0048fb56f2bb7890c7d89ce655e +size 27264 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png index 018ecda7a..8ddb74880 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:733cc46271c4402974db2536a55e6ecae3110856df73031ca48dad03745d852d -size 35375 +oid sha256:5196d5a20f639d833cf1ad979a290c144c97b41897fa5f0fa7454f5f4e8d3c11 +size 28697 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png index e5ce7eb3d..acca4b44e 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg444.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6543f546fac9d05ebdac7a534b0cc422f31bfd81067212a19cb3a52ad24560a8 -size 3978 +oid sha256:9894983085f3d3d45b8c4ea9acc680932f236a4602230046386130aa2af916f1 +size 4936 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png index 830a95bd7..ccdb19e4d 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24f637742951c438da5acbae6a93545830ea4d0065031572a3bdd1c96be15cce -size 48990 +oid sha256:7ce1a5d71cefd6527daecb8c1d9089f63189b833f1186971ddb89c6257d076ef +size 46745 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png index 23f1941dc..dc41461e0 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_testorig12.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad57cf87eade9ee6663f575b358eafaa869a16b3b02d0fb00ca8c683422b85a0 -size 47601 +oid sha256:26ef4244aa761c7b6c51534e414f79438753b9dbd1fbf45d2f1e2fd2dd4ec4c1 +size 46636 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png index f35c40ed8..66337b5c9 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck-subsample-1222.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a17478333c6ef0943aeb96faba1c0ea560995d0612564fe033b72b2949f4869f -size 66748 +oid sha256:91a522b32485af3be304c2d98a60b9906112637a4627d69a072976474cef7485 +size 66932 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png index 5f41fb523..76183356b 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_ycck.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:63f7105e9e2d0794b3f7225af2187a5717c12b806b68b33b98e08e0a0b1ffa79 -size 71051 +oid sha256:f374012655af194ebece2e9329da416be830d1cadd24a2bd584480d45751ea78 +size 58272 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png index 2386ae49a..ecf22f93c 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_BadEofProgressive.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e5d84fa88ac8b552c21c042f155801924240d764da365440d28eb8133950925 -size 568177 +oid sha256:36b557cd6d55389b8f02b811ecd2b14f1f1c440c7e8dd5a5a3c55cea57547203 +size 564682 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png index 3de8b1557..c144554ca 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_ExifUndefType.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb636a9f304cdef713b823adc7424108b4d44fada294c48a366278c9b9e7b4b5 -size 20163 +oid sha256:ff9554b1f421ca04e0580fbb505895f002b80298feb128a15cdc045a1625a490 +size 20111 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png index 07a7c3573..30bfb538f 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_fb.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2935d3dc66eec334eec65c1ed9fbb04046404a9c28bed413ac635878e63ea6cf -size 114688 +oid sha256:c2eb2bd4db9b67466d96fe154cd771d6780a24a798a0c051f307425f1b0e3d15 +size 115708 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png index 8241c36ac..e9a2a0bd8 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeProgressiveJpeg_progress.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b48730a7a149e13dd87a24e62c0704895b08a5ed1f8ea48a6a6a7248450750d -size 301416 +oid sha256:c5c5b5711041f22e8ee1afb344137c00ee80ba51e777689513e8dfeea855befa +size 297941 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png index 77a9d0d9c..4e554a34d 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:215cba73dfb0e19f75f6dc0c3fefca474bd65f57684a207a11d896e1637bb643 -size 1240827 +oid sha256:9750b54f5220020e0c3f5ce22e4f108cb70290750c613c29beeb49473d5c80a6 +size 1349665 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Issue3064_Rgba32_issue-3064.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Issue3064_Rgba32_issue-3064.png new file mode 100644 index 000000000..12692cb9e --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Issue3064_Rgba32_issue-3064.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73497e1eaddaa86cc915fe59a177e9ab6423d725ab4e6af21c8414c9edc6ceaf +size 347 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png index 0963e90b7..37e1e37cd 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c942b534baa51b8e46e88bd38d1ced319bccf1b55a5711ae5761697b7437fe4e -size 458321 +oid sha256:dcc32a36dcdfe3b9cfae17a81d3eb180248db4507ae93fdd4bb92e1e5e29950d +size 466215 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png index 1e5eaca4d..dbfa40378 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:163711226bdfa2f102b314435baea9f69ad1be1b11ef5ad8348358cd09a029ae -size 482963 +oid sha256:ae9a32eba21fa0bd2039803b5af8ffdba0409c104746455d5028741f214eb1a1 +size 479922 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png index 06bee00d7..5427b5c95 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6f7981f40bab5bffff3e7c9ea1676d224c173fabbaa6e7a920d7a9dabd58655f -size 464917 +oid sha256:e2bf66bb6d7eb3ff402b8564fb51bc951792ad47dc17fc9c7c886744638a59bb +size 469096 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png index 3ae12c657..787044068 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11b02b982c024e295915e88f56a428ad73068217a7ae625f705127ab8c35a4bf -size 477061 +oid sha256:e9c015e599b1710fd50c4ad61104fb03f27f211c13686cb162baba492ec4a935 +size 482675 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png index 0a2eb91cc..2ee122b8d 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a49967c20cccf824df4de3f105f5ddb44d7a602c072ce22caa38939f21f62505 -size 469827 +oid sha256:e25ba3afb3346633db52e91062c4266474bc9d9c0b3fcf686f1040b3a4d70d0d +size 476050 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png index fa554484b..a1e876d2b 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b33fc8fd03142aaaf8aabc39e084acd9e82e9222292e281953d92d65edcc1a7 -size 436111 +oid sha256:b80e88a7fdb27491687202c247eafbeb9d1c024affd5dc4633bcf15602fed990 +size 464921 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png new file mode 100644 index 000000000..8a27087f5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual-cLUT-only.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbe96e449dfeadc71bbfb0b48d2f56e6357d5b64a225153eed60263ea3356e71 +size 119987 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png new file mode 100644 index 000000000..586bc9647 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Perceptual.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e9e4da17b7270565fd592f4ff710ff317e1054794d966d33f11748defc7c35b +size 124944 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png new file mode 100644 index 000000000..5164defc1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_sRGB_Gray.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0eaf217570e283b6fb9a7d52ba5087d760d9a98827e507cbae299915871e1c31 +size 2595 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png index bf9556683..a73e5f31e 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cef85199e8560d6669766c094d078831024e44ac7fc537f8696f802c8e06138b -size 420141 +oid sha256:de73fa7a2cdd79763e40c51381f9a0cf5c7d43db2dcd7a86dad0a5b68377665f +size 421249 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png index e982d9034..16df99e04 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Bicubic_Calliphora_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bb6ed717a2af582d60ccd6c1c9c1ac92df0f8662755530b7e9063724835b23b -size 27709 +oid sha256:9be82bf220095b5650c7cd0a0eba9ee2b0fbe39881ff2bbde465ca9d2f94490f +size 27803 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png index 65aac22e9..98aac2c55 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Resize_Calliphora_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4804948a2ba604e383dd2dcc4ca4cac91c75ac97a0ab10bd884478429fa50a5 -size 28178 +oid sha256:b7e4b589fdbad2688895317fc760a834624f76cc9a027a65ce3cd6b35563ec85 +size 28514 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png index 65aac22e9..98aac2c55 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Combined_Resize_Calliphora_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a4804948a2ba604e383dd2dcc4ca4cac91c75ac97a0ab10bd884478429fa50a5 -size 28178 +oid sha256:b7e4b589fdbad2688895317fc760a834624f76cc9a027a65ce3cd6b35563ec85 +size 28514 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png index abe31b2be..665967330 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_IDCT_Resize_Calliphora_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc67170d70378ad8b8c0e1c1695b5c268341f0d26a6c788d1a8dffa8c90482a0 -size 102165 +oid sha256:d28335f6ad2421b6bc7e420c2e61ead73cd64f63ba7e1c9bd98ac1c140188ddb +size 104381 diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png index 87087adc5..9c01562f1 100644 --- a/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/JpegDecoder_Decode_Specialized_Scale_Resize_Calliphora_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae7f6ebfd9f2ddd85611827fda13eaf316d36d5187900458568f80b929effb9b -size 28291 +oid sha256:d0db0474f28d6302aae207b09f63e2de9e9ce6a9777a95dbac69ccb957d08daa +size 28381 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb48_rgb_binary_wide.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb48_rgb_binary_wide.png new file mode 100644 index 000000000..a1b407528 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb48_rgb_binary_wide.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bb443147c06e4d712421695f5cda1c9823c75ac8be7fbf6ce067c51d01445bf +size 5182 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgb24_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgb24_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..a180f0118 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgb24_flecks_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10ee142fc1d3638ebe53fdd21c0a4c53008801befcc5e986051b796561cae887 +size 237676 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_Issue_410_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_Issue_410_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..020facf41 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_Issue_410_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:511cb90e72fcb837e4c9a31561a3c914f5201452d4ca63502cb5219cb4dc42be +size 619 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_clocks_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_clocks_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..0f184812b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_clocks_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2dc73f1b4435a26125d910f005b5df7a540168a954f44c01a8e46df201adb1b6 +size 335851 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_colors_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_colors_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..74647f04f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_colors_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cdcc80a8c662c50d2f72ad8e123d595c6e80394538c05666b8d3531d651e71a +size 11270 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_screen_ImageThreshold-0_PerPixelManhattanThreshold-0.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_screen_ImageThreshold-0_PerPixelManhattanThreshold-0.png new file mode 100644 index 000000000..c3a61bde8 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_AppleCgBI_Rgba32_screen_ImageThreshold-0_PerPixelManhattanThreshold-0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00bb4c7b345389f5d95252c19d70fc2b654c4f0198e6a704b603da92b78e9a0a +size 102982 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png new file mode 100644 index 000000000..ffc983901 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b72c885278a066e63c013885c42b772275f25a5f0b2290aa38c87f3dbeac984b +size 81432 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual.png new file mode 100644 index 000000000..25a97ca48 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:936261278b1a9f5bf9a2bb4f8da09f2a82e1b5c693790e137c5f98fa4d885735 +size 81785 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray.png new file mode 100644 index 000000000..5a35cf579 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf856e49e4ece7e59eea684f6fa533ba313a36955be4703894f16b100283cb4a +size 2687 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba32.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba32.png new file mode 100644 index 000000000..270555a55 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:337e84b78fb07359a42e7eee0eed32e6728497c64aa30c6bd5ea8a3a5ec67ebc +size 5151 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba64.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba64.png new file mode 100644 index 000000000..dc5f4a559 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_sRGB_Gray_Interlaced_Rgba64.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:456ae30184b13aa2dc3d922db433017e076ff969862fe506436ed96c2d9be0a1 +size 6143 diff --git a/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png b/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png index c697cd4c2..37323155f 100644 --- a/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png +++ b/tests/Images/External/ReferenceOutput/PngDecoderTests/PngDecoder_Decode_Resize_splash_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c87a09b28b3f857e13a2bb420d16caa131a67c45b0fe31404756042f30de6d0 -size 28139 +oid sha256:a6a0e244af51f47f725dfcf1ef60705dd3223269db98685a1078b57eb2273c31 +size 23988 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png index 4c7830375..c10227c6b 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2469_Quantized_Encode_Artifacts_Rgba32_issue_2469.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1af50619f835b4470afac4553445176c121c3c9fa838dff937dcc56ae37941c3 -size 945821 +oid sha256:b33a960891c6b1e9cc6ac2ddc3cf49d8f49e0c749dfa7a67db49354988b129f6 +size 935007 diff --git a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png index b29213870..9c679acee 100644 --- a/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png +++ b/tests/Images/External/ReferenceOutput/PngEncoderTests/Issue2668_Quantized_Encode_Alpha_Rgba32_Issue_2668.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df34f8f3640b145add4f24f8003c288fe7991373b079a87b4be90842e18c82ae -size 8236 +oid sha256:ad1630f3da7c2b997d04c75de2ac1465255c977454461f2fdc7026b3f87e11f1 +size 8232 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png index 38c603855..fc2f4b0c6 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abce6af307a81a8ebac8e502142b00b2615403b5570c8dbe7b6895cfdd1a6d60 -size 66879 +oid sha256:ac986987f25d25ab964a5bef710fe81166cb643d85511906218b4f0e72e9e840 +size 30532 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png index f7ea0d006..e0357c3f1 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d4cda265a50aa26711efafdbcd947c9a01eff872611df5298920583f9a3d4224 -size 26458 +oid sha256:e0ada2a4d32a3a757b803dbf08148f113f5d358b31af79a77e97c660ce96c302 +size 1608 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png index 78c37cc44..18d91fe29 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:278a488a858b8eda141493fe00c617eb1f664196853da8341d7e5b7f231ddce4 -size 24645 +oid sha256:ffc30373989ec6857797b460931f011b30baaec633b095b6fc3d8fd5d43c77ec +size 2467 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png index b4740828d..c1b6694f5 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e03e79e6fab3a9e43041e54640a04c7cc3677709e7d879f9f410cf8afc7547a7 -size 42691 +oid sha256:9828ef0faf1a6709673cfe39028ed4202920d346bcc172bda6683bb3d1d0a7a3 +size 36577 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png index 3826753d5..a515475c9 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Bicubic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:543dbf5376386bf518830850645d69934e2ca17ab208ce3fd5274a6a172f5206 -size 10951 +oid sha256:6bff913e6e67129325203fae91278ca17407b10d99c4e4f571e6cfe3b5b7f93c +size 10889 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png index f9aa1ffe0..779e437ff 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Box.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d0cf291ebf5d8cebab1cd76e2830e5e2d2e0d9a050f7187da72680ead39110c -size 2757 +oid sha256:54b761b76d03216e7aa6238eee92755c03f7b016bffd1400be66ecf136b29c26 +size 2747 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png index 3826753d5..e165b5f3c 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_CatmullRom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:543dbf5376386bf518830850645d69934e2ca17ab208ce3fd5274a6a172f5206 -size 10951 +oid sha256:16da371a29269dade522b3d602beee8f769723c5712a348d960805b75619376d +size 10889 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png index 2f9109ba3..7d420da8c 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Hermite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57698b6666029a55edf8bd35a7ba96f68d224988cf01308a3af1c6606ae9d0b1 -size 10174 +oid sha256:b25b190603828131be8d82a27e019353c9bf80dcb38536e325abc5aa065762ed +size 10230 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png index 7dfec7898..96e71e12e 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fc7c9da04142a679887c714c43f1838eba0092a869140d234fce3412673207c6 -size 13575 +oid sha256:0cc07a20532c52151388c42d7add4f9749913c4dd7629253400a51d40760df23 +size 13566 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png index 6e3b97f2d..5e04f3fe3 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8b973f41f8afa39b94c71b307b7eb393953e2d083d56e1f0e8f43d6ab1f342a -size 16821 +oid sha256:64aae32ec91233b6a139d2f515db4a3e609fa3ab6c660cb53b05d672e7f70e6f +size 16795 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png index 6986c0391..8206fbdbe 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:122c1501e09516244f0db36e1cca373ff68514a18e84f57ed3072d52d6112e36 -size 17022 +oid sha256:c6167a1fb585b49167f4c8fa1f19111f10c825ea7d41ae4e266680d22b2bb28e +size 17094 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png index 76b53fabf..76aaf324a 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Lanczos8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12181516bce69c9302f15bba928fd530796449599cb9744a2411cc796788ee3b -size 18066 +oid sha256:131e831cc2e2d8eb4f5860d0e685b31006ab846433e38440b6a85a445aed1a12 +size 17890 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png index ae4242a42..10301fd32 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_MitchellNetravali.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1eb5accc5ada5b963ecef6ac15bfb1845f481e51aef63e06a522ea73bbeab945 -size 11194 +oid sha256:aedcc9342e0b37d60759330f62db446646c31da172e21d931ee8e8451ee720ae +size 11193 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png index efb6a2dee..1f736d040 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_NearestNeighbor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0418f0ea38ec19b407f2b5046d7ff0ed207189ad71db1e50e82c419d83620543 -size 2759 +oid sha256:34f21056cac1ec3f1bd37a6c50466210e7ca7d8263963d2c503535b40e5b31d8 +size 2752 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png index 976be43a3..b5269595c 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Robidoux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1233a9ab2c4b0b17b0248c3d40050c466330c22095287dfbdb8bf7dfbda4ff1f -size 11212 +oid sha256:b4e0cbe71672de45880111fb45c7b544203f67154060fa0707ba9216dfd6d883 +size 11217 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png index 04fb2e87e..81a20c24a 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_RobidouxSharp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e2912d4e42c7b76d9ff48a49921d6472e351662597d69b88bc3708683c7933e3 -size 11221 +oid sha256:e8208cf34114bc87c6244f83a73e7c9dd4455da2fb6d25c34e32ed2fef3cfc9a +size 11214 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png index b35d68aaf..4edc410d9 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Spline.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51b05c38647e0c1d88cc722e4109a882305073a065d2a27ccd3bee82f727127d -size 11775 +oid sha256:c4ee4328adcc71b1d9b3786ab2c03906aa725fefadd1d521206d5a04af421d8d +size 11711 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png index 64b9c6aba..f5877639e 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Triangle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b260e816b23a43d7efb7507897ba2f5dbb6a596dd67a5abd4c9a0c005e926ee0 -size 9748 +oid sha256:fbc694ac18a702c127c588bb9184bcc39a01c1b8be5ceecadeaab4477260afec +size 9984 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png index 29b95bf52..49633ed22 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithSampler_Rgba32_TestPattern150x150_Welch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50b03d627bb53048f5e56380500f116da4c503f5bb6a1b1d3c0d67ee4256d8f6 -size 15977 +oid sha256:8e85f331d7c4304fcd8ea8788da04982d9a5e43951be642bd7dbacd8907c3151 +size 15784 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png index 54dca2639..65e75b83e 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-Both.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96454548849147d7c7f0ca507c8521a7d5eaa7771f9f383cc836858870b52c57 -size 280 +oid sha256:92153056f19a20cc1d6ff65dd36ffd215eb50509cc3544e338e76c8d5665fb27 +size 278 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png index 41f94c9c7..7a9f2794c 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-LeftOrTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e94d224fdb284b6f1ba21b8caa66174edd7e6a3027f9dd03f4757e08296e6508 -size 212 +oid sha256:0db75a869ae36fbca7f57daa4495f2c16050b226474d203aba98cb8e0766d3fb +size 249 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png index 49cd1c837..4f01d8c13 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Bottom-RightOrBottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1162be9fa1f31bee8d3cba05c1422a1945621a412be11cce13d376efd5c679c -size 173 +oid sha256:09a80b11d888da121313d5f00ab0ec79ccf7bc49800135aa5eb411bd15fc6b86 +size 204 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png index 59f928178..8985e6476 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-Both.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ed262e9b885af773a4a40a4506e678630670e208bf7f9ec10307e943b166bed -size 258 +oid sha256:1ff446c4bb62d4492fc561a9dd48c4c0d95d8f4bcd9bbadf1675b2621baf9fa1 +size 212 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png index 57ee3dc2f..1f9f0557b 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-LeftOrTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a24f2cfc225d01294b8bbc5ca7d7f1738fb0b79217046eb9edf04e4c4c01851 -size 201 +oid sha256:2d76e8055ddbdaaa8f21117ab5e06d3e7d0f5da9d21dd2a992d82e284f028606 +size 235 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png index 7e47f43ff..c8c5696b1 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Left-RightOrBottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:938186fb3d0f468176988a9530efd22e66241a1361fff027005ec8a8ae323ff3 -size 197 +oid sha256:c7e594fd12ea7863d297d66852d3f80d5d3645636cdd39611c7b6fae068a6dc9 +size 230 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png index 0f756e781..434457ffc 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-Both.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4bc4b8ea7e7f10676d8de612fe6bc5144e100b95ff3fe7a1e3d4066a7684ce4d -size 239 +oid sha256:50f7f407d040b1071f7f6fbad96d6cfb2907d87060c671e74f6122ac5d381c30 +size 208 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png index b2d420886..d3810b9d6 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-LeftOrTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:345337f7dffa48d95251503ee2ae8e91db98b5cbe06b579d73c38a018c781544 -size 182 +oid sha256:f305e35f0f4fac1c099b5d9f0b2775c1bb17f382aba13a068dedff45eda4632d +size 215 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png index 4f0ad9d04..74d204d7c 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Right-RightOrBottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de4e2b71dade9dfb750a2c614a684963d6962958db79145c87fd23d9f0f8c005 -size 180 +oid sha256:c41784431d5a50746f66b3c34e966cb21fa8a088de797825633349cb077b4f97 +size 212 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png index 78bdb8bbb..308fe3454 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-Both.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d8b651663366e7543211635f337c229e2f88f1142886ea3a9b69587daaada97 -size 288 +oid sha256:7a6b2c8e072993c00a96692f10c7ebe6fcc6f4cfdcb9dff2d0aa6c65db54e1c4 +size 267 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png index 7015a0557..6111bcae1 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-LeftOrTop.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ab8df31f1716c05bb8687f79c7d1154f6cc6f65e3917abe60ecc42d0df173dc -size 215 +oid sha256:018fe6af5dd7ed2edd163bf3da5e22f8333d3e3287629a2065ebfd15b7a2a8b3 +size 256 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png index 67a765e8d..a0935a1ce 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithTaperMatrix_Rgba32_Solid30x30_(255,0,0,255)_Top-RightOrBottom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a1671da9ea7702a37a866fabfb3ca0d746233ee108594198f23cb563af43ae6 -size 180 +oid sha256:530501bed8301799b68ba3dc8d50c877ed3f58073ab1a8a283e8f776e761cd28 +size 200 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png index 4b2bb99d9..81863d3ef 100644 --- a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_With_Custom_Dimensions_Rgba32_TestPattern100x100_57.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e7dedec16ccd66543a1d44052a104957ba62099ba2f2ccc72285c233c2ae3fa -size 4411 +oid sha256:73a18d217d0db5e6ed55cbf52801b7f083e083ca2f0a6ac20d320fd29961e6e0 +size 4399 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_ErrorDither.png new file mode 100644 index 000000000..d2b62e63a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_ErrorDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27f6e8e195c4431dc7354a379152d3a8664582bc2bb1c8960ebf4088aa6505e2 +size 248709 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_NoDither.png new file mode 100644 index 000000000..ecbf328d3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_NoDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46b5751dc43e9ad5541913cf851ef1b061aa474a95283c712511531202d7015e +size 239326 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_OrderedDither.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_OrderedDither.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_HexadecatreeQuantizer_OrderedDither.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index 327366f5b..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0086044f12a7c58e49733f203af29a8aff2826ea654730274720eada15669254 -size 249163 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png deleted file mode 100644 index 3e0be536e..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_OctreeQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:85ee8479984aa52f837badbc49085c5448597fbfd987438fe25b58bad475e85f -size 239498 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png index 922c2bf9b..28db1b73a 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f -size 216030 +oid sha256:af40e835e2f3cf0f406e15248169d058dc1ae69219f2bc5c3413ecea4eb4985f +size 215873 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png index 922c2bf9b..28db1b73a 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f1462733e02d499b0d8c61ab835a27c7fee560fdc7fc521d20ec09bb4ccc80f -size 216030 +oid sha256:af40e835e2f3cf0f406e15248169d058dc1ae69219f2bc5c3413ecea4eb4985f +size 215873 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png index 29c93d14e..078c75c45 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7e6d91a3ec4f974af675dc360fd5fd623ec8773cdbc88c0a3a6506880838718a -size 226727 +oid sha256:5eb87f02c7924b764bbd2c951047b7204c56a0a1a0d6853a0fb3d30a56ed0184 +size 226633 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png index dbfab2b50..e80b9b8b1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7 -size 220192 +oid sha256:84b55eefd699cd74a1a7de958762b095f196275d2bbde2750936aed9a47f68f3 +size 220099 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png index dbfab2b50..e80b9b8b1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c68eba122814b5470e5f2e03e34190ff79e84e4b431ad8227355ce7ffcd4a6a7 -size 220192 +oid sha256:84b55eefd699cd74a1a7de958762b095f196275d2bbde2750936aed9a47f68f3 +size 220099 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png index 86655af42..ad899553d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6dbd3189b559941f91dd6e0aa15b34a3e5081477400678c2396c6a66d398876f -size 230883 +oid sha256:c4548abed72e4f833b33eed14392206d7232112fc651becb2351fdee27da5bc1 +size 230687 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png index 82d5e5d59..a30d69d17 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4df5b1bc2c291ec1cf599580d198b447278412576ab998e099cc21110e82b3d -size 263152 +oid sha256:832173c8ca6bd7a8bf417d83b459ccddb541daed1c31539bf596cacea455441d +size 263018 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png index d8a1178ad..e5591852b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_Bike_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df63a3d12e2998d5242b64169ac86e3df7ab4be585a80daddc3e3888dfcb7095 -size 262298 +oid sha256:15a6dc485f0c3fd4c9fbbdb6b50437d58d68210790e37f8aab32e66a864e2746 +size 261872 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png new file mode 100644 index 000000000..2e815d4d1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6eeed563b407940e2a05f068c42b52738e6e1217a1500c9230f7068ca4e9f1e +size 304162 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png new file mode 100644 index 000000000..a8f30e5f5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dc7dc55af4ef0741a66c569876ad8a2df27164a653baa5bae536e6d121b2c11 +size 300528 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png new file mode 100644 index 000000000..3ece7ee28 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b65e7903fbfa1ed0682221fdd86c6f0448b3f6a886cae5379720cce881a1f1e +size 305962 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index f29db004f..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce381c2d261b9b1ca61d8f6e2ff07b992283c327dc6b7cf53c7e5c9317abb7d3 -size 316443 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png deleted file mode 100644 index 284c3a270..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2bfc23a95df8a88ac6e2777d67f381e800d23647c162a9a97131a101bbb97143 -size 306703 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png deleted file mode 100644 index 5911faa72..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_OctreeQuantizer_OrderedDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9d3f58a108d933ec9ac0a5271af5b65d0a8ab9d521d54e48312b280cc42d71ac -size 322049 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png index 020562673..03b9a37f7 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be -size 269397 +oid sha256:97c277005703b029a9e791e4c9dc3adcbe06054885fdd31e361e8a0a0222a291 +size 268504 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png index 020562673..03b9a37f7 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a2aae04edebcaca9b95f30963201794887fa0eac954b64c68bfe529b14fa9be -size 269397 +oid sha256:97c277005703b029a9e791e4c9dc3adcbe06054885fdd31e361e8a0a0222a291 +size 268504 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png index 68d91fc43..a1d28a169 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WebSafePaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f3e9a338a5ae37c88ce0c348e0b655429220da051db3352779c277bb2dcb441 -size 270622 +oid sha256:b5fa657236e12cbb2a8d2cd747029723a6b3829b475f28626d7647d7b2150918 +size 271579 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png index 324bd9253..eba58870f 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c -size 284481 +oid sha256:532fa8044bb424b451343f89bf7cb954311641056bdbd5685cd7c4fa4ad8f3c8 +size 284056 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png index 324bd9253..eba58870f 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:752760327cc1416c171a920f1e0e95e34eae6d78bd0c7393a3be427bf3c8e55c -size 284481 +oid sha256:532fa8044bb424b451343f89bf7cb954311641056bdbd5685cd7c4fa4ad8f3c8 +size 284056 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png index 52bf2a163..de30e7574 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:293459538454e07bc9ea1e9df1fa5b0eb986fde7de42f6c25b43e4c8859bd28a -size 285370 +oid sha256:61ed5f4d77428be46357609d80a66e884dedbb8c255fdcc71d49eeba0eed2bf2 +size 285037 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png index 05be1395a..c56a90ad2 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90a2b7b3872c6eb1f1f039558d9f6ace92891c86951c801da01ad55b055fd670 -size 316544 +oid sha256:1cc2ef3cb819b5a82e0af32c3ab44aff0206530e291b00bdade58da2ebe4494a +size 308246 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png index d94d57759..c3ab7996d 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ff094e6bafe81e818bcbac69018dcfe29366389dfca0d63d8e05ef42896ffe1d -size 317309 +oid sha256:575c8d81152642fa0eec0ea9901d1941fea58b7686cfaac1d01e0bf59f393c4b +size 308330 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png index e016e3de6..47616cd31 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationInBox_CalliphoraPartial_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee0778aac671365dd0afae06cdcf8f36243bd9815f684b975f83e297bb694e63 -size 323979 +oid sha256:ba295a5ddb79bc61f0be9a28a636fdcc63055c26c46872d407fe20ff785f11ed +size 310415 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.25.png new file mode 100644 index 000000000..2f939d957 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.25.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8a5da54da08f7450ffb5b49c412e654215e2c2e72c32919abc78b77dc828f5 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.5.png new file mode 100644 index 000000000..9e8002ad1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9a87ef109c08411ca61d91ddcf010c272303a17abd90b6ba2204eac021055e5 +size 13665 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.75.png new file mode 100644 index 000000000..45e770bd9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.75.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81496d88b42edf4b39ab723d0b5414b56140892f45d30fc2435904b630fa9af5 +size 13886 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.png new file mode 100644 index 000000000..2f939d957 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8a5da54da08f7450ffb5b49c412e654215e2c2e72c32919abc78b77dc828f5 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_1.png new file mode 100644 index 000000000..c84edd138 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_ErrorDither_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:997e5281abd8cf3a587984ec1b7e31487ec5ddf16326d025124833d536e4ac27 +size 13910 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.25.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.25.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.25.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.5.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.5.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.5.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.75.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.75.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.75.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.png new file mode 100644 index 000000000..2f939d957 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e8a5da54da08f7450ffb5b49c412e654215e2c2e72c32919abc78b77dc828f5 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_1.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_1.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_HexadecatreeQuantizer_OrderedDither_1.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png deleted file mode 100644 index a2fb2a676..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.25.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png deleted file mode 100644 index 8d99eb49b..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.5.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:abfdd1e40c2c1d7fde419bda1da6e534ed989598e790b8ae4de35152a83f77a0 -size 13686 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png deleted file mode 100644 index bf93c39ff..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.75.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60c28eb1dc3c0416b20cec230917c0e4a70dd2929467bbab796ecbb04fe5a178 -size 13886 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png deleted file mode 100644 index a2fb2a676..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png deleted file mode 100644 index 457298b54..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_ErrorDither_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a523f097bf3b155f3823c5e400190b5d5e0d4470db7136576472c3257db76600 -size 13909 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png deleted file mode 100644 index a2fb2a676..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_OctreeQuantizer_OrderedDither_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:18a47a6fa0f7949daef6969a847d8bc04deeb16bb482211ec3a958bc63f23f89 -size 13158 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png index 878a36a47..f288b3c8b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WernerPaletteQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2bd11fa19fab712b5cd6c2b36d673c7dce904b5032b860d257b00e095e4aadf -size 13432 +oid sha256:dd31b6fc59e1f9f88230d57b39362b76cedd0bd94e15904f69071ba3f465e48d +size 13656 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png index eaf7e8241..62b1fb055 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d -size 13158 +oid sha256:0e88f74acac9cfa1a47a4402aa032975ec4bf698d51e6eb1ae103480e2e10489 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png index 02879b7a3..d2d2e3e4b 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4ac8b88b317281738d833fc71f52348d9f4f45ea5a1303dd91fdb8b42be4267 -size 13186 +oid sha256:dd738ee2a397bb1ee305f03c70e185dea6f67827dc15b9df1966cfe8c0f28040 +size 13177 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png index ba0509480..c444923a2 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1305d54f2139d4577490317051d6ce94a7fc8dd45b902d87a30fb04098dd4594 -size 13407 +oid sha256:2a2df64f89df17428415932c2ef0028d8ad408b5276264d99e6038b70473ebde +size 13417 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png index eaf7e8241..62b1fb055 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d -size 13158 +oid sha256:0e88f74acac9cfa1a47a4402aa032975ec4bf698d51e6eb1ae103480e2e10489 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png index b16a5a5c7..74cbbd758 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_ErrorDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3fc3a7ace123c330ea06072eb36dd5d65ed9154d4d0f55a828fc542c8a422c1 -size 13472 +oid sha256:234854be2a3f774a58baf79f20e68c7331b6caff486ab4b1e509a96e2a3d70b9 +size 13455 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png index 6adac16cf..a9bb2c316 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35757f2e0831cae2fbd3cc11ffaaae855e853ebaa9a1a5564b6568a5e1c442e9 -size 16031 +oid sha256:ef65ce360293ca5659730747087c15735c15df1143204acb60120a5b68cd7cd4 +size 15905 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png index 5d1030e6b..ceb918800 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6679d6d6f7c8b44461956b54654cea71180a2b0d43712d3775e60cbedd90cc82 -size 17520 +oid sha256:6618f169cf4b585979f8e9261af88fe4a61c3c40b453a159cb643cc062a6a9dc +size 17517 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png index eaf7e8241..62b1fb055 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4baf0e7bc4ae8b8a911d87f3a7af2bf3ef0235f77f3f509251f2d2f26cfb639d -size 13158 +oid sha256:0e88f74acac9cfa1a47a4402aa032975ec4bf698d51e6eb1ae103480e2e10489 +size 13160 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png index 567e5d6a3..f6cb17367 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantizationWithDitheringScale_david_WuQuantizer_OrderedDither_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5af5d16f875172d73f8426928fc8edaa4a6cab321a968b6c29fca32d0fba0df5 -size 18182 +oid sha256:4acf21f23978c83c9872bb2575ab45e4f0bbc86c8610c99479b1469fc12df5f2 +size 18112 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png new file mode 100644 index 000000000..b17bce9e3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_ErrorDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:822f4ae1d8676e2231a0c938296931be42a74c0a51b4ec63fdf6dcd17d64dc7e +size 83924 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_NoDither.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_NoDither.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_NoDither.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_OrderedDither.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_OrderedDither.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_HexadecatreeQuantizer_OrderedDither.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index e9782bc07..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b380eda5646fe97ee217ef711103001e54ee023fb8d95f7f3bbad19d886130da -size 83702 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png index 10daff76b..fd565c0a1 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d8ba00e2948337f77d935d98349958c6a520958671e9ec714ff1bfadfb130e72 -size 44622 +oid sha256:4ded8db323023a7c7620bba3b2259a549571442fe0a37883c7755ac69ae9d6d5 +size 44646 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png index 37e5035d8..c342e3a23 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3802cfe67638a24869d6cc9ace1d94460b4c0c26f2c91b12b95fa8f979de64bb -size 101579 +oid sha256:83c8403f5d0e5457721d992c1e6980134e8a65a1f646163a4f091cf34583ca02 +size 101417 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png index e72ea4b24..d07231c18 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bf2021eba9edbb2295924f8394472ac0bb237f0c462c39aa32a2074ef15f9acc -size 81771 +oid sha256:e5412b892143bb433804c662750a64a1660b2072520db53d76ec6897c636ec50 +size 81742 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png index 0997945e5..7d2070820 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_Bike_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d11b18946d373b995ecbb449c8c4cfcc7078aad1c8705997bcbf83131acde03 -size 102439 +oid sha256:a88a48586502de786aca0b36341cf6033fb3ec3ce7924ce1e2819fd14791ffe4 +size 102235 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png new file mode 100644 index 000000000..79711e2eb --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_ErrorDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22920fb2379dee7d12fee52f6a39b8e46e1e99f77b91f879c51bb33a981dfdcb +size 98851 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png new file mode 100644 index 000000000..fa6d4cb43 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_NoDither.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c7137e1b87d317d7e139cde8499deafa89f27bddba146cc5736f9c0566778c5 +size 81609 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png similarity index 100% rename from tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_OrderedDither.png rename to tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_HexadecatreeQuantizer_OrderedDither.png diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png deleted file mode 100644 index 314a05606..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_ErrorDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2236e81d33fcfb50afb9d5fd1a38c5ddf5d33fbb52de1c3204a4a9892fd334ce -size 99084 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png deleted file mode 100644 index 529304672..000000000 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_OctreeQuantizer_NoDither.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4b59097d1507236af2556ae5f2638360b223b7752cd4c8f760bc14673d811d0 -size 81709 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png index b51076bd1..0fe3d30bf 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WernerPaletteQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a8d9c0d81525d9f37d2f36946939040aea30edfc2b7ec0bf329fb49f6c7d73f -size 69896 +oid sha256:aee197677c3276d4abb8fc027358b38be26462374e364841781626f0aa67e1a4 +size 69769 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png index 7204abff4..ef86e7c48 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_ErrorDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4474b94e2d563938e10ec0526e7d94ba06b440db51b910604e752f7f9e814d66 -size 110757 +oid sha256:0b3c8dc7e653ef1846c7359e9a0f719bee91549846f160abb547cd0aab6a8a59 +size 110711 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png index 691623fc8..c65381c05 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_NoDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58a61c1d9a1d05acd484948c3e5c0496dbc74c0060f5de71741de39eae04ffa8 -size 103875 +oid sha256:4b95721a963def9e82dd32e277ed4594213920d7808ad26696d01e4f8fda842e +size 103855 diff --git a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png index e80e6c6e8..eb2c2a0c9 100644 --- a/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png +++ b/tests/Images/External/ReferenceOutput/QuantizerTests/ApplyQuantization_CalliphoraPartial_WuQuantizer_OrderedDither.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6649918c0394ead13c016a57b6a08561290651bccac88f7f15ba0e29dc5faa4 -size 110422 +oid sha256:cb174c104cdcf35433c98522a1d9d52ccf42e8927e0b59fec3556aeee8b15a47 +size 110505 diff --git a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png index f3b7b0e80..8f63e3277 100644 --- a/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png +++ b/tests/Images/External/ReferenceOutput/TgaDecoderTests/TgaDecoder_Decode_Resize_rgb_a_rle_UL_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cb67006d156cff0fa8d4d1b131ac0eaa98279ae6dcc477818b2fc65f2dfb77aa -size 23396 +oid sha256:d0ce3abf8024859d1375721c18049513106ade2f9529ec20864621e310ca0b1b +size 18786 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CIELAB.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CIELAB.png new file mode 100644 index 000000000..3863e7202 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CIELAB.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8dd7d54f362b33c2d2d4b4b3cb3bbece23c14138073403234db7b53012939101 +size 383 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CMYK.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CMYK.png new file mode 100644 index 000000000..bc97709c1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_CMYK.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:370af73b800622a671c0718b2c137ead8401adf20c39b45f153d2c9bb09b40ed +size 385 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB16.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB16.png new file mode 100644 index 000000000..3fcea773a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de9cfa53a82b169c533999908c7ace43aa35a8b456d5f0378b539669c7857d1c +size 386 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB8.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB8.png new file mode 100644 index 000000000..3fcea773a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual_RGB8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de9cfa53a82b169c533999908c7ace43aa35a8b456d5f0378b539669c7857d1c +size 386 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_Issue3031.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_Issue3031.png new file mode 100644 index 000000000..d5a017475 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_Issue3031.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c33a2f63836975bdc7631f26634b3fb0ae98bfaff730300877339cb568141cde +size 124 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png new file mode 100644 index 000000000..a3317a6b4 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_lsb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e50dfa103459d21642df5e1ca760081fbdfe3b7244624d9d87d8a20f45b51bbe +size 117953 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png new file mode 100644 index 000000000..a3317a6b4 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_64Bit_WithAssociatedAlpha_Rgba64_RgbaAssociatedAlpha16bit_msb.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e50dfa103459d21642df5e1ca760081fbdfe3b7244624d9d87d8a20f45b51bbe +size 117953 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_BigEndian_AssociatedAlpha_Rgba16161616_Rgba64_Issue3031.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_BigEndian_AssociatedAlpha_Rgba16161616_Rgba64_Issue3031.png new file mode 100644 index 000000000..d5a017475 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_BigEndian_AssociatedAlpha_Rgba16161616_Rgba64_Issue3031.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c33a2f63836975bdc7631f26634b3fb0ae98bfaff730300877339cb568141cde +size 124 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png index 06d60e030..6f35ecb55 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_Cmyk_Rgba32_Cmyk-jpeg.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f68db78d765a7f36570cd7b57a1f06cfca24c3b4916d0692a4aa051209ec327 -size 616 +oid sha256:b1ff0ce13bf0521b8a6f0d77806473c837c45428391786f751186a53fd951c0c +size 394 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png index 6150aacb3..aded240a1 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_JpegCompressedWithIssue2679_Issue2679.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6cd36c7e07a08e22cceecd28a056c80e5553a8c092bfc091e902d13bd5c46f4d -size 120054 +oid sha256:a94178ba1f0bd2edecdfd14c93196d1e195912f98875d875e1d79c8632676d03 +size 46525 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png index 97118c15b..addaa27bf 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_A.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4f77673028643af0ac02a8f6a1e2db14052177e3401c369391a8ff7e943770c -size 7679254 +oid sha256:13f51f69b061303d06b73fa7e626d7252a9fd14aa84c961ea894f6e7cd7218cc +size 7603818 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png index 52accc22d..445cad99c 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_ICC_Rgba32_Issue2454_B.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e616895c21fd8b19a216e8a3ef4968bd413589b5875efdac29860f019a710527 -size 7517284 +oid sha256:b6a35853e44d06a6f74e3aaf558e6a4b9c3209c6c3fa2c8f3ee650bf049ac0d3 +size 7624359 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png index 350d1af68..e88a98fa4 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_A.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d7911e059049c427229136479740fd62e2e09907549ec3e1421a6a60da6167cc -size 7840892 +oid sha256:9e91f6ae534b95a9e052158043e929e1da22e0851b4d6dfdb8b802605396758f +size 7888373 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png index 3dc99e604..29134afca 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_CanDecode_YccK_Rgba32_Issue2454_B.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:291f2033a7b4cfc10fb3301283c167b3fbc288bc173c95b21bc726bf076865af -size 7649213 +oid sha256:81bb1f05a6f72aebee41941b2390ca0a35c4368758af2d27b3e81bbee8901c56 +size 7956102 diff --git a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png index 412af0ac7..2f546df5b 100644 --- a/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png +++ b/tests/Images/External/ReferenceOutput/TiffDecoderTests/TiffDecoder_Decode_Resize_RgbaUnassociatedAlpha3bit_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53b9dabffaae6a9250bf16f201ef8c9e933327874e8be91785c4fb6cd7e787a8 -size 10767 +oid sha256:10b471070b26a6aedfc0fce762dd71090409494ce0728790278d8584636672a2 +size 9940 diff --git a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png index 331b8b30a..1a052e444 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png +++ b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.25.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a68aa183691be3240e881057cb2f5785e50228c9c5dd98163ba766b5cafa8b55 -size 88601 +oid sha256:1f312445263030ea79e2d1a05c62d19ade816c9cda9f0d3494ab76fde1e03ace +size 92534 diff --git a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png index c375fff78..769e172b7 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png +++ b/tests/Images/External/ReferenceOutput/Transforms/EntropyCropTest/EntropyCrop_MultiScanBaselineCMYK_0.75.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53a7bc1aa7279cce0b73568b6ed9b141377f7654b73dcf926c43c22dd908039a -size 88502 +oid sha256:672f167d514f01c625145701ea0d45ced503a6e84cae75fbdb67e6b0db179aee +size 92318 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png index 65bb77977..c8b2ee0ce 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-170.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3594547265b23603b1a76ff6bc6f0eab4af55d6e0070e53356123dfc7ae256f8 -size 9034 +oid sha256:881daeef5b8db99af8b89e7d4e968fb4c43e13c904e936aefa1d0156b767803e +size 9051 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png index 7c54b1b07..ccd3d8fa5 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5ae9ef073f3338b71d2a40fcf2e89d9b6ab62204d6de9b6a1f75f4705ee197f0 -size 10704 +oid sha256:86032d5b7e49574655c1cd54886ac57d6385714481ba6bd72176d858f884cd1a +size 10720 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png index b6e930224..30a81a5b9 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_170.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:994dda7da034595aa77d107652bea06c86077d24ef8a6883b18f1f509bb19928 -size 8906 +oid sha256:cb823498eacf5bab12f7607d230523a3488e26fcc10fe1b3ab51de900a9dff97 +size 8835 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png index d1ea99cf9..69f862a5a 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern100x50_50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fced9def2b41cbbf215a49ea6ef6baf4c3c041fd180671eb209db5c6e7177e5 -size 10470 +oid sha256:afe7ddbff155b918a4eff91af31e01100355c146cb9c8a12ab2496da8b22821d +size 10446 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png index 2f3f0f17f..b2f18f855 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-170.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29c5f48f1ece0b12854b4c44fba84fdfc9ac5751cdf564a32478dcdaed43b2a4 -size 9798 +oid sha256:be885eca2d9771be335a29d3da533a17376efa4c563996ac4cb37138fd1899eb +size 9826 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png index 5242a9d98..39c4304be 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_-50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7de58474c3f386c4ec31a9088d561a513f82c08d1157132d735169b847b9680 -size 11579 +oid sha256:afea3d7ec03c945d695b3cd85e9ea1d79cb35745256efe1e32273eb08789e5ce +size 11476 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png index 2af9d2fc2..e98d3115f 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_170.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3ef9b7051d7a5733dfe2534fddefdc28dfbc49d087355f46c4d945b04f0e3936 -size 9672 +oid sha256:096eba716663179933fe6eb526822ac9dedf83c3633d43df328ed33fb16900f6 +size 9687 diff --git a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png index 83c02764f..380b4b7d5 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png +++ b/tests/Images/External/ReferenceOutput/Transforms/RotateTests/Rotate_WithAngle_TestPattern50x100_50.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:825770c9b2e9f265d834eab6b40604df5508bf9bc5b4f82f5d3effd6d5a26935 -size 11434 +oid sha256:c7367063704e10827bb81f46ebb56b6fe87a816eb8ec258ca95d8e26d9276f0f +size 11408 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png index d6dba3f88..e8822425d 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_-20_-10.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e283463b0f450dd72cf303acccf3dd1ff7a31fe401ff0f288d67c4baefca240 -size 8742 +oid sha256:37acdbbcebe56ab98e17b025312e5860c471d807603c2ce6f4a50fd5f219c0f7 +size 8732 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png index 76bb244d5..0a798ce06 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Bgra32_TestPattern100x50_20_10.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:485d9d9ef955a04af43d17e6bc3952e9bf65a9752b6cf8ba9cbbe8f772f05a18 -size 8995 +oid sha256:722d191e930331e296a548641a80ac163430bdb4017e3d68e4638fbb1d6ed45c +size 9021 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png index c1c1d814f..8d4d2fd9b 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_-20_-10.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3d749ac365764051ea16bc39d1aff84c06faf282359805b58bb97c9eed7f0bb -size 6400 +oid sha256:ce2bbe927b718a1b4de05b2baad7016b69490d1b5dfb085420192b7ac6c0ec5d +size 6367 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png index 27608881e..2d5fd9e9f 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_IsNotBoundToSinglePixelType_Rgb24_TestPattern100x50_20_10.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8d82f2a15502b0a29aa4df1077ec90c88f9211f283fdc0edd7b059ed9b387441 -size 6334 +oid sha256:d79a7044e95a1ca032124366e4705bd93a866609547ebb489ff7d2228547cea5 +size 6330 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png index 340455428..a9de3011d 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Bicubic.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8e8afa56c5abb0e4b5895f35415db1178d041120d9f8306902f554cfaaada88d -size 26540 +oid sha256:677e4419a1cac8692da2d852f6e169c4a66ef8f164fffa65550fccb71bb54563 +size 26606 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png index 9ef786692..2ae230ac5 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Box.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2c174ef54b68f025352e25800f655fe3b94a0d3f75cb48bd2ac0e8d6931faf8 -size 24827 +oid sha256:0f700e854d2d4ee9c12150ef300e0041fa4706ae595eeea6869894a1e2947eaf +size 24963 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png index 14f774853..63f5f9959 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_CatmullRom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b56ceae2f350a1402beecc5b5e2930be1011a95fbf224cccf73b96f3931b646 -size 26531 +oid sha256:965248b773ea4b3f8e870ff598ccf88784dd700d9aa063b70b0146a00fc806bc +size 26613 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png index c8204eacf..bf1e06332 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Hermite.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:049ee7fc2bb758609a64149c338bfae2eab44755f53e6b7c25a5e8b8725ed8ac -size 24416 +oid sha256:cd8e29141de12f1c1a2b3fb057a58b5a017b6db3498023a8f77dc8ac02d7845c +size 24396 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png index 2bc57092a..c903716e7 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72c487a2fa3d608021b26a4d6b4517f8548fdcfc62fbafdd8649015dbec8ff87 -size 26504 +oid sha256:a28ac3e9bd2edc2aa7163275006f701672cd7f0b92454a0453a6e73387c70138 +size 26611 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png index fee364e21..5749ec1ac 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:099733c9d4490c86cfbb10a016e2dd073539a95f9d5ff9018cf9b5be5404fa13 -size 33435 +oid sha256:2a57780c4145cf1eef6028d7e58ef94ab6d47f80a024f43fe98297f2d398a7fc +size 33409 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png index 30325ccc6..5f98b4184 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27f2a2b21f8ae878e15120ea5a4a983bde7984b3468dc8426055885efc278fe6 -size 35547 +oid sha256:129f1770502c71fcbe67da35a6966a4c72e4d17ee70a8e951a5229db99d03089 +size 35607 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png index ff81256a7..979b0366a 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Lanczos8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b5cbe60e26e123e5a5cdf5a4e88305340f76d32a9c64a220c1fa7512f84e786 -size 39442 +oid sha256:0b7a58c5219ce2b6c91f98b7a75a9c89292e69d816451425b9b829d6a2b1711c +size 39420 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png index 263dd7426..6cfbcee61 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_MitchellNetravali.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:102cceb79acb1dfd7ec8887b4906e33456c774d48320d1624b3c73975d26f145 -size 25981 +oid sha256:b80944705b204ab37b1a1a0b7d0705fba4f4eae4ea9453ae8ed0e39104765ee9 +size 25836 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png index 9ef786692..2ae230ac5 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_NearestNeighbor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2c174ef54b68f025352e25800f655fe3b94a0d3f75cb48bd2ac0e8d6931faf8 -size 24827 +oid sha256:0f700e854d2d4ee9c12150ef300e0041fa4706ae595eeea6869894a1e2947eaf +size 24963 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png index 85bbd5ec3..8aeed9c8d 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Robidoux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e61629aeefac7e0a1a6b46b44ad86ed4a5ba0908bb3febc18bb5f9f3ded1c08d -size 25751 +oid sha256:314e15b8d690b17aa67ee856bcaf55afc62ce86c1b1d86b0f17dffe8739a2d17 +size 25739 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png index f200a5f95..e7e2c554b 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_RobidouxSharp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:45b1b48e1df393f4c435189c71a6bd3bccfe7a055d76d414a8d0c009b59fa0a0 -size 26145 +oid sha256:abf063ddd974a8eef6b24da9e58a10143d9fefa29369b11f7b27685312a0c74b +size 26084 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png index 434bb32a8..0d11b5565 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Spline.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d6d186f9e547f658b719bc033e3b110d64cf2a02caecc510d4e2f88359e69746 -size 24176 +oid sha256:f417e1771b2a367f857888ae90bd75f9922d9e5fc8826009ded592d9da010a44 +size 24155 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png index e3be1ffe5..0f305ddd3 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Triangle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:339b3299984f1450f1a8200e487964c0338b511b82e459d67a3583d0bd46b805 -size 24013 +oid sha256:6aebe33dad1ea3a709f54f234cb779956f5b561a9a5ecf23a95e7ec42d91c535 +size 23905 diff --git a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png index 7dbeeaf35..835d72907 100644 --- a/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png +++ b/tests/Images/External/ReferenceOutput/Transforms/SkewTests/Skew_WorksWithAllResamplers_ducky_Welch.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5335c6184829fdc405475bd34d2fae60cf6d5ae050b4d671ac5dd25242ff1368 -size 31888 +oid sha256:bb7684478edd471057076a408c6b32102838db466476929ee58e248f5c20a2b2 +size 31872 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png new file mode 100644 index 000000000..4a01423ee --- /dev/null +++ b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual-cLUT-only.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a2cbd483eed31cf7b410ef1ee45ae78e77b36e5b9c31f87764a1dfdb9c4e5c8 +size 79768 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual.png new file mode 100644 index 000000000..c46b369ef --- /dev/null +++ b/tests/Images/External/ReferenceOutput/WebpDecoderTests/Decode_WhenColorProfileHandlingIsConvert_ApplyIccProfile_Rgba32_Perceptual.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:547f3ddb5bdb3a3cb5c87440b65728be295c2b5f3c10a1b0b44299bb3d80e8d7 +size 79651 diff --git a/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png b/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png index 35b99df98..4592d77da 100644 --- a/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png +++ b/tests/Images/External/ReferenceOutput/WebpDecoderTests/WebpDecoder_Decode_Resize_bike_lossless_150_150.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d73ae8bfad75c8b169fb050653eb4f8351edf173d1cc121ff1e23e3f1e594a97 -size 36342 +oid sha256:c372b6317bc4806ffdbddee45ded7b3a8c9f8ee7b7a462d8ff770b60ce00e176 +size 28629 diff --git a/tests/Images/Input/Ani/Help.ani b/tests/Images/Input/Ani/Help.ani new file mode 100644 index 000000000..d623503ca --- /dev/null +++ b/tests/Images/Input/Ani/Help.ani @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c49cbb1ca0a3f268695a80df93b1ce2b2cba335a80e8244dd3a702863159bd99 +size 12998 diff --git a/tests/Images/Input/Ani/Work.ani b/tests/Images/Input/Ani/Work.ani new file mode 100644 index 000000000..d576244bb --- /dev/null +++ b/tests/Images/Input/Ani/Work.ani @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:740353739d3763addddd383614d125918781b8879f7c1ad3c770162a3e143a33 +size 1150338 diff --git a/tests/Images/Input/Ani/aero_busy.ani b/tests/Images/Input/Ani/aero_busy.ani new file mode 100644 index 000000000..e2fa9d31a --- /dev/null +++ b/tests/Images/Input/Ani/aero_busy.ani @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff38afb523490e1a9f157c0447bc616b19c22df88bdb45c163243d834e9745f8 +size 556304 diff --git a/tests/Images/Input/Exr/Calliphora_b44.exr b/tests/Images/Input/Exr/Calliphora_b44.exr new file mode 100644 index 000000000..ea008e070 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_b44.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7a6ac7fd879fc2dae646adaa14382aa10e5b8d02634af4668dd04bce09148151 +size 157973 diff --git a/tests/Images/Input/Exr/Calliphora_benchmark.exr b/tests/Images/Input/Exr/Calliphora_benchmark.exr new file mode 100644 index 000000000..da416b3c8 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_benchmark.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3368860692927e709365f2d37b92411068e77a0f23624ff57af6089ec69f357 +size 2592888 diff --git a/tests/Images/Input/Exr/Calliphora_float_pxr24.exr b/tests/Images/Input/Exr/Calliphora_float_pxr24.exr new file mode 100644 index 000000000..3c5d61e53 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_float_pxr24.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca6c29eb36395b923298d2bf4ba1f73fd7f081f7b1af2e72b94b92ad2acb638b +size 226997 diff --git a/tests/Images/Input/Exr/Calliphora_float_uncompressed.exr b/tests/Images/Input/Exr/Calliphora_float_uncompressed.exr new file mode 100644 index 000000000..04a5c035d --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_float_uncompressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:926996c647c1c54921db0a623e58e5edeb92b447cb4eceb53b8c0e86fd24f11e +size 720281 diff --git a/tests/Images/Input/Exr/Calliphora_gray.exr b/tests/Images/Input/Exr/Calliphora_gray.exr new file mode 100644 index 000000000..2aaeafbbe --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_gray.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a5daadcfd4ff0e45282d39d6c54f9a13651da3fd8841abda580e76661555470 +size 124245 diff --git a/tests/Images/Input/Exr/Calliphora_half_pxr24.exr b/tests/Images/Input/Exr/Calliphora_half_pxr24.exr new file mode 100644 index 000000000..17a90699b --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_half_pxr24.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:927778a73832b5ad68473e46df6be52076d98294271f2cd0ead66691db41b7bf +size 195063 diff --git a/tests/Images/Input/Exr/Calliphora_rgb.exr b/tests/Images/Input/Exr/Calliphora_rgb.exr new file mode 100644 index 000000000..5dc90343d --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_rgb.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f54e5e57df1b8cdf1b26418d5696dd9cefbd7ed3bf31cdcde06fd9a7bf5e3724 +size 362681 diff --git a/tests/Images/Input/Exr/Calliphora_rle.exr b/tests/Images/Input/Exr/Calliphora_rle.exr new file mode 100644 index 000000000..a956c9c40 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_rle.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccd25a952240a75d9c8b646be5001a2b006c053314e30cdf12e35ac865a31ae2 +size 292545 diff --git a/tests/Images/Input/Exr/Calliphora_uint32_uncompressed.exr b/tests/Images/Input/Exr/Calliphora_uint32_uncompressed.exr new file mode 100644 index 000000000..867cffaa1 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_uint32_uncompressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bed68d2e491cad0bdd0fa668ce57328951927d722f130ba7c5c5475f6a3e0d96 +size 289183 diff --git a/tests/Images/Input/Exr/Calliphora_uint_pxr24.exr b/tests/Images/Input/Exr/Calliphora_uint_pxr24.exr new file mode 100644 index 000000000..fd858abb0 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_uint_pxr24.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc72685224ba818ac77d914f5e4c91162503f88f775e16b5c745baef6bc7b790 +size 187914 diff --git a/tests/Images/Input/Exr/Calliphora_uncompressed.exr b/tests/Images/Input/Exr/Calliphora_uncompressed.exr new file mode 100644 index 000000000..5dc90343d --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_uncompressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f54e5e57df1b8cdf1b26418d5696dd9cefbd7ed3bf31cdcde06fd9a7bf5e3724 +size 362681 diff --git a/tests/Images/Input/Exr/Calliphora_uncompressed_rgba.exr b/tests/Images/Input/Exr/Calliphora_uncompressed_rgba.exr new file mode 100644 index 000000000..86bc1d354 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_uncompressed_rgba.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e9f6b5afa3c10c895ba67b51568295be40c2b1057224437600028487c581291 +size 481899 diff --git a/tests/Images/Input/Exr/Calliphora_zip.exr b/tests/Images/Input/Exr/Calliphora_zip.exr new file mode 100644 index 000000000..82dc0dc39 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_zip.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8b8e3b9a74179cb51a7a85031b3d177e109c20a56392804b10873273ff2163e +size 181871 diff --git a/tests/Images/Input/Exr/Calliphora_zips.exr b/tests/Images/Input/Exr/Calliphora_zips.exr new file mode 100644 index 000000000..6556adf00 --- /dev/null +++ b/tests/Images/Input/Exr/Calliphora_zips.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c6962a5b06c677648d5e22ff30ba8a28e7801fffb32259984e23bbaa97061b1 +size 220159 diff --git a/tests/Images/Input/Exr/rgba_uint_uncompressed.exr b/tests/Images/Input/Exr/rgba_uint_uncompressed.exr new file mode 100644 index 000000000..2738783b2 --- /dev/null +++ b/tests/Images/Input/Exr/rgba_uint_uncompressed.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b45345cb1e4539e5298ccce3ccfc0f27b7a469836bfd7aa13c4d9f6c52baa7a +size 482731 diff --git a/tests/Images/Input/Gif/issues/issue_2980.gif b/tests/Images/Input/Gif/issues/issue_2980.gif new file mode 100644 index 000000000..9c41a0f0b --- /dev/null +++ b/tests/Images/Input/Gif/issues/issue_2980.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3737a411dde845e20fc151e610434b96fb0a47297341db6f435e1ea64ae789b +size 507 diff --git a/tests/Images/Input/Gif/issues/issue_3142.gif b/tests/Images/Input/Gif/issues/issue_3142.gif new file mode 100644 index 000000000..6053eebcf --- /dev/null +++ b/tests/Images/Input/Gif/issues/issue_3142.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:982c394dbee297bb3bc5cc532827742260acb6e5aa07e1dca3f0b17e57bc3bbe +size 386565 diff --git a/tests/Images/Input/Jpg/icc-profiles/Perceptual-cLUT-only.jpg b/tests/Images/Input/Jpg/icc-profiles/Perceptual-cLUT-only.jpg new file mode 100644 index 000000000..7b2e57f65 --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/Perceptual-cLUT-only.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04e552f0bd68bddb40f35c456034b1bf1e590f37e990a28b2fe2e94753bbe685 +size 276191 diff --git a/tests/Images/Input/Jpg/icc-profiles/Perceptual.jpg b/tests/Images/Input/Jpg/icc-profiles/Perceptual.jpg new file mode 100644 index 000000000..879fd05ad --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/Perceptual.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74a0931e320ca938d7dc94c4ab7b27a15880732fc139718629a7234f34bdafba +size 297456 diff --git a/tests/Images/Input/Jpg/icc-profiles/issue-3064.jpg b/tests/Images/Input/Jpg/icc-profiles/issue-3064.jpg new file mode 100644 index 000000000..477372c03 --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/issue-3064.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e02fff450519423fd5746e610d65bd7296553252567e93de9c051250139e8adc +size 27537 diff --git a/tests/Images/Input/Jpg/icc-profiles/sRGB_Gray.jpg b/tests/Images/Input/Jpg/icc-profiles/sRGB_Gray.jpg new file mode 100644 index 000000000..2abd97686 --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/sRGB_Gray.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22892d1b7965d973c7d8925ad7d749988c6a36b333b264a55d389f1e4faa0245 +size 36854 diff --git a/tests/Images/Input/Jpg/issues/issue-2948-sos.jpg b/tests/Images/Input/Jpg/issues/issue-2948-sos.jpg new file mode 100644 index 000000000..d210e87e5 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue-2948-sos.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad9c3a6babb41c5aa2a46fbfe74ed5482028c73f48531cf144e1e324ca7988b3 +size 103789 diff --git a/tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg b/tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg new file mode 100644 index 000000000..e8897a511 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue3118-multiple-sof.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29d9073bc60cb8f5965993654ec5a949cdbacebe6365db9831ece860095ca85f +size 11541050 diff --git a/tests/Images/Input/Pbm/rgb_binary_wide.ppm b/tests/Images/Input/Pbm/rgb_binary_wide.ppm new file mode 100644 index 000000000..20f11554f --- /dev/null +++ b/tests/Images/Input/Pbm/rgb_binary_wide.ppm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10cb6013ed7f17fd29b857189e864b11057fc34cdbf6d719c14470f7f2c34743 +size 5235 diff --git a/tests/Images/Input/Png/animated/issue-animated-frame-count.png b/tests/Images/Input/Png/animated/issue-animated-frame-count.png new file mode 100644 index 000000000..88427f487 --- /dev/null +++ b/tests/Images/Input/Png/animated/issue-animated-frame-count.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af4e320f586ab26c55612a7ccfc98a8c99cd6a0efe8a70d379503751d06fe8bd +size 51542 diff --git a/tests/Images/Input/Png/cgbi/clocks.png b/tests/Images/Input/Png/cgbi/clocks.png new file mode 100644 index 000000000..d7cf18367 --- /dev/null +++ b/tests/Images/Input/Png/cgbi/clocks.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc462d8c2697060cde9a2e975ffb828d822ee3b0d4d12e3c5f081114176c036b +size 389981 diff --git a/tests/Images/Input/Png/cgbi/colors-cgbi-bitdepth16.png b/tests/Images/Input/Png/cgbi/colors-cgbi-bitdepth16.png new file mode 100644 index 000000000..18cfa9246 --- /dev/null +++ b/tests/Images/Input/Png/cgbi/colors-cgbi-bitdepth16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59610bc03f6ca867e5f71c574b3a0d1942c9e3a230c8a32bf3007cb82f286866 +size 12853 diff --git a/tests/Images/Input/Png/cgbi/colors-cgbi-palette.png b/tests/Images/Input/Png/cgbi/colors-cgbi-palette.png new file mode 100644 index 000000000..f6406559b --- /dev/null +++ b/tests/Images/Input/Png/cgbi/colors-cgbi-palette.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3a2f20c69ae423523a8f41887e3f37257a338f2220c2ea44d35c87daf8c3aa3 +size 12853 diff --git a/tests/Images/Input/Png/cgbi/colors.png b/tests/Images/Input/Png/cgbi/colors.png new file mode 100644 index 000000000..9b3b371bd --- /dev/null +++ b/tests/Images/Input/Png/cgbi/colors.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f34436f755e3c6d15341f29c992331045ae16ad413144ca798ede5c085c8e6a +size 12853 diff --git a/tests/Images/Input/Png/cgbi/flecks.png b/tests/Images/Input/Png/cgbi/flecks.png new file mode 100644 index 000000000..640044229 --- /dev/null +++ b/tests/Images/Input/Png/cgbi/flecks.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6be7b478594ba5e4d37bc135c881c0a16cf1c804fece5440bab997c7b69182f1 +size 187703 diff --git a/tests/Images/Input/Png/cgbi/screen.png b/tests/Images/Input/Png/cgbi/screen.png new file mode 100644 index 000000000..57eca3d46 --- /dev/null +++ b/tests/Images/Input/Png/cgbi/screen.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e9bfbac37a57b71fa27b38b21314bd49dbe1cb19a2eb9d0f272ec7be3b72a33 +size 94834 diff --git a/tests/Images/Input/Png/icc-profiles/Perceptual-cLUT-only.png b/tests/Images/Input/Png/icc-profiles/Perceptual-cLUT-only.png new file mode 100644 index 000000000..8ac1afde9 --- /dev/null +++ b/tests/Images/Input/Png/icc-profiles/Perceptual-cLUT-only.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c734cacc2c6e761bab088cac80ef09da7b56a545ce71c6cced4cac31e661795 +size 119811 diff --git a/tests/Images/Input/Png/icc-profiles/Perceptual.png b/tests/Images/Input/Png/icc-profiles/Perceptual.png new file mode 100644 index 000000000..cac9bcb1e --- /dev/null +++ b/tests/Images/Input/Png/icc-profiles/Perceptual.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208a325dedea4453b7accce1ec540452af2e9be0f8c1f636f1d61a463eb3a9ae +size 123151 diff --git a/tests/Images/Input/Png/icc-profiles/sRGB_Gray.png b/tests/Images/Input/Png/icc-profiles/sRGB_Gray.png new file mode 100644 index 000000000..3326936ce --- /dev/null +++ b/tests/Images/Input/Png/icc-profiles/sRGB_Gray.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64e0f6cc38750c83e6ff0cf1911e210c342900bb2cd6c88d3daed30c854e863 +size 4531 diff --git a/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba32.png b/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba32.png new file mode 100644 index 000000000..7afc51cfe --- /dev/null +++ b/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fc63cea5de188e76503bde2fce3ff84518af5064bb46d506420cd6d7e58285b +size 7237 diff --git a/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba64.png b/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba64.png new file mode 100644 index 000000000..822aca4f5 --- /dev/null +++ b/tests/Images/Input/Png/icc-profiles/sRGB_Gray_Interlaced_Rgba64.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64343871be4ad61451ef968fa9f07c6a11dee65d0f8fd718ae8c4941586aa60c +size 8227 diff --git a/tests/Images/Input/Png/iptc-profile.png b/tests/Images/Input/Png/iptc-profile.png new file mode 100644 index 000000000..fa4199a0c --- /dev/null +++ b/tests/Images/Input/Png/iptc-profile.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae7f5d11145762b6544b3e289fc6c3bcb13a5f4cd8511b02280da683bec4c96e +size 448011 diff --git a/tests/Images/Input/Png/issues/Issue_396_dragon.png b/tests/Images/Input/Png/issues/Issue_396_dragon.png new file mode 100644 index 000000000..4e8b2d891 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_396_dragon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:203aca58e0aa19cbb7ab0820ef55005af830bf4c9bfd1dd4d9967c7e1bc1d51a +size 494160 diff --git a/tests/Images/Input/Png/issues/Issue_396_hand_1.png b/tests/Images/Input/Png/issues/Issue_396_hand_1.png new file mode 100644 index 000000000..1624bce04 --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_396_hand_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ab791784ac46f9e50a63bd255f39ff8af236a922aab3510a29b473bd93bffd5 +size 16295 diff --git a/tests/Images/Input/Png/issues/Issue_396_hand_2.png b/tests/Images/Input/Png/issues/Issue_396_hand_2.png new file mode 100644 index 000000000..d12fa097b --- /dev/null +++ b/tests/Images/Input/Png/issues/Issue_396_hand_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbf54bcef6730b68b18770105a15448f984aaca6910693d4e65106a526af58d8 +size 17756 diff --git a/tests/Images/Input/Png/issues/issue_3000.png b/tests/Images/Input/Png/issues/issue_3000.png new file mode 100644 index 000000000..d7d7ad4c6 --- /dev/null +++ b/tests/Images/Input/Png/issues/issue_3000.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f33e2f343140b01d9c5c913f4ea695748e3a93df145bc0bf2f73763f2d24cadc +size 385 diff --git a/tests/Images/Input/Tiff/Issues/Issue2983.tiff b/tests/Images/Input/Tiff/Issues/Issue2983.tiff new file mode 100644 index 000000000..7332ca42a --- /dev/null +++ b/tests/Images/Input/Tiff/Issues/Issue2983.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bfe1d8660d11111cdf2674aedc43c1362dc8c2ecfab9b74b43a06c7c195863e +size 13311100 diff --git a/tests/Images/Input/Tiff/Issues/Issue3031.tiff b/tests/Images/Input/Tiff/Issues/Issue3031.tiff new file mode 100644 index 000000000..bc3ef7d7c --- /dev/null +++ b/tests/Images/Input/Tiff/Issues/Issue3031.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e4d2db56a1b7fdea09ed65eab1d10a02952821f6662ca77caa44b8c51b30310 +size 416 diff --git a/tests/Images/Input/Tiff/icc-profiles/Perceptual_CIELAB.tiff b/tests/Images/Input/Tiff/icc-profiles/Perceptual_CIELAB.tiff new file mode 100644 index 000000000..ab2aff820 --- /dev/null +++ b/tests/Images/Input/Tiff/icc-profiles/Perceptual_CIELAB.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9494eea65b2c5b6ef033fb89b4fee2ef97d07773ae4b3988da972e1ba152b890 +size 64418 diff --git a/tests/Images/Input/Tiff/icc-profiles/Perceptual_CMYK.tiff b/tests/Images/Input/Tiff/icc-profiles/Perceptual_CMYK.tiff new file mode 100644 index 000000000..7dd685fe3 --- /dev/null +++ b/tests/Images/Input/Tiff/icc-profiles/Perceptual_CMYK.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfb7fe9e93362fa121d97fa05f61242733d79fa9fa06bb7153e1773e827567dd +size 601124 diff --git a/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB16.tiff b/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB16.tiff new file mode 100644 index 000000000..369284819 --- /dev/null +++ b/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB16.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b0e89ac971c23c12c9edff6f9e9dd066fe99f36e28e89972343d50562dd7dbd +size 65260 diff --git a/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB8.tiff b/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB8.tiff new file mode 100644 index 000000000..e2c1ff54c --- /dev/null +++ b/tests/Images/Input/Tiff/icc-profiles/Perceptual_RGB8.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18b0e5ee58c9f53bf5414fc8244e98d65aa0bc6c3397e201399baff0cd378b23 +size 35236 diff --git a/tests/Images/Input/Webp/icc-profiles/Perceptual-cLUT-only.webp b/tests/Images/Input/Webp/icc-profiles/Perceptual-cLUT-only.webp new file mode 100644 index 000000000..4787b792a --- /dev/null +++ b/tests/Images/Input/Webp/icc-profiles/Perceptual-cLUT-only.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b7ef9eedd1b2a4b93f11ac52807c071f7d0a24513008c3e69b0d4a0fd9b70db +size 186596 diff --git a/tests/Images/Input/Webp/icc-profiles/Perceptual.webp b/tests/Images/Input/Webp/icc-profiles/Perceptual.webp new file mode 100644 index 000000000..b78504f43 --- /dev/null +++ b/tests/Images/Input/Webp/icc-profiles/Perceptual.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aad13221b103b60c21a19e7ecfbab047404f25269485d26fc5dee4be11188865 +size 189800 diff --git a/tests/coverlet.runsettings b/tests/coverlet.runsettings index cffce3540..f327b47a2 100644 --- a/tests/coverlet.runsettings +++ b/tests/coverlet.runsettings @@ -9,7 +9,7 @@ lcov - [SixLabors.*]* + [SixLabors.ImageSharp*]* true