Created
February 15, 2024 13:21
-
-
Save facelessuser/170f02fb17310e4f85615ea330769b78 to your computer and use it in GitHub Desktop.
Evaluating Scaled LH
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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