Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b580f47679 | |||
| b2e80a9f35 | |||
| 534f2423e9 | |||
| b5b9b12174 | |||
| 4ee9e23c5c | |||
| a5256aaa62 |
1
.gitignore
vendored
@@ -17,7 +17,6 @@ docs/_build/
|
||||
# local
|
||||
/Makefile
|
||||
|
||||
data/
|
||||
archive/
|
||||
|
||||
notebooks/color_spaces_manyview.ipynb
|
||||
|
||||
11
README.md
@@ -32,11 +32,11 @@ smoothly as a function of lightness within sRGB gamut bounds.
|
||||
|
||||
| Chroma curves | Color trajectories |
|
||||
|----------------------------------------------------------|------------------------------------------|
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
| Palette |
|
||||
|----------------------------------------------|
|
||||
|  |
|
||||
|  |
|
||||
|
||||
Chroma curves are designed specifically to establish a distinct role for each
|
||||
accent and are non-intersecting over the lightness domain (hence the distinct
|
||||
@@ -143,6 +143,13 @@ using the `monobiome` CLI:
|
||||
(`grassland`). Every part of this process can be customized: the scheme
|
||||
parameters, the scheme definitions/file, the app template.
|
||||
|
||||
The separation of duties here facilitates robustness: the palette colors can be
|
||||
tweaked (across versions, say) without changing how those colors get used in
|
||||
app themes. Scheme files don't hardcode color values, and app templates don't
|
||||
refer to palette variables at all. Scheme files bridge palette colors with
|
||||
*intended uses*, and templates need only align those uses under an app's
|
||||
expected config syntax.
|
||||
|
||||
Running these commands in sequence from the repo root should work
|
||||
out-of-the-box after having installed the CLI tool.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version = "1.5.1"
|
||||
version = "1.5.4"
|
||||
|
||||
[alpine]
|
||||
l10 = "#030303"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version = "1.5.1"
|
||||
version = "1.5.4"
|
||||
|
||||
[alpine]
|
||||
l10 = "oklch(10.0% 0.0000 0.0)"
|
||||
|
||||
BIN
images/release/1.5.2/chroma-bounds.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
images/release/1.5.2/chroma-curves.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
images/release/1.5.2/palette-bare.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
images/release/1.5.2/palette.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
images/release/1.5.3/chroma-bounds.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
images/release/1.5.3/chroma-curves.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
images/release/1.5.3/palette-bare.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
images/release/1.5.3/palette.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
images/release/1.5.4/chroma-bounds.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
images/release/1.5.4/chroma-curves.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
images/release/1.5.4/palette-bare.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
images/release/1.5.4/palette.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
65
monobiome/data/parameters.toml
Normal file
@@ -0,0 +1,65 @@
|
||||
L_min = 10
|
||||
L_max = 98
|
||||
L_step = 5
|
||||
|
||||
[monotone_C_map]
|
||||
alpine = 0
|
||||
badlands = 0.011
|
||||
chaparral = 0.011
|
||||
savanna = 0.011
|
||||
grassland = 0.011
|
||||
reef = 0.011
|
||||
tundra = 0.011
|
||||
heathland = 0.011
|
||||
moorland = 0.011
|
||||
|
||||
[h_weights]
|
||||
red = 3.0
|
||||
orange = 3.8
|
||||
yellow = 3.8
|
||||
green = 3.8
|
||||
cyan = 3.8
|
||||
blue = 3.6
|
||||
violet = 3.0
|
||||
magenta = 3.6
|
||||
|
||||
[h_L_offsets]
|
||||
red = 0
|
||||
orange = -5.5
|
||||
yellow = -13.5
|
||||
green = -12.5
|
||||
cyan = -10.5
|
||||
blue = 9
|
||||
violet = 7
|
||||
magenta = 2
|
||||
|
||||
[h_C_offsets]
|
||||
red = 0
|
||||
orange = -0.015
|
||||
yellow = -0.052
|
||||
green = -0.08
|
||||
cyan = -0.009
|
||||
blue = -0.01
|
||||
violet = -0.047
|
||||
magenta = -0.1
|
||||
|
||||
[monotone_h_map]
|
||||
alpine = 0
|
||||
badlands = 29
|
||||
chaparral = 62.5
|
||||
savanna = 104
|
||||
grassland = 148
|
||||
reef = 205
|
||||
tundra = 262
|
||||
heathland = 306
|
||||
moorland = 350
|
||||
|
||||
[accent_h_map]
|
||||
red = 29
|
||||
orange = 62.5
|
||||
yellow = 104
|
||||
green = 148
|
||||
cyan = 205
|
||||
blue = 262
|
||||
violet = 306
|
||||
magenta = 350
|
||||
@@ -5,6 +5,10 @@ from importlib.metadata import version
|
||||
|
||||
from coloraide import Color
|
||||
|
||||
from monobiome.util import (
|
||||
hex_from_rgb8,
|
||||
srgb8_from_color,
|
||||
)
|
||||
from monobiome.constants import (
|
||||
h_map,
|
||||
L_points,
|
||||
@@ -24,8 +28,8 @@ def compute_hlc_map(notation: str) -> dict[str, Any]:
|
||||
oklch = Color('oklch', [_l/100, _c, _h])
|
||||
|
||||
if notation == "hex":
|
||||
srgb = oklch.convert('srgb')
|
||||
c_str = srgb.to_string(hex=True)
|
||||
rgb8 = srgb8_from_color(oklch)
|
||||
c_str = hex_from_rgb8(rgb8)
|
||||
elif notation == "oklch":
|
||||
ol, oc, oh = oklch.convert('oklch').coords()
|
||||
c_str = f"oklch({ol*100:.1f}% {oc:.4f} {oh:.1f})"
|
||||
|
||||
@@ -5,6 +5,7 @@ import matplotlib.pyplot as plt
|
||||
from coloraide import Color
|
||||
from matplotlib.collections import LineCollection
|
||||
|
||||
from monobiome.util import srgb8_from_color
|
||||
from monobiome.palette import compute_hlc_map
|
||||
from monobiome.constants import (
|
||||
h_map,
|
||||
@@ -72,13 +73,12 @@ def plot_hue_chroma_bounds() -> tuple[plt.Figure, plt.Axes]:
|
||||
|
||||
return fig, axes
|
||||
|
||||
|
||||
def plot_hue_chroma_star() -> tuple[plt.Figure, plt.Axes]:
|
||||
fig, ax = plt.subplots(1, 1, figsize=(8, 6))
|
||||
|
||||
# uncomment to preview 5 core term colors
|
||||
colors = accent_h_map.keys()
|
||||
#colors = set(["red", "orange", "yellow", "green", "blue"])
|
||||
# uncomment to preview just the 5 core term colors
|
||||
# colors = set(["red", "orange", "yellow", "green", "blue"])
|
||||
|
||||
for h_str, _ in max_Cstar_Horder:
|
||||
Lpoints_Cstar = Lpoints_Cstar_Hmap[h_str]
|
||||
@@ -88,34 +88,22 @@ def plot_hue_chroma_star() -> tuple[plt.Figure, plt.Axes]:
|
||||
|
||||
_h = h_map[h_str]
|
||||
h_colors = [
|
||||
Color('oklch', [_l/100, _c, _h]).convert("srgb")
|
||||
Color(
|
||||
'oklch', [_l/100, _c, _h]
|
||||
).convert("srgb").fit(method="oklch-chroma")
|
||||
for _l, _c in zip(L_points, Lpoints_Cstar, strict=True)
|
||||
]
|
||||
|
||||
# # ax.fill_between(
|
||||
# ax.scatter(
|
||||
# L_points,
|
||||
# Lpoints_Cstar,
|
||||
# alpha=0.7,
|
||||
# c=h_colors,
|
||||
# #alpha=0.2,
|
||||
# #color='grey',
|
||||
# label=h_str
|
||||
# )
|
||||
# x, y = L_points, Lpoints_Cstar_Hmap[h_str]
|
||||
# n = int(0.45*len(x))
|
||||
# ax.text(x[n], y[n]-0.01, h_str, rotation=10, va='center', ha='left')
|
||||
|
||||
x = np.asarray(L_points)
|
||||
y = np.asarray(Lpoints_Cstar)
|
||||
pts = np.column_stack([x, y]).reshape(-1, 1, 2)
|
||||
segs = np.concatenate([pts[:-1], pts[1:]], axis=1)
|
||||
rgb = np.asarray(h_colors)
|
||||
seg_colors = (rgb[:-1] + rgb[1:]) / 2
|
||||
|
||||
lc = LineCollection(segs, colors=seg_colors, linewidth=3,
|
||||
capstyle="round", joinstyle="round",
|
||||
label=h_str)
|
||||
|
||||
ax.add_collection(lc)
|
||||
ax.autoscale_view()
|
||||
|
||||
@@ -126,12 +114,11 @@ def plot_hue_chroma_star() -> tuple[plt.Figure, plt.Axes]:
|
||||
|
||||
return fig, ax
|
||||
|
||||
|
||||
def palette_image(
|
||||
palette: dict[str, dict[int, str]],
|
||||
cell_size: int = 40,
|
||||
keys: list[str] | None = None
|
||||
) -> tuple[np.ndarray, list[str], list[list[int]], int, int]:
|
||||
) -> tuple[np.ndarray, list[str], list[list[int]]]:
|
||||
names = list(palette.keys()) if keys is None else keys
|
||||
|
||||
row_count = len(names)
|
||||
@@ -140,7 +127,7 @@ def palette_image(
|
||||
|
||||
h = row_count * cell_size
|
||||
w = max_cols * cell_size
|
||||
img = np.ones((h, w, 3), float)
|
||||
img = np.ones((h, w, 3), int)
|
||||
|
||||
lightness_keys_per_row = []
|
||||
|
||||
@@ -149,14 +136,12 @@ def palette_image(
|
||||
lkeys = sorted(shades.keys())
|
||||
lightness_keys_per_row.append(lkeys)
|
||||
for c, k in enumerate(lkeys):
|
||||
col = Color(shades[k]).convert("srgb").fit(method="clip")
|
||||
rgb = [col["r"], col["g"], col["b"]]
|
||||
rgb = srgb8_from_color(shades[k])
|
||||
r0, r1 = r * cell_size, (r + 1) * cell_size
|
||||
c0, c1 = c * cell_size, (c + 1) * cell_size
|
||||
img[r0:r1, c0:c1, :] = rgb
|
||||
|
||||
return img, names, lightness_keys_per_row, cell_size, max_cols
|
||||
|
||||
return img, names, lightness_keys_per_row
|
||||
|
||||
def show_palette(
|
||||
palette: dict[str, dict[int, str]],
|
||||
@@ -165,9 +150,7 @@ def show_palette(
|
||||
show_labels: bool = True,
|
||||
dpi: int = 100,
|
||||
) -> tuple[plt.Figure, plt.Axes]:
|
||||
img, names, keys, cell_size, max_cols = palette_image(
|
||||
palette, cell_size, keys=keys
|
||||
)
|
||||
img, names, _ = palette_image(palette, cell_size, keys=keys)
|
||||
|
||||
fig_w = img.shape[1] / 100
|
||||
fig_h = img.shape[0] / 100
|
||||
@@ -175,7 +158,7 @@ def show_palette(
|
||||
if show_labels:
|
||||
fig, ax = plt.subplots(figsize=(fig_w, fig_h), dpi=dpi)
|
||||
|
||||
ax.imshow(img, interpolation="none", origin="upper")
|
||||
ax.imshow(img, interpolation="nearest", origin="upper")
|
||||
ax.set_xticks([])
|
||||
|
||||
ytick_pos = [(i + 0.5) * cell_size for i in range(len(names))]
|
||||
|
||||
@@ -2,6 +2,7 @@ import math
|
||||
from types import GenericAlias
|
||||
from argparse import ArgumentParser, _SubParsersAction
|
||||
|
||||
import numpy as np
|
||||
from coloraide import Color
|
||||
|
||||
_SubParsersAction.__class_getitem__ = classmethod(GenericAlias)
|
||||
@@ -33,3 +34,13 @@ def oklch_distance(xc: Color, yc: Color) -> float:
|
||||
dz = l1 - l2
|
||||
|
||||
return (dx**2 + dy**2 + dz**2)**0.5
|
||||
|
||||
def srgb8_from_color(c: str | Color) -> np.ndarray:
|
||||
c = Color(c).convert("srgb").fit(method="oklch-chroma")
|
||||
rgb = np.array([c["r"], c["g"], c["b"]], dtype=float)
|
||||
rgb8 = np.clip(np.round(rgb * 255), 0, 255).astype(np.uint8)
|
||||
|
||||
return rgb8
|
||||
|
||||
def hex_from_rgb8(rgb8: np.ndarray) -> str:
|
||||
return f"#{int(rgb8[0]):02x}{int(rgb8[1]):02x}{int(rgb8[2]):02x}"
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "monobiome"
|
||||
version = "1.5.1"
|
||||
version = "1.5.4"
|
||||
description = "Monobiome color palette"
|
||||
requires-python = ">=3.12"
|
||||
authors = [
|
||||
|
||||
@@ -17,8 +17,8 @@ fig.savefig(Path(figure_dir, "chroma-curves.png"))
|
||||
# "oklch" causes some slight hex drift when later using an eyedropper
|
||||
hlc_map = compute_hlc_map("hex") # ("oklch")
|
||||
|
||||
fig, ax = plotting.show_palette(hlc_map)
|
||||
fig.savefig(Path(figure_dir, "palette.png"))
|
||||
fig, ax = plotting.show_palette(hlc_map, cell_size=25)
|
||||
fig.savefig(Path(figure_dir, "palette.png"), pad_inches=0)
|
||||
|
||||
fig, ax = plotting.show_palette(hlc_map, show_labels=False)
|
||||
fig.savefig(Path(figure_dir, "palette-bare.png"), pad_inches=0)
|
||||
|
||||
@@ -11,3 +11,6 @@ uv run monobiome palette -n oklch -f toml -o colors/oklch-palette.toml
|
||||
|
||||
# generate provided app config
|
||||
"$script_dir/generate.sh"
|
||||
|
||||
# generate release plots
|
||||
uv run "$script_dir/plots.py"
|
||||
|
||||