Add plus and extended pixel blend modes - #3163
Conversation
Introduce the `Plus` alpha composition mode and add support for ColorDodge, ColorBurn, SoftLight, Difference, Exclusion, Hue, Saturation, Color, and Luminosity across straight-alpha and associated-alpha pixel blenders. The generated blender/function tables and selector logic were updated to expose the new combinations, and tests were expanded to cover mode mapping, expected blend results, and parity between straight and premultiplied alpha paths.
There was a problem hiding this comment.
Pull request overview
This pull request extends ImageSharp’s pixel blending/compositing pipeline by introducing a new alpha composition mode (Plus) and adding multiple extended color blend modes, wiring them through the generated blender/function tables and selection logic, with expanded test coverage for mappings and result parity across straight vs premultiplied alpha.
Changes:
- Added
PixelAlphaCompositionMode.Plusand exposed corresponding blenders/selectors for both straight-alpha and associated-alpha paths. - Introduced extended color blending modes (ColorDodge, ColorBurn, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity) across supported composition modes and vector widths.
- Expanded tests to validate mode-to-blender mappings, expected blend/composite results, and parity between straight and premultiplied alpha implementations.
Reviewed changes
Copilot reviewed 12 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs | Extends blender mapping assertions and adds parity/expected-result tests for new modes and Plus. |
| tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs | Adds functional tests for new blend functions (scalar/AVX/AVX-512) and helper logic for test vectors. |
| src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs | Updates blender selection switch logic to expose the new color modes and Plus composition mode. |
| src/ImageSharp/PixelFormats/PixelColorBlendingMode.cs | Adds new PixelColorBlendingMode enum members with XML docs. |
| src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt | Updates T4 generation to include Plus composer and the extended blend mode set. |
| src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs | Implements extended blend math and adds the straight-alpha Plus compositor. |
| src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt | Updates T4 generation inputs to emit default blenders for Plus and extended modes. |
| src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.Generated.tt | Updates T4 generation inputs/logic to include Plus and extended modes for associated alpha. |
| src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPorterDuffFunctions.cs | Implements associated-alpha overlap terms for extended blend modes and associated-alpha Plus helpers. |
| src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.Generated.tt | Updates T4 generation inputs to emit associated-alpha blenders for Plus and extended modes. |
| src/ImageSharp/PixelFormats/PixelBlenders/AssociatedAlphaPixelBlenders.cs | Updates runtime selector logic for associated-alpha blenders to include extended modes and Plus. |
| src/ImageSharp/PixelFormats/PixelAlphaCompositionMode.cs | Adds the Plus alpha composition enum member and documentation. |
Suppressed comments (2)
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs:560
- Same issue as the other overloads: the Vector512 overload defaults to
Luminosityfor unhandled modes. Make the switch exhaustive so unsupported modes fail fast.
/// <inheritdoc cref="InvokeExtendedBlend(PixelColorBlendingMode, Vector4, Vector4, float)" />
private static Vector512<float> InvokeExtendedBlend(PixelColorBlendingMode mode, Vector512<float> backdrop, Vector512<float> source, Vector512<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),
};
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs:545
- Same issue as the Vector4 overload: the Vector256 overload defaults to
Luminosityfor unhandled modes, which can hide missing cases. Make the switch exhaustive and throw for unsupported values.
/// <inheritdoc cref="InvokeExtendedBlend(PixelColorBlendingMode, Vector4, Vector4, float)" />
private static Vector256<float> InvokeExtendedBlend(PixelColorBlendingMode mode, Vector256<float> backdrop, Vector256<float> source, Vector256<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),
};
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (5)
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs:473
- Exclusion() currently operates on all 4 lanes and does not clear/mask the alpha lane. Blend functions here are expected to return only RGB with W=0 (or masked for SIMD), otherwise the alpha lane can interfere with subsequent composition steps.
public static Vector4 Exclusion(Vector4 backdrop, Vector4 source)
=> backdrop + source - (2F * backdrop * source);
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs:531
- InvokeExtendedBlend (Vector4 overload) uses a default arm that maps any unhandled PixelColorBlendingMode to Luminosity. That can silently hide missing switch cases if this helper is reused/extended later; it’s safer for tests to throw for unknown modes.
PixelColorBlendingMode.Color => PorterDuffFunctions.ColorSrcOver(backdrop, source, amount),
_ => PorterDuffFunctions.LuminositySrcOver(backdrop, source, amount),
};
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs:546
- InvokeExtendedBlend (Vector256 overload) uses a default arm that maps any unhandled PixelColorBlendingMode to Luminosity, which can mask missing switch cases in tests. Prefer an explicit Luminosity arm and throw for unknown values.
PixelColorBlendingMode.Color => PorterDuffFunctions.ColorSrcOver(backdrop, source, amount),
_ => PorterDuffFunctions.LuminositySrcOver(backdrop, source, amount),
};
tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs:561
- InvokeExtendedBlend (Vector512 overload) uses a default arm that maps any unhandled PixelColorBlendingMode to Luminosity. In tests this can hide missing coverage for new modes; explicit Luminosity plus a throwing default keeps the helper fail-fast.
PixelColorBlendingMode.Color => PorterDuffFunctions.ColorSrcOver(backdrop, source, amount),
_ => PorterDuffFunctions.LuminositySrcOver(backdrop, source, amount),
};
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs:463
- Difference() currently returns a non-zero W component (it includes |backdrop.W - source.W|), but blend functions in this file conventionally return RGB with W cleared/masked. Leaving W populated can leak into composition math that assumes the blend result alpha lane is zero.
This issue also appears on line 472 of the same file.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Difference(Vector4 backdrop, Vector4 source)
=> Vector4.Abs(backdrop - source);
Updated [SixLabors.ImageSharp](https://github.com/SixLabors/ImageSharp) from 3.1.12 to 4.1.1. <details> <summary>Release notes</summary> _Sourced from [SixLabors.ImageSharp's releases](https://github.com/SixLabors/ImageSharp/releases)._ ## 4.1.1 ## What's Changed * Fix CCITT decompressor bounds checks and error handling by @JimBobSquarePants in SixLabors/ImageSharp#3176 **Full Changelog**: SixLabors/ImageSharp@v4.1.0...v4.1.1 ## 4.1.0 ## What's Changed * GIF: background handling & quantizer overflow fix by @JimBobSquarePants in SixLabors/ImageSharp#3133 * Validate PBM max pixel value by @JimBobSquarePants in SixLabors/ImageSharp#3134 * Add support for Apple CgBI PNG images by @Erik-White in SixLabors/ImageSharp#3136 * Fix GIF transparency handling and dither by @JimBobSquarePants in SixLabors/ImageSharp#3143 * Bump codecov/codecov-action from 6 to 7 by @dependabot[bot] in SixLabors/ImageSharp#3141 * Add BlendWithCoverage overloads. Optimize Rgba32 compatible shuffling. by @JimBobSquarePants in SixLabors/ImageSharp#3150 * Bump actions/checkout from 6 to 7 by @dependabot[bot] in SixLabors/ImageSharp#3146 * Bump actions/cache from 5 to 6 by @dependabot[bot] in SixLabors/ImageSharp#3149 * Fix convolution sampling for bounds smaller than the kernel radius by @JimBobSquarePants in SixLabors/ImageSharp#3152 * Enhance pixel formats and associated-alpha operations with optimizations by @JimBobSquarePants in SixLabors/ImageSharp#3154 * Bump actions/setup-dotnet from 5 to 6 by @dependabot[bot] in SixLabors/ImageSharp#3155 * Fix degeneracy check for resize transforms by @JimBobSquarePants in SixLabors/ImageSharp#3157 * Improvements to Apple CgBI PNG handling by @Erik-White in SixLabors/ImageSharp#3137 * Clone TIFF profiles into image metadata by @JimBobSquarePants in SixLabors/ImageSharp#3159 * Add AOT compiler seeds for pixel formats and ICO, CUR, QOI codecs by @JimBobSquarePants in SixLabors/ImageSharp#3160 * Normalize SIMD pipelines using TensorPrimitives_ by @JimBobSquarePants in SixLabors/ImageSharp#3161 * Add ANI decoder support by @Poker-sang in SixLabors/ImageSharp#2899 * Remove AntialiasThreshold from GraphicsOptions by @JimBobSquarePants in SixLabors/ImageSharp#3162 * Add plus and extended pixel blend modes by @JimBobSquarePants in SixLabors/ImageSharp#3163 * Fix 16-bit binary PBM sample byte order by @JimBobSquarePants in SixLabors/ImageSharp#3164 * Make MemoryAllocator limits configurable and extensible by @JimBobSquarePants in SixLabors/ImageSharp#3165 ## New Contributors * @Erik-White made their first contribution in SixLabors/ImageSharp#3136 **Full Changelog**: SixLabors/ImageSharp@v4.0.0...v4.1.0 ## 4.0.0 ## What's Changed * Update to net8 by @stefannikolei in SixLabors/ImageSharp#2583 * Handle dedup of local palette of 256 length - Main by @JimBobSquarePants in SixLabors/ImageSharp#2607 * Replace custom Crc32 by @JimBobSquarePants in SixLabors/ImageSharp#2611 * Sync 3.1 DrawImage fixes by @tocsoft in SixLabors/ImageSharp#2612 * Fix handling gif encoding for global palettes - Main by @JimBobSquarePants in SixLabors/ImageSharp#2615 * Bump actions/setup-dotnet from 3 to 4 by @dependabot[bot] in SixLabors/ImageSharp#2613 * Adjusted the casing of the Webp format name by @jscarle in SixLabors/ImageSharp#2623 * Fix Paeth Filter decode on platforms that do not support Ssse3 - Main by @JimBobSquarePants in SixLabors/ImageSharp#2620 * Fix WebP animation speed bug by @marklagendijk in SixLabors/ImageSharp#2624 * Promote PixelTypeInfo to Pixel by @stefannikolei in SixLabors/ImageSharp#2601 * TGA: Treat 32 bit True Color images always as transparent by @brianpopow in SixLabors/ImageSharp#2643 * Modernize and optimize pixel format operations across platforms. by @JimBobSquarePants in SixLabors/ImageSharp#2645 * Cleanup SimdUtils by @JimBobSquarePants in SixLabors/ImageSharp#2654 * Bump actions/cache from 3 to 4 by @dependabot[bot] in SixLabors/ImageSharp#2648 * Bump codecov/codecov-action from 3 to 4 by @dependabot[bot] in SixLabors/ImageSharp#2657 * Bump NuGet/setup-nuget from 1 to 2 by @dependabot[bot] in SixLabors/ImageSharp#2658 * Add v3.1.x fixes #2673 and #2674 into main. by @JimBobSquarePants in SixLabors/ImageSharp#2675 * Add fixes 2668, 2676, and 2677 to main by @JimBobSquarePants in SixLabors/ImageSharp#2678 * Merge 2681 to v4 Main by @JimBobSquarePants in SixLabors/ImageSharp#2690 * Add JPEG COM marker support by @RobertMut in SixLabors/ImageSharp#2641 * Bump actions/upload-artifact from 3 to 4 by @dependabot[bot] in SixLabors/ImageSharp#2625 * Only exit JPEG scan decoding after multiple EOF hits by @JimBobSquarePants in SixLabors/ImageSharp#2701 * V4 Ensure VP8X alpha flag is updated correctly. by @JimBobSquarePants in SixLabors/ImageSharp#2703 * Fix animated png handling (issue #2708) by @SpaceCheetah in SixLabors/ImageSharp#2710 * Merge latest release from v3 by @JimBobSquarePants in SixLabors/ImageSharp#2720 * Fix MacOS jobs by @antonfirsov in SixLabors/ImageSharp#2728 * Fix async-over-sync issue in Image.DecodeAsync() by @kroymann in SixLabors/ImageSharp#2725 * Fix overflow in MemoryAllocator.Create(options) by @antonfirsov in SixLabors/ImageSharp#2730 * GifDecoder: Limit lzw bits to a maximum of 12 bits by @brianpopow in SixLabors/ImageSharp#2744 * GifDecoder : Allow skipping bad metadata using identify by @JimBobSquarePants in SixLabors/ImageSharp#2749 * Add ICO and CUR file decoder. by @frg2089 in SixLabors/ImageSharp#2579 * v4 - Fix off-by-one error when centering a transform. by @JimBobSquarePants in SixLabors/ImageSharp#2761 * v4 Fix 2758 by @JimBobSquarePants in SixLabors/ImageSharp#2764 * Simplify Color Space Conversion APIs by @JimBobSquarePants in SixLabors/ImageSharp#2739 * Webp: Fix Issue 2763 by @brianpopow in SixLabors/ImageSharp#2767 * V4 Correctly break during Png decoding by @JimBobSquarePants in SixLabors/ImageSharp#2773 * V4 : Fix filtering on PNG encode. by @JimBobSquarePants in SixLabors/ImageSharp#2778 * Fix #2779 buffer overrun by @KirillAldashkin in SixLabors/ImageSharp#2780 * Fix ImageMetadata docs typo by @lofcz in SixLabors/ImageSharp#2781 * Add API for metadata conversion between formats. by @JimBobSquarePants in SixLabors/ImageSharp#2751 * Tiff decoder: Fix issue 2679 by @brianpopow in SixLabors/ImageSharp#2789 * Replace PngCrcChunkHandling by @JimBobSquarePants in SixLabors/ImageSharp#2786 * Add tagname to debugger visualization for Exif- and Iptc-values, to facilitate easier debugging and discovery by @lassevk in SixLabors/ImageSharp#2787 * V4 - Correctly handle transform spaces when building transform matrices. by @JimBobSquarePants in SixLabors/ImageSharp#2795 * Allow decoding Tiff of different frame size. by @JimBobSquarePants in SixLabors/ImageSharp#2788 * Add progressive JPEG encoder by @ardabada in SixLabors/ImageSharp#2740 * Fix using dither in BmpEncoder when bit per pixel is <= 4 by @mistoll in SixLabors/ImageSharp#2819 * Add QuadDistortion to ProjectiveTransformBuilder by @Socolin in SixLabors/ImageSharp#2748 * WEBP : Use Correct Width With AlphaDecoder by @JimBobSquarePants in SixLabors/ImageSharp#2823 ... (truncated) Commits viewable in [compare view](SixLabors/ImageSharp@v3.1.12...v4.1.1). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: iht <IhateTrains@users.noreply.github.com>
Prerequisites
Description
Introduce the
Plusalpha composition mode and add support for ColorDodge, ColorBurn, SoftLight, Difference, Exclusion, Hue, Saturation, Color, and Luminosity across straight-alpha and associated-alpha pixel blenders. The generated blender/function tables and selector logic were updated to expose the new combinations, and tests were expanded to cover mode mapping, expected blend results, and parity between straight and premultiplied alpha paths.