-
Notifications
You must be signed in to change notification settings - Fork 691
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-4] Expected behavior around lightness 0 or 100% in Oklab/Oklch/Lab/LCH? #10109
Comments
My focus here is on the desired behavior of the syntax that has already shipped, and how to get back to an interoperable state ASAP. I understand that chroma reduction is one possible answer to the problem, but I'd like to discuss desired behavior of Oklab/Oklch/Lab/LCH independent of how it's achieved. To visualize the space these gradients are in, here are Oklch hue slices where I've marked the line between (These are from https://foolip.github.io/okplay/slice/ with editing.) My observations:
The feedback I've heard from @argyleink is that the HSV-like effect of channel clipping makes it easier to make vibrant gradients, which makes sense since the top right will always be very saturated. It does result in visible hue shifts however. |
@romainmenke Do you know where this is spec'd? That wouldn't be a straight line in Oklab space, it would be two line segments. |
How is this not a straight line?
Because gamut mapping (as specified) happens after, it is only then that That is also why I wrote |
Yes, it makes it easier, but gamut mapping doesn't make vibrant gradients impossible. We really have to be careful to keep priorities in order here.
Can you elaborate on the visible lines when using gamut mapping? Do you have source code that could be verified by others. |
it's also as screens get better, the gradient's vibrance gets better too. it feels like a future proofed vibrant gradient / color request, rather than one I have to hold its hand for each gamut increase. it's nice to request a super vibrant color and let devices grow into it, define once instead of multiple times. the author intent is, i want a vibrant color for this screen, do what you can, and do it as vibrant as you can (since i asked for vibrance). |
I took
If you drag around the hue in https://foolip.github.io/okplay/slice/ with "reduce chroma" you'll be able to see a triangle shape moving around, which corresponds to the edge of the sRGB gamut. The source for that demo is https://github.com/foolip/okplay/blob/main/slice/slice.js. I've also added Color.js as an option and it also has these effects. (The difference is that "reduce chroma" just bisects for 10 iterations which should get closer to the gamut hull than with the JND stop condition.) |
I think you will always be able to pick out a triangle using this method, how could you not? There is a region where, suddenly, the chroma no longer changes. If you zoom out like this, how are your eyes not going to see this? You are plotting in OkLCh, and any gamut in this space produces a triangle hue slice. Within that triangle, the chroma and lightness changes, but then outside the gamut, only lightness changes. When you plot things like this, your eye will pick that out. Even if you smooth the edges, you'll just make a blurry triangle. We are zoomed so far out that we can see everything, and we want to tell our eyes to not see where the chroma stops changing, it just not possible. |
I do think that better chroma reduction techniques can reduce this. I'm not sure if it can perfectly be eliminated. Especially when you take a large bird's eye view like this. It is easier to see the imperfections this far out. |
As an example, here we use a little more accurate chroma reduction approach than CSS MINDE which reduces the triangle more, but we still get a triangle. But again, we are so zoomed out that we can easily pick out the changes. But let's take a slice, now it is hard to tell. Let's take another slice from the far right. Doesn't look so bad now as we can't take everything in all at once. |
The main question remains of what behavior is desirable at or close to 100%. That area is mostly real colors (bright and colorful) with physical meaning, although some imaginary colors around yellow-green too. I think the options are:
To be clear, I'm not talking about gamut mapping in general, between all possible color spaces, just how Oklab and Lab should ideally behave. I'm also not sure of the options are practical to implement in practice in a browser engine. |
The specification has always been very clear on this. It used to be a result of clamping which led to many surprising results and discontinuities. Making it a result of gamut mapping was a better way of achieving the desired result. So now we are questioning that original premise, that a lightness of 100% is white and a lightness of 0% is black. Right? |
That the spec is clear doesn't by itself solve the problem at hand.
The answers are different just below 100%, for
How do we get out of this situation? |
Does it? The specification was always very clear that it should be displayed as white. Getting consensus on this seems like the most important first step :) |
What the CSS syntax |
It is both a very colorful color, but also a very bright color, neither of which can be represented in the SDR gamuts we are talking about. In the SDR gamuts, it is an impossible color. Whatever it means, we can never capture it, we can only capture parts of it. You can extend the coordinates of the SDR gamut to allow round-trip calculations, but you will never be able to display it in that gamut.
Well, what are you doing? No matter what you do in terms of gamut mapping, you are wrong. All you can do is find the "best" possible color based on how you are working and what attributes you deem most important because you can't represent all the attributes of that color. You can accurately represent the lightness in this case and preserve the integrity of the hue, sacrificing its chroma, which is what CSS currently recommends, and it gives you white, but yes, it is still wrong. This doesn't make white a special case, the SDR gamut cannot represent any colors with that lightness level except white. It's a shortcut that skips reducing the chroma, which will give you white anyway. Now, clamping it at parse time may be a special case. You can try and show a darker color that will sacrifice hue and/or lightness to find the closest color that retains as much chroma as possible, it will also be wrong. The point is, whatever you choose, you can never capture that color, only specific attributes of that color. Depending on how you are working with the color, some attributes of that color are more important to you. For contrast with text (a big CSS use case) or trying to create tones, the lightness attribute, preserving the true hue may be the most important. In pictures, the closest color that has chroma close to the original (sacrificing both lightness and hue) may be more important as the colors need to maintain chroma relative to their neighbor pixels to be less jarring. These are two different use cases, and neither solution is always better. Consider Google's HCT color space in their Material Color Utilities. They've developed a color system to create palettes for apps and sites. For this to work, they wanted to have to have relatively predictable contrast and have decent hue preservation. They mashed together CIELab (D65) lightness, which provides good contrast between lightness levels, and used CAM16 chroma and hue, which while not the best at preserving hue, it does ok. When it is creating its tonal palettes, it is absolutely reducing chroma to gamut map those colors when you specify lightness levels for a color. That is how it builds its tonal palettes. I know this because I've been able to replicate HCT outside of their Material Color Utilities that expands its functionality to wide gamuts, while replicating its ability to return equivalent tonal palettes: https://facelessuser.github.io/coloraide/colors/hct/#tonal-palettes. It's the only way to give consistent hues and control lightness from destroying contrast. Neither approach in terms of gamut mapping is wrong, but they provide different utility. Neither will capture the "true definition" of the color. As far as an SDR spec that wants to focus on text on background colors, reducing chroma makes the most sense (at least to me). Now, how do you represent HDR colors? I feel this is getting into topics that should be in the HDR color spec. Not everyone will always want to represent HDR colors at all times. |
@facelessuser certainly something needs to be sacrificed and there are tradeoffs. Unfortunately the answer cannot be "it depends", as I've requested to discuss this topic in the breakout session March 27. |
The reality is that it does depend, and since only one thing can be done, you need to decide on what is most important and do that. That is my point. And CSS is trying to target CSS-defined colors, not images. These two things should not be confused. |
@facelessuser wrote:
This is a very important observation. This is an "impossible problem", of the form "how do we make sense out of nonsensical inputs?". The true plane of The true plane of I would like to focus our efforts on providing a space like |
Agree, |
Channel clipping does not preserve lightness (at all) and also affects hue, compared to chroma reduction which preserves both. Consider this slice of We also see a similar, but smaller, result for very dark colors (same demo) |
Looking at this test and the results (in Firefox Nightly and Chrome Canary, I don't have an Apple device handy to check Safari rn) I observe that Chrome passes the test currently and we have interop across three browsers reported on wpt.fyi. The behavior mandated by the WPT test conforms to the current CSS Color 4 spec text, and we no longer have the undesirable discontinuity. Declare victory and close? |
I filed this issue, and I am satisfied now that the discontinuity is no longer present. The tests ensure that there is no discontinuity, but not what the actual color is. That is the general issue of gamut mapping, in another issue. |
When adjusting lightness in Oklab/Oklch/Lab/LCH, what is expected behavior?
This is related to handling out-of-gamut colors, because:
I'm filing a new issue, because since Chrome 120, lightness exactly equalling 0 or 100% are mapped to black and white respectively. This led to a discontinuity and browsers now handle these cases differently:
https://wpt.live/css/css-color/oklab-l-almost-0.html (subtle difference)
https://wpt.live/css/css-color/oklab-l-almost-1.html (very apparent difference)
See also results on wpt.fyi
In web-platform-tests/wpt#45073 (comment), @svgeesus explained that the spec previously "effectively mandated a discontinuity", but that's no longer the case, so Chrome appropriate fails these tests now.
Oklab gradients with endpoints close to 0/100% are also affected. Demo at https://codepen.io/foolip/pen/zYXZPYr, with Chrome rendering inlined here:
Lightness 0-100%:

Lightness 0-99.9%:

Lightness 0.1-100%:

Lightness 0.1-99.9%:

In Firefox and Safari, all gradients look like the last case.
The fastest way to get back to an interoperable state would be to revert the Chrome change, so that all browsers do channel clipping, but ideally the behavior would only change once. I'm filing this issue as background reading for the breakout session planned on March 27.
The text was updated successfully, but these errors were encountered: