Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ee9e23c5c | |||
| a5256aaa62 | |||
| 99ed1c9557 | |||
| 356442395e | |||
| fcaa25e2f5 | |||
| 4a5f8cbb39 | |||
| eb02be9582 | |||
| 74c22552df |
2
.gitignore
vendored
@@ -23,3 +23,5 @@ archive/
|
||||
notebooks/color_spaces_manyview.ipynb
|
||||
notebooks/oklch_srgb_spherical.ipynb
|
||||
notebooks/v1.4.0/
|
||||
notebooks/v1.5.1/
|
||||
CHECKLIST.md
|
||||
|
||||
67
README.md
@@ -30,13 +30,13 @@ both of which have fixed hue values and vary from 10% to 98% lightness.
|
||||
Monotone curves have fixed chroma, whereas the accent curves' chroma varies
|
||||
smoothly as a function of lightness within sRGB gamut bounds.
|
||||
|
||||
| Chroma curves | Color trajectories |
|
||||
|---|---|
|
||||
|  |  |
|
||||
| Chroma curves | Color trajectories |
|
||||
|----------------------------------------------------------|------------------------------------------|
|
||||
|  |  |
|
||||
|
||||
| Palette |
|
||||
|---|
|
||||
|  |
|
||||
| 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
|
||||
@@ -60,8 +60,8 @@ varying only in lightness from dark to light grey.
|
||||
|
||||
## Themes
|
||||
|
||||
| Dark themes | Light themes |
|
||||
|---|---|
|
||||
| Dark themes | Light themes |
|
||||
|----------------------------------------|------------------------------------------|
|
||||
|  |  |
|
||||
|
||||
Themes are derived from the `monobiome` palette by selecting a monotone base
|
||||
@@ -78,10 +78,10 @@ to the background.
|
||||
The following plots show the intersection of the sphere centered at a fixed
|
||||
background color (`alpine` biome with a lightness of 20) under variable radii:
|
||||
|
||||
| | `-l 20 -d 0.3` | `-l 20 -d 0.4` | `-l 20 -d 0.5` |
|
||||
|---|---|---|---|
|
||||
| Color visualization |  |  |  |
|
||||
| Editor preview |  |  |  |
|
||||
| | `-l 20 -d 0.3` | `-l 20 -d 0.4` | `-l 20 -d 0.5` |
|
||||
|---------------------|---------------------------------------------------|---------------------------------------------------|---------------------------------------------------|
|
||||
| Color visualization |  |  |  |
|
||||
| Editor preview |  |  |  |
|
||||
|
||||
In short, the base lightness (`-l`) dictates the brightness of the background,
|
||||
and the contrast (`-d`) controls how perceptually distinct the accent colors
|
||||
@@ -117,10 +117,10 @@ theme pipeline can be seen in detail below:
|
||||

