2 Commits
1.5.1 ... 1.5.2

Author SHA1 Message Date
4ee9e23c5c implement consistent quantization of srgb8 for palette figure 2026-01-21 18:22:30 -08:00
a5256aaa62 clean up plotting method signatures 2026-01-16 16:34:00 -08:00
40 changed files with 39 additions and 38 deletions

View File

@@ -1,4 +1,4 @@
version = "1.5.1" version = "1.5.2"
[alpine] [alpine]
l10 = "#030303" l10 = "#030303"

View File

@@ -1,4 +1,4 @@
version = "1.5.1" version = "1.5.2"
[alpine] [alpine]
l10 = "oklch(10.0% 0.0000 0.0)" l10 = "oklch(10.0% 0.0000 0.0)"

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -5,6 +5,10 @@ from importlib.metadata import version
from coloraide import Color from coloraide import Color
from monobiome.util import (
hex_from_rgb8,
srgb8_from_color,
)
from monobiome.constants import ( from monobiome.constants import (
h_map, h_map,
L_points, L_points,
@@ -24,8 +28,8 @@ def compute_hlc_map(notation: str) -> dict[str, Any]:
oklch = Color('oklch', [_l/100, _c, _h]) oklch = Color('oklch', [_l/100, _c, _h])
if notation == "hex": if notation == "hex":
srgb = oklch.convert('srgb') rgb8 = srgb8_from_color(oklch)
c_str = srgb.to_string(hex=True) c_str = hex_from_rgb8(rgb8)
elif notation == "oklch": elif notation == "oklch":
ol, oc, oh = oklch.convert('oklch').coords() ol, oc, oh = oklch.convert('oklch').coords()
c_str = f"oklch({ol*100:.1f}% {oc:.4f} {oh:.1f})" c_str = f"oklch({ol*100:.1f}% {oc:.4f} {oh:.1f})"

View File

@@ -5,6 +5,7 @@ import matplotlib.pyplot as plt
from coloraide import Color from coloraide import Color
from matplotlib.collections import LineCollection from matplotlib.collections import LineCollection
from monobiome.util import srgb8_from_color
from monobiome.palette import compute_hlc_map from monobiome.palette import compute_hlc_map
from monobiome.constants import ( from monobiome.constants import (
h_map, h_map,
@@ -72,12 +73,11 @@ def plot_hue_chroma_bounds() -> tuple[plt.Figure, plt.Axes]:
return fig, axes return fig, axes
def plot_hue_chroma_star() -> tuple[plt.Figure, plt.Axes]: def plot_hue_chroma_star() -> tuple[plt.Figure, plt.Axes]:
fig, ax = plt.subplots(1, 1, figsize=(8, 6)) fig, ax = plt.subplots(1, 1, figsize=(8, 6))
# uncomment to preview 5 core term colors
colors = accent_h_map.keys() colors = accent_h_map.keys()
# uncomment to preview just the 5 core term colors
# colors = set(["red", "orange", "yellow", "green", "blue"]) # colors = set(["red", "orange", "yellow", "green", "blue"])
for h_str, _ in max_Cstar_Horder: for h_str, _ in max_Cstar_Horder:
@@ -88,34 +88,22 @@ def plot_hue_chroma_star() -> tuple[plt.Figure, plt.Axes]:
_h = h_map[h_str] _h = h_map[h_str]
h_colors = [ 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) 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) x = np.asarray(L_points)
y = np.asarray(Lpoints_Cstar) y = np.asarray(Lpoints_Cstar)
pts = np.column_stack([x, y]).reshape(-1, 1, 2) pts = np.column_stack([x, y]).reshape(-1, 1, 2)
segs = np.concatenate([pts[:-1], pts[1:]], axis=1) segs = np.concatenate([pts[:-1], pts[1:]], axis=1)
rgb = np.asarray(h_colors) rgb = np.asarray(h_colors)
seg_colors = (rgb[:-1] + rgb[1:]) / 2 seg_colors = (rgb[:-1] + rgb[1:]) / 2
lc = LineCollection(segs, colors=seg_colors, linewidth=3, lc = LineCollection(segs, colors=seg_colors, linewidth=3,
capstyle="round", joinstyle="round", capstyle="round", joinstyle="round",
label=h_str) label=h_str)
ax.add_collection(lc) ax.add_collection(lc)
ax.autoscale_view() ax.autoscale_view()
@@ -126,12 +114,11 @@ def plot_hue_chroma_star() -> tuple[plt.Figure, plt.Axes]:
return fig, ax return fig, ax
def palette_image( def palette_image(
palette: dict[str, dict[int, str]], palette: dict[str, dict[int, str]],
cell_size: int = 40, cell_size: int = 40,
keys: list[str] | None = None 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 names = list(palette.keys()) if keys is None else keys
row_count = len(names) row_count = len(names)
@@ -140,7 +127,7 @@ def palette_image(
h = row_count * cell_size h = row_count * cell_size
w = max_cols * 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 = [] lightness_keys_per_row = []
@@ -149,14 +136,12 @@ def palette_image(
lkeys = sorted(shades.keys()) lkeys = sorted(shades.keys())
lightness_keys_per_row.append(lkeys) lightness_keys_per_row.append(lkeys)
for c, k in enumerate(lkeys): for c, k in enumerate(lkeys):
col = Color(shades[k]).convert("srgb").fit(method="clip") rgb = srgb8_from_color(shades[k])
rgb = [col["r"], col["g"], col["b"]]
r0, r1 = r * cell_size, (r + 1) * cell_size r0, r1 = r * cell_size, (r + 1) * cell_size
c0, c1 = c * cell_size, (c + 1) * cell_size c0, c1 = c * cell_size, (c + 1) * cell_size
img[r0:r1, c0:c1, :] = rgb 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( def show_palette(
palette: dict[str, dict[int, str]], palette: dict[str, dict[int, str]],
@@ -165,9 +150,7 @@ def show_palette(
show_labels: bool = True, show_labels: bool = True,
dpi: int = 100, dpi: int = 100,
) -> tuple[plt.Figure, plt.Axes]: ) -> tuple[plt.Figure, plt.Axes]:
img, names, keys, cell_size, max_cols = palette_image( img, names, _ = palette_image(palette, cell_size, keys=keys)
palette, cell_size, keys=keys
)
fig_w = img.shape[1] / 100 fig_w = img.shape[1] / 100
fig_h = img.shape[0] / 100 fig_h = img.shape[0] / 100
@@ -175,7 +158,7 @@ def show_palette(
if show_labels: if show_labels:
fig, ax = plt.subplots(figsize=(fig_w, fig_h), dpi=dpi) 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([]) ax.set_xticks([])
ytick_pos = [(i + 0.5) * cell_size for i in range(len(names))] ytick_pos = [(i + 0.5) * cell_size for i in range(len(names))]

View File

@@ -2,6 +2,7 @@ import math
from types import GenericAlias from types import GenericAlias
from argparse import ArgumentParser, _SubParsersAction from argparse import ArgumentParser, _SubParsersAction
import numpy as np
from coloraide import Color from coloraide import Color
_SubParsersAction.__class_getitem__ = classmethod(GenericAlias) _SubParsersAction.__class_getitem__ = classmethod(GenericAlias)
@@ -33,3 +34,13 @@ def oklch_distance(xc: Color, yc: Color) -> float:
dz = l1 - l2 dz = l1 - l2
return (dx**2 + dy**2 + dz**2)**0.5 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}"

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "monobiome" name = "monobiome"
version = "1.5.1" version = "1.5.2"
description = "Monobiome color palette" description = "Monobiome color palette"
requires-python = ">=3.12" requires-python = ">=3.12"
authors = [ authors = [

View File

@@ -17,8 +17,8 @@ fig.savefig(Path(figure_dir, "chroma-curves.png"))
# "oklch" causes some slight hex drift when later using an eyedropper # "oklch" causes some slight hex drift when later using an eyedropper
hlc_map = compute_hlc_map("hex") # ("oklch") hlc_map = compute_hlc_map("hex") # ("oklch")
fig, ax = plotting.show_palette(hlc_map) fig, ax = plotting.show_palette(hlc_map, cell_size=25)
fig.savefig(Path(figure_dir, "palette.png")) fig.savefig(Path(figure_dir, "palette.png"), pad_inches=0)
fig, ax = plotting.show_palette(hlc_map, show_labels=False) fig, ax = plotting.show_palette(hlc_map, show_labels=False)
fig.savefig(Path(figure_dir, "palette-bare.png"), pad_inches=0) fig.savefig(Path(figure_dir, "palette-bare.png"), pad_inches=0)

View File

@@ -11,3 +11,6 @@ uv run monobiome palette -n oklch -f toml -o colors/oklch-palette.toml
# generate provided app config # generate provided app config
"$script_dir/generate.sh" "$script_dir/generate.sh"
# generate release plots
uv run "$script_dir/plots.py"

2
uv.lock generated
View File

@@ -882,7 +882,7 @@ wheels = [
[[package]] [[package]]
name = "monobiome" name = "monobiome"
version = "1.5.1" version = "1.5.2"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "coloraide" }, { name = "coloraide" },