Comments (13)
Ah I see, it skirts right next to the boundary for a long while before it finally touches. I agree, that doesn't match expectation; a brighter yellow (with slightly less red) is much closer to the author's intent.
Hmm, getting this right is a little tricky. Seems like the right approach might be to check, on each hop, if we're close to the gamut in absolute terms (that is, if a small channel clip would bring us in-gamut, with minimal color-warping), and if so, treat that as in-gamut for the purpose of the binary search.
That should find us a value with minimal-to-zero blue in this case, and only a tiny warp in hue.
I wonder if we want to be slightly more sophisticated than binary search, too - start the search with a linear spread of probes along the chrome-reduction path, then weight closeness-to-gamut against amount-of-chroma-reduction to find the interval to search in.
from color.js.
Hmmm, that final suggestion of mine might also help us deal with the "overhang" problem, where the varying-chroma line has multiple in-gamut and out-of-gamut sections. Binary search can skip over the overhang, even if it's close to the starting color, and force us to the much lower-chroma solution; and if the starting color is between the two in-gamut segments, looking only at decreased chroma values will never find the higher-chroma segment, even if it's just barely higher than the starting color!
So doing a quick probe of several locations, mostly below but some above, the starting color and checking if they're "close" to being in-gamut (that is, if channel-clipping would be only a tiny change), could really help.
from color.js.
Looking at the blue component, the first few Chroma reduction steps go:
- -1.27 (original color)
- -0.827
- -0.307
- 0.157
so after 3 iterations the blue component is well inside gamut. Smaller Chroma steps, or binary search that explores the interval between -0.307 and 0.157, would get us close to zero on blue.
Perhaps, to detect "close to boundary" compute ΔE2000 between the current color and a per-component clipped version of the current color?
from color.js.
I was thinking even simpler - just a quick "in the output space, is the distance between the current point and a channel-clipped version below ε?", but doing the distance computation in ΔE is probably smarter. ^_^
from color.js.
I did some calculations. deltaE2000 between P3 yellow and sRGB yellow is 5 (noticeable side by side, but fairly similar) while deltaE2000 between P3 yellow and our super-desaturated gamut mapped yellow is a whopping 22.4.
from color.js.
Ok even better, looking at the values Lea posted before and doing a clip when it is close, we get a deltaE2000 of 0.79 which is barely visible:
from color.js.
First of all, nice.
Second, yeah, I've been thinking about this somewhat wrong. Our goal is to find the color as close to the original as possible which is in the output gamut. So we don't need to do any of that "balance out the chroma reduction vs the clipping distance", we just need to minimize the ΔE along the chroma-reduction line.
So yeah, sample a smattering of points in either direction (each 10 points of chroma?) from the starting point along the chroma increase/decrease line, channel-clipping each into the output gamut and measuring the ΔE from the starting color. Find the point with the smallest ΔE, and sub-sample on either side to find the minimum ΔE within the precision we want to care about.
I'm curious if there are any degenerate cases here we'd want to guard against, where the minimum ΔE would be for a channel-clipped color but still be fairly large. It's probably better to stay constant-hue-and-lightness in that case, right? So maybe check if the smallest ΔE from the initial sampling is above some threshold (5? 10?) and if so, just stick with the normal binary-search along the chroma-reduction line (aided by the fact that you already know approximately where it is, so you can start the search pretty accurately); this way you'll only shift the hue/lightness a tiny bit when it's warranted.
from color.js.
A recent analysis Colour gamut mapping between small and large colour gamuts: Part I. gamut compression
Note that most color gamut mapping research is concerned with natural, photographic images. Images containing vector-style graphics and type are rarely considered. Colors that form a palette of colors used in a Web page are pretty much never considered. However, the paper above examines several approaches including mapping towards the black point or white point rather than along lines of constant Lightness. In addition to CIE Lab, it also examines CAM02-UCS and Jzazbz for gamut mapping
from color.js.
This is very well illustrated in Kenichiro Masaoka, Yuichi Kusakabe, Takayuki Yamashita, Yukihiro Nishida, Tetsuomi Ikeda, and Masayuki Sugawara. Algorithm Design for Gamut Mapping From UHDTV to HDTV. Journal of Display Technology, vol. 12, No. 7, July 2016
Their GMA works in Lab and is hue preserving except for yellow and cyan highlights, where is is lightness preserving with a hue shift.
from color.js.
Here is P3 yellow, with LCH Chroma reduced to the neutral axis. The RGB values are linear-light P3. The color wedge shows sRGB values, if in gamut; salmon, if outside sRGB and red if outside P3. Notice the red curve goes up (so, out of gamut) before finally dropping again.
https://drafts.csswg.org/css-color-4/images/lab-yellow-LCH-fade.svg
Here is the same thing but at each stage, I calculate the deltaE2000 between the current color and the color clipped to sRGB. If the deltaE is less than 2, the clipped color is displayed. Notice the red curve hugs the top edge now because clipping to sRGB also means it is inside P3 gamut. Notice how we get an in-gamut color much earlier. Starting Chroma for P3 yellow is 123.27 and we get an in-gamut color by Chroma 103. I did also try with deltaE76 but (in addition to it wildly over estimating the color difference for very saturated colors) it was a bit worse on gamut mapping, giving an in-gamut value at a Chroma of 95.
https://drafts.csswg.org/css-color-4/images/lab-yellow-LCH-clip-fade.svg
(Obligatory sigh that GitHub lamely does not allow SVG images, even though it displays them just fine if they are in a repo)
from color.js.
from color.js.
So it is better, but I can still get closer by manually tweaking LCH chroma until the deltaE is at minimum
from color.js.
Although the current algorithm doesn't find the optimal result, a deltaE2000 error of 0.3 is imperceptible. This issue can be closed.
from color.js.
Related Issues (20)
- How should angle coordinates be handled in nonstandard color spaces HOT 9
- Support `rec2100-linear`? HOT 1
- Docs: getAll(), setAll() HOT 2
- Docs: mapping of color space names - CSS space ids/function names, Color.js serialized, and Color.js space ids HOT 6
- Document `getAll()` and `setAll()` HOT 1
- Carrying forward analogous components in color interpolation HOT 5
- Gray color is not correctly cast to a string HOT 7
- New API to normalize color values without NaN. HOT 1
- Tests for HSLuv/HPLuv clogging up the test runner HOT 10
- Auto-generated API docs seem not to be using TypeScript files HOT 6
- How to have a single source of truth for types and docs? HOT 11
- 'https://colorjs.io/docs/spaces' doesn't always load the list of color spaces HOT 2
- Add interpolation support for OkLrCH HOT 7
- Color scales: gradient-like interpolation across multiple color stops HOT 4
- Scientific notation causes error in OKLCH? HOT 2
- new Color("--acescg").space.coords[i].range does not match coordinates of actual colors HOT 30
- OKHsl is on docs but actually not in the published version HOT 2
- Make it possible to serialize to the same format as parsed
- OkHSL/OkHSV does not use NaN for hue-less colors HOT 2
- Color.js is very error-happy
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from color.js.