csswg-drafts
csswg-drafts copied to clipboard
[css-color-4] Need a future-proof way to adjust lightness of a color
The current relative color syntax offers the promise of being able to adjust properties such as the lightness of a color. When increasing lightness of a color in the oklch
color space, one very quickly goes far out of the gamut of current displays. This creates a problem wherein the content author is not seeing the color that they specified. In the future, that content will be within the capabilities of display hardware, and the result will not be what the author saw when they created the page.
The request in this issue is to create a mechanism wherein authors can avoid this problem.
Worked example:
Suppose I like the color color(srgb 1 0.09 0.54)
. I may be tempted to create a lighter version of this color by doing:
- Using relative color syntax as
oklch(from color(srgb 1 0.09 0.54) 90% c h)
- The original color, in oklch, is
oklch(65.28% 0.2572 0)
- And so the resulting color is
oklch(90% 0.2572 0)
- This color is equivalent to
color(srgb 1.37 0.516 0.843)
- This color is currently outside of the gamut of almost all displays.
- On an sRGB display, CSS gamut mapping would display it as
color(srgb 1 0.785 0.862)
The problem is that this gamut mapped rendition on sRGB displays is not remotely close to the true meaning of the color. By playing some games with the settings of one’s monitor, some displays are capable of producing the true color. On Chrome, because of some quirky implementation details (which are likely to change/be fixed), you can see the true color (for a slightly different example value) here on some displays. The following is a photo of the true color (which doesn’t quite do it justice):
One option for fixing this would be to add more features to gamut mapping, to allow a user to specify a maximum gamut. This is discussed in #10038
Another option would be to expose the okhsl
space as suggested in #8659. This space has s=100%
map to sRGB gamut and L=100%
is always white. Values of s>100%
can reach other gamuts, but it might be worth adding versions of this space where s=100%
maps to P3 and Rec2020.
To work the above example on okhsl
, we see that the desired behavior comes out naturally.
- We would express this as
okhsl(from color(srgb 1 0.09 0.54) h s 90%)
- The original color, in oklch, is
okhsl(0deg 100% 59.67%)
- So the resulting color is
okhsl(0deg 100% 90%)
- Which is equivalent to
color(srgb 1 0.09 0.54)
This resulting color is future-proof. It is also using the color space okhsl
exactly as it is intended.
How does okhsl()
behave when it is given inputs that would go out of srgb
gamut in equivalent functions.
For example rgb(from color(display-p3 1 0.5 0.5) r g b)
describes a color with rgb()
that is still outside the srgb
space.
How would okhsl()
behave in such cases?
Would it still gamut map inputs, or clip?
In other words how do you give this strong guarantee of "always within gamut" without have surprising differences between css color functions.
The okhsl
space can express an arbitrarily wide gamut with S
coordinates above 100%
.
For that particular color, for the particular operation that I was doing, it works quite well. Performing okhsl(from color(display-p3 1 0.5 0.5) h s 90%)
gets us the color color(display-p3 0.9946 0.8428 0.8328)
, which is quite reasonable.
Over in the page, the computation is just "keep doing what you were doing up to the boundary of the gamut"
if (s < mid) {
... C = t * k_1 / (1.f - k_2 * t);
} else {
... C = k_0 + t * k_1 / (1.f - k_2 * t);
}
For some values that really ramps out quickly. That color color(display-p3 1 0.5 0.5)
is one such example -- it has something like S=495%
(according to colorjs.io), which is a bit much, and has problems for L
substitutions at other values. The color color(display-p3 1 0 0)
is an example that isn't. It maps to okhsl(28.96 108.1% 59.19%)
.
I think it would be a good idea to update okhsl
have a more controlled expansion after S>100%
so that, say, Rec2020 be contained within S<=150%
, and also to include the aforementioned 100%
means P3/Rec2020 variants.
I think I follow what you are saying.
I am not sure what the added value is of okhsl()
when it can also express values that are out of gamut.
Is it purely that L 100%
is always white and that L
never goes beyond 100%
?
If so, is then the main concern with the other color notations that it is ambiguous if an author intends to express an sdr color value or an hdr color value when writing color(srgb 2 1 1)
?
Okhsl doesn't really scale well past the current targetted space, though Okhsv does handle it a bit better. I guess in its current implementation, you can target other RGB spaces (within reason).
For those interested, here are implementations of Okhsl and Ohsv for Display P3 and Rec. 2020. Basically, it is just like Okhsl and Okhsv for sRGB, but you use linear RGB matrices for the targeted RGB gamuts and generate the coefficients using this. This doesn't work for all RGB spaces. IIRC it broke down for ProPhoto. I also didn't bother to create one for A98 RGB, but I think it would work. You can view the calculated coefficients by clicking edit in the linked live preview. This is just for educational purposes and not an endorsement for the idea of CSS using such spaces. Personally, I think these spaces do have limitations and are only rough approximations. They sacrifice some of the perceptual qualities of OkLCh to make essentially color pickers for Oklab and OkLCh, but such utility does have value.
Okhsl doesn't really scale well past the current targetted space, though Okhsv does handle it a bit better. I guess in its current implementation, you can target other RGB spaces (within reason).
Yes, I completely agree. And thank you for your visualizations of this on the okhsl issue.
For those interested, here are implementations of Okhsl and Ohsv for Display P3 and Rec. 2020. Basically, it is just like Okhsl and Okhsv for sRGB, but you use linear RGB matrices for the targeted RGB gamuts and generate the coefficients using this.
Thanks, that's great work! The best way to solve the problem of "users want to use relative color syntax to adjust lightness of colors, etc" would be to provide those spaces, rather than adding workarounds to try to make oklch
behave like them.