142 lines
3.6 KiB
Python
142 lines
3.6 KiB
Python
import tomllib
|
|
from importlib.resources import files
|
|
|
|
import numpy as np
|
|
|
|
from monobiome.curve import (
|
|
l_maxC_h,
|
|
bezier_y_at_x,
|
|
)
|
|
|
|
parameters_file = files("monobiome.data") / "parameters.toml"
|
|
parameters = tomllib.load(parameters_file.open("rb"))
|
|
|
|
L_min: int = parameters.get("L_min", 10)
|
|
L_max: int = parameters.get("L_max", 98)
|
|
L_step: int = parameters.get("L_step", 5)
|
|
|
|
L_points: list[int] = list(range(L_min, L_max+1))
|
|
|
|
# L-space just affects accuracy of chroma max
|
|
L_space = np.arange(0, 100 + L_step, L_step)
|
|
|
|
monotone_C_map = parameters.get("monotone_C_map", {})
|
|
h_weights = parameters.get("h_weights", {})
|
|
h_L_offsets = parameters.get("h_L_offsets", {})
|
|
h_C_offsets = parameters.get("h_C_offsets", {})
|
|
monotone_h_map = parameters.get("monotone_h_map", {})
|
|
accent_h_map = parameters.get("accent_h_map", {})
|
|
h_map = {**monotone_h_map, **accent_h_map}
|
|
|
|
"""
|
|
Compute chroma maxima at provided lightness levels across hues.
|
|
|
|
A map with max chroma values for each hue across lightness space
|
|
|
|
{
|
|
"red": [ Cmax@L=10, Cmax@L=11, Cmax@L=12, ... ],
|
|
"orange": [ Cmax@L=10, Cmax@L=11, Cmax@L=12, ... ],
|
|
...
|
|
}
|
|
"""
|
|
Lspace_Cmax_Hmap = {
|
|
h_str: [l_maxC_h(_L, _h) for _L in L_space]
|
|
for h_str, _h in h_map.items()
|
|
}
|
|
|
|
|
|
"""
|
|
Set QBR curves, *unbounded* chroma curves for all hues
|
|
|
|
1. Raw bezier chroma values for each hue across the lightness space
|
|
|
|
Lpoints_Cqbr_Hmap = {
|
|
"red": [ Bezier@L=10, Bezier@L=11, Bezier@L=12, ... ],
|
|
...
|
|
}
|
|
|
|
2. Three bezier control points for each hue's chroma curve
|
|
|
|
QBR_ctrl_Hmap = {
|
|
"red": np.array([
|
|
[ x1, y1 ],
|
|
[ x2, y2 ],
|
|
[ x3, y3 ]
|
|
]),
|
|
...
|
|
}
|
|
"""
|
|
Lpoints_Cqbr_Hmap = {}
|
|
QBR_ctrl_Hmap = {}
|
|
|
|
for h_str, _h in monotone_h_map.items():
|
|
Lpoints_Cqbr_Hmap[h_str] = np.array(
|
|
[monotone_C_map[h_str]]*len(L_points)
|
|
)
|
|
|
|
for h_str, _h in accent_h_map.items():
|
|
Lspace_Cmax = Lspace_Cmax_Hmap[h_str]
|
|
|
|
# get L value of max chroma; will be a bezier control
|
|
L_Cmax_idx = np.argmax(Lspace_Cmax)
|
|
L_Cmax = L_space[L_Cmax_idx]
|
|
|
|
# offset control point by any preset x-shift
|
|
L_Cmax += h_L_offsets[h_str]
|
|
|
|
# and get max C at the L offset
|
|
Cmax = l_maxC_h(L_Cmax, _h)
|
|
|
|
# set 3 control points; shift by any global linear offest
|
|
C_offset = h_C_offsets.get(h_str, 0)
|
|
|
|
p_0 = np.array([0, 0])
|
|
p_Cmax = np.array([L_Cmax, Cmax + C_offset])
|
|
p_100 = np.array([100, 0])
|
|
|
|
B_L_points = bezier_y_at_x(
|
|
p_0, p_Cmax, p_100,
|
|
h_weights.get(h_str, 1),
|
|
L_points
|
|
)
|
|
Lpoints_Cqbr_Hmap[h_str] = B_L_points
|
|
QBR_ctrl_Hmap[h_str] = np.vstack([p_0, p_Cmax, p_100])
|
|
|
|
|
|
"""
|
|
Bezier chroma values, but bounded to attainable gamut colors (bezier fit
|
|
can produce invalid chroma values)
|
|
|
|
h_L_points_Cstar = {
|
|
"red": [ bounded-bezier@L=10, bounded-bezier@L=11, ... ],
|
|
...
|
|
}
|
|
"""
|
|
Lpoints_Cstar_Hmap = {}
|
|
|
|
for h_str, L_points_C in Lpoints_Cqbr_Hmap.items():
|
|
_h = h_map[h_str]
|
|
|
|
Lpoints_Cstar_Hmap[h_str] = [
|
|
max(0, min(_C, l_maxC_h(_L, _h)))
|
|
for _L, _C in zip(L_points, L_points_C, strict=True)
|
|
]
|
|
|
|
|
|
# strictly enforce curve bounds s.t. there are no intersections
|
|
# order is determined by the max attained chromap
|
|
max_Cstar_Horder = [
|
|
(h_str, max(Lpoints_Cstar))
|
|
for h_str, Lpoints_Cstar in Lpoints_Cstar_Hmap.items()
|
|
]
|
|
max_Cstar_Horder = sorted(max_Cstar_Horder, key=lambda t: t[1], reverse=True)
|
|
|
|
for i in range(len(max_Cstar_Horder)-1):
|
|
outer_h, _ = max_Cstar_Horder[i]
|
|
inner_h, _ = max_Cstar_Horder[i+1]
|
|
|
|
Lpoints_Cstar_Hmap[inner_h] = [
|
|
min(inner_c, Lpoints_Cstar_Hmap[outer_h][ci])
|
|
for ci, inner_c in enumerate(Lpoints_Cstar_Hmap[inner_h])
|
|
]
|