marginally restucture package, tweak full hue spread 1.4.0
This commit is contained in:
19
monobiome/__main__.py
Normal file
19
monobiome/__main__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from monobiome.cli import create_parser, configure_logging
|
||||
|
||||
def main() -> None:
|
||||
parser = create_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
# skim off log level to handle higher-level option
|
||||
if hasattr(args, "log_level") and args.log_level is not None:
|
||||
configure_logging(args.log_level)
|
||||
|
||||
if "func" in args:
|
||||
args.func(args)
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
32
monobiome/cli/__init__.py
Normal file
32
monobiome/cli/__init__.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from monobiome.cli import generate, scheme
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
def configure_logging(log_level: int) -> None:
|
||||
"""
|
||||
Configure logger's logging level.
|
||||
"""
|
||||
|
||||
logger.setLevel(log_level)
|
||||
|
||||
def create_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Accent modeling CLI",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-level",
|
||||
type=int,
|
||||
metavar="int",
|
||||
choices=[10, 20, 30, 40, 50],
|
||||
help="Log level: 10=DEBUG, 20=INFO, 30=WARNING, 40=ERROR, 50=CRITICAL",
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(help="subcommand help")
|
||||
|
||||
generate.register_parser(subparsers)
|
||||
scheme.register_parser(subparsers)
|
||||
|
||||
return parser
|
||||
31
monobiome/cli/generate.py
Normal file
31
monobiome/cli/generate.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import argparse
|
||||
|
||||
def generate_scheme(args: argparse.Namespace) -> None:
|
||||
run_from_json(args.parameters_json, args.parameters_file)
|
||||
|
||||
|
||||
def register_parser(subparsers: _SubparserType) -> None:
|
||||
parser = subparsers.add_parser(
|
||||
"generate",
|
||||
help="generate theme variants"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--contrast-method",
|
||||
type=str,
|
||||
help="Raw JSON string with train parameters",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--contrast-level",
|
||||
type=str,
|
||||
help="Raw JSON string with train parameters",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b",
|
||||
"-base-lightness",
|
||||
type=str,
|
||||
help="Minimum lightness level",
|
||||
)
|
||||
parser.set_defaults(func=generate_scheme)
|
||||
0
monobiome/cli/scheme.py
Normal file
0
monobiome/cli/scheme.py
Normal file
@@ -24,13 +24,14 @@ from monobiome.constants import (
|
||||
|
||||
|
||||
@cache
|
||||
def max_C_Lh(L, h, space='srgb', eps=1e-6, tol=1e-9):
|
||||
'''
|
||||
Binary search for max chroma at fixed lightness and hue
|
||||
def L_maxC_h(L, h, space='srgb', eps=1e-6, tol=1e-9):
|
||||
"""
|
||||
Binary search for max attainable OKLCH chroma at fixed lightness and hue.
|
||||
|
||||
Parameters:
|
||||
L: lightness percentage
|
||||
'''
|
||||
"""
|
||||
|
||||
def C_in_gamut(C):
|
||||
return Color('oklch', [L/100, C, h]).convert(space).in_gamut(tolerance=tol)
|
||||
|
||||
@@ -48,67 +49,130 @@ def max_C_Lh(L, h, space='srgb', eps=1e-6, tol=1e-9):
|
||||
return Cmax
|
||||
|
||||
def quad_bezier_rational(P0, P1, P2, w, t):
|
||||
"""
|
||||
Compute the point values of a quadratic rational Bezier curve.
|
||||
|
||||
Uses `P0`, `P1`, and `P2` as the three control points of the curve. `w`
|
||||
controls the weight toward the middle control point ("sharpness" of the
|
||||
curve"), and `t` is the number of sample points used along the curve.
|
||||
"""
|
||||
|
||||
t = np.asarray(t)[:, None]
|
||||
num = (1-t)**2*P0 + 2*w*(1-t)*t*P1 + t**2*P2
|
||||
den = (1-t)**2 + 2*w*(1-t)*t + t**2
|
||||
|
||||
return num/den
|
||||
return num / den
|
||||
|
||||
def bezier_y_at_x(P0, P1, P2, w, x_query, n=400):
|
||||
"""
|
||||
For the provided QBR parameters, provide the curve value at the given
|
||||
input.
|
||||
"""
|
||||
|
||||
t = np.linspace(0, 1, n)
|
||||
B = quad_bezier_rational(P0, P1, P2, w, t)
|
||||
x_vals, y_vals = B[:, 0], B[:, 1]
|
||||
|
||||
return np.interp(x_query, x_vals, y_vals)
|
||||
|
||||
def Lspace_Cmax_Hmap(h_map: dict[str, float], L_space):
|
||||
"""
|
||||
Compute chroma maxima at provided lightness levels across hues.
|
||||
|
||||
# compute C max values over each point in L space
|
||||
h_Lspace_Cmax = {
|
||||
h_str: [max_C_Lh(_L, _h) for _L in L_space]
|
||||
for h_str, _h in h_map.items()
|
||||
}
|
||||
Parameters:
|
||||
h_map: map from hue names to hue values
|
||||
L_space: array-like set of lightness values
|
||||
|
||||
# compute *unbounded* chroma curves for all hues
|
||||
h_L_points_C = {}
|
||||
h_ctrl_L_C = {}
|
||||
Returns:
|
||||
A map with max chroma values for each hue across lightness space
|
||||
|
||||
for h_str, _h in monotone_h_map.items():
|
||||
h_L_points_C[h_str] = np.array([monotone_C_map[h_str]]*len(L_points))
|
||||
{
|
||||
"red": [ Cmax@L=10, Cmax@L=11, Cmax@L=12, ... ],
|
||||
"orange": [ Cmax@L=10, Cmax@L=11, Cmax@L=12, ... ],
|
||||
...
|
||||
}
|
||||
"""
|
||||
# compute C max values over each point in L space
|
||||
|
||||
h_Lspace_Cmax = {
|
||||
h_str: [max_C_Lh(_L, _h) for _L in L_space]
|
||||
for h_str, _h in h_map.items()
|
||||
}
|
||||
|
||||
return h_Lspace_Cmax
|
||||
|
||||
def ():
|
||||
"""
|
||||
|
||||
for h_str, _h in accent_h_map.items():
|
||||
Lspace_Cmax = h_Lspace_Cmax[h_str]
|
||||
|
||||
raw bezier chroma values for each hue across the lightness space
|
||||
h_L_points_C = {
|
||||
"red": [ Bezier@L=10, Bezier@L=11, Bezier@L=12, ... ],
|
||||
...
|
||||
}
|
||||
|
||||
# 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]
|
||||
three bezier control points for each hue's chroma curve
|
||||
h_ctrl_L_C = {
|
||||
"red": np.array([
|
||||
[ x1, y1 ],
|
||||
[ x2, y2 ],
|
||||
[ x3, y3 ]
|
||||
]),
|
||||
...
|
||||
}
|
||||
"""
|
||||
|
||||
# offset control point by any preset x-shift
|
||||
L_Cmax += h_L_offsets[h_str]
|
||||
# compute *unbounded* chroma curves for all hues
|
||||
h_L_points_C = {}
|
||||
h_ctrl_L_C = {}
|
||||
|
||||
# and get max C at the L offset
|
||||
Cmax = max_C_Lh(L_Cmax, _h)
|
||||
for h_str, _h in monotone_h_map.items():
|
||||
h_L_points_C[h_str] = np.array([monotone_C_map[h_str]]*len(L_points))
|
||||
|
||||
for h_str, _h in accent_h_map.items():
|
||||
Lspace_Cmax = h_Lspace_Cmax[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]
|
||||
|
||||
# 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)
|
||||
h_L_points_C[h_str] = B_L_points
|
||||
h_ctrl_L_C[h_str] = np.vstack([p_0, p_Cmax, p_100])
|
||||
# offset control point by any preset x-shift
|
||||
L_Cmax += h_L_offsets[h_str]
|
||||
|
||||
# compute full set of final chroma curves; limits every point to in-gamut max
|
||||
h_LC_color_map = {}
|
||||
h_L_points_Cstar = {}
|
||||
# and get max C at the L offset
|
||||
Cmax = max_C_Lh(L_Cmax, _h)
|
||||
|
||||
for h_str, L_points_C in h_L_points_C.items():
|
||||
_h = h_map[h_str]
|
||||
# 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)
|
||||
h_L_points_C[h_str] = B_L_points
|
||||
h_ctrl_L_C[h_str] = np.vstack([p_0, p_Cmax, p_100])
|
||||
|
||||
h_L_points_Cstar[h_str] = [
|
||||
max(0, min(_C, max_C_Lh(_L, _h)))
|
||||
for _L, _C in zip(L_points, L_points_C)
|
||||
]
|
||||
|
||||
def ():
|
||||
"""
|
||||
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, ... ],
|
||||
...
|
||||
}
|
||||
"""
|
||||
|
||||
# compute full set of final chroma curves; limits every point to in-gamut max
|
||||
h_LC_color_map = {}
|
||||
h_L_points_Cstar = {}
|
||||
|
||||
for h_str, L_points_C in h_L_points_C.items():
|
||||
_h = h_map[h_str]
|
||||
|
||||
h_L_points_Cstar[h_str] = [
|
||||
max(0, min(_C, max_C_Lh(_L, _h)))
|
||||
for _L, _C in zip(L_points, L_points_C)
|
||||
]
|
||||
|
||||
# if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user