Skip to content

Add plus and extended pixel blend modes - #3163

Merged
JimBobSquarePants merged 2 commits into
mainfrom
js/colrv1-blending-modes
Aug 6, 2026
Merged

Add plus and extended pixel blend modes#3163
JimBobSquarePants merged 2 commits into
mainfrom
js/colrv1-blending-modes

Conversation

@JimBobSquarePants

Copy link
Copy Markdown
Member

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

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.

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.
@JimBobSquarePants JimBobSquarePants added this to the v4.1.0 milestone Aug 6, 2026
Copilot AI lite review requested due to automatic review settings August 6, 2026 22:14
Comment thread src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs Dismissed
Comment thread src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs Dismissed
Comment thread src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs Dismissed
Comment thread src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs Dismissed
Comment thread src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs Dismissed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.Plus and 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 Luminosity for 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 Luminosity for 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>
Copilot AI review requested due to automatic review settings August 6, 2026 22:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants