Re: [csswg-drafts] [css-color-4] Channel clipping breaks author expectations, especially when using 'perceptually uniform' spaces (#9449)

Hi everyone!

Since there is a big discussion here around Oklab and gamut reduction, I thought I'd chime in with some perspective. If you don't know me, I'm a software engineer working mostly in the games industry and related areas, and created Oklab a few years ago. 

First of all this is certainly a hard problem with a lot of tradeoffs. I have worked with and thought a lot about some of these things, but I certainly don't have any complete answers. My experience is also mostly from a video game/film/photography point of view, which has somewhat different tradeoffs. Some of the ideas I'll present are bit half-baked as well, they are things I've been wanting to develop into blog posts/for customers but haven't had an opportunity to yet. That being said, I hope I am able to provide some useful insights. 

Hope you find this useful, let me know if you want me to elaborate something more!

### Oklab and imaginary colors

As you have observed Oklab and other similar models fall apart when moving away from real colors. Of course there is no correct way of modeling things like hue and chrome of imaginary colors, however in practical use cases imaginary colors tend to pop up and need to be dealt with. In particular when working with camera data, sensor spectral sensitivity doesn't match CIE color matching functions and the process of mapping camera raw data to colors typically creates imaginary colors. For this reason I've done some exploration of extending Oklab to imaginary colors, so that it at least doesn't fall part. 

Here is a Google Colab notebook showing an experiment with this: https://colab.research.google.com/drive/1_uoLM95LJKTiI7MECG_PjBrd32v-3W3o?usp=sharing

The approach in the experiment alters visible colors a bit too much though, should be possible to change the approach a bit to make it only alter color further from normal color spaces. So, if you end up needing to allow imaginary colors, something along these lines can stop gamut mapping from blowing up. It does require further work though, so not something to use as is.

Here's a plot of hue lines in standard Oklab (red) and the extended version (green):
![Extended Oklab](https://github.com/w3c/csswg-drafts/assets/1515602/1cd9e21d-a2b6-4bc5-9228-f91c8911d4fb)

### Gamut mapping

First of all, I do think it is important that gamut mapping is well specified and I think debug tools that clearly show colors outside the current gamut are super important (but I'm assuming that is discussed elsewhere). For example: I would not want to author something with colors within p3, check that it looks great on both p3 and sRGB only for it to look different in sRGB on a different platform.

On the topic of gamut mapping itself:

From what I understand you are looking at doing gamut mapping with the restrictions that all colors already with the target gamut are unchanged. I understand that this is probably impossible to get away from, but it is good to be aware that it is a particularly hard case to solve well for two reasons:

RGB gamuts have hard edges/corners, so you can't have a gradient running on the hull of the gamut without the risk of crossing edges, resulting in harsh visible lines. There is a way out, kind of, which is to have the gradient slow down and pause at edges/corners (from a mathematical point of view you introduce saddle points). This introduces other issues, but gets rid of the hard edges at least. 

Here is a visualization of the gamut hull of sRGB:
![sRGB gamut hull with sharp edge](https://github.com/w3c/csswg-drafts/assets/1515602/05b31974-29b2-4c22-8a4e-bd21456f0918)
Here's a modified version where the gradient "pauses" at edges and corners:
![sRGB gamut hull with saddle points](https://github.com/w3c/csswg-drafts/assets/1515602/8db89e88-d572-4f76-85b6-11e508d474aa)

A lot of the time it would be better to target a slightly smaller version of the gamut with rounded corners rather than the full RGB cube. This is of course easy to do in a specific use case, but I understand that this restriction is hard to introduce in a standard meant for general use. 

The other challenge is not altering colors already within gamut. As soon as you have a gradient going from an in gamut color to a color outside the gamut, you are going to get an abrupt change in direction and there isn't really a way around that.  

For best results I guess you could open up for allowing users to specify target gamuts different than standard RGB gamuts and gamut mapping that allows altering colors in gamut. That is probably a huge can of worms though. If a user cares enough I guess they could also do this themselves also, as long as they know the user's color gamut.

### My gamut mapping experiments 

I've done some exploration of gamut reduction with the above restrictions and I think we have explored some things in similar directions to your discussions. In particular, there is a direct conflict between a mapping that is as smooth as possible and works well for gradients (by introducing saddle points), and preserving hue/lightness.

I've experimented with a practical and computationally fairly cheap way of getting results that balance hue preserving with smoothness and allows experimentation with finding the right balance. The approach is a bit similar to some of the experiments you have shared here. Gamut mapping is done as a two step process:

1. Do a hue preserving gamut reduction to a smooth gamut shape, somewhat larger than the target gamut space. This still produces values outside 0.0-1.0 of the target gamut, but the values are closer. In the example below the mapping is done in straight lines towards L=0.7 with an "amphora"-shaped rotationally symmetrical gamut.
2. In RGB map the values to 0.0-1.0. This can be a simple per channel clip but I think there are better mappings that ensure smoother results. This step is not hue preserving, but makes the result smoother by pushing values towards the RGB cube's edges/corners.

Straight projection to the sRGB hull looks like this, with hard edges and large areas of low chroma:
![Hue preserving](https://github.com/w3c/csswg-drafts/assets/1515602/4f6a62aa-3468-41d9-99a7-c35e61888854)

With the method described above, you get this. While for from perfect, it is at least less jarring:
![With some hue distortions](https://github.com/w3c/csswg-drafts/assets/1515602/dd868684-d0d9-49ae-a2b7-6c793375ceab)

I don't think this is the best results you could get with this method, with a better gamut shape and different mapping to RGB you should be able to improve things more. Here is the source for this experiment: https://www.shadertoy.com/view/M3sGDs

### Developing a new cylindrical Oklab version 

I find the idea of developing a okhsl-like model but wide enough to include all practically usable colors quite intriguing, but it isn't something I have considered before so that don't much insight yet. I think it would probably be better to use a shape closer to the visible gamut or something like that, rather than p3/rec2020, due to the sharp corners of RGB gamuts. 

Similarly to okhsl you would end up with a tradeoff between "reaching the edge" and getting consistent chroma as you alter hue (especially because of yellows), but at least it would be smoother than okhsl that has to deal with the sRGB gamut. I guess you could also imagine just setting s=1 to be the most saturated real color. That would result in imaginary colors, but not as many as unbounded oklab. One thing to also consider is that the shape of the visible gamut is different for different whitepoints, so what is an impossible value for C for a given hue is not the same in D65 as it is in D50 etc.



 





-- 
GitHub Notification of comment by bottosson
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/9449#issuecomment-1948512394 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Friday, 16 February 2024 14:43:52 UTC