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] Channel clipping breaks author expectations, especially when using 'perceptually uniform' spaces #9449

Open
mirisuzanne opened this issue Oct 9, 2023 · 181 comments
Labels
css-color-4 Current Work

Comments

@mirisuzanne
Copy link
Contributor

mirisuzanne commented Oct 9, 2023

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.

  • Here's a codepen demo showing two colors with the same hue and lightness values, but vastly different perceptual lightness in the results.
  • Here's a tool for comparing gamut-mapping options - set the lightness low (eg 0.25) and clip gives colors which are over-saturated/too light, set the lightness high (eg .85) and clip gives over-saturated and too dark.

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

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

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

jamesnw commented Oct 11, 2023

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- #8794

@svgeesus
Copy link
Contributor

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)

Browsers don't support this part of CSS Color 4 yet.
So if you want to have correct colors on all displays you should include both narrow and wide gamut color values. This new plugin helps you to do just that.https://t.co/FyYQOElo90 pic.twitter.com/JSMD2ddEzT

— CSS Tools (@css_tools_) October 9, 2023

image

@svgeesus
Copy link
Contributor

Here is that PostCSS GMA plugin btw

@svgeesus
Copy link
Contributor

svgeesus commented Oct 11, 2023

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

jamesnw commented Oct 12, 2023

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

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

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

jamesnw commented Nov 29, 2023

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

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

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

mirisuzanne commented Nov 30, 2023

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

romainmenke commented Nov 30, 2023

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.

  • red is color(display-p3 0.92 0.2 0.14) (notice that the green and blue channels have different non-zero values)
  • color(display-p3 1 0 0) is a slightly different hue and is also brighter than red

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.


  • red is the purest "red" in the srgb color space
  • color(display-p3 1 0 0) is the purest "red" in the display-p3 color space

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

ccameron-chromium commented Dec 1, 2023

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 author of the content has no way to visualize what they've truly specified.
  • The author may not like the representation on a more powerful display than the one they authored the content on.
  • The author has no way to constrain what they've authored to be exactly what they see on their authoring display.

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

@romainmenke
Copy link
Member

romainmenke commented Dec 1, 2023

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

facelessuser commented Dec 1, 2023

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

svgeesus commented Dec 1, 2023

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.

@w3c w3c deleted a comment from sobotka Dec 15, 2023
@ccameron-chromium
Copy link

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

mirisuzanne commented Jan 24, 2024

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.

@mirisuzanne mirisuzanne changed the title [css-color-4] (ok)lch implementations break the entire purpose for authors [css-color-4] Channel clipping breaks author expectations, especially when using 'perceptually uniform' spaces Jan 24, 2024
@ccameron-chromium
Copy link

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

eeeps commented Jan 24, 2024

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

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.

  • If the priority is to preserve lightness and hue, at the expense of chroma (this matches my experience in the field, but my experience is not universal) the current spec does that decently well using oklch and ΔEOK.
    • In the thread above, @facelessuser points out some tradeoffs with oklch vs lch as the base model, which would warrant a a side-by-side comparison and decision.
  • Is there a solid counter-proposal for a multi-channel mapping algorithm? Can we bring examples to a telecon, and discuss the tradeoffs that we're willing to make?
  • The other hinted-at suggestion, which I may be reading incorrectly, is that different formats represent different authors intents in some heuristic way? Which might lead us to different mapping for different color formats? I can see some logic to that - I would reach for different formats to achieve different goals, but it leads to some strange outcomes:
    • The same out-of-gamut color, on the same narrower-gamut device, is mapped to a different result for reasons that may not be obvious.
    • On the plus side, we're providing tools to change the format of a color, so authors would have an escape hatch?
    • Browsers have to maintain several mapping algorithms? Is that reasonable?

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

@eeeps
Copy link
Contributor

eeeps commented Mar 27, 2024

@ccameron-chromium

If someone can create a page that uses P3 colors that the feel gets mauled by clipping to sRGB, then please send a P3 snapshot of it. I've tried as hard as I can and I cannot produce such a thing.

This is a bit of a contrived example, but if we take color(display-p3 1 0.25 0.3), clipping gives #ff1b43 and the spec algorithm gives #ff4050. On a #242424 background, the spec's result (which is noticeably, if not hugely, lighter) passes the WCAG AA contrast requirement for normal text while the clipped result fails.

