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-color-hdr] How should checking for percentages summing to 0 work in dynamic-range-limit-mix() and color-mix() when calc() is used? #11678
If the sum of all percentages is 0%, then the function is invalid.
If the intent is that this is checked at parse time, which is when things are usually marked invalid, what should happen for calc() values that aren't resolvable until style building?
This should be the same as with color-mix. I vaguely remember there being some words like "if it can be determined to be 0% then it is invalid". But maybe we should change both of those to say "if it sums to 0% then distribute all parameters evenly". That would keep things simpler.
I think there could be some ambiguity and/or inconsistency with accepting a sum of 0%:
The proposed solution would compute:
mix(a 0%, b 0%) to be mix(a 50%, b 50%)
mix(a 0%, b 0%, c 0%) to be mix(a 33%, b 33%, c 33%)
However, since we have the other kind of equivalence mix(a 50%, b 50%) == mix(a 50%, b 50%, c 0%), it would be consistent that mix(a 0%, b 0%) == mix(a 0%, b 0%, c 0%), and now we have a conflict as they result in something different!
(Or did I miss some detail?)
The CSS Working Group just discussed [css-color-hdr] How should checking for percentages summing to 0 work in dynamic-range-limit-mix() when calc() is used?, and agreed to the following:
RESOLVED: remove the sentence in spec that says if sum is zero, treat as invalid; possibly add new prose about splitting the percentage, and update tests
The full IRC log of that discussion
<jcraig> weinig: issue is that the spec for dynamic color mix has rules applied at parse time that check if value is zero
<jcraig> if all percentages add up to zero, they are invalid, but calc() may not know if it's invalid yet
<jcraig> options: either define at calc time what do in that scenario, mark as invalid at computed style time, or have it behave as if all percentages are the same; evenly divided
<jcraig> someone brought up the a concern with 50 high 50 low example, nothing constrained, you'd get 33/33/33 (not sure if I scribed that correctly)
<jcraig> currently we even split it, but worth considering when animating the ramps to zero
<jcraig> ChrisL: people will ramp it for animation so we should ensure it's not jarring
<jcraig> weinig: no current test for that.... but yes, should fix that
<jcraig> propose if all zero, treat as all at 33%
<jcraig> ChrisL: sounds reasonable
<jcraig> proposal: remove the sentence in spec that says if sum is zero, treat as invalid
<jcraig> weining: i can take on an item to work up the correct prose for that change to splitting the percentage
<jcraig> s/treat as invalid/treat as invalid; possibly add new prose about splitting the percentage, and update tests/
<jcraig> astearns: no objections resolved...
<jcraig> rrsagent, make minutes
<RRSAgent> I have made the request to generate https://www.w3.org/2025/02/26-css-minutes.html jcraig
<jcraig> RESOLVED: remove the sentence in spec that says if sum is zero, treat as invalid; possibly add new prose about splitting the percentage, and update tests
However, since we have the other kind of equivalence mix(a 50%, b 50%) == mix(a 50%, b 50%, c 0%), it would be consistent that mix(a 0%, b 0%) == mix(a 0%, b 0%, c 0%), and now we have a conflict as they result in something different!
(Or did I miss some detail?)
Yeah, I think the right answer is to just make the output transparent black when the sum is zero.
The existing behavior can also be conceptualized as adding an extra argument of transparent N%, where N is the leftover % needed to sum to 100%, and in a premultiplied color space that's exactly identical to the current behavior and is well-defined all the way down to 0%. In a non-premultiplied space you have to handle things slightly more manually (thus the current text), but continuing to treat this as the preferred mental model, the limit behavior at 0% should still result in transparent.
Yeah, I think the right answer is to just make the output transparent black when the sum is zero.
Currently, for color-mix(), browsers mark the property value as invalid (which was correct until 2 weeks ago). However we can see what they would do otherwise by making the percentages very close to zero (like 0.01%) and then using RCS to make the alpha 1 again.
background-color:oklab(from color-mix(in oklab, red 0.01%, green 0.01%) l a b /1);
Live test shows that they do not produce transparent black; they produce the actual mixed color. So we would have a discontinuity at 0%. Which may be the best we can do.
The current spec text needs to be updated, because with percentages summing to zero there is no defined color produced.
in a premultiplied color space that's exactly identical to the current behavior and is well-defined all the way down to 0%. In a non-premultiplied space you have to handle things slightly more manually (thus the current text),
Ah, my mistake, I didn't realize the text had behavior for premultiplying polar spaces. Cool. I assumed the manual handling of the bonus alpha was a required step, not just a convenience to avoid invoking the full interpolation machinery just to do a trivial alpha adjustment to the result.
[example with using tiny equal %] So we would have a discontinuity at 0%. Which may be the best we can do.
Yeah, I think it's the best generic behavior to do - just have some reasonably neutral default value that gets triggered by this corner case.
After all, you can't rely on limit arguments for this - if the initial %s are 20% and 80%, and they gradually scale down to 0%, they'll also arrive at 0% at the same time, but with a totally different interpolated hue. So any assumption you make about mixing weights will be wrong for some examples.
"Might as well make them all equally wrong" isn't always the best thing to do, of course, but when talking about it generically, like with dynamic-range-limit-mix(), it's not clear there's any better answer. Just let the "default" value fill in any missing %, and if all the specified %s are 0, then the default value is what you get, regardless of any fancier mixing mechanics you'd get with small non-zero %s. I presume for dynamic-range-limit-mix() we'd say the default value is the initial value of dynamic-range-limit.
(Note #6245 (comment) where we're defining the *-mix() family more generically, and explicitly talk about a "neutral" element taking up the leftover %. We didn't mention this corner case, but it follows naturally, I think.)
changed the title [-][css-color-hdr] How should checking for percentages summing to 0 work in dynamic-range-limit-mix() when calc() is used?[/-][+][css-color-hdr] How should checking for percentages summing to 0 work in `dynamic-range-limit-mix()` and `color-mix()` when calc() is used?[/+]on Mar 26, 2025
I updated the issue title because a lot of the discussion has been about color-mix() and it is hard to disentangle into a separate issue.
So, per WG resolution, the sentence in Color 5 about zero being invalid has been removed. That means we continue on to step 6:
Otherwise, if both are provided and add up to less than 100%, the sum is saved as an alpha multiplier. They are then scaled accordingly so that they add up to 100%.
That is going to need to be altered because, while we now have an alpha multiplier (zero), no possible scaling will make p1 and p2 sum to 100. Which is fine because we can just return the result, without any interpolation, in that corner case.
@weinig@tabatkins For color-mix(), since we removed the validity check, it doesn't matter whether the zero percentages are specified with calc() or directly as 0%. So I think the spec is now self-consistent and WPT that check for zero-sum being invalid can be edited to check instead for transparent.
Now on to dynamic-range-limit-mix(). We removed the invalidity check, so we arrive at step 6:
Otherwise, the computed value is dynamic-range-limit-mix(), with parameters standard, constrained-high, and no-limit, in that order, and percentages p_standard, p_constrained_high, and p_no_limit, omitting parameters with a percentage equal to 0%.
If they all sum to zero then all parameters are omitted, which we can't really have
dynamic-range-limit-mix()
so I guess we need an all-zero check that returns the initial value of ``dynamic-range-limit`.
One other issue on the topic of calc(). If the result of calc() is outside of the range [0,100], should the value be clamped to that range (otherwise we start to have to worry about negative numbers, etc).
Activity
weinig commentedon Feb 9, 2025
cc (@svgeesus, @ccameron-chromium)
ccameron-chromium commentedon Feb 10, 2025
This should be the same as with
color-mix. I vaguely remember there being some words like "if it can be determined to be 0% then it is invalid". But maybe we should change both of those to say "if it sums to 0% then distribute all parameters evenly". That would keep things simpler.weinig commentedon Feb 10, 2025
Ah! Looks like I had the same concern with color-mix when implementing that, https://github.com/WebKit/WebKit/blob/main/Source/WebCore/css/parser/CSSPropertyParserConsumer%2BColor.cpp#L617.
I think your suggestion makes sense.
svgeesus commentedon Feb 10, 2025
I see that we only have two WPT tests (14 subtests in total), none of which tests
ccameron-chromium commentedon Feb 10, 2025
For
dynamic-range-limit, this test is testing the behavior. But I'm totally fine with changing it.squelart commentedon Feb 10, 2025
I think there could be some ambiguity and/or inconsistency with accepting a sum of 0%:
The proposed solution would compute:
mix(a 0%, b 0%)to bemix(a 50%, b 50%)mix(a 0%, b 0%, c 0%)to bemix(a 33%, b 33%, c 33%)However, since we have the other kind of equivalence
mix(a 50%, b 50%) == mix(a 50%, b 50%, c 0%), it would be consistent thatmix(a 0%, b 0%) == mix(a 0%, b 0%, c 0%), and now we have a conflict as they result in something different!(Or did I miss some detail?)
css-meeting-bot commentedon Feb 26, 2025
The CSS Working Group just discussed
[css-color-hdr] How should checking for percentages summing to 0 work in dynamic-range-limit-mix() when calc() is used?, and agreed to the following:RESOLVED: remove the sentence in spec that says if sum is zero, treat as invalid; possibly add new prose about splitting the percentage, and update testsThe full IRC log of that discussion
<jcraig> weinig: issue is that the spec for dynamic color mix has rules applied at parse time that check if value is zero<jcraig> if all percentages add up to zero, they are invalid, but calc() may not know if it's invalid yet
<jcraig> options: either define at calc time what do in that scenario, mark as invalid at computed style time, or have it behave as if all percentages are the same; evenly divided
<jcraig> someone brought up the a concern with 50 high 50 low example, nothing constrained, you'd get 33/33/33 (not sure if I scribed that correctly)
<jcraig> currently we even split it, but worth considering when animating the ramps to zero
<jcraig> ChrisL: people will ramp it for animation so we should ensure it's not jarring
<jcraig> weinig: no current test for that.... but yes, should fix that
<jcraig> propose if all zero, treat as all at 33%
<jcraig> ChrisL: sounds reasonable
<jcraig> proposal: remove the sentence in spec that says if sum is zero, treat as invalid
<jcraig> weining: i can take on an item to work up the correct prose for that change to splitting the percentage
<jcraig> s/treat as invalid/treat as invalid; possibly add new prose about splitting the percentage, and update tests/
<jcraig> astearns: no objections resolved...
<jcraig> rrsagent, make minutes
<RRSAgent> I have made the request to generate https://www.w3.org/2025/02/26-css-minutes.html jcraig
<jcraig> RESOLVED: remove the sentence in spec that says if sum is zero, treat as invalid; possibly add new prose about splitting the percentage, and update tests
tabatkins commentedon Feb 26, 2025
Yeah, I think the right answer is to just make the output transparent black when the sum is zero.
The existing behavior can also be conceptualized as adding an extra argument of
transparent N%, where N is the leftover % needed to sum to 100%, and in a premultiplied color space that's exactly identical to the current behavior and is well-defined all the way down to 0%. In a non-premultiplied space you have to handle things slightly more manually (thus the current text), but continuing to treat this as the preferred mental model, the limit behavior at 0% should still result intransparent.[css-color] Remove the "invalid if sum to zero" wording, #11678
svgeesus commentedon Mar 12, 2025
@tabatkins That work for
color-mix(). It isn't clear what the equivalent conclusion would be fordynamic-range-limit-mix().svgeesus commentedon Mar 13, 2025
Currently, for
color-mix(), browsers mark the property value as invalid (which was correct until 2 weeks ago). However we can see what they would do otherwise by making the percentages very close to zero (like 0.01%) and then using RCS to make the alpha 1 again.Live test shows that they do not produce transparent black; they produce the actual mixed color. So we would have a discontinuity at 0%. Which may be the best we can do.
The current spec text needs to be updated, because with percentages summing to zero there is no defined color produced.
svgeesus commentedon Mar 13, 2025
@tabatkins wrote
I don't understand why you state that the current text is non-premultiplied. Color 5 3.2. Calculating the Result of color-mix references Color 4 12. Color Interpolation in which step 5 is "changing the color components to premultiplied form".
tabatkins commentedon Mar 13, 2025
Ah, my mistake, I didn't realize the text had behavior for premultiplying polar spaces. Cool. I assumed the manual handling of the bonus alpha was a required step, not just a convenience to avoid invoking the full interpolation machinery just to do a trivial alpha adjustment to the result.
Yeah, I think it's the best generic behavior to do - just have some reasonably neutral default value that gets triggered by this corner case.
After all, you can't rely on limit arguments for this - if the initial %s are 20% and 80%, and they gradually scale down to 0%, they'll also arrive at 0% at the same time, but with a totally different interpolated hue. So any assumption you make about mixing weights will be wrong for some examples.
"Might as well make them all equally wrong" isn't always the best thing to do, of course, but when talking about it generically, like with dynamic-range-limit-mix(), it's not clear there's any better answer. Just let the "default" value fill in any missing %, and if all the specified %s are 0, then the default value is what you get, regardless of any fancier mixing mechanics you'd get with small non-zero %s. I presume for dynamic-range-limit-mix() we'd say the default value is the initial value of
dynamic-range-limit.(Note #6245 (comment) where we're defining the
*-mix()family more generically, and explicitly talk about a "neutral" element taking up the leftover %. We didn't mention this corner case, but it follows naturally, I think.)[-][css-color-hdr] How should checking for percentages summing to 0 work in dynamic-range-limit-mix() when calc() is used?[/-][+][css-color-hdr] How should checking for percentages summing to 0 work in `dynamic-range-limit-mix()` and `color-mix()` when calc() is used?[/+]svgeesus commentedon Mar 26, 2025
I updated the issue title because a lot of the discussion has been about
color-mix()and it is hard to disentangle into a separate issue.So, per WG resolution, the sentence in Color 5 about zero being invalid has been removed. That means we continue on to step 6:
That is going to need to be altered because, while we now have an alpha multiplier (zero), no possible scaling will make
p1andp2sum to 100. Which is fine because we can just return the result, without any interpolation, in that corner case.[css-color-5] Defined that in color-mix(), if the percentages sum to …
svgeesus commentedon Mar 26, 2025
@weinig @tabatkins For
color-mix(), since we removed the validity check, it doesn't matter whether the zero percentages are specified withcalc()or directly as0%. So I think the spec is now self-consistent and WPT that check for zero-sum being invalid can be edited to check instead fortransparent.Now on to
dynamic-range-limit-mix(). We removed the invalidity check, so we arrive at step 6:If they all sum to zero then all parameters are omitted, which we can't really have
dynamic-range-limit-mix()so I guess we need an all-zero check that returns the initial value of ``dynamic-range-limit`.
ccameron-chromium commentedon May 22, 2025
The proposed change sounds good to me.
One other issue on the topic of
calc(). If the result ofcalc()is outside of the range[0,100], should the value be clamped to that range (otherwise we start to have to worry about negative numbers, etc).svgeesus commentedon May 22, 2025
Yes, I believe that
calc()result should be clamped.dynamic-range-limit: Handle extraordinary values from calc()
tabatkins commentedon May 22, 2025
Yup, all math functions clamp their output to the allowed range for their context, automatically.
[css-color-hdr-1] Clarify that the computed value of dynamic-range-li…
svgeesus commentedon Jun 19, 2025
Since @tabatkins said that calc() always clamps, I didn't mention it explicitly.