dotfiles/common/frogarized/frogarized.py

125 lines
3.5 KiB
Python
Executable file

#!/usr/bin/env nix-shell
#! nix-shell -i python3 --pure
#! nix-shell -p python3 python3Packages.colorspacious python3Packages.numpy
# vim: filetype=python
import argparse
import json
import colorspacious
import numpy as np
import numpy.typing as npt
# Original values for the Solarized color scheme,
# created by Ethan Schoonover (https://ethanschoonover.com/solarized/)
SOLARIZED_LAB = np.array(
[
[15, -12, -12],
[20, -12, -12],
[45, -7, -7],
[50, -7, -7],
[60, -6, -3],
[65, -5, -2],
[92, -0, 10],
[97, 0, 10],
[50, 65, 45],
[50, 50, 55],
[60, 10, 65],
[60, -20, 65],
[60, -35, -5],
[55, -10, -45],
[50, 15, -45],
[50, 65, -5],
]
)
# I couldn't get a perfect translation of Solarized L*a*b values into sRGB,
# so here is upstream's translation for reference
SOLARIZED_RGB = np.array(
[
[0, 43, 54],
[7, 54, 66],
[88, 110, 117],
[101, 123, 131],
[131, 148, 150],
[147, 161, 161],
[238, 232, 213],
[253, 246, 227],
[220, 50, 47],
[203, 75, 22],
[181, 137, 0],
[133, 153, 0],
[42, 161, 152],
[38, 139, 210],
[108, 113, 196],
[211, 54, 130],
]
)
# Parse arguments
parser = argparse.ArgumentParser(
description="Generate a base16-theme based derived from Solarized"
)
_ = parser.add_argument("--source", choices=["lab", "rgb"], default="lab")
_ = parser.add_argument("--lightness_factor", type=float, default=1.0)
_ = parser.add_argument("--chroma-factor", type=float, default=1.0)
_ = parser.add_argument("--hue_shift", type=float, default=-75.0)
_ = parser.add_argument(
"--polarity", choices=["dark", "light"], default="dark"
)
_ = parser.add_argument(
"--output", choices=["json", "truecolor"], default="truecolor"
)
args = parser.parse_args()
def cspace_convert(
array: npt.NDArray[np.float64], src: str, dst: str
) -> npt.NDArray[np.float64]:
return colorspacious.cspace_convert(array, src, dst)
# Convert source to JCh color space
if args.source == "lab":
solarized_jch = cspace_convert(SOLARIZED_LAB, "CIELab", "JCh")
else: # rgb
solarized_jch = cspace_convert(SOLARIZED_RGB, "sRGB255", "JCh")
# Build frogarized theme
jch_factor: list[float] = [args.lightness_factor, args.chroma_factor, 1]
jch_shift: list[float] = [0, 0, args.hue_shift]
frogarized_jch = np.vstack(
[solarized_jch[:8] * jch_factor + jch_shift, solarized_jch[8:]]
)
# Convert frogarized to RGB
frogarized_srgb = cspace_convert(frogarized_jch, "JCh", "sRGB255")
frogarized_rgb = np.asarray(
np.rint(np.clip(frogarized_srgb, 0, 255)), dtype=np.uint8
)
if args.polarity == "light":
frogarized_rgb = np.vstack([frogarized_rgb[7::-1], frogarized_rgb[8:]])
# Output
palette: dict[str, str] = {}
for i in range(16):
rgb = frogarized_rgb[i]
r, g, b = rgb
hexa = f"#{r:02x}{g:02x}{b:02x}"
palette[f"base{i:02X}"] = hexa
if args.output == "truecolor":
print(f"\033[48;2;{r};{g};{b}m{hexa}\033[0m") # ]]
# treesitter is silly and will consider brackets in strings
# as indentation, hence the comment above
if args.output == "json":
scheme = palette.copy()
scheme.update(
{
"slug": f"frogarized-{args.polarity}",
"scheme": f"Frogarized {args.polarity.title()}",
"author": "Geoffrey Frogeye (with work from Ethan Schoonover)",
}
)
print(json.dumps(scheme, indent=4))