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

[apps/gamut-mapping] Add edge seeker algorithm #448

Merged
merged 2 commits into from
Feb 27, 2024

Conversation

ardov
Copy link
Contributor

@ardov ardov commented Feb 20, 2024

Added an algorithm that generates a lookup table as a first step and then uses it to accurately predict highest chroma for color.

Copy link

netlify bot commented Feb 20, 2024

Deploy Preview for colorjs ready!

Name Link
🔨 Latest commit 85e535f
🔍 Latest deploy log https://app.netlify.com/sites/colorjs/deploys/65dde349006cd90008a3f7f2
😎 Deploy Preview https://deploy-preview-448--colorjs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@facelessuser
Copy link
Collaborator

Just an FYI, playing with it in the Netlify preview, the algorithm seems to have some failing scenarios. I haven't investigated closely to determine the precise issues, but those would need to be addressed. I'll comment on specific cases when I have a chance unless they solved before I get around to it.

@ardov
Copy link
Contributor Author

ardov commented Feb 25, 2024

Thank you for taking a look!
By failing scenarios do you mean higher delta E values like for oklch(92% 0.3 90) or something else?

@facelessuser
Copy link
Collaborator

I mean errors getting thrown. It may be an issue when you ported it to this library, but when you are incrementing through colors, it starts to not update. Looking in the browser console, you can see errors. I'm not in front of my computer right now, but I'd run it through some ranges and see for yourself.

@facelessuser
Copy link
Collaborator

@ardov, it's a lot of errors like this:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'h')
    at lerpLutItemsByHue (utils.js:31:18)
    at getLutItem (utils.js:84:9)
    at getMaxChroma (makeEdgeSeeker.js:18:19)
    at Proxy.compute (methods.js:62:20)
    at map-color.js:61:27
    at Array.map (<anonymous>)
    at Proxy.mapped (map-color.js:58:59)
    at ReactiveEffect.run (vue.esm-browser.js:610:25)
    at get value (vue.esm-browser.js:1550:39)
    at Object.get [as mapped] (vue.esm-browser.js:4956:30)

@ardov
Copy link
Contributor Author

ardov commented Feb 25, 2024

@facelessuser oh I see, thank you. I forgot to add hue normalization here so it fails when the hue is outside 0-360 range

@facelessuser
Copy link
Collaborator

Makes sense. It seems to be working now.

Copy link
Collaborator

@facelessuser facelessuser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to work well. As it is an app, and not going into the core library, I don't intend on extensively going through every line of the code, so generally, it LGTM. I'll let other reviewers chime in if they have specific issues.

@LeaVerou
Copy link
Member

LGTM. We should probably change the UI at some point to facilitate comparing across so many gamut mapping algorithms.

@facelessuser
Copy link
Collaborator

LGTM. We should probably change the UI at some point to facilitate comparing across so many gamut mapping algorithms.

@LeaVerou It would also be nice to compare some practical cases with some of these algorithms, like interpolation, etc. Do you get color banding? Are they uniform? etc.

I think ∆E is a fine metric, but sometimes clipping has great ∆E and the colors are still not great.

@ardov
Copy link
Contributor Author

ardov commented Feb 26, 2024

My approach to testing was a bit different. I used this flow:

  • Take the color on the surface of an RGB cube.
  • Convert it to Oklch.
  • Increase chroma and feed the color to the algorithm.
  • Then measure the distance between the original color and where we landed.

There are ~390K hex colors on the surface of the cube. I tested each of them and it helped identify weaknesses.

Of course, this method only works if we agree that chroma clamping is the way.

@facelessuser
Copy link
Collaborator

facelessuser commented Feb 26, 2024

I was speaking generally, not specifically about your GMA. It's good to see how they would perform, not just at reducing chroma, but how well the colors that are produced are useable in real-world settings. Are they sometimes greatly over-corrected, under-corrected, and does that have a greatly noticeable visual impact? Some over and under correction is expected.

Generally, I think chroma clamping is the way, and all these approaches so far seem to essentially be doing the same thing to greater or lesser accuracy, and I don't think perfect accuracy is required, but finding that threshold of speed vs complexity vs useability I think is important.

  1. CSS reduces it slowly through bisection and clips before it gets too close.
  2. Scale LH uses an approach to scale the color to essentially saturate it within the RGB space then corrects it L & H in the perceptual space and then saturate it again. Some deviation in hue is expected with doing this in RGB and correcting afterwards, but there is a more substantial lightness shift that is noticeable in some cases.
  3. RayTrace approach runs a ray to the out-of-gamut color to the reduced chroma form of the color as defined in the perceptual space, but in the RGB cube (because it is easy to ray trace a perfectly aligned cube). It takes a few passes, correcting L & H and backing off the chroma a bit each time to use a better ray. Then it clips at the end. Just to note, there are some areas where the hue does deviates to a larger existent, but it seems more tolerable with the lightness being more constant.

Each one gets a better ∆E at different times. If you run them through gradients, they look mostly comparable, but Scale LH has some noticeable lightness deviations under certain conditions. You just don't know how these actually play out until you run some real-world cases. Example shows Scale LH (top), Ray Trace (middle), and CSS (bottom).

Screenshot 2024-02-26 at 11 39 42 AM

I just think it would be interesting. For instance, yours has a very tight chroma clamping, how does that look and compare to others? Is there a downside to really tight chroma clamping? I may end up playing with yours just to see how it performs. It's definitely a more sizeable, complex one to implement but the chroma clamping seems pretty close :).

@ardov
Copy link
Contributor Author

ardov commented Feb 26, 2024

Yeah, agree, real-world scenarios should be the priority.

I think there is no need to implement my algorithm to test the colors. It's easier to tweak CSS a bit.
I focused on performance and tried to strictly follow the CSS 4 guidelines. In terms of results the main difference between CSS and my algorithm is that CSS clips as soon as the ∆E < JND. So this difference exists only because of that clipping. And if you reduce JND to 0.001 the results should be identical.

@facelessuser
Copy link
Collaborator

Yep, I'm not suggesting it should match CSS. A pre-calculated LUT is always going to be tough to beat as you don't have to approximate the data as you have it all there 🙂.

@facelessuser
Copy link
Collaborator

I think this just needs a rebase. I'd be happy to merge after that.

@ardov
Copy link
Contributor Author

ardov commented Feb 27, 2024

Damn, tried to rebase and now there are conflicts 😬 I'll figure them out later today.

@ardov
Copy link
Contributor Author

ardov commented Feb 27, 2024

Ok, now it seems to be fine. The first time I rebased to the wrong branch 🙈

@facelessuser facelessuser merged commit 8ae3b44 into color-js:main Feb 27, 2024
5 checks passed
jgerigmeyer added a commit that referenced this pull request Feb 28, 2024
* main:
  Audit function parameter and return types (2/2) (#457)
  Always show delta display to help confirm it is in gamut (#458)
  Final clip is required in Ray tracing
  [apps/gamut-mapping] Add edge seeker algorithm (#448)
  Audit function parameter and return types (1/2) (#456)
  Rework raytracing limiting hue shift to no more than 3 degrees at worst
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

Successfully merging this pull request may close these issues.

None yet

3 participants