I don't know how many P3 colors' mappings fall on opposite sides of the WCAG 4.5:1 knife's edge like this when mapped to sRGB, but I do know that when mapping to smaller spaces, preserving lightness/contrast is the best thing to do by default; for folks who want chroma, there are media queries.

Edit – here's a page showing the difference: https://codepen.io/eeeps/pen/yLroqyE?editors=1100

@facelessuser
Copy link

This is a bit of a contrived example, but if we take color(display-p3 1 0.25 0.3), clipping gives #ff1b43 and the spec algorithm gives #ff4050. On a #242424 background, the spec's result (which is noticeably, if not hugely, lighter) passes the WCAG AA contrast requirement for normal text while the clipped result fails.

I think flaws can be found in both clipping and chroma reduction (CSS's recommendation). You can also find arguments for both cases depending on which cases you choose to highlight, but when we are talking about contrast, chroma reduction is simply easier to work with. Chroma reduction will not always be the best if we are talking about displaying images, it's an entirely different use case. This is why I think it is important to pin down exactly what CSS is trying to solve. When you are arguing multiple, incompatible problems, it is difficult to come to a resolution.

This shows what happens with various approaches. The case below is using high chroma (0.4 in OkLCh) and a lightness range of 0 - 1 and a hue range of 0 - 360. The first is clipping, the second is chroma reduction, and the third is the experimental Chromium approach which is kind of a middle ground (gamut map to Rec 2020 and clip to lesser gamuts).

clip-constant-chroma

chroma-reduction-constant-chroma

chromium

@ccameron-chromium
Copy link

ccameron-chromium commented Mar 27, 2024

@ccameron-chromium

If someone can create a page that uses P3 colors that the feel gets mauled by clipping to sRGB, then please send a P3 snapshot of it. I've tried as hard as I can and I cannot produce such a thing.

This is a bit of a contrived example, but if we take color(display-p3 1 0.25 0.3), clipping gives #ff1b43 and the spec algorithm gives #ff4050. On a #242424 background, the spec's result (which is noticeably, if not hugely, lighter) passes the WCAG AA contrast requirement for normal text while the clipped result fails.

If one changes the background by one pixel value from#242424 to #252525, then #ff4050 also fails, so this example shows a place where the result of gamut mapping very marginally passed while clipping marginally failed.

Meanwhile color(display-p3 1 0.25 0.3) passes all the way up to #292929, where both sRGB results fail hard.

[EDIT-long-after-the-fact]: There are also examples that fall the other way. For example: color(display-p3 0 1 0) is clipped to #00FF00, passes on a background of #616161, but is gamut mapped preserving lightness to #00FB28 fails. The direction that I want us to go is to a place that allows the features CSSGM is trying to provide but that doesn't break color matching. Preserving lightness isn't acceptable for image/video/canvas, and I'm trying to make the case that it isn't great for CSS either. If we were to define CSSGM loosely as "minimize the perceptual difference between the specified and displayed color while preserving hue", then that is acceptable to apply to image/video/canvas, so we wouldn't need to break color matching, and we'd go implement it!

@ccameron-chromium
Copy link

ccameron-chromium commented Mar 27, 2024

So if I create a highly unusual ICC profile where the A2B0 tag says that pixel value (1,2,3) maps to XYZD50 value (0.385, 0.717, 0.097), then this pixel will exactly match the color(srgb 0 1 0), because they have the same value in XYZD50 space.

This turns out to be a can of worms.

Chromium's CMS implementation of general A2B parsing (both for perceptual and for relative rendering intents) has bugs. We interpret XYZD50 wrong and also likely interpret Lab wrong. From what I can tell, there isn't a single profile that uses A2B that is interoperable. (EDIT: this might be pessimistic)

I'll have to write up an example profile to verify this, but I'm fairly certain that all browsers prioritize A2B0 (perceptual mapping) above A2B1 (relative mapping).

Nope! In the presence of both A2B0 (perceptual) and A2B1 (relative), Chromium and Firefox use A2B0 while WebKit prefers A2B1. When only A2B0 is present then that is used by all three. And only Chromium parsed the A2B1-only profiles I produced (but they may be malformed for all I know). And then you layer the bugs on top of this.

So A2B ICC profiles are interoperability wasteland. I stick by the theory that one can color match via the PCS (and it looks like that's the case), but that theory would need to wade through a wasteland of bugs to be of any value, and it's also a fairly marginal case.

We should also add some WPT tests.

Edit: The bugs only happens when some fields are omitted, so I was able to produce the aforementioned image where pixel value (1,2,3) map to rgb(0%,50%,0%). This color matches on WebKit and Chromium, but has some issues on Gecko that I'm looking into (likely more ICC bugs).
pixel-123-but-green

@ccameron-chromium
Copy link

@LeaVerou said:

A point I made in this discussion that I haven't seen made in the thread:

@ccameron-chromium: You mentioned several times how being able to specify imaginary or wildly out of gamut colors is "dangerous" because authors are specifying colors without really realizing the actual colors they are specifying, and when display technology evolves, their output will actually be degraded because they were relying on the mapped color.

However, gamut mapping is a red herring here. As long as you can specify nonexistent colors, you will have this problem no matter what since you have to display them somehow. You can have this problem with clipping as well, people can still rely on the clipped color.

Yes, this is true. No matter what we do with this sort of input, it's going to be "wrong" by some definition, and clipping is just a "simple wrong". And it's also very hard to identify what input is intentional versus unintentional.

This is why I hope that we can put effort into enabling a safe parameter space (be that okhsl, or some built-in guardrails for oklch). If we fix that problem, then users can take advantage of the nice features of things like relative color syntax, without running this risk.

@eeeps
Copy link
Contributor

eeeps commented Mar 28, 2024

@ccameron-chromium I think what you're proposing is special gamut mapping for oklch, a new space to replace it for color-picking-in-code purposes, and clipping for other color spaces. Correct me if I'm wrong! But if that's the case -- don't we need gamut mapping for all unbounded syntaxes? In Chrome right now, this includes anything you can write with color().

@facelessuser
Copy link

facelessuser commented Mar 28, 2024

Technically, gamut mapping was the guard rails, but I guess now we want to pre-apply that to a space so people aren't confused?

@ccameron-chromium
Copy link

@ccameron-chromium I think what you're proposing is

special gamut mapping for oklch, a new space to replace it for color-picking-in-code purposes

Yes -- either a fix to oklch or an alternative that avoids its problems.

and clipping for other color spaces

Not quite. I want to ensure color matching, interoperability across browsers, and battery life. Clipping provides this (sort-of), but it isn't great. As we get far out (like, beyond Rec2020), there is a big benefit to preserving hue, and the cost is fairly low. But I wouldn't want to prescribe how to go from P3 to sRGB.

don't we need gamut mapping for all unbounded syntaxes? In Chrome right now, this includes anything you can write with color().

It's clear to a content author when they are going out of the sRGB gamut in color(srgb R G B) syntax because their RGB parameters are no longer in [0,1]. The problem with oklch is that there is no clear signal that the color is unreasonable or non-existent.

@facelessuser
Copy link

Part of me feels like if people are scared of getting a color out of gamut, tooling could identify such cases with warnings and/or squeeze the color into gamut for them. It wouldn't surprise me if such tooling crops up if that is a real concern. I'm not against introducing spaces that can accommodate this, but I feel like that is a separate issue to providing a gamut mapping algorithm. Both can be pursued though.

@jamesnw
Copy link

jamesnw commented Mar 28, 2024

It's clear to a content author when they are going out of the sRGB gamut in color(srgb R G B) syntax because their RGB parameters are no longer in [0,1]. The problem with oklch is that there is no clear signal that the color is unreasonable or non-existent.

This is only true of color(srgb RGB) because it specifically refers to the smallest gamut. The predefined display-p3 spaces would have similar guardrails for p3 capable displays, but not for sRGB displays. color(xyz-d50 0.5 0 0.5) seems numerically like a reasonable color but is outside the rec2020 gamut.

Given the non-cubic nature of gamuts, I also don't think it is possible to provide a space that simultaneously is perceptually uniform and provides a clear channel-based signal when a color is out of gamut, without removing a significant portion of colors describable by the space within the guardrails.

I concur with @facelessuser about the need for tooling for providing guidance around out of gamut colors. Some already exists, and I think additional tooling will emerge as consensus is reached about what happens with out of gamut colors.

@eeeps
Copy link
Contributor

eeeps commented Mar 28, 2024

@ccameron-chromium I strongly feel that whether an author specifies oklch(70% 0.34 22), color(rec2020 0.99171 0.13938 0.14796), or color(rgb 123.17% -32.225% 14.91%), they've referred to the same point in XYZ space, and a UA trying to map that to a target display has the same (difficult, tradeoff-y) job ahead of it. I don't think there's enough author intent (or, perhaps, lack of intent) in the color syntax chosen to do better things for users depending on what space the author used.

I agree that a hue-lightness-chroma space that is bounded to common wider-than-sRGB gamuts, sacrificing perceptual uniformity for author WYSIWYG-certainty, would be a useful thing to add, and should be pursued, outside of this issue. But it won't prevent authors from specifying colors outside of their and their users' display gamuts; tooling can help authors; this issue is about figuring out what to do for users.

@LeaVerou
Copy link
Member

@ccameron-chromium said:

Yes, this is true. No matter what we do with this sort of input, it's going to be "wrong" by some definition, and clipping is just a "simple wrong". And it's also very hard to identify what input is intentional versus unintentional.

This is why I hope that we can put effort into enabling a safe parameter space (be that okhsl, or some built-in guardrails for oklch). If we fix that problem, then users can take advantage of the nice features of things like relative color syntax, without running this risk.

These are not mutually exclusive. We can put effort into designing better color spaces for color manipulation AND implementing a decent GMA, which will be needed regardless. You're already clipping, so I don't understand what the reasoning is to not do better. It doesn't stop you from designing new color spaces. The algorithms are already there, just tell us what the performance constraints are and we can get you one that maximizes quality while fitting within them.

@jamesnw
Copy link

jamesnw commented Mar 28, 2024

The slides shared by @ccameron-chromium contained a few assertions I was curious to test, so I wrote a script to iterate through many colors, with results here.

Clipping P3 to sRGB produces extremely high quality results, and almost no existing displays are >>P3.

For colors in p3 and out of sRGB, clip retains chroma better on average, but chroma reduction retains lightness and hue better on average. Chroma reduction has a slightly better DeltaE2000, but it's very close. Overall, the differences aren't large, partly because the magnitude of the change is not large. Clipping P3 to sRGB produces acceptable results, in my mind.

Clipping Rec2020 to sRGB is unknown quality.
Evidence suggests that clipping produces good quality here.
Evidence suggests that CSS gamut mapping produces poor quality.

I'm seeing the same thing as with p3 > sRGB here- clipping retains chroma slightly better, but has much higher shifts to lightness and hue. Chroma reduction again has a slightly better DeltaE2000. I'm not seeing evidence here to point to clipping outperforming chroma reduction.

I'd welcome thoughts on additional metrics to compare, and additional eyes on the results. Looking at the full results, it seems that @ccameron-chromium's point that clipping works well when you're close to the destination gamut holds true. However, the further you need to map, the more of a shift clipping gives, especially in lightness and hue. If you look just at the colors that are outside of rec2020 (which I mapped to p3), chroma reduction almost matches clipping for maintaining chroma, but vastly outperforms clipping for lightness and hue.

@mirisuzanne
Copy link
Contributor Author

Yeah, I find those 'quality' claims a bit strange, considering I've preferred the mapped result in basically all the linked demos. 🤷🏻‍♀️ Clearly we are aiming at different goals.

@ccameron-chromium
Copy link

The algorithms are already there, just tell us what the performance constraints are and we can get you one that maximizes quality while fitting within them.

I think the biggest sticking point is not performance, but rather color matching between CSS, images, video, and canvas. This is a long-shipping feature with interoperability across all browsers, works by default (no extra syntax), for all colors, and for all inputs (including for images with color profiles and perceptual color profiles).

If color matching is to be preserved, then whatever is done with extremely-out-of-gamut colors needs to be done uniformly across all content types.

Re: performance:

Performance matters less and less as you to wider and wider gamuts. I need the option to clip P3 to sRGB because that is actually a critical performance path (fortunately, clipping is good enough there). I may want to clip Rec2020 to sRGB in some extreme case (but it's less likely). No (non-HDR) image or video is going to specify oklch(90% 90% 0deg), so I'm quite okay with that particular pixel requiring some nontrivial math.

I'd like that the browser have some flexibility as to where it draws the line between gamut mapping and clipping. In some circumstances, we may gamut map all the way down to the display's gamut. In some cases, we gamut map to P3 and then clip to the display's gamut (e.g, to color match with something that is being color managed by hardware).

Also of note is that while a GPU can do lots without breaking a sweat, there are browser implementations that render pixels on the CPU, and that comes with some severe restrictions.

Re: chroma reduction:

This was something that I wanted to get into more in the discussion (it's in the slides), but didn't. Chroma reduction isn't acceptable for images and videos and canvases (I think this is generally agreed upon), but it also severely violates the meaning of CSS colors. As an example, this is the true L=1 plane of oklch, darkened so that you can see its intersection with the P3 gamut on a normal display.

L1-planes

It isn't white -- it's very colorful! If one wants that plane to be white, one wants a different space. Imposing transformations on all colors to make it so that this particular plane that isn't white be white is going to complicate things.

Because oklab is a perceptually uniform space (so geometric distances correspond to perceptual distances), I would want to do something roughly equivalent to finding the nearest point to the gamut (in euclidean distance) that has the same hue. That's vaguely what's gone on the right (though math was done sloppily).

Re: interop:

For inputs that are nonsensical, interoperability is probably the most important priority.

All browsers currently clip, and this has a fairly decent interoperability story. If all browsers were to agree to do the aforementioned hue-preserving projection to "something pretty near the display's gamut" (the lazy implementation would be to first hard-code Rec2020, and refine from there) the interop story would be strictly better than clipping.

There would be a discontinuity as different browsers implement it, but if there is alignment on that, I'd be in favor of it.

@romainmenke
Copy link
Member

Trying to convince each other that either clipping or gamut mapping is the one true way forward also doesn't seem to be working :)

There seems to be value in both.
That there are examples where either performs poorly doesn't negate that.

I think it comes down to:

  • is it implementable?
  • how can we give authors an opt-in or opt-out?

I don't agree with the position that needing one more feature is a sign that gamut mapping is a bad idea. We are adding new features all over the place all the time.

@facelessuser
Copy link

This was something that I wanted to get into more in the discussion (it's in the slides), but didn't. Chroma reduction isn't acceptable for images and videos and canvases (I think this is generally agreed upon),

I don't disagree with image and video handling, etc. They are all already within their gamut, and most people just want to the colors to be similar. This is a specific use case.

but it also severely violates the meaning of CSS colors. As an example, this is the true L=1 plane of oklch, darkened so that you can see its intersection with the P3 gamut on a normal display.

It doesn't severely violate the meaning of CSS colors. You can make an argument that all forms of gamut mapping violate the meaning of colors depending on what attribute you focus on. I think chroma reduction retains quite a lot of the meaning of the colors, hue and lightness. Clipping focuses on having similar colorfulness and a hue in the ballpark.

There are plenty of use cases where gamut mapping by preserving lightness is desirable, tones and providing "better" contrast when mixing or creating gradients. I won't make a claim that it will give you perfect contrast, but it will be better. These aren't invalid, and desiring them doesn't invalidate the meaning of colors. It is simply a different use case.

So what are the goals of CSS colors? Not images or videos, but CSS colors? That's the real question.

@ccameron-chromium
Copy link

ccameron-chromium commented Mar 28, 2024

So what are the goals of CSS colors? Not images or videos, but CSS colors? That's the real question.

It comes back to color matching. If colors can match between CSS, images, video, and canvas, then CSS colors cannot be taken in isolation.

In terms of preferences about "prefer lightness" versus "prefer chroma", that is best expressed directly by using the intended colors, rather than using very-out-of-gamut or imaginary colors and then imposing something on all colors that happens to match a preference.

@romainmenke
Copy link
Member

romainmenke commented Mar 28, 2024

This is becoming an exceedingly long thread and positions haven't moved closer together.
I think we can all agree that we don't agree :)

Can we explore having both behaviors?

Authors could pick if they want CSS colors to either clip or gamut map, this would affect the whole document.
Canvas is a scripted API and can have extra arguments to pick the behavior.
Images and video would retain their current behavior.

@facelessuser
Copy link

Authors could pick if they want CSS colors to either clip or gamut map, this would affect the whole document.
Canvas is a scripted API and can have extra arguments to pick the behavior.
Images and video would retain their current behavior.

I think this is a great compromise. I think it would be a shame if the ease of targeting decent contrast was lost just to favor color matching of images. There is real utility here.

@LeaVerou
Copy link
Member

LeaVerou commented Apr 1, 2024

This has been proposed many times, and did not seem to bring us any closer to consensus. I suspect this would simply move the disagreement from "what should happen all the time?" to "what should happen by default?" and we still wouldn't have consensus.
But perhaps @ccameron-chromium can clarify (since my understanding is he’s the only one pushing against gamut mapping, at least vocally).

@frivoal
Copy link
Collaborator

frivoal commented Apr 2, 2024

@ccameron-chromium

[…] is treated better by clipping than by the CSS gamut mapping algorithm

I think there's been repeated statements by those in favor of using a GMA that the one currently specified in the spec is not the front runner (I think Scale LH might be). So while you may have a point that the currently specified CSS gma isn't great, that doesn't sound like an argument against gma in general.

@jyasskin
Copy link
Member

jyasskin commented Apr 3, 2024

  1. @ccameron-chromium said in [css-color-4] Channel clipping breaks author expectations, especially when using 'perceptually uniform' spaces #9449 (comment) that he'd be fine with a gamut-mapping rule of the form "minimize the perceptual difference between the specified and displayed color while preserving hue". He's been opposed to mapping algorithms that strictly preserve lightness since he doesn't think he could apply them to images, but he's fine with a better algorithm than clipping if it can also be applied to images.
  2. I see a lot of concern in the above discussion about breaking accessibility, specifically measured as WCAG 2 conformance. Now, accessibility doesn't suddenly break when you cross a 4.5 WCAG-2 contrast ratio; it gets gradually worse as contrast decreases, and the WCAG group had to draw a line somewhere. WCAG 3 is probably going to draw a different line with a different definition of contrast.
  3. It's hard to figure out an author's intent when presented with a color in isolation. The oklch(90% 90% 0deg) in @mirisuzanne's codepen in the OP could have been the result of several different thought processes:
    1. oklch(from #905 calc(l * 2) calc(c * 2) h): making a known color brighter, to the limit of the user's display
    2. oklch(from #FCC l 90% h): making a known color more vibrant, to the limit of the user's display
    3. oklch(from color(rec2020 0.959 0.047 0.515) 90% c h): making a user-chosen color light enough to contrast with #444. (unlikely for this particular result color, but I'm not sure how the spec should determine that)
    4. oklch(from color(rec2020 0.805 0.948 -0.728) l c calc(h-120)): making a triadic palette from a user-chosen color. (even more unlikely for this particular result color, but same difficulties with having the spec figure that out)
  4. Of those thought processes, only (iii) has a specific contrast goal. The others almost certainly have constraints, but the browser can't easily tell what they are. (One could imagine looking for a surrounding contrast-color() call, but that seems like it'll interact badly with the idea of computed values, and it won't help pages written today.)
  5. If we want to be conservative, we could try to ensure the final gamut-mapping algorithm preserves the WCAG contrast the author asked for in (iii). Surprisingly, the author hasn't asked for a specific WCAG score by picking an oklch lightness value. @foolip pointed out to me that none of the WCAG contrast definitions use the same definition of "lightness" as either L*a*b* or OkLCH. So I played around with https://jsfiddle.net/mahq9fjL/ and found that for oklch(90% 90% 0deg), for a lightness difference that, for grey, gives a WCAG 2 7:1 contrast ratio, you can vary the chroma and hue and get contrast ratios anywhere from 6:1 to 8:1. For an ACPA score of -90 against grey, varying chroma and hue can find scores from -83 to -101. Constraining to colors in P3, it seems like lightness changes of about 0.03 fall into the hue-caused imprecision in the WCAG scores.
  6. If we want to be less conservative, we could mandate that gamut mapping algorithms only change lightness by ∆L, and then authors who want to adjust user-chosen colors into a particular contrast score just need to increase their adjustment by ∆L to be confident it won't be broken by gamut mapping. I don't know what value of ∆L will be acceptable for images, or if it needs to vary depending on the starting lightness level, but the experiments in https://bottosson.github.io/posts/gamutclipping/ seem to indicate that some constraint on the change in lightness can work for images.

@romainmenke
Copy link
Member

romainmenke commented Apr 3, 2024

One indicator of user intent is that we actually implemented and shipped gamut mapping in PostCSS. The explicit feature is currently downloaded 500k times each week. The fallback for oklch is older and is downloaded 3.500k times each week, this also generates gamut mapped fallbacks. Both are part of PostCSS Preset Env.

We haven't received a single report about the generated colors.
No one reached out saying that they wanted oklch and other color notations to have clipping behavior instead.

Even if the intent were ambiguous then that would mean we need to give authors control over this. It is not an argument against gamut mapping.


Can we get an answer on the implementation constraints for gamut mapping?

@jyasskin
Copy link
Member

jyasskin commented Apr 3, 2024

@romainmenke I'm not arguing against a better gamut mapping algorithm than clipping, and neither is @ccameron-chromium at this point. The question is what kind of gamut mapping to pick. I agree that we likely need to give authors more control, although as @LeaVerou pointed out, we do also have to pick a default.

I can't say for sure exactly what Chrome's implementation constraints are, since I'm not @ccameron-chromium. The biggest one, though, seems to be that the same mapping algorithm has to produce good results for both images and CSS colors. He's also said that the problem space is novel and unknown enough that CSS shouldn't mandate a particular algorithm, but rather set a goal for implementations to meet. (E.g. "find the perceptually closest color of the same hue", probably within some error bound.) I'm not certain that's correct in the medium-term, but I am concerned by seeing folks talk about particular algorithms before defining the goals those algorithms are meant to achieve. It could make sense to agree on some goals, watch what implementations do, and then try to align the exact results later.

@frivoal
Copy link
Collaborator

frivoal commented Apr 4, 2024

I'm not arguing against a better gamut mapping algorithm than clipping, and neither is @ccameron-chromium at this point.

Then I misunderstood @ccameron-chromium, as I thought he was. @ccameron-chromium Can you clarify?

@LeaVerou
Copy link
Member

LeaVerou commented Apr 4, 2024

@jyasskin

  1. @ccameron-chromium said in [css-color-4] Channel clipping breaks author expectations, especially when using 'perceptually uniform' spaces #9449 (comment) that he'd be fine with a gamut-mapping rule of the form "minimize the perceptual difference between the specified and displayed color while preserving hue".

That is not testable so making such a statement normative means very little.

He's been opposed to mapping algorithms that strictly preserve lightness since he doesn't think he could apply them to images, but he's fine with a better algorithm than clipping if it can also be applied to images.

What I don't yet understand is, does he want this to be the default or just possible? The use cases he brought to the group were from "partners" who have engineering teams for whom applying a CSS declaration on the root (or whatever other opt-in we come up with) is no big deal.

Also, as has been said in the thread many times, the best GMA for CSS is likely not a good GMA for images, since the use cases are different.

(One could imagine looking for a surrounding contrast-color() call,

Please don't link to contrast-color() in L6, that’s likely going to change quite a lot. However there is a contrast-color() MVP in css-color-5 which has WG consensus and is pretty much ready for implementations: https://drafts.csswg.org/css-color-5/#contrast-color (relevant issue: #9166 )

  1. It's hard to figure out an author's intent when presented with a color in isolation.

I think what most use cases share in terms of goals is the desire to minimize the difference between the two colors.
There are well established algorithms for color difference (the ΔΕ algorithms) that take into account how each component contributes to the perceptual difference between two colors and produce a single number. I think a goal framed around minimizing ΔΕ (for some ΔΕ algorithm) is likely the best default here, with opt-ins that make different tradeoffs.

  1. If we want to be conservative, we could try to ensure the final gamut-mapping algorithm preserves the WCAG contrast the author asked for in (iii). Surprisingly, the author hasn't asked for a specific WCAG score by picking an oklch lightness value. @foolip pointed out to me that none of the WCAG contrast definitions use the same definition of "lightness" as either Lab* or OkLCH. So I played around with jsfiddle.net/mahq9fjL and found that for oklch(90% 90% 0deg), for a lightness difference that, for grey, gives a WCAG 2 7:1 contrast ratio, you can vary the chroma and hue and get contrast ratios anywhere from 6:1 to 8:1. For an ACPA score of -90 against grey, varying chroma and hue can find scores from -83 to -101. Constraining to colors in P3, it seems like lightness changes of about 0.03 fall into the hue-caused imprecision in the WCAG scores.

I’m actually surprised the differences you found were this small! Last I checked, I couldn't even use L to reliably pick between white or black text!

  1. If we want to be less conservative, we could mandate that gamut mapping algorithms only change lightness by ∆L, and then authors who want to adjust user-chosen colors into a particular contrast score just need to increase their adjustment by ∆L to be confident it won't be broken by gamut mapping. I don't know what value of ∆L will be acceptable for images, or if it needs to vary depending on the starting lightness level, but the experiments in bottosson.github.io/posts/gamutclipping seem to indicate that some constraint on the change in lightness can work for images.

I literally asked @ccameron-chromium if he would be ok with precisely that and he said no, that he wants the bulk of the gamut mapping operations to be anything goes with only the tiny bit at the end being normative.

@jyasskin
Copy link
Member

jyasskin commented Apr 4, 2024

... a gamut-mapping rule of the form "minimize the perceptual difference between the specified and displayed color while preserving hue".

That is not testable so making such a statement normative means very little.

I had trouble finding the tests for what's currently in the spec. https://www.w3.org/TR/css-color-4/#css-gamut-mapping just says "Used values of color are not exposed to script, making this hard to test in an automated manner." Given a way to measure the output, I think one could test this with a rule that "the output color has to have a ΔΕ < X from the in-gamut color of the same hue as the target color with the least ΔΕ from the target color."

I think what most use cases share in terms of goals is the desire to minimize the difference between the two colors. ... I think a goal framed around minimizing ΔΕ (for some ΔΕ algorithm) is likely the best default here, with opt-ins that make different tradeoffs.
...
does [Chris C] want [image matching] to be the default or just possible?

I'm trying to figure this out too, and in particular, what would go wrong if it's not the default. That said, if CSS does wind up preferring a "minimize the ΔΕ from the specified color" goal, that matches what @ccameron-chromium has said he can accept for images, so maybe we can actually have a compatible default.

+1 for other opt-ins, especially the "preserve contrast" one.

I literally asked @ccameron-chromium if he would be ok with precisely that and he said no, that he wants the bulk of the gamut mapping operations to be anything goes with only the tiny bit at the end being normative.

I'm hopeful that the "prefer" in his March 5 answer, and his more recent March 27 statement that "... and we'd go implement it!" indicates some room to compromise.

@jyasskin
Copy link
Member

jyasskin commented Apr 4, 2024

I should add that finding a compromise with Chromium isn't sufficient. We also need WebKit (@smfr?) and Gecko (@emilio?) to agree.

@LeaVerou
Copy link
Member

LeaVerou commented Apr 4, 2024

@jyasskin

I had trouble finding the tests for what's currently in the spec.

First, I think we all have consensus that what is currently in the spec needs to be replaced, there have been several GMAs designed since that balance tradeoffs much better.

w3.org/TR/css-color-4/#css-gamut-mapping just says "Used values of color are not exposed to script, making this hard to test in an automated manner."

Yes, in an automated matter. But something like "minimize the perceptual difference between the specified and displayed color while preserving hue" is not testable at all.

That said, I think we should actually discuss whether exposing the screen gamut is enough of a fingerprinting vector that we don't want to expose it. I personally don't think so but others may disagree.

Given a way to measure the output, I think one could test this with a rule that "the output color has to have a ΔΕ < X from the in-gamut color of the same hue as the target color with the least ΔΕ from the target color."

Yup, that is pretty much exactly what I proposed and Chris C rejected 😀 Note that the guardrail cannot be in terms of ΔΕ, since that takes chroma into account as well, so a color with ΔΕ < X may not exist. But we can have guardrails for ΔL and ΔH, since a color that fits within those constraints is guaranteed to exist, even in the strictest form where ΔL = ΔH = 0 (you just lose a lot of chroma).

If we want to frame it around ΔΕ, the X would need to somehow depend on the chroma difference between the colors, it cannot be a static number.

I'm trying to figure this out too, and in particular, what would go wrong if it's not the default. That said, if CSS does wind up preferring a "minimize the ΔΕ from the specified color" goal, that matches what @ccameron-chromium has said he can accept for images, so maybe we can actually have a compatible default.

Can we make the goals for images a bit more concrete? Is preserving hue the main point of contentment? That people here want to prioritize preserving lightness and @ccameron-chromium wants to prioritize preserving hue? Because if so, we can have our cake and eat it too! We'd just specify low thresholds (or even 0) for both 😁
Again, as I have said many times in this thread, give us an exact set of requirements and we can come up with a spec/GMA that matches them. But there has been a lot of pushback by [Chris C] and no actual concrete requirements. In the most recent breakout, my takeaway about [Chris C]’s position was that he wants to just not gamut map.

I'm hopeful that the "prefer" in his March 5 answer, and his more recent March 27 statement that "... and we'd go implement it!" indicates some room to compromise.

Links to comments would be appreciated since this is a long thread and expanding it all to search by date is very slow.

I should add that finding a compromise with Chromium isn't sufficient. We also need WebKit (@smfr?) and Gecko (@emilio?) to agree.

It would definitely be good to hear from other vendors, though given the lack of pushback on their side, I suspect once we reach a compromise with Chromium they would be happy to follow (WebKit had even implemented an earlier version of the CSS GMA).

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
Feb 2024 Agenda
Wednesday morning
Development

No branches or pull requests