import math from typing import Sequence, Tuple from .charts_color_linear_to_srgb import linear_to_srgb from .charts_color_parse_hex import parse_hex from .charts_color_srgb_to_linear import srgb_to_linear from .charts_color_to_hex import to_hex from .charts_color_types import Rgb from .math_pow import pow Lab = Tuple[float, float, float] # Björn Ottosson's matrices, "A perceptual color space for image processing" # (2020), https://bottosson.github.io/posts/oklab/, applied to linear sRGB. def _to_oklab(hex: str) -> Lab: # noqa: A002 c = parse_hex(hex) r = srgb_to_linear(c.r) g = srgb_to_linear(c.g) b = srgb_to_linear(c.b) l = pow(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b, 1 / 3) m = pow(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b, 1 / 3) s = pow(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b, 1 / 3) return ( 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s, 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s, 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s, ) def _from_oklab(lab: Lab) -> str: L, A, B = lab l0 = L + 0.3963377774 * A + 0.2158037573 * B m0 = L - 0.1055613458 * A - 0.0638541728 * B s0 = L - 0.0894841775 * A - 1.2914855480 * B l = l0 * l0 * l0 m = m0 * m0 * m0 s = s0 * s0 * s0 return to_hex( Rgb( r=linear_to_srgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s), g=linear_to_srgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s), b=linear_to_srgb(-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s), ) ) def interpolate_color(ramp: Sequence[str], t: float) -> str: """The colour a fraction t of the way along a ramp of evenly spaced stops, mixed in OKLab so equal steps look equal.""" if len(ramp) < 2: raise ValueError("ramp needs at least 2 colours, received %d" % len(ramp)) if isinstance(t, bool) or not isinstance(t, (int, float)) or not math.isfinite(t) or t < 0 or t > 1: raise ValueError("t must be between 0 and 1, received %r" % (t,)) position = float(t) * (len(ramp) - 1) i = math.floor(position) if i > len(ramp) - 2: i = len(ramp) - 2 u = position - i a = _to_oklab(ramp[i]) b = _to_oklab(ramp[i + 1]) return _from_oklab((a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u, a[2] + (b[2] - a[2]) * u))