Skip to content

Instantly share code, notes, and snippets.

@facelessuser
Last active September 8, 2021 17:44
Show Gist options
  • Save facelessuser/4f2eab96411bbb7cadfd57cfea233ac7 to your computer and use it in GitHub Desktop.
Save facelessuser/4f2eab96411bbb7cadfd57cfea233ac7 to your computer and use it in GitHub Desktop.
Din99o Lab and Lch
"""
Din99o class.
https://de.wikipedia.org/wiki/DIN99-Farbraum
"""
from coloraide.spaces import RE_DEFAULT_MATCH
from coloraide.spaces.xyz import XYZ
from coloraide.spaces.lch.base import LchBase
from coloraide.spaces.lab.base import LabBase, lab_to_xyz, xyz_to_lab
from coloraide import util
import re
import math
ACHROMATIC_THRESHOLD = 0.000000000002
KE = 1
KCH = 1
# --- Din99o ---
RADS = math.radians(26)
FACTOR = 0.83
C1 = 303.67
C2 = 0.0039
C3 = 0.075
C4 = 0.0435
# --- Din99 ---
# RADS = math.radians(16)
# FACTOR = 0.7
# C1 = 105.51
# C2 = 0.0158
# C3 = 0.045
# C4 = 0.045
def lab_to_din99o(lab):
"""XYZ to Din99o."""
l, a, b = lab
l99o = C1 * math.log(1 + C2 * l) / KE
if a == 0 and b == 0:
a99o = b99o = 0
else:
eo = a * math.cos(RADS) + b * math.sin(RADS)
fo = FACTOR * (b * math.cos(RADS) - a * math.sin(RADS))
go = math.sqrt(eo ** 2 + fo ** 2)
c99o = math.log(1 + C3 * go) / (C4 * KE * KCH)
h99o = math.atan2(fo, eo) + RADS
a99o = c99o * math.cos(h99o)
b99o = c99o * math.sin(h99o)
return [l99o, a99o, b99o]
def din99o_lab_to_lch(lab):
"""
Convert Din99o Lab to Lch.
Hue is in radians.
"""
l99o, a99o, b99o = lab
h99o = math.atan2(b99o, a99o)
c99o = math.sqrt(a99o ** 2 + b99o ** 2)
return [l99o, c99o, h99o]
def din99o_to_lab(din99o):
"""Din99o to XYZ."""
l99o, c99o, h99o = din99o_lab_to_lch(din99o)
g = (math.exp(C4 * c99o * KCH * KE) - 1) / C3
e = g * math.cos(h99o - RADS)
f = g * math.sin(h99o - RADS)
return [
(math.exp((l99o * KE) / C1) - 1) / C2,
e * math.cos(RADS) - (f / FACTOR) * math.sin(RADS),
e * math.sin(RADS) + (f / FACTOR) * math.cos(RADS)
]
class Din99o(LabBase):
"""Din99o class."""
SPACE = "din99o-lab"
SERIALIZE = ("--din99o-lab",)
CHANNEL_NAMES = ("lightness", "a", "b", "alpha")
DEFAULT_MATCH = re.compile(RE_DEFAULT_MATCH.format(color_space='|'.join(SERIALIZE), channels=3))
WHITE = "D65"
@classmethod
def _to_xyz(cls, parent, lab):
"""To XYZ."""
return parent.chromatic_adaptation(cls.WHITE, XYZ.WHITE, lab_to_xyz(din99o_to_lab(lab), cls.white()))
@classmethod
def _from_xyz(cls, parent, xyz):
"""From XYZ."""
return lab_to_din99o(xyz_to_lab(parent.chromatic_adaptation(XYZ.WHITE, cls.WHITE, xyz), cls.white()))
def lch_to_lab(lch):
"""Din99o Lch to lab."""
l, c, h = lch
h = util.no_nan(h)
# If, for whatever reason (mainly direct user input),
# if chroma is less than zero, clamp to zero.
if c < 0.0:
c = 0.0
return [
l,
c * math.cos(math.radians(h)),
c * math.sin(math.radians(h))
]
def lab_to_lch(lab):
"""Din99o Lab to Lch."""
l, a, b = lab
h = math.degrees(math.atan2(b, a))
c = math.sqrt(a ** 2 + b ** 2)
# Achromatic colors will often get extremely close, but not quite hit zero.
# Essentially, we want to discard noise through rounding and such.
if c <= ACHROMATIC_THRESHOLD:
h = util.NaN
return [l, c, util.constrain_hue(h)]
class Din99oLch(LchBase):
"""Lch D65 class."""
SPACE = "din99o-lch"
SERIALIZE = ("--din99o-lch",)
DEFAULT_MATCH = re.compile(RE_DEFAULT_MATCH.format(color_space='|'.join(SERIALIZE), channels=3))
WHITE = "D65"
@classmethod
def _to_din99o_lab(cls, parent, lch):
"""To Lab."""
return lch_to_lab(lch)
@classmethod
def _from_din99o_lab(cls, parent, lab):
"""To Lab."""
return lab_to_lch(lab)
@classmethod
def _to_xyz(cls, parent, lch):
"""To XYZ."""
return Din99o._to_xyz(parent, cls._to_din99o_lab(parent, lch))
@classmethod
def _from_xyz(cls, parent, xyz):
"""From XYZ."""
return cls._from_din99o_lab(parent, Din99o._from_xyz(parent, xyz))
Color("blue").interpolate(
"white",
space='lab'
)
Color("blue").interpolate(
"white",
space='din99o-lab'
)
Color("blue").interpolate(
"white",
space='jzazbz'
)
Color("blue").interpolate(
"white",
space='oklab'
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment