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

Gamut Mapping and HDR colors #630

Open
lloydk opened this issue Jan 12, 2025 · 5 comments
Open

Gamut Mapping and HDR colors #630

lloydk opened this issue Jan 12, 2025 · 5 comments

Comments

@lloydk
Copy link
Collaborator

lloydk commented Jan 12, 2025

From color-js/apps#11

For example color(display-p3 0 1.5 0) is color(rec2020 0.7082 1.528 0.1721) and this ,correctly,gets gamut mapped because Rec BT.2020 is an SDR colorspace.

But color(rec2100-linear 0.5039 2.389 0.0447) should not be gamut mapped, it is an HDR space where (1 1 1) is SDR white and HDR colors have one or more components above 1.

I suspect this is because rec2100-linear is inheriting too much from the implementation of rec2020-linear.

I think the fix for rec2100-linear is to use refRange for the coords instead of range. This would make the space unbounded so no gamut mapping would occur.

How should gamut mapping work for other color spaces like rec2100-pq, rec2100-hlg, acescg and acescc that have range coordinates when the coordinates of a color are outside of the range of the color space.

For example:

new Color({space: "rec2100pq", coords: [1, 1, 1]}).toString({options: {inGamut: true}})
'color(rec2100-pq 1 1 1)'
new Color({space: "rec2100pq", coords: [1.01, 1, 1]}).toString({options: {inGamut: true}})
'color(rec2100-pq 0.58069 0.58069 0.58069)'

It seems a bit strange that color(rec2100-pq 1.01 1 1) is gamut mapped to color(rec2100-pq 0.58069 0.58069 0.58069). I would have expected the result to be color(rec2100-pq 1 1 1) when gamut mapping to the same color space (for rec2100-pq) but maybe my intuition is wrong?

@facelessuser
Copy link
Collaborator

It seems a bit strange that color(rec2100-pq 1.01 1 1) is gamut mapped to color(rec2100-pq 0.58069 0.58069 0.58069). I would have expected the result to be color(rec2100-pq 1 1 1) when gamut mapping to the same color space (for rec2100-pq) but maybe my intuition is wrong?

The gamut mapping that Color.js currently employs is based on the current CSS spec, and it is currently SDR-centric with very specific rules about when lightness exceeds Oklab 100% which is based on SDR white. This essentially forces things to get clipped to white. Generally, I don't think this makes sense for an HDR gamut.

In practical cases, it is probably not likely that many devices would utilize the full headroom that a given HDR space offers, so often you would need to specify an HDR limit, likely less than the gamut could handle. I realize that, at some point, we may want to have a more complicated approach to gamut mapping HDR spaces, but I would think in the short term, instead of assuming an SDR limit, we could just remove that limiter for HDR spaces, either that or you could clamp to the HDR limit, but you need to have a bounded space communicate it's max lightness somehow, either by having it specified in some common form or calculating it on the fly.

I think the fix for rec2100-linear is to use refRange for the coords instead of range. This would make the space unbounded so no gamut mapping would occur.

If we want to treat Rec2100-linear as limitless, that seems fine to me. I wouldn't normally check its gamut as it doesn't define a real HDR limit and is more used as a linear working environment for PQ and HLG.

@facelessuser
Copy link
Collaborator

facelessuser commented Jan 26, 2025

I think the fix for rec2100-linear is to use refRange for the coords instead of range. This would make the space unbounded so no gamut mapping would occur.

I was thinking about this again. Another approach could be to do what ACES does and just give Linear Rec. 2100 enormous headroom like ACES (65504). That way you could also still have a lower limit and a nearly unbounded upper that should accommodate pretty much any other space. It would also allow you to keep an appropriate lower limit.

@lloydk
Copy link
Collaborator Author

lloydk commented Jan 26, 2025

I was thinking about this again. Another approach could be to do what ACES does and just give Linear Rec. 2100 enormous headroom like ACES (65504). That way you could also still have a lower limit and a nearly unbounded upper that should accommodate pretty much any other space. It would also allow you to keep an appropriate lower limit.

I thought about that as well but the CSS Color HDR Module Level 1 spec currently defines the Percent reference range as

Percent reference range | for R,G,B: 0% = 0.0, 100% = 1.0

So the color space would need to have a refRange to handle the conversion from/to percent values and I'm guessing that we would need to make some changes and do a fair bit of testing to allow color space coords to have both a refRange and a range property.

@facelessuser
Copy link
Collaborator

Yeah, I guess the alternative would be a way to define a lower, bound end and an upper, unbound reference end. I think right now it's all or none.

@facelessuser
Copy link
Collaborator

Just using a reference range for upper and lower is probably the easiest though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants