Skip to content

Instantly share code, notes, and snippets.

@facelessuser
Created February 15, 2024 13:21
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/170f02fb17310e4f85615ea330769b78 to your computer and use it in GitHub Desktop.
Save facelessuser/170f02fb17310e4f85615ea330769b78 to your computer and use it in GitHub Desktop.
Evaluating Scaled LH
# pragma: init
from coloraide.gamut import Fit
from coloraide.spaces import RGBish
from coloraide import algebra as alg
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]
class OkLChScale2(Fit):
"""
Gamut mapping by scaling.
Expected gamut mapping spaces are RGB type spaces.
For best results, linear light RGB spaces are preferred.
"""
NAME = "oklch-scale2"
SPACE = "oklch"
ITERATIONS = 2
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])))
# Get the LCh form of the color and the achromatic LCh (fully reduced chroma) of the same color
orig = color.space()
mapcolor = color.convert(self.SPACE, norm=False) if orig != self.SPACE else color.clone().normalize(nans=False)
achroma = mapcolor.clone().set('c', 0)
# Scale the chroma based on the channel that is furthest out of gamut.
for x in range(self.ITERATIONS):
self.scale(mapcolor, achroma, space)
# Clip in the target gamut in case we are still out of gamut.
return color.update(mapcolor.clip(space)).clip(space)
def scale(self, mapcolor, achroma, space):
"""
Scale the chroma based on the channel that is furthest out of gamut.
If the channel is out of gamut, use inverse interpolation to see what factor
would be needed to get the channel in gamut when interpolating between itself
and an achromatic version of itself. This is used as a rough approximation to
then scale the chroma between itself and the achromatic version of itself.
"""
deltas = []
for a, b in zip(mapcolor.convert(space).coords(), achroma.convert(space).coords()):
if a > 1:
deltas.append(alg.ilerp(a, b, 1))
elif a < 0:
deltas.append(alg.ilerp(a, b, 0))
if deltas:
mapcolor['c'] = alg.lerp(mapcolor['c'], achroma['c'], max(deltas))
from coloraide.everything import ColorAll as Base
class ColorReduce(Base):
FIT = 'oklch-chroma'
class ColorScale(Base):
FIT = 'oklch-scale'
ColorScale.register(OkLChScale())
class ColorScale2(Base):
FIT = 'oklch-scale2'
ColorScale2.register(OkLChScale2())
# pragma: init
c1 = Color('oklch', [0.7, 0.5, 0])
c2 = Color('oklch', [0.7, 0.5, 360])
ColorReduce.interpolate([c1, c2], space='oklch', hue='specified')
ColorScale2.interpolate([c1, c2], space='oklch', hue='specified')
ColorScale.interpolate([c1, c2], space='oklch', hue='specified')
c1 = Color('oklch', [0.3, 0.5, 0])
c2 = Color('oklch', [0.3, 0.5, 360])
ColorReduce.interpolate([c1, c2], space='oklch', hue='specified')
ColorScale2.interpolate([c1, c2], space='oklch', hue='specified')
ColorScale.interpolate([c1, c2], space='oklch', hue='specified')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment