Skip to content

[css-compositing-2] Remove or fix definition of plus-darker #447

Open

Description

@jakearchibald

https://drafts.fxtf.org/compositing/#porterduffcompositingoperators_plus_darker

The definition here seems plain wrong. For instance:

co = max(0, 1 - αs x Cs + 1 - αb x Cb)
// Assuming
// αs = 1
// αb = 1
// Cs = 0.5
// Cb = 0
co = max(0, 1 - 1 x 0.5 + 1 - 1 x 0)
co = max(0, 1.5)
co = 1.5

max(0, x) is used, which suggests x will be a maximum of 1, but the definition produces values 0-2 (assuming pixel values are 0-1).

Safari supports the plus-darker value for mix-blend-mode.

The definition in Apple's docs matches the spec, so it's incorrect for the same reasons. The implementation is closed source, so I can't check how it actually works.

It seems like, on WebKit Linux (via cairo), plus-darker is an alias of darken, which doesn't match what I see in Safari. I might not be interpreting the source correctly.

We have not heard any use-cases for plus-darker, although maybe that will become clearer with correct documentation.

Activity

  1. pacocoursey commented on Mar 29, 2023

    @pacocoursey

    @jakearchibald Would you consider advocating for plus-darker to be brought back into the spec and/or implementation? It's extremely useful for increased vibrancy of translucent materials, which are becoming more common on the web thanks to the power of filters and blend modes. Apple uses both plus-lighter and plus-filter blend modes across all of their operating systems, iOS in particular.

    Here's an example (taken directly from Apple's macOS Sketch Design Template) where plus-darker on WebKit boosts vibrancy and legibility:

    MmkqKuKg@2x

    (codepen)

    It also seems silly to only implement plus-lighter without its opposite!

  2. dbaron commented on Mar 30, 2023

    @dbaron
    Member

    The first problem here is that nobody's published a formula for what Apple's plus-darker is that we believe is correct.

    For what it's worth, if I were to try to make up a plus-darker formula that was the opposite of plus-lighter but darkening, I think I'd probably write:

    Fa = 1; Fb = 1;
    co = max(0, 1 - αs x Cs - αb x Cb);
    αo = min(1, αs + αb);

    I have not tested to see if this does anything close to reasonable.

    (On a related topic, the only difference between lighter and plus-lighter appears to be the clamping to the [0, 1] range.)

  3. dbaron commented on Aug 30, 2023

    @dbaron
    Member

    I wrote a test in web-platform-tests/wpt#41713 that tests the composite modes in canvas. It turns out my test objected to the formula in #447 (comment) because it wasn't producing properly premultiplied colors: there were cases where the color result was greater than the alpha result, which isn't allowed.

    However, my formula was sort of close; it appears (based on fiddling with my test) that what Safari implements for plus-darker is:

    co = min(1, αs + αb) - min(1, αs x (1 - Cs) + αb x (1 - Cb));
    αo = min(1, αs + αb);

    (It's possible there's a simpler way to write this, but that's the expression I figured out that leads to Safari passing the test for plus-darker.)

  4. changed the title [-][css-compositing-2] Remove plus-darker[/-] [+][css-compositing-2] Remove or fix definition of plus-darker[/+] on Aug 30, 2023
  5. smfr commented on Oct 31, 2023

    @smfr

    Comments in Apple source code say:

    //        /* R = MAX(0, 1 - ((1 - D) + (1 - S))). {R, S and D are premultiplied
    //           so actually R = MAX(0, (Da+Sa) - ((Da - D) + (Sa - S))) } */
    //                         = MAX(0, Da + Sa - Da + D - Sa + S
    //                         = MAX(0, D + S)
    

    Where R=result, D=destination, S=source as premultiplied values.

  6. css-meeting-bot commented on Nov 15, 2023

    @css-meeting-bot
    Member

    The CSS Working Group just discussed [css-compositing-2] Remove or fix definition of plus-darker, and agreed to the following:

    • RESOLVED: define plus-darker based on research results in the issue and raise issue to deal with max/min in various cases
    The full IRC log of that discussion <bramus> astearns: there was a q here on what was actually implemented, and we got some feedback from impl. is that enough to know what to do?
    <bramus> vmpstr: generally we want to align with webkit here in the spec
    <bramus> vmpstr: and then implement from thereon
    <bramus> dbaron: yes, it seems like the results I got from testing with safari and what apple (simon) says agrees, so we should put that in the spec
    <bramus> … one side comment is that i am interest in feedback from color experts on how this is supposed to work outside of srgb
    <chris> q+
    <bramus> … some formulas have max fns in them in interesting ways that assume the color space runs from 0 to 1 (I think)
    <bramus> … would be good to get that written down by experts
    <astearns> ack chris
    <bramus> … so this is a call for interested folks to look at it
    <bramus> chris: I can give a brief explanation
    <bramus> astearns: or we could resolve to define this for srgb and open issue for the rest (if that is needed)
    <bramus> chris: sgtm
    <bramus> … and that was a good question dbaron
    <bramus> dbaron: (agrees)
    <bramus> astearns: would the possible improvmenets need to be added to ???
    <bramus> dbaron: not all, maybe 3 or 4
    <bramus> astearns: PROPOSED RESOLUTION: define plus-darker based on research results in the issue and raise issue to deal with max/min in various cases
    <bramus> RESOLVED: define plus-darker based on research results in the issue and raise issue to deal with max/min in various cases
  7. magcius commented on Dec 26, 2023

    @magcius

    The logic in here is hard to follow.

    The definition in Apple's docs matches the spec, so it's incorrect for the same reasons. The implementation is closed source, so I can't check how it actually works.

    That's correct, except I think this is a typo when it was integrated into AppKit. The original blend mode can be found in CoreGraphics with an important twist: 1 - ((1 - S) + (1 - D)). This 1 - flips everything on its head, and simplifies to S + D - 1. It's additive, but there's an extra -1 tossed into one of the sides for good measure. That doesn't give you the generalized formula for blending with alpha, though.

    However, my formula was sort of close; it appears (based on fiddling with my test) that what Safari implements for plus-darker is:

    co = min(1, αs + αb) - min(1, αs x (1 - Cs) + αb x (1 - Cb));
    αo = min(1, αs + αb);

    I don't have an easy system to test CoreCG with, so I'm going to assume this is the actual formula. min(1, ...) on the left here is loadbearing, perhaps unintentionally. It's what's stopping this from becoming the same as plus-lighter. Normally, αs + αb would add to 2.0 for two opaque images, but since the min caps it to 1 it effectively adds the - 1 bias on the right-hand side. Note, however, that if you have two images with alphas that don't add to more than 1, it becomes the same as plus-lighter.

    There are some other formulas that you could come up with, like S + D - Sa*Da that I think would try to take this into account, but I can't think of any correct way to blend darker that doesn't make compromises somewhere.

    Comments in Apple source code say:

    //        /* R = MAX(0, 1 - ((1 - D) + (1 - S))). {R, S and D are premultiplied
    //           so actually R = MAX(0, (Da+Sa) - ((Da - D) + (Sa - S))) } */
    //                         = MAX(0, Da + Sa - Da + D - Sa + S
    //                         = MAX(0, D + S)
    

    This is clearly incorrect, as should be evidenced by the resulting formula being effectively the same as plus-lighter (additive blending). You can't swap out the 1 for both "Da+Sa" and "Da"/"Sa" without a clamp.

  8. Nevodev commented on Jun 27, 2025

    @Nevodev

    I wrote a test in web-platform-tests/wpt#41713 that tests the composite modes in canvas. It turns out my test objected to the formula in #447 (comment) because it wasn't producing properly premultiplied colors: there were cases where the color result was greater than the alpha result, which isn't allowed.

    However, my formula was sort of close; it appears (based on fiddling with my test) that what Safari implements for plus-darker is:

    co = min(1, αs + αb) - min(1, αs x (1 - Cs) + αb x (1 - Cb)); αo = min(1, αs + αb);

    (It's possible there's a simpler way to write this, but that's the expression I figured out that leads to Safari passing the test for plus-darker.)

    I think the formula should be this:

    co = max(0, Cb - (1 - Cs) * αs) = max(0, Cb + Cs * αs - αs);
    αo = min(1, αs + αb);

    This fits perfectly with what I see in Figma.

  9. dbaron commented on Jun 27, 2025

    @dbaron
    Member

    I think the formula should be this:

    co = max(0, Cb - (1 - Cs) * αs) = max(0, Cb + Cs * αs - αs); αo = min(1, αs + αb);

    This fits perfectly with what I see in Figma.

    Based on testing Safari with editing the above into https://wpt.live/css/compositing/canvas-composite-modes.html that doesn't appear to work, at the very least because it doesn't produce proper premultiplied results. The first error is:

    assert_equals: premultiplication zero check: g=0 a=0 plus-darker g=47 a=0 expected 0 but got 0.1843137254901961

    which indicates that it's producing a color output larger than the alpha output, which is not valid for something that is expected to produce premultiplied outputs.

    It also seems wrong because it doesn't treat source and destination the same, whereas the other formulas for plus-lighter and plus-darker do not change if you swap source and destination.

  10. Nevodev commented on Jun 27, 2025

    @Nevodev

    I think the formula should be this:
    co = max(0, Cb - (1 - Cs) * αs) = max(0, Cb + Cs * αs - αs); αo = min(1, αs + αb);
    This fits perfectly with what I see in Figma.

    Based on testing Safari with editing the above into https://wpt.live/css/compositing/canvas-composite-modes.html that doesn't appear to work, at the very least because it doesn't produce proper premultiplied results. The first error is:

    assert_equals: premultiplication zero check: g=0 a=0 plus-darker g=47 a=0 expected 0 but got 0.1843137254901961

    which indicates that it's producing a color output larger than the alpha output, which is not valid for something that is expected to produce premultiplied outputs.

    It also seems wrong because it doesn't treat source and destination the same, whereas the other formulas for plus-lighter and plus-darker do not change if you swap source and destination.

    I thought that could work. I tried calculate the RGB values myself, and the results were fitting well with Figma. Don't know how that weird number 0.1843137254901961 appears.

  11. dbaron commented on Jun 27, 2025

    @dbaron
    Member

    That number is 47/255. Your formula produces that as the green component given the (non-premultiplied) inputs. (The formulas are expected to take non-premultiplied inputs and produce premultiplied outputs.)

  12. heitbrink0 commented on Jul 27, 2025

    @heitbrink0

    That number is 47/255. Your formula produces that as the green component given the (non-premultiplied) inputs. (The formulas are expected to take non-premultiplied inputs and produce premultiplied outputs.)

    Maybe try
    co = max(0, Cb * αb - (1 - Cs) * αs)
    αo = min(1, αs + αb)

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions