-
Notifications
You must be signed in to change notification settings - Fork 657
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] Disagreements over gamut mapping #7610
Comments
13.1 is, as stated, a non-normative introduction to the topic. 13.2 is normative.
Thanks for pointing out the possible ambiguity, I agree that The would be better.
Yes. HSL (and HWB) are unable to represent out-or-sRGB-gamut colors.
"If anything"? Colors which are out of the display device gamut cannot be displayed (by definition). Gamut mapping for images (and video) has different constraints to solid colors, such as preservation of image detail. For photographic images, a perceptual rendering intent should preserve most of the overall image appearance but may change in-gamut colors. For non-photographic images such as charts and diagrams, a relative colorimetric intent will give more similar results to the CSS GMA (but not identical, because the ICC code path will be using CIE Lab as the gamut mapping space). Yes, that means that out of screen gamut colors may map differently in images and in CSS, particularly wildly OOG colors such as those outside the spectral locus. These are much less likely in photographic images but can occur in synthetic images.
The same "this is a different code path" rules i.e. not the same as CSS. In addition to the GMA there will also be tone mapping, unless the display has the same peak white as the mastering display. All of that is generally a black box, especially for HDR video. |
Sure, but for display, does the spec require this specific algorithm and not e.g. just clipping each color component to [0,1]? |
Suppose a page specifies the color "color(display-p3 1 0 0)", and then has an image element with an image in Display P3 color space, with pixel value (1, 0, 0). Should those two colors be identical when displayed on-screen? |
Similarly to the above, what if both CSS and a 2D canvas use the color "color(display-p3 1 0 0)"? What about a WebGL canvas that writes a pixel value (1, 0, 0) and has specified its color space to be "display-p3". I believe that it is a goal to allow color matching between CSS, images, and canvases (ideally video, too, but there is longstanding historical divergence in interpretations of rec601 and rec709 -- something for another discussion -- I'd love to resolve that, maybe WebCodecs will let us). In order to guarantee color matching between CSS and images, it is necessary that the same processing be applied to pixels coming from CSS and pixels coming from images. This precludes a scheme of "apply gamut mapping to just CSS colors and not (necessarily) images". This can be fixed by gamut mapping images and canvases. That, however, comes at a significant performance cost. For CSS colors, there are lots of places in the pipeline to allow injection of various transforms at very little (or no) extra cost. For canvas elements, they are often handed directly to the display controller, which has only fixed-function hardware for color management, and is incapable of performing gamut mapping. The hardware is evolving, and there is a big push to support per-plane 3D LUTs (which would allow hardware tone mapping and hardware gamut mapping), but I'm not sure they exist in shipping devices (let alone are universal). If we were to require gamut mapping for canvases, it would mean that no canvas that can express any color value outside of the gamut of the display can be represented as a hardware overlay. That would be catastrophic for battery life, especially on mobile devices. (There's almost no limit to the work that we will do to keep a buffer in a hardware overlay plane). This line of reasoning leads me to the position that gamut mapping should be put as the responsibility of the underlying operating system, display controller, and even display device(!). For the next few years (until display controllers all have 3D LUTs, completing yet another turn of the wheel of reincarnation), this will mean that out-of-gamut colors may not look as good as they could on some devices. Content authors for whom this is a matter of grave concern may use the media queries (or the exact query of primaries, which is part of the HDR canvas proposal), to ensure they do not serve content outside of the gamut of the target device. |
Cc-ing @weinig, since he seems to have done the work on gamut mapping in WebKit. FWIW, I don't think “someone makes a DCI-P3 canvas or WebGL context on an sRGB device” is a use case that should be driving for our general color decisions. It seems very niche compared to the amount of pages that simply use CSS colors and regular images (or sRGB canvases). And AIUI, “colors won't look good on some devices” essentially means “on every non-Apple device in existence” for at least the next couple of years… |
@weinig might you be able to summarize the "architecture" of color handling in Safari? From testing in Safari Technology Preview 151 it's clear that something has changed from the previous behavior of clamping each component separately. One of the test cases that @sesse used was It would be great to understand a bit about where in the pipeline such mapping happens, and whether it only applies to out-of-gamut cases like |
Bear in mind that the |
@ccameron-chromium good point, it's also worth testing somewhere where luminance is in SDR range. I think |
It's not clamping to sRGB Red, it is clamping in the Display P3 color space. It isn't doing any fancy gamut mapping, just simple clipping in Display P3, as most new Macs have Display P3 monitors. Safari, due to its "color management" then works in Display P3. I guess this is new behavior between stable and STP. That's all that happening. >>> Color('color(srgb 1.05 0 0)').convert('display-p3').clip()
color(display-p3 0.96358 0.21239 0.14773 / 1) As far as I can tell, Safari doesn't do anything but simple clipping currently. On Display P3 systems, it will clip to the Display P3 color space, and on sRGB systems, it will clip to sRGB. We can see this by using >>> Color('color(srgb 2 0 0)').convert('display-p3').clip()
color(display-p3 1 0.44226 0.32203 / 1) If it was doing gamut mapping as described in the CSS spec, the results would probably be white as the color would be beyond the luminance of Display P3. The CSS spec currently recommends gamut mapping in Oklch, so if we look at the slice of Oklch in which gamut mapping would occur, limiting the gamut to Display P3, we can see why it would go to white. >>> Color('color(srgb 2 0 0)').convert('display-p3').fit(method='oklch-chroma')
color(display-p3 1 1 1 / 1) EDIT: I don't have anything to do with Safari, and this is all just based on simple observation. I don't know if they have more advanced gamut mapping planned or not. |
When I tested something like color(srgb 10 0 0) in Safari TP, it would go towards a very pale yellow on the internal Display-P3 screen. This is incompatible with the notion of a simple clipping in DCI-P3 space. |
That is not what I see at all in Safari TP, I get white on my Display P3 monitor. Unless there is some other special feature you have enabled that I don't. This makes sense as |
But that is, indeed, incompatible with the theory that it's just clipping each component in display-p3. Anyway, let's wait for the people who actually implemented this to chime in? |
Feel free to test that out with some photographic images (you will need to override the rendering intent in the ICC profile, which will likely be set to perceptual). Wanting solid colors and images to gamut map the same is a great goal, and sounds reasonable until one looks into what is currently done for gamut mapping images. For an in-depth overview, I recommend Color Gamut Mapping by Ján Morovič |
Yes but to clarify: there are plenty of non-Apple devices (laptops, tablets, phones) with WCG (P3-ish or Adobe RGB-ish) screens. It is not the hardware that is lacking, but WebKit browsers being confined to Apple hardware, and non-WebKit browsers being confined to sRGB. |
It's not really a theory, and I'm not following the above statement as getting
That's fine 🤷. Just thought I'd try and save you some time. |
I should probably state that everything I've said only applies to colors, not images and such. I've done no comparisons as to what any browser does with images or videos. |
One other clarification, my statements are also based on the idea that a Display P3 monitor is using a Display P3 color profile. If you were using a different color profile for your monitor, your color results will be different. You can actually try this out and see the differences. |
Not sure I can comprehensively summarize the architecture of color handling in WebKit in a succinct manner, but I can explain what our current behavior and longer term intentions are with out-of-gamut colors. Currently, WebKit keeps colors in their described form (so, for instance, A change we plan to make is that instead of using the platform specific gamut mapping, we are going to use the CSS Gamut mapping algorithm (https://drafts.csswg.org/css-color-4/#css-gamut-mapping). We are still trying to determine whether it makes sense to gamut map to the exact color space of the underlying context (usually the color space of the display) or to instead pick a color space based on the gamut matched by the |
I should also add that we already apply the CSS Gamut mapping algorithm in WebKit in at least one script observable place (the ones in the previous comment are not), by using the For example, if you have: color-mix(in hsl, color(srgb 2 0 0) 50%, color(srgb 0 0 2) 50%) We first gamut map the two inputs into the sRBG gamut, and then mix them. This is specified here: https://drafts.csswg.org/css-color-5/#color-mix-result |
I want to get back to the color matching issue, and take the image part out of the equation. Consider the following 3 pieces of web content:
Should these appear the same when displayed on a device that has a gamut that is, say, halfway between sRGB and P3? If not, why not? If a content author wants to display something that part-div, part-2D-canvas, and part-WebGL, should it be possible for the author to color-match between these elements, so that there are no seams in their content, or not? |
Thanks you @weinig, that's very helpful! You mention that computed values are unchanged, and there's a bit of https://drafts.csswg.org/css-color-4/#color-function that I'd like your take on:
Following the spec on this point would make the result of gamut mapping visible via |
@ccameron-chromium my 2c is that making those the 3 cases display the same color is important, and hopefully there is a solution that preserves this in most or all situations. I think gamut mapping to match the actual display is the "risky" operation here, if that's done for To me this suggests that gamut mapping to match the actual display should happen late in the pipeline, unobservable to web contents, including when reading back pixels from a canvas. It's still necessary to do something with |
Would this be script-visible, or just for display? If it's script-visible, it would sound better to map to the “ideal” space (sRGB or DCI-P3), so that you get consistent results from machine to machine (less confusing, easier to test in WPT, less fingerprint risk). If it's only about display, I may have (weak) opinions but I think it's out-of-scope for the spec, and probably subtle enough that either way is fine. |
In addition to @svgeesus' points, there are different performance allowances to gamut mapping the relatively few CSS colors defined in stylesheets (even with interpolation) compared to gamut mapping every pixel on an image or video. For CSS colors, we can afford to prioritize getting a better color even if that's not realistic for perf reasons for images or videos.
Is there a reason you are linking to your own version of the spec? At first I thought it was done to include commentary as annotations but I don't see any (unless I missed it). |
drafts.csswg.org was down. It's a pure mirror (updated every night from GitHub). |
Oh, very interesting. I don't believe that was in the spec when I was last working on this (I made an intentional change to match the spec and to not clamp around a year ago if I remember correctly), so thank you for bringing this to my attention. @svgeesus @LeaVerou, what was the driving motivation behind this change? To me, it has some unfortunate downsides, and I am not clear what you gain from it over the (or perhaps just my) previous interpretation which was that out-of-gamut colors would be preserved all the way to use time. (the benefits being that values round trip cleanly and that you can use things like out of gamut color(srgb ...) to express things like Display-P3 colors, which is a common practice in Apple's graphics stack these days). Ultimately, if the spec authors think this behavior is preferable, this is an easy thing for us to implement (we used to do a clamping gamut mapping here after all, I could just plug in the CSS Gamut Mapping algorithm instead) and I will be happy to. One thing to consider, once the use cases are better understood, is whether it makes sense to have both variants, bounded and extended. In Apple's graphics stack, we have both for all RGB like color spaces, kCGColorSpaceSRGB which is bounded and kCGColorSpaceExtendedSRGB which is unbounded (actually, there are four, bounded and gamma encoded, bounded and linear, extended and gamma encoded, extended and linear, and I have expressed some interest in the past in considering how we might be able to provide ways to express all of these in CSS, for instance color(srgb-bounded ...), color(srgb-bounded-linear ...), color(srgb ...), color(srgb-linear ...)). |
The intention is for it to not be script visible, as this would, as you note, would allow using the display's color space. There are few places where getting the actual color space of the display is challenging from the engine (though not insurmountable), so I think having the guarantee be a little looser and match the color-gamut media query would be be preferable. Ultimately, I don't think authors relying on colors outside the gamut that the color-gamut media query resolves to is all that useful a thing for authors (e.g. how important that on a display that has a gamut between P3 and Rec2020, out of P3 gamut colors get fully realized?) to do, but I could be convinced otherwise by compelling use cases. |
My recollection is that CSSWG went back and forth on this, between computed value time and used value time; and the main driver was handling system colors in forced color mode, and handling currentColor. I think that mapping to the display gamut should be as late as possible, so that out of gamut values like I would need to dig into the commit history to see when that was changed and in response to what issue, but from memory it was to do with handling system colors. |
Hi @svgeesus, I was trying to limit the thought experiment to CSS color's as used by authors. Given we don't expect images video to use the same gamut mapping, being able to rely on them to match author specified colors out side of the gamut seems unlikely to be practical or useful. That said, if we think there are going to be common displays between Display-P3 and Rec2020, we should consider (with the clear caution and understanding that it will increase the finger printing surface area) adding to the list of color-gamuts that the media query can match against. |
CSS Color 4 expresses extended and gamma encoded for all the predefined |
Ah, I see. Given that clarification, I agree. |
Right. I don't think this is an immediate need, but we should revisit it periodically as displays improve over the next few years. |
As simple per-coordinate clip has been mentioned, I am reminded of a canvas GMA example I put together which compares
A constant-lightness plane of the OKLCH space is mapped to sRGB using the three methods. Significant shifts in both hue and lightness are seen with clip. In other words, the gamut mapped color is a poor representation of the original, oog color. Now that we have |
I am a bit confused about this. It seems like the current spec text requires If you can find the details of the "forced color mode, and handling currentColor" rationale, I would be quite interested. |
A data point with respect to 2D canvas (which has supported display-p3 ImageData for a while now, on more than one browser). Consider the following code which writes color(display-p3 1 0 0) to an sRGB canvas and reads it back.
This code returns the color [255, 0, 0, 255] (the clipped, not-gamut-mapped value) in all browsers that support ImageDataSettings. In all browsers that support color level 4 syntax, if you replace the
then you still get the same (clipped, not-gamut-mapped) result. When I was writing the spec change for WCG canvas, I was definitely intending "relative colorimetric intent" to mean "clipping" (although I now see that there are various definitions of various vagueness for this). I'm pretty sure there are WPT tests that enforce this behavior, too (with inputs that are images, too). |
Also, one more demo about color matching (between images and CSS colors, though it can apply to canvas via ImageData as well). From what I can tell, all browsers support color matching between CSS colors, images, and canvases. |
The basic definition of relative colorimetric is that:
The third point can be achieved with various levels of fidelity. Simple clipping is the fastest, and produces the worst results. Choosing the color with the lowest deltaE to the original color (MINDE) is better, but still not good because we are most sensitive to changes in hue, then to changes in lightness, and least of all to changes in chroma. Choosing a color with lower chroma and the same hue and lightness gives good results, but depends on which color space and distance metric is being used and can over desaturate in some cases. Choosing the color with lowest OKLCH chroma, using deltaEOK, and finishing up by using a local MINDE step once the difference is below a Just Noticable Difference is what CSS Color 4 specifies, and so far gives the highest quality result for single colors and colors in gradients. It is also more efficient and higher quality than the previously specified method (chroma reduction in CIE LCH, deltaE2000, local MINDE). Since canvas is driven from script, it is easy for script authors to use their own gamut mapping stage if they wish (like my demo does). Canvas clip can then be seen as a failsafe (if they get this wrong, or don't do it). |
My understanding is that the color-gamut media query exist to allow content authors to do exactly this (with or without script) for CSS Colors. For instance, one can do custom gamut mapping with the following CSS:
This solves the problem of rendering out-of-gamut CSS Colors, without introducing any regressions in color matching behavior or performance. It also gives the content author full control over how their page is to do gamut mapping (the author can even implement a perceptual-like mapping if they want). |
The color-gamut MQ distinguishes between three broad cases: normal sRGB-ish, wide (like most of P3 or Adobe RGB) and ultrawide (like most of 2020). Unlike the proposed extensions to handle HDR canvas, the actual capabilities (such as exact primary chromaticities) of the display are not exposed. Note that the lack of automatic gamut mapping makes animation harder (several values in different color spaces need to be animated together). Also, your example doesn't do any gamut mapping, It exposes author pre-calculated gamut mapped colors, which may or may not be displayable on a given device within the broad class of devices matched by the MQ. Further, this example does not extend well when colors are generated by calculations (such as |
I do not think that's preferable at all, moving a window to another screen with a different gamut should not cause the computed style of colors to change! @svgeesus will do some digging on why this changed, since he also doesn't think it's a good idea. |
Given how trivially easy it is to specify out of gamut colors with e.g. LCH or OKLCH, having to do manual gamut mapping to not get a terrible result is completely unacceptable. Not to mention that the I think there is a fundamental misunderstanding permeating this discussion. Going out of gamut is not some edge case we can relegate to script or manual computation, it will be very very common once authors can specify wide gamut colors, so it's important to handle it well and not sweep it under the rug. |
I went ahead and wrote up an example of the behavior where gamut mapping is applied only to CSS colors, but not images and canvases. Consider this page, which has a canvas, a CSS color, and an image.
This uses the gamut mapping math in this notebook. Is this above characterization correct? |
Yes, this highlights an important issue. Should someone creating content specify colors (significantly) outside of the capabilities of the device on which they are authoring content? Encouraging that strikes me as a bad idea -- the author is making content without knowing what it actually looks like. Just in the above example, the color(p3 1 0 0) gamut-mapped to sRGB looks is less-saturated than srgb(1 0 0), which is the opposite of what actually happens when it is rendered on an a P3 display. The parameterization and interpolation of LCH is highly desirable. The ability to swing wildly out of gamut isn't. What about the following compromise: take away LCH, and replace it with something like LCH-P3, with gamut mapping to P3 baked in? |
Yes, this is correct (assuming the gamut mapping math is correct, which is @svgeesus' purview). Yes, if you gamut map a CSS color and Canvas does not do gamut mapping, it logically follows that you will not get the same color if gamut mapping is needed. Are you implying that because canvas produces broken results, CSS needs to do so as well? It is far more important to get a color closer to the author intent (@svgeesus demonstrated above what kind of poor results clipping produces), rather than to accommodate the odd case where the same CSS color is displayed next to the same canvas color. That said, ideally Canvas should also add a mode that properly gamut maps OOG colors. Perhaps you would be interested to work on that proposal? We can discuss gamut mapping algorithms all you want, and I think it's highly likely that a better algorithm exists than what is currently in the spec, and I’m sure both I and all other CSS Color editors would welcome input towards producing a better gamut mapping algorithm. However, if the proposal here is to remove gamut mapping from the spec entirely, or to make it an informative note and let UAs get away with simple per-component clipping, I would be willing to formally object to that. It would render wide gamut colors unusable and is both author-hostile, user-hostile, and harmful for accessibility. |
Colors are not always specified manually, they are often generated, either via script, or user input (on another machine!), or CSS color modification functions, or interpolation etc.
sRGB seemed plenty enough back in the 90s. Can we please not repeat the same mistake, just with P3 this time? We cannot be solving the same problems every few years. Also, I'm not sure what you mean by "The parameterization and interpolation of LCH is highly desirable". The advantage of LCH (and OKLCH) is perceptual uniformity. I'm not sure how your proposed LCH-P3 would maintain that. If you want to specify LCH colors that are constrained to the P3 gamut, you can do this already via Relative Color Syntax once that deals with |
It would look like this (the spec has it already) However, that section seems to run straight into some other text, unrelated to RCS of |
This is a case from a partner, developing a graphic design application. The main work area is a low latency WebGL canvas in P3 (pushed to a hardware overlay whenever possible), and they have a color picker tool that is CSS. Another example is a logo that is represented as an image, and the content author wants text on the page to match the logo's color. This is also something that users have been able to do forever with sRGB images. If an image is sRGB, then specifying that exact same pixel value as a CSS color has always produced an identical color. I don't think we should sweep away that functionality. With respect to author intent, I've elaborated on the above example with this test page here.
|
This is something we often do as well, and something that we would like to be able to do in the future. We sometimes need to match colors across images, canvas, css and sometimes video. An example is when publishing editorial articles with graphical elements that has matching text-color, accent-color and/or background color across images, css and canvas elements. When we do, it would be nice if we could set the gamut-mapping being used on images and canvas so that out of display gamut colors do match. Not all images are photography, not all videos are “photographic”. This does not mean that the css gamut mapping should be the default setting on say images. Only that it would be useful for authors to control the gamut mapping used when images, canvas (or video) contain graphical elements and not “photography”. |
@weinig wrote:
You were right (and conforming to other parts of the spec) to avoid clamping. I dug into this via Git blame and it has in fact been in the spec for over two years, being added here. A couple of points though:
Indeed, elsewhere in the spec (such as color interpolation) we emphasize that out of gamut colors are preserved as-is for intermediate calculations:
So this is not a recent change which you missed; it is basically a leftover piece of spec prose that I missed, which no longer applies because all
Exactly. Also, in the case of gamut mapping for display, there is no reason to do this at computed-value time, which then affects round-tripping as you say. Nor should it be at used value time (remembering firstly that used value is the computed value after layout, and layout does not affect color; also that getComputedStyle returns the used value for colors, not the computed value). Instead, gamut mapping for display is a much better fit for an actual value which is not exposed to script:
The user agent being forced to use only colors that the current display can physically produce (like p3 or rec2020 colors on an sRGB display) seems to fit actual values perfectly. |
There's been some discussions around gamut mapping: https://csswg.sesse.net/css-color-4/#css-gamut-mapping
The questions in point are:
The text was updated successfully, but these errors were encountered: