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] Gamut Mapping with Oklch - Odd Results #7071

Open
facelessuser opened this issue Feb 18, 2022 · 12 comments
Open

[css-color-4] Gamut Mapping with Oklch - Odd Results #7071

facelessuser opened this issue Feb 18, 2022 · 12 comments
Labels
css-color-4 Current Work

Comments

@facelessuser
Copy link

I'd like to preface this, stating, Oklab and Oklch work great as interpolation spaces, but I am questioning how well it works for gamut mapping (at least with the current algorithm). Now, I understand that Oklch does a much better job at preserving hue, but in practice, when gamut mapping spaces, it seems to give odd results (in very specific cases).

Consider these different interpolations. We are interpolating from green to blue in various spaces. All colors are gamut mapped to sRGB.

The first set of examples is gamut mapping the interpolated colors into sRGB with Oklch chroma reduction. Notice how poor the results are for the Lch interpolation. Also, notice the harsher transition in the Oklch interpolation example on the far right, it is more subtle (this is very minor, but we'll see the difference when compared to Lch gamut mapping).

Screen Shot 2022-02-18 at 1 35 56 PM

Here we are using Lch to gamut map. Everything gives nice, clean transitions and gamut mapping is visually more appealing.

Screen Shot 2022-02-18 at 1 36 07 PM

Obviously, such cases are specific to very certain color cases, but this case doesn't seem too far-fetched of one.

Granted, the interpolations of Oklab and Oklch generally look nicer on both, but gamut mapping with Oklch chroma reduction vs Lch chroma reduction turns out very differently.

Maybe this is all generally expected and accepted, but I wanted to at least bring the topic up as it would be a very jarring case when interpolating simple colors like green and blue with Lch. Ignoring whether hue is preserved better or not through the gamut mapping process with one space vs the other. Visually, Lch seems to give better results for what I think people would expect.

@romainmenke
Copy link
Member

LCH result seems to correlate with what you can see here in the top left corner :

https://romainmenke.github.io/lab-lch-display-p3/lab-interactive.html

Screenshot 2022-02-21 at 19 45 20

Is it possible that the values in your interpolation go outside the spectral locus and are not real colors? That is what that corner is in my experiments.

I can imagine that with interpolation you can easily hit this area.

@facelessuser
Copy link
Author

facelessuser commented Feb 21, 2022

Is it possible that the values in your interpolation go outside the spectral locus and are not real colors?

It may. And I could certainly analyze and plot the point relative to the spectral locus, but I'm probably less concerned with the why as much as I am with understanding whether this is truly the results that are desired for the gamut mapping algorithm in CSS.

Every color space has strengths and weaknesses. Oklab/Oklch is generally a far better interpolation space than Lab/Lch. What I am questioning is whether it works as well as the gamut mapping space (at least in the algorithm's current form).

There are a number of cases where I think a user would get something saner than maybe what is returned with using Oklch (as the algorithm currently works). Even in the example where we are interpolating in Oklch and then mapping with Oklch, the transition to blue has a very sharp transition. This is exaggerated far more in the Lch interpolation/Oklch mapping.

I guess what it boils down to is what the priority in the gamut mapping algorithm is. Personally, I'd take slightly less "accurate" mapping if, visually, it gave something close enough to what I'd expect in most common, practical use cases. Jarring transitions, like in the opening post, makes it far less attractive.

I just figured it was worth bringing up as a discussion point.

@svgeesus
Copy link
Contributor

@facelessuser wrote:

I just figured it was worth bringing up as a discussion point.

Oh, it certainly is worth bringing up, thanks for doing so.

Worth pointing out that interpolation in a polar space is frequently going to produce out of gamut intermediate colors, because of the irregular gamut shape of all RGB spaces. We have a way to take a straight line between two points (use a rectangular space for interpolation) and to take the maximal-chroma arc, but not a way to take a shallower arc (apart from adding intermediate stops).

On a first glance, the flat area in the OKLCH-mapped CIE LCH interpolation is troubling; it is as if the whole area has a single hue (in OKLCH) but different hues in CIE LCH. But then one might expect to see a similar effect for the CIE LCH-mapped OKLCH interpolation.

I need to investigate this more deeply, with a look at the numerical values for the generated interpolation and for the gamut-mapped results, at key points along the gradient.

@romainmenke wrote:

LCH result seems to correlate with what you can see here in the top left corner :

Looking at your page, was the first column generated in coloraide too, like this:

color.convert('oklch').fit('srgb', method='oklch-chroma').convert('srgb').fit('srgb', method='clip').coords();

or by some other method?

@facelessuser
Copy link
Author

facelessuser commented Feb 22, 2022

Oh, it certainly is worth bringing up, thanks for doing so.

No problem 🙂.

If it is helpful, here is the example live.

@romainmenke
Copy link
Member

romainmenke commented Feb 23, 2022

@svgeesus wrote :

Looking at your page, was the first column generated in coloraide too, like this:
or by some other method?

This was created using the sample code in this repo.
But the same effect can be observed with color.js.

There is a noticeable shift in luminosity for non-real colors in the teal region.
Which is not really an issue when gamut mapping a single color defined by a stylesheet author, but becomes hard to avoid with interpolation for things like gradients.

https://colorjs.io/notebook/

let colorALAB = new Color('lab(50%, -127, -127)');
let colorAOKLCH = colorALAB.to('oklch');
let colorA = new Color('color(oklch 0.42 1.95 191)');
let colorB = colorA.toGamut({method: 'oklch.chroma', space: 'srgb'});
let colorC = colorB.to('srgb');

let colorA2LAB = new Color('lab(50%, 0, 127)');
let colorA2OKLCH = colorA2LAB.to('oklch');
let colorA2 = new Color('color(oklch 0.56 0.19 102)');
let colorB2 = colorA2.toGamut({method: 'oklch.chroma', space: 'srgb'});
let colorC2 = colorB2.to('srgb');

I do not want to drag the conversion off topic, so please ignore this if it is not relevant.


Update :

Do please ignore the above.
I seem to have stumbled on an unrelated bug, will need to investigate that first.
color.js results are correct and do not have the effect I mentioned.

@facelessuser
Copy link
Author

facelessuser commented Feb 23, 2022

I don't think Color.js has quite implemented things yet to work the same with Oklch. Notice the Hue shift in your example for colorB: color(oklch 0.57 0.1 219). If hue was held constant in oklch by just reducing chroma, you'd only see a slight change in hue due to clipping. I suspect this may be done in Lch still.

You can see here that single colors do, in fact, have the same issue. It is not related to interpolation: live example

We can see in the example that the hue is held pretty much constant with only mild deviations in hue and lightness due to clipping.

>>> colorALAB = Color('lab(50% -127 -127)')
>>> colorAOKLCH = colorALAB.convert('oklch');
>>> colorA = Color('color(--oklch 0.42 1.95 191)')
>>> colorB = colorA.clone().fit("srgb", method='oklch-chroma')
>>> colorC = colorB.convert('srgb')
>>> colorA, colorB, colorC
(color(--oklch 0.42 1.95 191 / 1), color(--oklch 0.42897 0.0739 190.78 / 1), color(srgb 0 0.36119 0.34903 / 1))

And we get the same weird colors that were seen when interpolating:

Screen Shot 2022-02-23 at 6 27 09 AM

EDIT: The first color above is normal looking only because the visualizer uses Lch chroma reduction to show colors by default. The other two we forced Oklch chroma reduction.

@svgeesus
Copy link
Contributor

I don't think Color.js has quite implemented things yet to work the same with Oklch.

Yes, the oklch gamut mapping has been implemented, but is still on a separate branch because I always meant to test it more before merging to main.

@svgeesus
Copy link
Contributor

Three of the four [+127, -127] corners of the CIE Lab a,b plane are outside the spectral locus:
UCS-rec2020-labcorners

@facelessuser
Copy link
Author

Interestingly, when the problematic interpolation colors are diluted with some white, the color in Oklch increases in hue throughout the interpolation. But when we don't, the hue increases up to a point, but then drops (through dark green-ish area) and then ramps back up at the end.

In the good interpolation, it steadily increases from ~142 - ~264.

In the problematic interpolation, the hue goes from ~142 - ~203 and then drops to as low as ~192 - ~198 and then rises more rapidly at the end catching back up to ~264.

Screen Shot 2022-02-23 at 6 48 03 PM

@danburzo
Copy link

danburzo commented Mar 28, 2022

To corroborate some results, here's what I noticed in my demo, if you try to gamut map CIELCH in OKLCH (that is, Illustrate space: LCH D50, Mapping space: OKLCH), even with simple chroma reduction.

Shifts in hue:
Screenshot 2022-03-28 at 21 47 55

And this bothersome band in the blues:

Screenshot 2022-03-28 at 21 47 47

These two effects make it sound like there's some sort of clipping going on in the conversion process? The plots use culori.js, so I'm not excluding implementation errors, and in my particular implementation gamut mapping CIELCH in OKLCH space involves the following conversion chain: cielch -> cielab -> xyz d50 -> xyz d65 -> lrgb -> rgb -> lrgb -> xyz d65 -> oklab -> oklch -> (chroma reduction) and back, so there are several moving pieces.

@svgeesus
Copy link
Contributor

svgeesus commented Jul 4, 2022

I did some poking around and find weird discontinuities on CIE LCH as well.

Also, exploring the CIE Lab L=50 plane over a,b = -125 to +125 I am struck by the OKLab a value in the -125,-125 corner:

lab(50% 125 125) = oklab(60.58% 0.357 0.152)
lab(50% -125 125) = oklab(53.41% -0.32 0.194)
lab(50% -125 -125) = oklab(42.27% -1.9 -0.37)
lab(50% 125 -125) = oklab(65.4% 0.257 -0.35)

@svgeesus
Copy link
Contributor

svgeesus commented Jul 4, 2022

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
None yet
Development

No branches or pull requests

4 participants