Skip to content

Instantly share code, notes, and snippets.

@facelessuser
Last active February 18, 2022 17:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save facelessuser/f7adf843a4f82b049a3de00dc16a07a8 to your computer and use it in GitHub Desktop.
Save facelessuser/f7adf843a4f82b049a3de00dc16a07a8 to your computer and use it in GitHub Desktop.
CSS Level 4 Gamut mapping
from coloraide.gamut import Fit, clip_channels
from coloraide.util import NaN
class OklchChroma(Fit):
"""Lch chroma gamut mapping class."""
NAME = "oklch-chroma"
EPSILON = 0.0001
LIMIT = 0.02
DE = "ok"
SPACE = "oklch"
SPACE_COORDINATE = "{}.chroma".format(SPACE)
MIN_LIGHTNESS = 0
MAX_LIGHTNESS = 1
@classmethod
def fit(cls, color, **kwargs):
"""
Gamut mapping via Oklch chroma.
"""
space = color.space()
mapcolor = color.convert(cls.SPACE)
lightness = mapcolor.lightness
# Return white or black if lightness is out of range
if lightness >= cls.MAX_LIGHTNESS or lightness <= cls.MIN_LIGHTNESS:
mapcolor.chroma = 0
mapcolor.hue = NaN
clip_channels(color.update(mapcolor))
return
# Set initial chroma boundaries
low = 0.0
high = mapcolor.chroma
clip_channels(color.update(mapcolor))
# Adjust chroma (using binary search).
# This helps preserve the other attributes of the color.
# Compress chroma until we are are right at the JND edge of being out of gamut.
if not mapcolor.in_gamut(space):
while True:
mapcolor.chroma = (high + low) * 0.5
if mapcolor.in_gamut(space, tolerance=0):
low = mapcolor.chroma
else:
clip_channels(color.update(mapcolor))
if mapcolor.delta_e(color, method=cls.DE) < cls.LIMIT:
break
high = mapcolor.chroma
class Color2(Color): ...
Color2.register(OklchChroma, overwrite=True)
Color('green').interpolate('blue')
Color2('green').interpolate('blue')
Color('red').interpolate('blue', space="oklch")
Color2('red').interpolate('blue', space="oklch")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment