You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[css-compositing-2] Remove or fix definition of plus-darker #447
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.
@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.
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.)
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.)
// /* 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.
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
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.
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.)
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 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.
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.
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.)
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.)
Activity
pacocoursey commentedon Mar 29, 2023
@jakearchibald Would you consider advocating for
plus-darkerto 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 bothplus-lighterandplus-filterblend 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-darkeron WebKit boosts vibrancy and legibility:(codepen)
It also seems silly to only implement
plus-lighterwithout its opposite!dbaron commentedon Mar 30, 2023
The first problem here is that nobody's published a formula for what Apple's
plus-darkeris that we believe is correct.For what it's worth, if I were to try to make up a
plus-darkerformula that was the opposite ofplus-lighterbut 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
lighterandplus-lighterappears to be the clamping to the [0, 1] range.)Add canvas-based test for composite modes.
dbaron commentedon Aug 30, 2023
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-darkeris: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.)[-][css-compositing-2] Remove plus-darker[/-][+][css-compositing-2] Remove or fix definition of plus-darker[/+]Add canvas-based test for composite modes.
Add canvas-based test for composite modes.
Add canvas-based test for composite modes.
Bug 1850735 [wpt PR 41713] - Add canvas-based test for composite mode…
Bug 1850735 [wpt PR 41713] - Add canvas-based test for composite mode…
smfr commentedon Oct 31, 2023
Comments in Apple source code say:
Where R=result, D=destination, S=source as premultiplied values.
css-meeting-bot commentedon Nov 15, 2023
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 casesThe 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
Add canvas-based test for composite modes.
magcius commentedon Dec 26, 2023
The logic in here is hard to follow.
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)). This1 -flips everything on its head, and simplifies toS + 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.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 + αbwould add to 2.0 for two opaque images, but since the min caps it to 1 it effectively adds the- 1bias 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*Dathat 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.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.
Nevodev commentedon Jun 27, 2025
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.
dbaron commentedon Jun 27, 2025
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:
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-lighterandplus-darkerdo not change if you swap source and destination.Nevodev commentedon Jun 27, 2025
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.1843137254901961appears.dbaron commentedon Jun 27, 2025
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.)heitbrink0 commentedon Jul 27, 2025
Maybe try
co = max(0, Cb * αb - (1 - Cs) * αs)
αo = min(1, αs + αb)