|
||||
|
||||
This figure demonstrates how `kitty` themes are generated, but the process is
|
||||
generic to any palette, scheme, and app. This implemented in two stages using
|
||||
the `monobiome` CLI:
|
||||
generic to any palette, scheme, and app. This is implemented in two stages
|
||||
using the `monobiome` CLI:
|
||||
|
||||
- First generate the scheme file, the definitions that respect perceptual
|
||||
- First generate the scheme file, the lightness choices that achieve perceptual
|
||||
uniformity of accents with respect to the base monotone:
|
||||
|
||||
```sh
|
||||
@@ -137,19 +137,14 @@ the `monobiome` CLI:
|
||||
monobiome fill scheme.toml templates/kitty/active.theme -o kitty.theme
|
||||
```
|
||||
|
||||
This writes a concrete theme to `kitty.theme` that matches the user
|
||||
preferences, i.e., the contrast (`-d`), background lightness (`-l`), mode
|
||||
(`dark`), and biome (`grassland`). Every part of this process can be
|
||||
customized: the scheme parameters, the scheme definitions/file, the app
|
||||
template.
|
||||
This writes a concrete `kitty` theme to `kitty.theme` that matches the user
|
||||
preferences as captured in the previously generated scheme file, i.e., the
|
||||
contrast (`-d`), background lightness (`-l`), mode (`dark`), and biome
|
||||
(`grassland`). Every part of this process can be customized: the scheme
|
||||
parameters, the scheme definitions/file, the app template.
|
||||
|
||||
Running these commands in sequence from the repo root should work
|
||||
out-of-the-box, after having installed the CLI tool.
|
||||
|
||||
The `monobiome` CLI
|
||||
produces the scheme file for requested parameters, and the [`symconf`][3] CLI
|
||||
pushes palette colors through the scheme and into the app templates to yield a
|
||||
concrete theme.
|
||||
out-of-the-box after having installed the CLI tool.
|
||||
|
||||
## Applications
|
||||
This repo provides palette-agnostic theme templates for `kitty`,
|
||||
@@ -178,7 +173,7 @@ One can set these themes for the provided applications as follows:
|
||||
```
|
||||
|
||||
Themes are generated using the [`kitty` theme
|
||||
template](templates/apps/kitty/templates/active.theme).
|
||||
template](templates/kitty/active.theme).
|
||||
|
||||
- `vim`/`neovim`
|
||||
|
||||
@@ -193,7 +188,7 @@ One can set these themes for the provided applications as follows:
|
||||
```
|
||||
|
||||
Themes are generated using the [`vim` theme
|
||||
template](templates/apps/nvim/templates/theme.vim).
|
||||
template](templates/nvim/theme.vim).
|
||||
|
||||
- `fzf`
|
||||
|
||||
@@ -206,7 +201,7 @@ One can set these themes for the provided applications as follows:
|
||||
```
|
||||
|
||||
Themes are generated using the [`fzf` theme
|
||||
template](templates/apps/fzf/templates/active.theme).
|
||||
template](templates/fzf/active.theme).
|
||||
|
||||
- Firefox
|
||||
|
||||
@@ -214,13 +209,11 @@ One can set these themes for the provided applications as follows:
|
||||
add-ons][2], and switch between light/dark schemes based on system settings.
|
||||
You can also download raw XPI files for each theme in `app-config/firefox/`,
|
||||
each of which is generated using the [Firefox `manifest.json`
|
||||
template](templates/apps/firefox/templates/none-dark.manifest.json).
|
||||
template](templates/firefox/auto-manifest.json).
|
||||
|
||||
Static [light][4] and [dark][5] themes are additionally available (i.e., that
|
||||
don't change with system settings).
|
||||
|
||||

|
||||
|
||||
## CLI installation
|
||||
A brief theme generation guide was provided in the [Generation
|
||||
section](#generation), making use of the `monobiome` CLI. This tool can be
|
||||
@@ -232,7 +225,7 @@ uv tool install monobiome
|
||||
pipx install monobiome
|
||||
```
|
||||
|
||||
The `monobiome` has provides three subcommands:
|
||||
`monobiome` provides three subcommands:
|
||||
|
||||
- `monobiome palette`: generate palette files from raw parameterized curves
|
||||
|
||||
@@ -295,6 +288,14 @@ The `monobiome` has provides three subcommands:
|
||||
output file to write filled template
|
||||
```
|
||||
|
||||
## Config management
|
||||
The `monobiome` CLI tool attempts to provide the minimal functionality needed
|
||||
to produce customized themes for individual applications. If seeking a more
|
||||
holistic, system-wide approach, you might consider using [`symconf`][3], a
|
||||
general-purpose application config manager. `symconf` provides the templating
|
||||
subsystem used for `monobiome` internals, and can be configured to apply live
|
||||
theme updates to many apps with a single command line invocation.
|
||||
|
||||
|
||||
[1]: https://github.com/isa/TextMate-Themes/blob/master/monoindustrial.tmTheme
|
||||
[2]: https://addons.mozilla.org/en-US/firefox/collections/18495484/monobiome/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version = "1.5.0"
|
||||
version = "1.5.2"
|
||||
|
||||
[alpine]
|
||||
l10 = "#030303"
|
||||
@@ -271,7 +271,7 @@ l94 = "#f1eae4"
|
||||
l95 = "#f4ede7"
|
||||
l96 = "#f7f0ea"
|
||||
l97 = "#fbf4ee"
|
||||
l98 = "#fef7f1"
|
||||
l98 = "#fdf7f2"
|
||||
|
||||
[savanna]
|
||||
l10 = "#040301"
|
||||
@@ -362,7 +362,7 @@ l94 = "#ecece3"
|
||||
l95 = "#efefe7"
|
||||
l96 = "#f3f2ea"
|
||||
l97 = "#f6f6ed"
|
||||
l98 = "#f9f9f1"
|
||||
l98 = "#f9f9f2"
|
||||
|
||||
[grassland]
|
||||
l10 = "#020402"
|
||||
@@ -453,7 +453,7 @@ l94 = "#e6ede7"
|
||||
l95 = "#eaf1ea"
|
||||
l96 = "#edf4ee"
|
||||
l97 = "#f0f7f1"
|
||||
l98 = "#f4fbf4"
|
||||
l98 = "#f4faf5"
|
||||
|
||||
[reef]
|
||||
l10 = "#010404"
|
||||
@@ -544,7 +544,7 @@ l94 = "#e3edef"
|
||||
l95 = "#e7f1f2"
|
||||
l96 = "#eaf4f5"
|
||||
l97 = "#edf7f9"
|
||||
l98 = "#f0fbfc"
|
||||
l98 = "#f2fafb"
|
||||
|
||||
[tundra]
|
||||
l10 = "#020306"
|
||||
@@ -726,7 +726,7 @@ l94 = "#ede9f1"
|
||||
l95 = "#f0edf4"
|
||||
l96 = "#f3f0f8"
|
||||
l97 = "#f7f3fb"
|
||||
l98 = "#faf7ff"
|
||||
l98 = "#faf7fe"
|
||||
|
||||
[moorland]
|
||||
l10 = "#050204"
|
||||
@@ -817,7 +817,7 @@ l94 = "#f1e8ec"
|
||||
l95 = "#f5ecef"
|
||||
l96 = "#f8eff3"
|
||||
l97 = "#fbf2f6"
|
||||
l98 = "#fff6f9"
|
||||
l98 = "#fef6f9"
|
||||
|
||||
[red]
|
||||
l10 = "#0d0000"
|
||||
@@ -1443,18 +1443,18 @@ l83 = "#d6b8f7"
|
||||
l84 = "#d8bcf8"
|
||||
l85 = "#dbc1f9"
|
||||
l86 = "#ddc5f9"
|
||||
l87 = "#e0c9fa"
|
||||
l88 = "#e2cdfb"
|
||||
l89 = "#e5d1fb"
|
||||
l90 = "#e7d5fc"
|
||||
l91 = "#e9d9fc"
|
||||
l92 = "#ecdefd"
|
||||
l93 = "#eee2fd"
|
||||
l94 = "#f1e6fe"
|
||||
l95 = "#f3eafe"
|
||||
l96 = "#f5eefe"
|
||||
l97 = "#f8f2fe"
|
||||
l98 = "#faf7ff"
|
||||
l87 = "#dfc9f9"
|
||||
l88 = "#e2cdfa"
|
||||
l89 = "#e4d2fa"
|
||||
l90 = "#e6d6fa"
|
||||
l91 = "#e9dafb"
|
||||
l92 = "#ebdefb"
|
||||
l93 = "#eee2fb"
|
||||
l94 = "#f0e6fc"
|
||||
l95 = "#f2ebfc"
|
||||
l96 = "#f5effd"
|
||||
l97 = "#f7f3fd"
|
||||
l98 = "#faf7fe"
|
||||
|
||||
[magenta]
|
||||
l10 = "#080104"
|
||||
@@ -1540,9 +1540,9 @@ l89 = "#f7cde0"
|
||||
l90 = "#f8d2e2"
|
||||
l91 = "#f9d6e5"
|
||||
l92 = "#fadbe8"
|
||||
l93 = "#fbdfeb"
|
||||
l94 = "#fce4ee"
|
||||
l93 = "#fadfeb"
|
||||
l94 = "#fbe4ee"
|
||||
l95 = "#fce8f1"
|
||||
l96 = "#fdedf4"
|
||||
l97 = "#fef1f6"
|
||||
l96 = "#fdedf3"
|
||||
l97 = "#fdf1f6"
|
||||
l98 = "#fef6f9"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version = "1.5.0"
|
||||
version = "1.5.2"
|
||||
|
||||
[alpine]
|
||||
l10 = "oklch(10.0% 0.0000 0.0)"
|
||||
@@ -180,7 +180,7 @@ l94 = "oklch(94.0% 0.0110 29.0)"
|
||||
l95 = "oklch(95.0% 0.0110 29.0)"
|
||||
l96 = "oklch(96.0% 0.0110 29.0)"
|
||||
l97 = "oklch(97.0% 0.0110 29.0)"
|
||||
l98 = "oklch(98.0% 0.0097 29.0)"
|
||||
l98 = "oklch(98.0% 0.0092 29.0)"
|
||||
|
||||
[chaparral]
|
||||
l10 = "oklch(10.0% 0.0110 62.5)"
|
||||
@@ -271,7 +271,7 @@ l94 = "oklch(94.0% 0.0110 62.5)"
|
||||
l95 = "oklch(95.0% 0.0110 62.5)"
|
||||
l96 = "oklch(96.0% 0.0110 62.5)"
|
||||
l97 = "oklch(97.0% 0.0110 62.5)"
|
||||
l98 = "oklch(98.0% 0.0110 62.5)"
|
||||
l98 = "oklch(98.0% 0.0092 62.5)"
|
||||
|
||||
[savanna]
|
||||
l10 = "oklch(10.0% 0.0110 104.0)"
|
||||
@@ -362,7 +362,7 @@ l94 = "oklch(94.0% 0.0110 104.0)"
|
||||
l95 = "oklch(95.0% 0.0110 104.0)"
|
||||
l96 = "oklch(96.0% 0.0110 104.0)"
|
||||
l97 = "oklch(97.0% 0.0110 104.0)"
|
||||
l98 = "oklch(98.0% 0.0110 104.0)"
|
||||
l98 = "oklch(98.0% 0.0092 104.0)"
|
||||
|
||||
[grassland]
|
||||
l10 = "oklch(10.0% 0.0110 148.0)"
|
||||
@@ -453,7 +453,7 @@ l94 = "oklch(94.0% 0.0110 148.0)"
|
||||
l95 = "oklch(95.0% 0.0110 148.0)"
|
||||
l96 = "oklch(96.0% 0.0110 148.0)"
|
||||
l97 = "oklch(97.0% 0.0110 148.0)"
|
||||
l98 = "oklch(98.0% 0.0110 148.0)"
|
||||
l98 = "oklch(98.0% 0.0092 148.0)"
|
||||
|
||||
[reef]
|
||||
l10 = "oklch(10.0% 0.0110 205.0)"
|
||||
@@ -544,7 +544,7 @@ l94 = "oklch(94.0% 0.0110 205.0)"
|
||||
l95 = "oklch(95.0% 0.0110 205.0)"
|
||||
l96 = "oklch(96.0% 0.0110 205.0)"
|
||||
l97 = "oklch(97.0% 0.0110 205.0)"
|
||||
l98 = "oklch(98.0% 0.0110 205.0)"
|
||||
l98 = "oklch(98.0% 0.0092 205.0)"
|
||||
|
||||
[tundra]
|
||||
l10 = "oklch(10.0% 0.0110 262.0)"
|
||||
@@ -635,7 +635,7 @@ l94 = "oklch(94.0% 0.0110 262.0)"
|
||||
l95 = "oklch(95.0% 0.0110 262.0)"
|
||||
l96 = "oklch(96.0% 0.0110 262.0)"
|
||||
l97 = "oklch(97.0% 0.0110 262.0)"
|
||||
l98 = "oklch(98.0% 0.0094 262.0)"
|
||||
l98 = "oklch(98.0% 0.0092 262.0)"
|
||||
|
||||
[heathland]
|
||||
l10 = "oklch(10.0% 0.0110 306.0)"
|
||||
@@ -726,7 +726,7 @@ l94 = "oklch(94.0% 0.0110 306.0)"
|
||||
l95 = "oklch(95.0% 0.0110 306.0)"
|
||||
l96 = "oklch(96.0% 0.0110 306.0)"
|
||||
l97 = "oklch(97.0% 0.0110 306.0)"
|
||||
l98 = "oklch(98.0% 0.0110 306.0)"
|
||||
l98 = "oklch(98.0% 0.0092 306.0)"
|
||||
|
||||
[moorland]
|
||||
l10 = "oklch(10.0% 0.0110 350.0)"
|
||||
@@ -817,7 +817,7 @@ l94 = "oklch(94.0% 0.0110 350.0)"
|
||||
l95 = "oklch(95.0% 0.0110 350.0)"
|
||||
l96 = "oklch(96.0% 0.0110 350.0)"
|
||||
l97 = "oklch(97.0% 0.0110 350.0)"
|
||||
l98 = "oklch(98.0% 0.0110 350.0)"
|
||||
l98 = "oklch(98.0% 0.0092 350.0)"
|
||||
|
||||
[red]
|
||||
l10 = "oklch(10.0% 0.0406 29.0)"
|
||||
@@ -1087,10 +1087,10 @@ l91 = "oklch(91.0% 0.0402 104.0)"
|
||||
l92 = "oklch(92.0% 0.0360 104.0)"
|
||||
l93 = "oklch(93.0% 0.0317 104.0)"
|
||||
l94 = "oklch(94.0% 0.0273 104.0)"
|
||||
l95 = "oklch(95.0% 0.0229 104.0)"
|
||||
l96 = "oklch(96.0% 0.0184 104.0)"
|
||||
l95 = "oklch(95.0% 0.0228 104.0)"
|
||||
l96 = "oklch(96.0% 0.0183 104.0)"
|
||||
l97 = "oklch(97.0% 0.0138 104.0)"
|
||||
l98 = "oklch(98.0% 0.0093 104.0)"
|
||||
l98 = "oklch(98.0% 0.0092 104.0)"
|
||||
|
||||
[green]
|
||||
l10 = "oklch(10.0% 0.0178 148.0)"
|
||||
@@ -1442,19 +1442,19 @@ l82 = "oklch(82.0% 0.0964 306.0)"
|
||||
l83 = "oklch(83.0% 0.0915 306.0)"
|
||||
l84 = "oklch(84.0% 0.0865 306.0)"
|
||||
l85 = "oklch(85.0% 0.0815 306.0)"
|
||||
l86 = "oklch(86.0% 0.0764 306.0)"
|
||||
l87 = "oklch(87.0% 0.0712 306.0)"
|
||||
l88 = "oklch(88.0% 0.0660 306.0)"
|
||||
l89 = "oklch(89.0% 0.0607 306.0)"
|
||||
l90 = "oklch(90.0% 0.0554 306.0)"
|
||||
l91 = "oklch(91.0% 0.0500 306.0)"
|
||||
l92 = "oklch(92.0% 0.0446 306.0)"
|
||||
l93 = "oklch(93.0% 0.0391 306.0)"
|
||||
l94 = "oklch(94.0% 0.0336 306.0)"
|
||||
l95 = "oklch(95.0% 0.0281 306.0)"
|
||||
l96 = "oklch(96.0% 0.0225 306.0)"
|
||||
l97 = "oklch(97.0% 0.0169 306.0)"
|
||||
l98 = "oklch(98.0% 0.0113 306.0)"
|
||||
l86 = "oklch(86.0% 0.0758 306.0)"
|
||||
l87 = "oklch(87.0% 0.0697 306.0)"
|
||||
l88 = "oklch(88.0% 0.0638 306.0)"
|
||||
l89 = "oklch(89.0% 0.0579 306.0)"
|
||||
l90 = "oklch(90.0% 0.0522 306.0)"
|
||||
l91 = "oklch(91.0% 0.0466 306.0)"
|
||||
l92 = "oklch(92.0% 0.0410 306.0)"
|
||||
l93 = "oklch(93.0% 0.0356 306.0)"
|
||||
l94 = "oklch(94.0% 0.0302 306.0)"
|
||||
l95 = "oklch(95.0% 0.0250 306.0)"
|
||||
l96 = "oklch(96.0% 0.0198 306.0)"
|
||||
l97 = "oklch(97.0% 0.0147 306.0)"
|
||||
l98 = "oklch(98.0% 0.0097 306.0)"
|
||||
|
||||
[magenta]
|
||||
l10 = "oklch(10.0% 0.0242 350.0)"
|
||||
@@ -1538,11 +1538,11 @@ l87 = "oklch(87.0% 0.0619 350.0)"
|
||||
l88 = "oklch(88.0% 0.0574 350.0)"
|
||||
l89 = "oklch(89.0% 0.0528 350.0)"
|
||||
l90 = "oklch(90.0% 0.0481 350.0)"
|
||||
l91 = "oklch(91.0% 0.0435 350.0)"
|
||||
l92 = "oklch(92.0% 0.0387 350.0)"
|
||||
l93 = "oklch(93.0% 0.0340 350.0)"
|
||||
l94 = "oklch(94.0% 0.0292 350.0)"
|
||||
l95 = "oklch(95.0% 0.0244 350.0)"
|
||||
l96 = "oklch(96.0% 0.0196 350.0)"
|
||||
l97 = "oklch(97.0% 0.0147 350.0)"
|
||||
l98 = "oklch(98.0% 0.0098 350.0)"
|
||||
l91 = "oklch(91.0% 0.0433 350.0)"
|
||||
l92 = "oklch(92.0% 0.0384 350.0)"
|
||||
l93 = "oklch(93.0% 0.0334 350.0)"
|
||||
l94 = "oklch(94.0% 0.0285 350.0)"
|
||||
l95 = "oklch(95.0% 0.0237 350.0)"
|
||||
l96 = "oklch(96.0% 0.0189 350.0)"
|
||||
l97 = "oklch(97.0% 0.0141 350.0)"
|
||||
l98 = "oklch(98.0% 0.0094 350.0)"
|
||||
|
||||
BIN
images/curves/cstar-curves-v151.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
images/release/1.5.1/chroma-bounds.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
images/release/1.5.1/chroma-curves.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
images/release/1.5.1/palette-bare.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
images/release/1.5.1/palette.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
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 |
@@ -17,7 +17,7 @@ def register_parser(subparsers: _SubparserType) -> None:
|
||||
type=str,
|
||||
default="hex",
|
||||
choices=["hex", "oklch"],
|
||||
help="Color notation to export (either hex or oklch)",
|
||||
help="color notation to export (either hex or oklch)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
@@ -25,13 +25,13 @@ def register_parser(subparsers: _SubparserType) -> None:
|
||||
type=str,
|
||||
default="toml",
|
||||
choices=["json", "toml"],
|
||||
help="Format of palette file (either JSON or TOML)",
|
||||
help="format of palette file (either JSON or TOML)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
type=str,
|
||||
help="Output file to write palette content",
|
||||
help="output file to write palette content",
|
||||
)
|
||||
|
||||
parser.set_defaults(func=handle_palette)
|
||||
|
||||
@@ -16,13 +16,13 @@ def register_parser(subparsers: _SubparserType) -> None:
|
||||
"mode",
|
||||
type=str,
|
||||
choices=["dark", "light"],
|
||||
help="Scheme mode (light or dark)"
|
||||
help="scheme mode (light or dark)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"biome",
|
||||
type=str,
|
||||
choices=list(monotone_h_map.keys()),
|
||||
help="Biome setting for scheme."
|
||||
help="biome setting for scheme"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
@@ -30,7 +30,7 @@ def register_parser(subparsers: _SubparserType) -> None:
|
||||
type=str,
|
||||
default="oklch",
|
||||
choices=["wcag", "oklch", "lightness"],
|
||||
help="Metric to use for measuring swatch distances."
|
||||
help="metric to use for measuring swatch distances"
|
||||
)
|
||||
|
||||
# e.g., wcag=4.5; oklch=0.40; lightness=40
|
||||
@@ -39,13 +39,13 @@ def register_parser(subparsers: _SubparserType) -> None:
|
||||
"--distance",
|
||||
type=float,
|
||||
default=0.40,
|
||||
help="Distance threshold for specified metric",
|
||||
help="distance threshold for specified metric",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
type=str,
|
||||
help="Output file to write scheme content",
|
||||
help="output file to write scheme content",
|
||||
)
|
||||
|
||||
# these params remain rooted in lightness; no need to accommodate metric
|
||||
@@ -58,13 +58,13 @@ def register_parser(subparsers: _SubparserType) -> None:
|
||||
"--l-base",
|
||||
type=int,
|
||||
default=20,
|
||||
help="Minimum lightness level (default: 20)",
|
||||
help="minimum lightness level (default: 20)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--l-step",
|
||||
type=int,
|
||||
default=5,
|
||||
help="Lightness step size (default: 5)",
|
||||
help="lightness step size (default: 5)",
|
||||
)
|
||||
|
||||
# gaps
|
||||
@@ -72,19 +72,19 @@ def register_parser(subparsers: _SubparserType) -> None:
|
||||
"--fg-gap",
|
||||
type=int,
|
||||
default=50,
|
||||
help="Foreground lightness gap (default: 50)",
|
||||
help="foreground lightness gap (default: 50)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--grey-gap",
|
||||
type=int,
|
||||
default=30,
|
||||
help="Grey lightness gap (default: 30)",
|
||||
help="grey lightness gap (default: 30)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--term-fg-gap",
|
||||
type=int,
|
||||
default=65,
|
||||
help="Terminal foreground lightness gap (default: 60)",
|
||||
help="terminal foreground lightness gap (default: 60)",
|
||||
)
|
||||
|
||||
parser.set_defaults(func=handle_scheme)
|
||||
|
||||
@@ -121,3 +121,21 @@ for h_str, L_points_C in Lpoints_Cqbr_Hmap.items():
|
||||
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])
|
||||
]
|
||||
|
||||
@@ -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})"
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from importlib.metadata import version
|
||||
|
||||
import numpy as np
|
||||
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,
|
||||
@@ -10,11 +14,13 @@ from monobiome.constants import (
|
||||
accent_h_map,
|
||||
monotone_h_map,
|
||||
Lspace_Cmax_Hmap,
|
||||
max_Cstar_Horder,
|
||||
Lpoints_Cstar_Hmap,
|
||||
)
|
||||
|
||||
VERSION = version("monobiome")
|
||||
|
||||
def plot_hue_chroma_bounds() -> None:
|
||||
def plot_hue_chroma_bounds() -> tuple[plt.Figure, plt.Axes]:
|
||||
name_h_map = {}
|
||||
ax_h_map = {}
|
||||
fig, axes = plt.subplots(
|
||||
@@ -63,43 +69,56 @@ def plot_hue_chroma_bounds() -> None:
|
||||
ncol=3
|
||||
)
|
||||
|
||||
plt.suptitle("$C^*$ curves for hue groups")
|
||||
plt.show()
|
||||
plt.suptitle(f"$C^*$ curves for hue groups (v{VERSION})")
|
||||
|
||||
return fig, axes
|
||||
|
||||
def plot_hue_chroma_star() -> None:
|
||||
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]
|
||||
|
||||
for h_str in Lpoints_Cstar_Hmap:
|
||||
if h_str not in accent_h_map or h_str not in colors:
|
||||
continue
|
||||
ax.fill_between(
|
||||
L_points,
|
||||
Lpoints_Cstar_Hmap[h_str],
|
||||
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')
|
||||
_h = h_map[h_str]
|
||||
h_colors = [
|
||||
Color(
|
||||
'oklch', [_l/100, _c, _h]
|
||||
).convert("srgb").fit(method="oklch-chroma")
|
||||
for _l, _c in zip(L_points, Lpoints_Cstar, strict=True)
|
||||
]
|
||||
|
||||
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()
|
||||
|
||||
ax.set_xlabel("Lightness (%)")
|
||||
ax.set_xticks([L_points[0], 45, 50, 55, 60, 65, 70, L_points[-1]])
|
||||
plt.suptitle("$C^*$ curves (v1.4.0)")
|
||||
fig.show()
|
||||
|
||||
plt.suptitle(f"$C^*$ curves (v{VERSION})")
|
||||
|
||||
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)
|
||||
@@ -108,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 = []
|
||||
|
||||
@@ -117,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]],
|
||||
@@ -133,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
|
||||
@@ -143,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.0"
|
||||
version = "1.5.2"
|
||||
description = "Monobiome color palette"
|
||||
requires-python = ">=3.12"
|
||||
authors = [
|
||||
|
||||
82
scheme.toml
@@ -1,82 +0,0 @@
|
||||
# ++ monobiome scheme file ++
|
||||
# ++ generated CLI @ 1.5.0 ++
|
||||
version = "1.5.0"
|
||||
mode = "dark"
|
||||
biome = "moorland"
|
||||
metric = "oklch"
|
||||
distance = "0.42"
|
||||
l_base = "20"
|
||||
l_step = "5"
|
||||
fg_gap = "50"
|
||||
grey_gap = "30"
|
||||
term_fg_gap = "65"
|
||||
bg0 = "f{{moorland.l20}}"
|
||||
bg1 = "f{{moorland.l25}}"
|
||||
bg2 = "f{{moorland.l30}}"
|
||||
bg3 = "f{{moorland.l35}}"
|
||||
fg3 = "f{{moorland.l70}}"
|
||||
fg2 = "f{{moorland.l75}}"
|
||||
fg1 = "f{{moorland.l80}}"
|
||||
fg0 = "f{{moorland.l85}}"
|
||||
black = "f{{moorland.l20}}"
|
||||
grey = "f{{moorland.l50}}"
|
||||
white = "f{{moorland.l75}}"
|
||||
red = "f{{red.l59}}"
|
||||
orange = "f{{orange.l61}}"
|
||||
yellow = "f{{yellow.l62}}"
|
||||
green = "f{{green.l61}}"
|
||||
cyan = "f{{cyan.l61}}"
|
||||
blue = "f{{blue.l60}}"
|
||||
violet = "f{{violet.l60}}"
|
||||
magenta = "f{{orange.l61}}"
|
||||
|
||||
[term]
|
||||
background = "f{{moorland.l25}}"
|
||||
selection_bg = "f{{moorland.l30}}"
|
||||
selection_fg = "f{{moorland.l85}}"
|
||||
foreground = "f{{moorland.l90}}"
|
||||
cursor = "f{{moorland.l85}}"
|
||||
cursor_text = "f{{moorland.l30}}"
|
||||
|
||||
[term.normal]
|
||||
black = "f{{moorland.l25}}"
|
||||
grey = "f{{moorland.l55}}"
|
||||
white = "f{{moorland.l80}}"
|
||||
red = "f{{red.l64}}"
|
||||
yellow = "f{{yellow.l67}}"
|
||||
green = "f{{green.l66}}"
|
||||
cyan = "f{{blue.l65}}"
|
||||
blue = "f{{blue.l65}}"
|
||||
magenta = "f{{orange.l66}}"
|
||||
|
||||
[term.bright]
|
||||
black = "f{{moorland.l35}}"
|
||||
grey = "f{{moorland.l65}}"
|
||||
white = "f{{moorland.l90}}"
|
||||
red = "f{{red.l75}}"
|
||||
yellow = "f{{yellow.l77}}"
|
||||
green = "f{{green.l76}}"
|
||||
cyan = "f{{blue.l76}}"
|
||||
blue = "f{{blue.l76}}"
|
||||
magenta = "f{{orange.l76}}"
|
||||
|
||||
[vim]
|
||||
bg0 = "f{{moorland.l25}}"
|
||||
bg1 = "f{{moorland.l30}}"
|
||||
bg2 = "f{{moorland.l35}}"
|
||||
bg3 = "f{{moorland.l40}}"
|
||||
fg3 = "f{{moorland.l75}}"
|
||||
fg2 = "f{{moorland.l80}}"
|
||||
fg1 = "f{{moorland.l85}}"
|
||||
fg0 = "f{{moorland.l90}}"
|
||||
black = "f{{moorland.l25}}"
|
||||
grey = "f{{moorland.l55}}"
|
||||
white = "f{{moorland.l80}}"
|
||||
red = "f{{red.l64}}"
|
||||
orange = "f{{orange.l66}}"
|
||||
yellow = "f{{yellow.l67}}"
|
||||
green = "f{{green.l66}}"
|
||||
cyan = "f{{green.l66}}"
|
||||
blue = "f{{blue.l65}}"
|
||||
violet = "f{{blue.l65}}"
|
||||
magenta = "f{{red.l64}}"
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# note: this script is not portable; to be run in the monobiome scripts/
|
||||
# directory (notice the `rm` invocations)
|
||||
# note: this script is not portable; script to be placed in the monobiome
|
||||
# scripts/ directory and run from the repo root (notice the `rm` invocations)
|
||||
|
||||
# clean existing config
|
||||
rm -rf app-config/*
|
||||
@@ -37,6 +37,9 @@ for biome in "${biomes[@]}"; do
|
||||
done
|
||||
done
|
||||
|
||||
# remove lingering scheme file
|
||||
rm scheme.toml
|
||||
|
||||
cd app-config/firefox
|
||||
shopt -s nullglob
|
||||
for f in *; do
|
||||
|
||||
24
scripts/plots.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from pathlib import Path
|
||||
|
||||
import monobiome as mb
|
||||
from monobiome import plotting
|
||||
from monobiome.palette import compute_hlc_map
|
||||
|
||||
figure_dir = Path(f"images/release/{mb.__version__}")
|
||||
figure_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
fig, ax = plotting.plot_hue_chroma_bounds()
|
||||
fig.savefig(Path(figure_dir, "chroma-bounds.png"))
|
||||
|
||||
fig, ax = plotting.plot_hue_chroma_star()
|
||||
fig.savefig(Path(figure_dir, "chroma-curves.png"))
|
||||
|
||||
# compute the palette as hex to ensure the rendering matches; using
|
||||
# "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, 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"
|
||||
|
||||