Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[css-color-hdr] How should checking for percentages summing to 0 work in dynamic-range-limit-mix() when calc() is used? #11678

Open
weinig opened this issue Feb 9, 2025 · 12 comments

Comments

@weinig
Copy link
Contributor

weinig commented Feb 9, 2025

The text for dynamic-range-color-mix() contains this statement:

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?

@weinig
Copy link
Contributor Author

weinig commented Feb 9, 2025

cc (@svgeesus, @ccameron-chromium)

@ccameron-chromium
Copy link
Contributor

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
Copy link
Contributor Author

weinig commented 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
Copy link
Contributor

I see that we only have two WPT tests (14 subtests in total), none of which tests

If the percentages sum to zero, the function is invalid.

@ccameron-chromium
Copy link
Contributor

For dynamic-range-limit, this test is testing the behavior. But I'm totally fine with changing it.

@squelart
Copy link
Contributor

squelart commented 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 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?)

@css-meeting-bot
Copy link
Member

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

@tabatkins
Copy link
Member

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.

@svgeesus
Copy link
Contributor

Yeah, I think the right answer is to just make the output transparent black when the sum is zero.

@tabatkins That work for color-mix(). It isn't clear what the equivalent conclusion would be for dynamic-range-limit-mix().

@svgeesus
Copy link
Contributor

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.

@svgeesus
Copy link
Contributor

@tabatkins wrote

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

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
Copy link
Member

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

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

No branches or pull requests

6 participants