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

Suggestion to specify target gamut for clampChroma() #213

Closed
danburzo opened this issue Nov 20, 2023 Discussed in #212 · 1 comment
Closed

Suggestion to specify target gamut for clampChroma() #213

danburzo opened this issue Nov 20, 2023 Discussed in #212 · 1 comment

Comments

@danburzo
Copy link
Collaborator

Discussed in #212

Originally posted by dokozero November 20, 2023
Hello @danburzo,

I use Culori for the OkColor Figma plugin and it's quite useful, however, regarding gamut clipping by the chroma only, when I saw clampGamut() for the first time, I thought that it has an option to specify the target gamut to clamp.

As it was not the case, I ended up making a local modified version that uses inGamut() instead of displayable(), as I read from doc that it was equivalent to inGamut('rgb'). However, I had to duplicate culori's main js file into my project.

With this update, I'm able to easily clamp to sRGB or P3 gamut. I don't use toGamut() as I need to keep the same hue and lightness.

I saw previous issue regarding this topic and my suggestion would be to add a new optional target gamut param to clampChroma(), default to 'rgb' for retro-compatibility:

function clampChroma(color, mode = 'lch', targetGamut = 'rgb') {
  const isInTargetGamut = inGamut(targetGamut)

  color = prepare_default(color)
  if (color === void 0 || isInTargetGamut(color)) return color
  let conv = converter_default(color.mode)
  color = converter_default(mode)(color)
  let clamped = { ...color, c: 0 }
  if (!isInTargetGamut(clamped)) {
    return conv(fixup_rgb(rgb(clamped)))
  }
  let start = 0
  let end = color.c
  let range = getMode(mode).ranges.c
  let resolution = (range[1] - range[0]) / Math.pow(2, 13)
  let _last_good_c
  while (end - start > resolution) {
    clamped.c = start + (end - start) * 0.5
    if (isInTargetGamut(clamped)) {
      _last_good_c = clamped.c
      start = clamped.c
    } else {
      end = clamped.c
    }
  }
  return conv(isInTargetGamut(clamped) ? clamped : { ...clamped, c: _last_good_c })
}

For comparison, current code:

function clampChroma(color, mode = 'lch') {
  color = prepare_default(color)
  if (color === void 0 || displayable(color)) return color
  let conv = converter_default(color.mode)
  color = converter_default(mode)(color)
  let clamped = { ...color, c: 0 }
  if (!displayable(clamped)) {
    return conv(fixup_rgb(rgb(clamped)))
  }
  let start = 0
  let end = color.c
  let range = getMode(mode).ranges.c
  let resolution = (range[1] - range[0]) / Math.pow(2, 13)
  let _last_good_c
  while (end - start > resolution) {
    clamped.c = start + (end - start) * 0.5
    if (displayable(clamped)) {
      _last_good_c = clamped.c
      start = clamped.c
    } else {
      end = clamped.c
    }
  }
  return conv(displayable(clamped) ? clamped : { ...clamped, c: _last_good_c })
}

I saw your answer from #211, with:

oklch(toGamut('p3', 'oklch', differenceEuclidean('oklch'), 0)("oklch(70% 0.4 200)")) 

But personally, I think the new param on clampChroma() would be nice.

Thanks for your time.

@danburzo
Copy link
Collaborator Author

Fixed in culori@3.3.0

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

1 participant