Skip to content

Instantly share code, notes, and snippets.

@facelessuser
Last active February 18, 2024 22:34
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/9ed4c9b719404e8ae338484c92fc1f7a to your computer and use it in GitHub Desktop.
Save facelessuser/9ed4c9b719404e8ae338484c92fc1f7a to your computer and use it in GitHub Desktop.
Scale LH and tones
# pragma: init
from coloraide.gamut import Fit
from coloraide.spaces import RGBish
class OkLChScale(Fit):
"""
Gamut mapping by scaling.
Expected gamut mapping spaces are RGB type spaces.
For best results, linear light RGB spaces are preferred.
"""
NAME = "oklch-scale"
SPACE = "oklch"
def fit(self, color, space, **kwargs):
"""Scale the color within its gamut but preserve L and h as much as possible."""
# Requires an RGB-ish space, preferably a linear space.
if not isinstance(color.CS_MAP[space], RGBish):
raise ValueError("Scaling only works in an RGBish color space, not {}".format(type(color.CS_MAP[space])))
# For now, if a non-linear CSS variant is specified, just use the linear form.
if space in {'srgb', 'display-p3', 'rec2020', 'a98-rgb', 'prophoto-rgb'}:
space += '-linear'
orig = color.space()
mapcolor = color.convert(self.SPACE, norm=False) if orig != self.SPACE else color.clone().normalize(nans=False)
gamutcolor = color.convert(space, norm=False) if orig != space else color.clone().normalize(nans=False)
self.scale(gamutcolor)
gamutcolor.set(
{
self.SPACE + '.l': mapcolor['l'],
self.SPACE + '.h': mapcolor['h']
}
)
self.scale(gamutcolor)
color.update(gamutcolor)
def scale(self, color):
"""Scale the RGB color within its gamut."""
deltas = [c - 0.5 for c in color[:-1]]
max_distance = max(abs(c) for c in deltas)
scalingFactor = max_distance / 0.5;
color[:-1] = [c / scalingFactor + 0.5 for c in deltas]
from coloraide.everything import ColorAll as Base
class Color(Base): ...
Color.register(OkLChScale())
# pragma: init
K_1 = 0.173
K_2 = 0.004
K_3 = (1.0 + K_1) / (1.0 + K_2)
def toe_inv(x: float) -> float:
"""Inverse toe function for L_r."""
return (x ** 2 + K_1 * x) / (K_3 * (x + K_2))
def reduce_oklch_tonal_palette(c):
c = Color(c).convert('oklch')
tones = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100]
return [c.clone().set('l', toe_inv(tone / 100)).fit('srgb', method='oklch-chroma') for tone in tones]
def scale_oklch_tonal_palette(c):
c = Color(c).convert('oklch')
tones = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100]
return [c.clone().set('l', tone / 100).fit('srgb', method='oklch-scale') for tone in tones]
Steps(scale_oklch_tonal_palette('red'))
Steps(reduce_oklch_tonal_palette('red'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment