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-4] Need a future-proof way to adjust lightness of a color #10110

Open
ccameron-chromium opened this issue Mar 21, 2024 · 8 comments
Open
Labels
css-color-4 Current Work

Comments

@ccameron-chromium
Copy link

ccameron-chromium commented Mar 21, 2024

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

photo

@ccameron-chromium
Copy link
Author

ccameron-chromium commented Mar 21, 2024

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.

@romainmenke
Copy link
Member

romainmenke commented Mar 21, 2024

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.

@ccameron-chromium
Copy link
Author

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.

@romainmenke
Copy link
Member

romainmenke commented Mar 21, 2024

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

@facelessuser
Copy link

facelessuser commented Mar 21, 2024

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.

Screenshot 2024-03-21 at 5 23 32 PM

@ccameron-chromium
Copy link
Author

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.

@LeaVerou
Copy link
Member

LeaVerou commented Jun 12, 2024

Related: I’ve been studying designer-crafted color palettes where every color is hand-tweaked by a designer to be aesthetically pleasing, and plotting them to see how they relate to L, C, H coordinates. It’s very early stage but might still be useful. You can find the data & visualizations here: https://palettes.colorjs.io/

@astearns
Copy link
Member

This issue was going to be discussed at our last face-to-face but we did not get to it, and so it got bumped back to Agenda+. But I’m wondering whether we are actually ready to resolve on something here? If we are, could someone propose a resolution and add Agenda+ again?

@astearns astearns removed the Agenda+ label Aug 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
css-color-4 Current Work
Projects
Status: Wednesday morning
Development

No branches or pull requests

6 participants