w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.35k stars 641 forks source link

[css-color-4] Channel clipping breaks author expectations, especially when using 'perceptually uniform' spaces #9449

Open mirisuzanne opened 9 months ago

mirisuzanne commented 9 months ago

This is not an issue in the css-color-4 spec, but in all the implementations. While issues have been filed on the individual bug trackers, I wanted to raise the issue with the CSSWG since it seems like this was an intentional decision agreed to by the browser vendors.

Here are the individual bug reports:

And, as I understand, the decision was made in these CSSWG issues:

I'm opening a separate issue because I don't have strong feelings about all the details of a gamut mapping algorithm, but I'm pretty frustrated about the state of what browsers shipped here, and I think we need to do something to fix it asap. From an authoring perspective it's entirely unusable, and it breaks the fundamental promise of the format: providing perceptually-uniform lightness.

This is the format that authors were most excited about, and it doesn't do what we told them it does. I really wish this feature hadn't shipped at all, since it clearly wasn't ready to ship. Adding agenda+ because I think this deserves more eyes on it, and more urgency in fixing it.

romainmenke commented 8 months ago

I really wish this feature hadn't shipped at all, since it clearly wasn't ready to ship.

An important aspect is that there is no feature detection for gamut mapping. Authors can't write a supports query that will make it possible to progressively use (extremely) wide gamut colors safely in browser versions that do not support gamut mapping.

facelessuser commented 8 months ago

It's probably important to note that there likely isn't a perfect gamut mapping approach, each will probably have some quirks and can be useful if their limits are understood, but anything is better than clipping, which currently is what all browsers do.

OkLCh generally does well for gamut mapping when colors are within the model's ideal range. OkLCh seems to do decent up through rec2020 as the color space maintains a reasonable geometric shape. It also avoids the purple shift that occurs when gamut mapping with LCh.

rec200_in_oklch https://facelessuser.github.io/coloraide/demos/3d_models.html?space=oklch&gamut=rec2020&edges=false&aspect=false&ortho=false

But we can see that the geometry of the OkLCh space becomes quite distorted for a space like ProPhoto RGB which extends past the visible gamut. This distortion helps contribute to issues like https://github.com/w3c/csswg-drafts/issues/7071.

prophoto_in_oklch https://facelessuser.github.io/coloraide/demos/3d_models.html?space=oklch&gamut=prophoto-rgb&edges=false&aspect=false&ortho=false

Gamut mapping with LCh has its own issues, purple shift in the blue range as an example, but the space does hold its shape much better with extreme gamuts allowing for more consistent mapping, but still, some corner cases exists, like with bright yellows due to the geometry in that hue region.

prophoto_in_lch

https://facelessuser.github.io/coloraide/demos/3d_models.html?space=lch&gamut=prophoto-rgb&edges=false&aspect=false&ortho=false

Clipping is still probably worse than either of these options:

Screenshot 2023-10-09 at 6 08 20 PM
jamesnw commented 8 months ago

The same perceptual lightness shift is also present in (ok)lab. Here's a Codepen demo showing (ok)lab and (ok)lch with consistent lightness values, and changing only the a or chroma channels, respectively.

Ok(lab) bug reports-

Both these browser bug reports and in the issue description itself also talk about the separate but connected issue around powerless components. No browsers have implemented this portion of the spec (which is also present on LCH, Oklab, and Oklch specs).

If the lightness of a Lab color is 0%, or 100% both the a and b components are [powerless](https://www.w3.org/TR/css-color-4/#powerless-color-component) and the color represents black, or white, respectively.

These all should be black and white-

There is also related conversation here- https://github.com/w3c/csswg-drafts/issues/8794

svgeesus commented 8 months ago

I'm pretty frustrated about the state of what browsers shipped here, and I think we need to do something to fix it asap. From an authoring perspective it's entirely unusable, and it breaks the fundamental promise of the format: providing perceptually-uniform lightness.

I agree. After all the work that went into finding a good Gamut Mapping Algorithm that was hue-preserving, lightness-preserving, and thus allowed the closest approximation to a specified color that was out of gamut of the display device, we end up with naive clip shipping in browsers which gives massive hue shifts and even bigger lightness shifts.

And this was done out of a misguided attempt to make 2D Canvas (which is drawing millions of pixels) align with displayed images (which will be using a perceptual gamut mapping, to preserve overall look and image detail) and with CSS (where you have maybe a hundred or so colors in all the stylesheets on a page). Trading off authoring complexity and frustration for minimal gains in computing efficiency of the implementation,

Here is an example: the CSS Color 4 GMA with Oklch on the left, the (old) CSS Color 4 GMA with CIE LCH and DeltaE2000 on the right, and in the middle naive clip which, as cal clearly be seen, for these light colors gives a much darker result quite unlike the requested color.

oklab-gma-clip

Its a screen shot from this demo with OK lightness set to 0.95.

Which is why we see preprocessor plugings like this which take your CSS and auto-generate sRGB fallbacks (using the CSS Color 4 GMA)

image

svgeesus commented 8 months ago

Here is that PostCSS GMA plugin btw

svgeesus commented 8 months ago

If the lightness of a Lab color is 0%, or 100% both the a and b components are powerless and the color represents black, or white, respectively.

That portion of the spec has changed because of

it now says:

If the lightness of a Lab color (after clamping) is 0%, or 100% the color will be displayed as black, or white, respectively due to gamut mapping to the display.

which is more correct - the specified color does have chroma, but because of the lightness it will be out of gamut of any SDR display (where the brightest color that can be displayed is media white).

jamesnw commented 8 months ago

Sorry- I was looking at an outdated version of the spec.

I see that L=0 is black and L=1 is white is now covered in the CSS Gamut Mapping to an RGB Destination section-

For colors which are out of range on the Lightness axis, white is returned in the destination color space if the Lightness is greater than or equal to 1.0, while black is returned in the destination color space if the Lightness is less than or equal to 0.0.

svgeesus commented 8 months ago

Sorry- I was looking at an outdated version of the spec.

No problem, we should update the official TR version more often (I keep meaning to but then there is always more to do). But the Editor's Draft is the right place to look for the latest version.

svgeesus commented 7 months ago

@jamesnw wrote:

Here's a Codepen demo showing (ok)lab and (ok)lch with consistent lightness values, and changing only the a or chroma channels, respectively.

So that demo has oklab(90% 0.36 0) which is out of gamut for all RGB colorspaces (even prophoto-rgb) and is rgb(152.937% 10.3745% 83.2625%).

Because Chrome and Firefox do naive clipping, that becomes rgb(100% 10.3745% 83.2625%) which is a much darker fuchsia and is oklch(0.6836 0.29009 338.36). Lightness changed from 90% to 68% because of the clip; oklab(90% 0.36 0) is oklch(0.9 0.36 0) so we also see the hue shifted 21.64 degrees because of the clip.

A CSS gamut mapped version of oklab(90% 0.36 0) to the sRGB gamut is rgb(100% 73.3771% 82.2121%) and taking that back to oklch is oklch(0.861 0.08294 357.323). We still have a lightness shift (to avoid excessive chroma loss), but less so: 90% became 86.1% and a small hue shift too, 2.677 degrees. Much better than the naive clip though.

On a P3 screen, we start from color(display-p3 1.40598 0.3464 0.8253) which CSS gamut mapped to P3 is color(display-p3 1 0.72344 0.82079) and taking that back to Oklch it is oklch(0.86331 0.10669 357.684). Lightness and he shifts similar to the sRGB case, but a better chroma due to the wider gamut P3 screen.

@mirisuzanne wrote:

Here's a codepen demo showing two colors with the same hue and lightness values, but vastly different perceptual lightness in the results.

Similarly this has [oklch(90% 90% 0deg)] which is rgb(152.937% 10.3745% 83.2625%) so naive clip gives rgb(100% 10.3745% 83.2625%) which is oklch(0.6836 0.29009 338.36), a change in lightness from 90% to 68.36% and a change in hue of 21.64deg.

TLDR; clip is a terrible gamut mapping replacement (unless the colors to be clipped are barely out of gamut)

jamesnw commented 7 months ago

So that demo has oklab(90% 0.36 0) which is out of gamut for all RGB colorspaces (even prophoto-rgb) and is rgb(152.937% 10.3745% 83.2625%).

Thanks for the walkthrough of the issue here. I made a Codepen that compares the CSS Algorithm outputs for sRGB and display-p3 with a naive clip (and a comparison with the browser's adjustment, so we can compare when that is fixed).

ccameron-chromium commented 7 months ago

I agree that the oklab and oklch spaces work best when paired with gamut mapping, and I think that the CSS gamut mapping algorithm produces good results for these spaces.

However, I do believe that the CSS gamut mapping algorithm can be inappropriate to apply to other things like display-p3 colors, because doing so can produce results that are undesirable (e.g, this example with reds). The CSS gamut mapping algorithm is really built for oklab and oklch (I might be tempted to call it something like "okl gamut mapping")

I think that the best way forward would be to "bake" CSS gamut mapping in to the definitions of oklab and oklch.

The difficulty is to define exactly what "baking" to do. Mapping to the display's gamut might be okay, but on sRGB-ish devices, it might do surprising things. When drawing to a canvas, the mapping cannot depend on the device's color space (ignoring fingerprinting, we just wouldn't want the non-determinism), and I don't think that mapping to the canvas' space would be that good (sRGB is the default and is very narrow).

One scheme would be something where we bake a well-known gamut into oklab and oklch, so we end up in effect having oklab-srgb or oklab-p3 or oklab-rec2020 (and the vanilla oklab defaults to one of those). I'm not a huge fan of this. The resulting geometries in oklab are very nonconvex and can have some sharp edges.

A better scheme could be to define a standard polyhedron to always do gamut mapping to, then I think that would be a really good way forward. This polyhedron should be big -- maybe as big as the spectral colors. And it could be made to be convex. (And maybe we could define it as being smooth).

I've been using this tool to visualize some of these options.

svgeesus commented 7 months ago

The CSS gamut mapping algorithm is really built for oklab and oklch (I might be tempted to call it something like "okl gamut mapping")

No, it really isn't. It doesn't care whether the out of gamut color came from prophoto-rgb() or whatever. It is built to use oklch as the color space in which gamut mapping happens, yes. And so your conclusion that

I think that the best way forward would be to "bake" CSS gamut mapping in to the definitions of oklab and oklch.

is entirely unjustified.

mirisuzanne commented 7 months ago

The linked ('red/redder') example demonstrates to me is that rgb clipping works well when there is only one rgb channel in use. That seems like the extreme special case to me. Maybe there could be special handing of single-channel rgb in a gamut mapping algorithm? But as soon as you start combining channels in any color space, channel-clipping will cause hue-shift. That's the 99% case, and the case that gamut mapping is designed to solve.

(and while it may be better to have the 'redder' red in that case, even a slightly desaturated red is a much closer to user-intent than we get from the channel-clipping failure cases. At least it's still red!)

romainmenke commented 7 months ago

I think the red/redder example is a sidetrack because it starts from an incorrect assumption.

It makes the assumption that color(display-p3 1 0 0) is a redder red but that is untrue.

Gamut mapping from color(display-p3 1 0 0) to srgb color space does not result in red because it never was a redder red. It will contain traces of the source being a slightly different hue and being brighter.


These both happen to have the maximum value in a single channel and zero values in the others in their respective color space.

Connecting these two values and assuming that one gamut maps to the other is incorrect. It is seeing a pattern where there is none.

ccameron-chromium commented 7 months ago

With respect to the "red redder", it is not obvious to me that this (the gamut mapped result) is a desirable representation of this (the original, needs a P3 monitor). I don't think that projecting along constant luminance is the right thing to do in the general case, but we can drop that for now.

I would really like to apply gamut mapping to oklab and oklch spaces.

The difficulty with the oklab and oklch space is that, when used correctly, they produce extremely out-of-gamut colors. For example, suppose I like this particular blue (I think it's oklab(46.64% 0 -0.32)), and I want to do a gradient from black to white through that blue. Here's that blue in oklab (sorry, I wrote oklch in the images...).

Visualization of the P3 gamut in oklab space, with a blue color circled

This is what the gradient looks like, with CSS gamut mapping, going from oklab(0% 0 -0.32) through oklab(100% 0 -0.32). It looks great!

Gamut with slicing plane showing gradient from black to white through the previous blue

But there is a problem here! There is a real mapping from points in this 3D space to chromaticity values, and this gradient does not accurately represent those colors.

At the top, at L=99%, the true color is a brilliant blue, but what we see here is almost-white. In the demo app, set gamut mapping to "none" (and enable the various flags), and you'll see this true color.

At the bottom, at L=1%, the chromaticity of the colors is a physically impossible color. In this picture I've added the spectral colors -- everything outside of the convex hull of the spectral colors is not a physically realizable color.

Gamut with spectral colors

The problem with oklab and oklch is that they can produce extremely out of gamut and physically impossible colors. A designer working with these colors, say, on a modern P3 display, may like what they see, but:

The simplest solution that I see to this problem is to bake gamut mapping into the definitions of oklab and oklch.

romainmenke commented 7 months ago

@ccameron-chromium I am sorry, but I am not really following.

To me it seems that there are some misconceptions leading to incorrect conclusions about the new color notations, interpolation and gamut mapping.

I am not sure if this particular issue is the best place to answer these questions.

The focus of this issue is that browsers shipped color notations that can express wide gamut colors without implementing gamut mapping.

New issues for your specific questions would be easier to resolve without causing noise in this issue :)

facelessuser commented 7 months ago

I am sorry, but I am not really following.

@romainmenke I think what is being suggested is just to map the points more 3 dimensionally instead of just mapping by reducing chroma in one dimension. It's mapping the geometry of a wider gamut surface down to a smaller gamut surface. This sacrifices preserving lightness, chroma, and hue to gain something the suggester thinks would be more intuitive. What is better can be subjective based on what your intent is.

CSS has chosen to preserve as much of the original color intent as possible by only reducing chroma (though some minor hue and lightness are sacrificed with the clipping via MINDE).

It is like what is expressed in this Oklab gamut mapping article. Do you just project along the chroma dimension? Or do you project in the lightness dimension as well, and if so, then by what degree do you project in the lightness dimension, or how much original lightness are you willing to sacrifice to get what you think is a "better" color? The suggestion being made is to do this more in 3 dimensions, most likely sacrificing more hue and lightness than CSS currently does.

EDIT: I do realize I am oversimplifying the 3-dimensional transform being suggested.

The focus of this issue is that browsers shipped color notations that can express wide gamut colors without implementing gamut mapping.

New issues for your specific questions would be easier to resolve without causing noise in this issue :)

I agree, this probably deserves a separate topic if a different algorithm wants to be discussed.

svgeesus commented 7 months ago

The difficulty with the oklab and oklch space is that, when used correctly, they produce extremely out-of-gamut colors.

This is factually incorrect. Highly out of gamut colors can be produced in pretty much any colorspace, including sRGB.

ccameron-chromium commented 5 months ago

The difficulty with the oklab and oklch space is that, when used correctly, they produce extremely out-of-gamut colors.

This is factually incorrect. Highly out of gamut colors can be produced in pretty much any colorspace, including sRGB.

I think that the difference is whether or not it is obvious to the content author when they are entering the danger zone.

If specifying colors in an RGB color space, there's the clear signal that "if the parameters are outside of the [0,1] interval, then I'm playing with fire". True, one can specify color(srgb 0 -1 999), but it's clear that that is to set oneself up for a bad day.

Meanwhile, in something like oklab and oklch, the guardrails are less obvious. In the example from the codepen, the endpoints are oklch(90% 10% 0deg) and oklch(90% 90% 0deg). One of these is in the sRGB gamut and the other one is way outside of the the gamut of any existing monitor. Which is which? It's not obvious just by reading them.

It turns out that oklch(90% 90% 0deg) is the one that is way far outside of the gamut of what any existing monitor can produce. It's equivalent to color(rec2020 1.295 0.434 0.797).

For this reason, I think that we should provide content authors with a space that is perceptually uniform but does not suffer from this problem. A cylindrical space something like okhsl would be nice. (That particular formulation is very tightly tied to the sRGB gamut, so it won't do as a verbatim drop-in). The CSS gamut mapping algorithm has the effect of cylinder-ifying the oklab and oklch spaces.

mirisuzanne commented 5 months ago

Sure, there might be an even more perfect color space down the road. It's totally theoretical, but the theory is great. And if that perfect space ever ships in CSS, colors will continue to go out-of-gamut. And authors should get better behavior than random clipping when that happens.

We can't put this on authors, and expect them to just keep all their colors in the gamut. Because the web (by design) is an unreliable context. A cylindrical space with integer boundaries won't change that. We can't expect authors to carefully manage their colors across a web for everyone, on everything. Even the most carefully crafted rec2020 colors will continue to go outside some remaining sRGB monitors. When that happens, CSS should try and help provide a 'close match' for the majority of use-cases, rather than throwing up our hands.

When clipping is anywhere close to author intent, it's pure luck. That's not a solution, it's a stopped clock. We need an approach to out-of-gamut colors that attempts to maintain author intent. Now that browsers have shipped color-mix() and 'perceptually uniform' spaces, that need is even more urgent.

The default behavior should help get 90% of use-cases close-enough. And then we can provide additional tools for authors that need additional precision around the edges.

ccameron-chromium commented 5 months ago

We can't put this on authors, and expect them to just keep all their colors in the gamut.

I agree with this statement. It shouldn't be expected that an author ensure that all colors be in the gamut of the target device.

What concerns me is something slightly different: Should an author specify a color when they do not (or physically cannot) know what that color actually looks like?

Should an author specify colors that are very far outside of the gamut of any existing display (including the one that the author is using)?

Should an author specify colors that do not physically exist?

eeeps commented 5 months ago

Should an author specify a color when they do not (or physically cannot) know what that color actually looks like?

"How do we help authors make sensible choices?" is a good question, but it's not the question that's being posed here, which is "how do we best adapt arbitrary content for users on varied hardware"?

And doing worse things for users – especially users on less-capable devices – is not a good answer to the "how do we help authors" question.

mirisuzanne commented 5 months ago

If the concern is about color formats that give easy access to a very wide gamut, rather than a concern with adapting those colors for display, then browsers shipped the wrong half of the spec. Now authors are encouraged to use the fully interop/supported wide gamut formats, and there's absolutely no safety net in place to ensure those formats work for people on the other end. This is what has me confused.

We can't put this on authors, and expect them to just keep all their colors in the gamut.

I agree with this statement. It shouldn't be expected that an author ensure that all colors be in the gamut of the target device.

In that case, a solution that is specific to (ok)l** formats is not a solution to this issue. We can't 'bake gamut mapping' into a few wide-gamut formats and be done with it. We would still need gamut mapping for other wide-gamut formats, which may still render on narrower-gamut displays. If we want to also change how a few formats work, that should be a separate issue for discussion.

Trying to move gamut mapping forward, I see a few options on the table.

Did I miss something? Can we narrow in on a path forward?

ccameron-chromium commented 5 months ago

Can we bring examples to a telecon

I would love to discuss this in a more interactive format!

authors are encouraged to use the fully interop/supported wide gamut formats, and there's absolutely no safety net in place to ensure those formats work for people on the other end.

Yes, I agree that we made mistakes in the specification here, sorry. I'm trying to straighten things out to provide a user-friendly perceptually-uniform color space. I was hoping to have a fix to test in Canary ready by now, but holidays and things have slowed things down.

We can't put this on authors, and expect them to just keep all their colors in the gamut.

I agree with this statement. It shouldn't be expected that an author ensure that all colors be in the gamut of the target device.

In that case, a solution that is specific to (ok)l** formats is not a solution to this issue. We can't 'bake gamut mapping' into a few wide-gamut formats and be done with it.

I'll try rephrasing what I meant by this part:

What concerns me is something slightly different: Should an author specify a color when they do not (or physically cannot) know what that color actually looks like?

I think we need to separate two problems here that both fall under the wider umbrella of "stuff is out of gamut".

point-not-real

Can we narrow in on a path forward?

I think we need to separate two core features that are being discussed.

I think that we should narrow our focus to Feature A: Provide a user-friendly percetually-uniform color space. Maybe we can patch up oklab and oklch to do this, or maybe we should introduce something else that avoids producing meaningless values.

I don't think we should try to solve Feature B (general gamut mapping) as a way to fix problems in a particular solution for Feature A. I also think that Feature B should not be solved at this level of the stack.

To respond to the specific points you brought up:

romainmenke commented 5 months ago

@ccameron-chromium Am I reading it correctly that you would prefer gamut mapping to be completely removed from the specification?

I think this is very problematic at this point in time because so much work has been done with gamut mapping in mind. It is not a part that can be dropped and that the remainder of css-color-4 still makes sense.

That is also the core of this issue. Implementers shipped part of the specification but didn't ship gamut mapping. So authors are now discovering the various ways in which color notations have weird outcomes.

Are there specific hurdles that make implementation impossible?


For the purpose of this issue it might be best to discuss alternatives in separate issues and link back to them here?

Then this issue can remain focussed on the fact that gamut mapping is an integral part of css-color-4 but hasn't been implemented.

eeeps commented 5 months ago

We are only trying to solve this problem because we see it as a solution to the problem of "impose meaning on physically meaningless colors values"

I am interested in a standard solution to gamut mapping as an author because I want to be able to – for instance – test my designs on one color e-ink display, and not on every color e-ink display. Which, in the world you describe, might map things using very different strategies. Authors want to be able to "soft-proof" what wide-gamut colors are going to look like on lower-gamut displays and get predictable results.

mirisuzanne commented 5 months ago

I agree with @romainmenke and @eeeps here. I'd add that color-mix() and the relative color syntax (which are very popular proposals) also lead to "shipping colors you haven't seen". Color level 4 and 5 are build around these tools for generating and adjusting colors on the fly, in a way that relies heavily on gamut mapping to clean up the edge cases.

I can see some argument that some people use gamut-specific formats because they are aiming to stay inside a particular gamut. But I don't trust that assumption to hold up reliably enough we can build a heuristic around it. Often people use formats because that's the output of a third-party tool, or because someone told them they could access 'more' colors that way.

But mostly I'm skeptical that out-of-gamut colors map neatly into distinct problems, in a way that allows us to address one or the other. Even when you remove non-visible colors, these formats are designed to not have a gamut. That's part of the feature. In order for these formats to be perceptually uniform, they need the odd shape. And in order to be future-proof, they need to cover a large range of possible colors. Taken together, that means:

If we impose a baked-in limit for (ok)lab/lch formats, I expect:

For the purpose of this issue it might be best to discuss alternatives in separate issues and link back to them here?

Then this issue can remain focussed on the fact that gamut mapping is an integral part of css-color-4 but hasn't been implemented.

I agree. It seems to me now that the core issue here is not about the specifics of a gamut-mapping algorithm, but a complete objection to the path the CSSWG has taken in colors 4 and 5. If we're going to move forward with any solution, we have to first agree on what problem we're trying to solve.

ccameron-chromium commented 5 months ago

Let's try to discuss more at the F2F. It's going to be a bit hard to fit things in (there are two almost-conflicting color conferences), but I think there might be an hour on Monday we can do.

Color level 4 and 5 are build around these tools for generating and adjusting colors on the fly, in a way that relies heavily on gamut mapping to clean up the edge cases.

This specific method of cleaning up edge cases has a lot of effects ([0], [1], [2], [3]), and it took sitting down and trying to implement this to see them. The lesson I take from this is that we needed to have more interaction between implementation and spec in developing these ideas, and that we as implementers should have given stronger feedback earlier in the process.

I'd add that color-mix() and the relative color syntax (which are very popular proposals) also lead to "shipping colors you haven't seen".

Using relative-color syntax to manipulate oklch colors will run into exactly these problems, and so I agree that we need to get the spec sorted out before moving forward.

With color-mix() it's usually safe (if the two endpoints that you are mixing are both within in a given gamut, then, because the gamuts are mostly-most-of-the-time convex, the line segment between them will still be in the gamut).

  • Planning for future devices will always result in a wide swath of currently-unsupported colors. There's not a way to design future-proof formats without significant gamut-mapping in the present
  • We end up impacting wider gamut colors that are not yet supported, but are 'real' colors. That's just inventing a new arbitrary gamut, and then we need new formats again every 10 years.

Using the syntax of color(srgb R G B), one can specify any color (at one's own risk), so the ability to specify colors is there, although it might not be the most ergonomic API, and I think that's okay. We can add more ergonomic APIs for new hardware as it comes available.

[0] I worry that this approach of future-proofing will have the opposite effect that it is intending. The current approach encourages content authors today to specify things like the color oklch(90% 90 0deg). They will use that color because they like how it looks on their (probably P3) display.

But in 10 years, that color will likely look extremely different, and the content will not look how the author intended for it to look. Then we'll find ourselves having to fix the problem that existing content looks wrong on new displays, and I suspect that the "fix" will be to retroactively impose something like gamut mapping to P3 to all oklch colors.

As an example of how extreme this can be, you can see this page which shows something very close to what oklch(90% 90 0deg) really is. (Needs Macbook Pro and Chromium ... and it's a bug so hopefully the bug will be fixed at some point).

[1] Breaking color matching with images and canvases.

[2] 2D canvas ambiguity. What space should CSS colors be mapped to in 2D canvases, where the target gamut is unknowable? Especially for things like floating-point 2D canvases

[3] Several numerical and definitional issues with the algorithm.

The way forward that I propose is to introduce safe, ergonomic color spaces like okhsl, along with an extension like okhsl-p3 (and maybe okhsl-rec2020). That way, authors can create content knowing "I'm specifying exactly what I'm seeing and nothing more", and removes the need for gamut mapping to clean up edge cases.

romainmenke commented 5 months ago

I think that [0] doesn't actually make sense. No one is going to manually pick numbers and use trial and error to define a color palette with oklch.

Designers are going to continue using color pickers and CSS authors will continue to copy/paste the specified colors into CSS source code.

If someone writes oklch(90% 90% 0deg) they show clear intent that they want a very bright and extremely high chroma pink. That it is gamut mapped to a dull pink is only a limitation of the current hardware.


I am also not reading anything inherently unimplementable about gamut mapping. Only that it might give results that in very specific cases might surprise authors.

Without gamut mapping however we are stuck with a bunch of capabilities that have been shipped and that continually surprise authors in the most basic cases.

ccameron-chromium commented 5 months ago

Including a photo of the test page at https://ccameron-chromium.github.io/webgl-examples/oklab.html

magenta

In this photo, the top color is the exact color that is specified by oklch(90% 90% 0deg). I don't think authors should specify the color oklch(90% 90% 0deg) intending anything other than exactly that color of that top bar.

brianosman commented 5 months ago

I think that [0] doesn't actually make sense. No one is going to manually pick numbers and use trial and error to define a color palette with oklch.

How long before someone makes an oklch color-picker, though? And as soon as that exists, designers will flock to it, because "it lets me pick from more colors". These are might be unintended consequences, but they're not un-forseeable.

romainmenke commented 5 months ago

How long before someone makes an oklch color-picker, though? And as soon as that exists, designers will flock to it, because "it lets me pick from more colors". These are might be unintended consequences, but they're not un-forseeable.

This already exists and it doesn't have any of the issues mentioned above. It is aware and informative about color space gamuts.

https://oklch.com/


Edit:

A color picker based on oklch is not the same as manually picking numbers and using trial and error. A color picker gives instant feedback on all aspects of the chosen color.

Manually picking numbers and trying it out in something like a codepen would only show a representation of the intent of writing oklch(90% 90% 0deg)

ccameron-chromium commented 5 months ago

I think that [0] doesn't actually make sense.

I'm sorry. I tried very hard to understand what's going on here, and spent a long time trying to write a concise response.

romainmenke commented 5 months ago

Let me try and rephrase :)

I read [0] as :

The gamut mapped value for oklch(90% 90% 0deg) is very far from the color it actually describes. This isn't future proof because the displayed color will change over time. This change is not what the author intended.


I disagree with that.

If an author writes oklch(90% 90% 0deg) then they are stating that they want a very bright and extremely high chroma pink. That change over time as hardware improves is what the author intends.

romainmenke commented 5 months ago

@ccameron-chromium On re-reading https://github.com/w3c/csswg-drafts/issues/9449#issuecomment-1929768236 I am unsure if you found my reply hard to understand or if you tried very hard to understand in general what is going on with gamut mapping and this problem space.

If the second, then I apologize. It is not my intention to drive this thread towards a negative discourse.

I do appreciate all the hard work that you are putting in with prototyping gamut mapping and giving feedback here.

svgeesus commented 5 months ago

No one is going to manually pick numbers and use trial and error to define a color palette with oklch.

How long before someone makes an oklch color-picker, though?

Has already happened: https://oklch.com/

brianosman commented 5 months ago

This already exists and it doesn't have any of the issues mentioned above. It is aware and informative about color space gamuts.

https://oklch.com/

Right - because that picker was created by someone that generally understands the issues. Alas, this is the web, and making color pickers is easy. The existence of a single, good LCH picker won't preclude many more from being created. (Yes, many of these are technically just LCH pickers, but the authors are sure to "update" them. Also note that several of those have no gamut guardrails.)

You might disagree that it's a problem, but people ARE going to be picking and using colors that are well beyond the capabilities of any device (and therefore, not the color they saw when they picked it).

facelessuser commented 5 months ago

Okhsl is not a perfect representation of sRGB, it is an approximation. You can get away with clipping the colors within its bounds, that is what the author of Okhsl is basically doing in his color picker. Okhsl can create out of gamut colors, not greatly out of gamut, but out of gamut none the less.

You could create variants for P3 and Rec. 2020 using the original script provided by the author to generate the necessary constants: https://colab.research.google.com/drive/1JdXHhEyjjEE--19ZPH1bZV_LiGQBndzs. I've done this in experiments, and it does work OK, but wider gamuts are more fuzzy at the bounds. But there are less desirable aspects of these spaces when looking at them as a general purpose spaces.

Screenshot 2024-02-06 at 9 39 18 AM Screenshot 2024-02-06 at 9 34 59 AM

I'm sure these could be implemented in CSS preprocessor or even external color pickers if existing OkLCh color pickers are not sufficient, but I'm not sure CSS needs these spaces internally, but I'm sure some will disagree. I'm also not sure why color pickers such as https://oklch.com/ would not be sufficient either. People will adapt and tooling will emerge to help people.

mirisuzanne commented 5 months ago

I agree with @ccameron-chromium that people are going to get this wrong, and we're already seeing it. Play with https://gradient.style/ for a bit, and you will generate lovely vibrant gradients… that have very little in common with the actual colors being requested. If you just want a nice vibrant gradient, it works great to crank up the chroma and lightness, and then adjust (arbitrary clipped) hues to get what looks good.

But I disagree about the solution to that problem. Gamut mapping isn't just there to help give authors more expected output - it's the only way we can preserve legibility for readers. For the majority of color-use in CSS, maintaining predictable contrast has to be our top priority. That's true in color spaces that are gamut-less, and also when clamping rec-2020 colors for an sRGB display. It doesn't matter how the authors got out of gamut, the priority of CSS should be to maintain contrast as well as possible when it happens.

I understand that it's not the right solution for every use-case, in the same way that overflow:visible is not always appropriate. But then we should provide an explicit escape-hatch for those situations.

ccameron-chromium commented 4 months ago

Some questions to ponder for discussion at the F2F:

Question 1: Suppose an author has specified the color oklch(90% 90% 0deg). What is the desired behavior? With reference to the previous image, is the desired behavior that on sRGB displays the viewer will see the second bar (srgb(100% 79% 86%)) but on some future display the viewer will see the truly specified color ("brilliant magenta")?

By desired behavior, we should address this both as "is this what the spec currently says should happen" versus "is this desirable behavior (regardless of the spec)".

Question 2: Suppose that the same color, color(display-p3 1 0 0) is specified in an image, in CSS, and in a canvas (2D, WebGL, or WebGPU). That page is then viewed on an sRGB display. Should the resulting color be the same for the image, the CSS, and the canvas?

Question 3: Suppose a page is authored on a wide color gamut display (say P3) and is then viewed on a narrow gamut display (say sRGB). The viewing operating system is capable of mapping the content to the display's capabilities (either by SMPTE ST 2086 or an ICC profile). Should the mapping of P3 to sRGB be done by the CSS algorithm or by the operating system?

[edit: added Q4] Question 4: Should a content author on a specific display specify colors significantly outside of the gamut of their display?

There are several other issues that I'd like to discuss (especially with respect to the numerical properties of the specified algorithm), but might overrun the time we will have.

romainmenke commented 4 months ago

For Question 1 I have been thinking about color-scheme.

The color-scheme property allows an element to indicate which color schemes it is designed to be rendered with.

Maybe something similar is needed for gamuts? So that authors can declare for an entire page or per element which gamuts they tested with and are supported by their design. If set to display-p3 then everything will be gamut mapped to display-p3 even on hardware that supports large gamuts

ccameron-chromium commented 4 months ago

Chrome Canary now has the following in chrome://flags.

flag

This narrow change (or something like it) is in my opinion the best path forward for this issue, based on the implications of what I think are the best answers to the above questions.

(Note that the implementation is incomplete and you may see lots of grey if you're on an unsupported path, and there are some numerical shortcuts taken).

romainmenke commented 4 months ago

@ccameron-chromium this implementation is still limited to only oklch and oklab? Any other notation isn't gamut mapped?

argyleink commented 4 months ago

@ccameron-chromium this implementation is still limited to only oklch and oklab? Any other notation isn't gamut mapped?

LCH was gamut mapped for me when testing

jamesnw commented 4 months ago

Based on my reading of the current changes in Chrome Canary, and experimentation, it looks like this is only applied to oklab and oklch.

Gamut mapped- https://www.oddcontrast.com/#oklab__oklab(90~_0.9_0)__oklab(0~_0_0) https://www.oddcontrast.com/#oklch__oklch(0.9_0.9_0)__oklch(0_0_0)

Example:

image

Not gamut mapped- https://www.oddcontrast.com/#lab__lab(77.003_276.19_8.2413)__lab(0_0_0) https://www.oddcontrast.com/#lch__lch(77.003_276.32_1.7091)__lch(0_0_0)

Example:

image
argyleink commented 4 months ago

ah yes, confirm, i had a typo in my demo. lch is still clipped. here's linked to try:

LCH high vibrance palettes

OKLCH high vibrance palettes

mirisuzanne commented 4 months ago

I opened this issue to try and step away from the specific numerical mechanics of an algorithm, and ensure that we first agree on the desired behavior. At that point we can better judge if the given algorithms achieve the goal we've agreed on. Without that, the mathematical discussions just seem to go in circles – solving different problems.

I also think it's pretty essential that in conversations about CSS color we don't lose track of the far most common use-cases – setting background and foreground colors. We set them on the overall page, on sections of the page, individual components, buttons, links, inputs, etc. While color-matching across images and canvas elements is a real use-case that we should consider and provide tools far, it is extremely less common than day-to-day backgrounds and foregrounds. So when discussing the default behavior for out-of-gamut colors, I think it would be a mistake to leave out the central question:

When we look at the adjustments being applied in the example above, it complicates the question of 'which result is closer' to the original:

locking oklch(90% 90% 0deg) to the p3 gamut with colorjs.io, mapping results in oklch(88.8% 0.0864 356) while clipping gives oklch(74% 0.273 343) (farther on both lightness and hue, only closer in chroma) (I grayed out the first result, since it can't be displayed without either clipping or mapping)

the colorjs.io code used ```js let color = new Color("oklch(90% 90% 0deg)"); color.clone().toGamut({method:'css', space:'p3'}); color.clone().toGamut({method:'clip', space:'p3'}); ```

Do we mean that individual rgb channels are maintained? Is that a priority for readers? Or are we trying to maintain more perceptual concepts like lightness, hue, and chroma? The clipping variation maintains high chroma by pushing both lightness and hue farther from the requested color.

(ok) l c h
requested 90% 0.36 0deg
mapped 88.8% 0.0864 356deg
clipped 74% 0.273 343deg
ccameron-chromium commented 4 months ago

I opened this issue to try and step away from the specific numerical mechanics of an algorithm Without that, the mathematical discussions just seem to go in circles – solving different problems.

Agree.

While color-matching across images and canvas elements is a real use-case that we should consider and provide tools far, it is extremely less common than day-to-day backgrounds and foregrounds.

Color matching has worked for sRGB content, in all browsers, effectively forever. For images with non-sRGB color profiles, I think it's several years for all browsers (and >10 for at least one). So this is a long established capability, and I would not want to be cavalier about regressing it.

The breaking of color matching is also a warning sign that we're probably doing things wrong. Recent anecdote: Color matching for HLG/PQ HDR images is impossible, and that was always a wrinkle in the discussion. Over the last two years we've found that HLG/PQ images are "the Wrong Way To Do It", and no phones shoot HLG/PQ HDR images. Instead, all phones that shoot HDR shoot gainmap images, which are capable of color matching. Color matching isn't the only reason why gainmap images are "The Right Way To Do It", but it is an indicative one. (In my opinion, the way to enable things like HDR for CSS is a dual-grading approach similar to gainmaps).

(*) I'm hesitant to say that indeed there are exactly zero phones that shoot HLG/PQ still images -- my certainty is high, but not 100%.

So when discussing the default behavior for out-of-gamut colors, I think it would be a mistake to leave out the central question:

  • When the viewer is not able to see the exact foreground/background colors requested, what aspects of those colors should ideally be preserved?

I'm going to go around in circles a bit here (sorry!), but I think that whether or not we need to answer that question depends on the answer to the question of "should content authors be specifying CSS colors that they themselves cannot produce on the monitor they are using to author the content, or that colors that physically don't exist?"

Content authors are effectively limited to seeing P3 today (for widely available hardware), and so the only problem we are then solving is the problem of "map P3 to sRGB".

In terms of "what aspects of those colors should be preserved", when limited to "map P3 to sRGB", and with a focus on accessibility and legibility, the answer I've come to is "it really doesn't matter". I've been unable to create a page that is legible in P3 but is not legible in sRGB, irrespective of what gamut mapping is done. Please let me know if you're able to do so. I've really tried!

I've only run that experiment with P3 displays, because I only have an Apple XDR display (ahem). I haven't seen if this generalizes to the "map Rec2020 to sRGB" problem because I've never seen Rec2020. My guess that legibility would not be affected (and if a page's accessibility is affected by sRGB-versus-Rec2020 gamut, then the page must be atrociously inaccessible for the large fraction of the population perceives a 2D or 1D gamut).

With that in mind, I see CSS gamut mapping as solving the problem of: "we want to enable content authors to specify colors that no commercially available display can produce, and colors that are physically impossible".

In my opinion we should instead solve the problem of "enable content authors to use perceptually uniform color schemes that stay within commonly available screen gamuts" directly, rather than introducing a harder problem in the middle.

faceless2 commented 4 months ago

"we want to enable content authors to specify colors that no commercially available display can produce, and colors that are physically impossible"

Creating a gradient or an animation between two in-gamut colors can easily result in an out-of-gamut color, and an author has no practical way of telling if this is going to happen. The only way to prevent this is to force interpolation into a space where all colors are always in-gamut.

ccameron-chromium commented 4 months ago

"we want to enable content authors to specify colors that no commercially available display can produce, and colors that are physically impossible"

Creating a gradient or an animation between two in-gamut colors can easily result in an out-of-gamut color, and an author has no practical way of telling if this is going to happen.

From the earlier comment

if the two endpoints that you are mixing are both within in a given gamut, then, because the gamuts are mostly-most-of-the-time convex, the line segment between them will still be in the gamut

For completeness, the only way one can land a non-trivial way from the gamut would be to do a hue-based gradient at extreme saturation, and even that wouldn't stray all too far, since the gamuts are aren't too far from being conical in oklab space.

The only way to prevent this is to force interpolation into a space where all colors are always in-gamut.

I think that's a direction to a robust solution to all of the problems here!

If we were to bake gamut mapping (to, say, rec2020) into the definition of oklab and oklch, then that would make the oklch(90% 90% 0deg) problems in this issue go away.

True, baking a specific gamut mapping is not forever-future-proof. But it's better to update the spec as needed than to have behavior update itself in unpredictable ways over time (e.g, as a "yes" answer to Q1 above would).