8 Commits
1.5.0 ... 1.5.2

52 changed files with 218 additions and 219 deletions

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ archive/
notebooks/color_spaces_manyview.ipynb notebooks/color_spaces_manyview.ipynb
notebooks/oklch_srgb_spherical.ipynb notebooks/oklch_srgb_spherical.ipynb
notebooks/v1.4.0/ notebooks/v1.4.0/
notebooks/v1.5.1/
CHECKLIST.md

View File

@@ -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 Monotone curves have fixed chroma, whereas the accent curves' chroma varies
smoothly as a function of lightness within sRGB gamut bounds. smoothly as a function of lightness within sRGB gamut bounds.
| Chroma curves | Color trajectories | | Chroma curves | Color trajectories |
|---|---| |----------------------------------------------------------|------------------------------------------|
| ![Chroma curves](images/curves/cstar-curves-v140.png) | ![Trajectories](images/trajectories.gif) | | ![Chroma curves](images/release/1.5.1/chroma-curves.png) | ![Trajectories](images/trajectories.gif) |
| Palette | | Palette |
|---| |----------------------------------------------|
| ![Palette](images/palette.png) | | ![Palette](images/release/1.5.1/palette.png) |
Chroma curves are designed specifically to establish a distinct role for each Chroma curves are designed specifically to establish a distinct role for each
accent and are non-intersecting over the lightness domain (hence the distinct 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 ## Themes
| Dark themes | Light themes | | Dark themes | Light themes |
|---|---| |----------------------------------------|------------------------------------------|
| ![Dark themes](images/dark_themes.png) | ![Light themes](images/light_themes.png) | | ![Dark themes](images/dark_themes.png) | ![Light themes](images/light_themes.png) |
Themes are derived from the `monobiome` palette by selecting a monotone base 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 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: 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` | | | `-l 20 -d 0.3` | `-l 20 -d 0.4` | `-l 20 -d 0.5` |
|---|---|---|---| |---------------------|---------------------------------------------------|---------------------------------------------------|---------------------------------------------------|
| Color visualization | ![](images/oklch/mb_b20_d30.gif) | ![](images/oklch/mb_b20_d40.gif) | ![](images/oklch/mb_b20_d50.gif) | | Color visualization | ![](images/oklch/mb_b20_d30.gif) | ![](images/oklch/mb_b20_d40.gif) | ![](images/oklch/mb_b20_d50.gif) |
| Editor preview | ![](images/render/v140-demo-alpine-dark-d0.3.png) | ![](images/render/v140-demo-alpine-dark-d0.4.png) | ![](images/render/v140-demo-alpine-dark-d0.5.png) | | Editor preview | ![](images/render/v140-demo-alpine-dark-d0.3.png) | ![](images/render/v140-demo-alpine-dark-d0.4.png) | ![](images/render/v140-demo-alpine-dark-d0.5.png) |
In short, the base lightness (`-l`) dictates the brightness of the background, In short, the base lightness (`-l`) dictates the brightness of the background,
and the contrast (`-d`) controls how perceptually distinct the accent colors and the contrast (`-d`) controls how perceptually distinct the accent colors
@@ -117,10 +117,10 @@ theme pipeline can be seen in detail below:
![Generation pipeline](images/theme_generation_pipeline.png) ![Generation pipeline](images/theme_generation_pipeline.png)
This figure demonstrates how `kitty` themes are generated, but the process is 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 generic to any palette, scheme, and app. This is implemented in two stages
the `monobiome` CLI: 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: uniformity of accents with respect to the base monotone:
```sh ```sh
@@ -137,19 +137,14 @@ the `monobiome` CLI:
monobiome fill scheme.toml templates/kitty/active.theme -o kitty.theme monobiome fill scheme.toml templates/kitty/active.theme -o kitty.theme
``` ```
This writes a concrete theme to `kitty.theme` that matches the user This writes a concrete `kitty` theme to `kitty.theme` that matches the user
preferences, i.e., the contrast (`-d`), background lightness (`-l`), mode preferences as captured in the previously generated scheme file, i.e., the
(`dark`), and biome (`grassland`). Every part of this process can be contrast (`-d`), background lightness (`-l`), mode (`dark`), and biome
customized: the scheme parameters, the scheme definitions/file, the app (`grassland`). Every part of this process can be customized: the scheme
template. parameters, the scheme definitions/file, the app template.
Running these commands in sequence from the repo root should work Running these commands in sequence from the repo root should work
out-of-the-box, after having installed the CLI tool. 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.
## Applications ## Applications
This repo provides palette-agnostic theme templates for `kitty`, 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 Themes are generated using the [`kitty` theme
template](templates/apps/kitty/templates/active.theme). template](templates/kitty/active.theme).
- `vim`/`neovim` - `vim`/`neovim`
@@ -193,7 +188,7 @@ One can set these themes for the provided applications as follows:
``` ```
Themes are generated using the [`vim` theme Themes are generated using the [`vim` theme
template](templates/apps/nvim/templates/theme.vim). template](templates/nvim/theme.vim).
- `fzf` - `fzf`
@@ -206,7 +201,7 @@ One can set these themes for the provided applications as follows:
``` ```
Themes are generated using the [`fzf` theme Themes are generated using the [`fzf` theme
template](templates/apps/fzf/templates/active.theme). template](templates/fzf/active.theme).
- Firefox - 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. 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/`, You can also download raw XPI files for each theme in `app-config/firefox/`,
each of which is generated using the [Firefox `manifest.json` 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 Static [light][4] and [dark][5] themes are additionally available (i.e., that
don't change with system settings). don't change with system settings).
![Firefox theme previews](images/firefox/themes.png)
## CLI installation ## CLI installation
A brief theme generation guide was provided in the [Generation A brief theme generation guide was provided in the [Generation
section](#generation), making use of the `monobiome` CLI. This tool can be section](#generation), making use of the `monobiome` CLI. This tool can be
@@ -232,7 +225,7 @@ uv tool install monobiome
pipx install monobiome pipx install monobiome
``` ```
The `monobiome` has provides three subcommands: `monobiome` provides three subcommands:
- `monobiome palette`: generate palette files from raw parameterized curves - `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 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 [1]: https://github.com/isa/TextMate-Themes/blob/master/monoindustrial.tmTheme
[2]: https://addons.mozilla.org/en-US/firefox/collections/18495484/monobiome/ [2]: https://addons.mozilla.org/en-US/firefox/collections/18495484/monobiome/

View File

@@ -1,4 +1,4 @@
version = "1.5.0" version = "1.5.2"
[alpine] [alpine]
l10 = "#030303" l10 = "#030303"
@@ -271,7 +271,7 @@ l94 = "#f1eae4"
l95 = "#f4ede7" l95 = "#f4ede7"
l96 = "#f7f0ea" l96 = "#f7f0ea"
l97 = "#fbf4ee" l97 = "#fbf4ee"
l98 = "#fef7f1" l98 = "#fdf7f2"
[savanna] [savanna]
l10 = "#040301" l10 = "#040301"
@@ -362,7 +362,7 @@ l94 = "#ecece3"
l95 = "#efefe7" l95 = "#efefe7"
l96 = "#f3f2ea" l96 = "#f3f2ea"
l97 = "#f6f6ed" l97 = "#f6f6ed"
l98 = "#f9f9f1" l98 = "#f9f9f2"
[grassland] [grassland]
l10 = "#020402" l10 = "#020402"
@@ -453,7 +453,7 @@ l94 = "#e6ede7"
l95 = "#eaf1ea" l95 = "#eaf1ea"
l96 = "#edf4ee" l96 = "#edf4ee"
l97 = "#f0f7f1" l97 = "#f0f7f1"
l98 = "#f4fbf4" l98 = "#f4faf5"
[reef] [reef]
l10 = "#010404" l10 = "#010404"
@@ -544,7 +544,7 @@ l94 = "#e3edef"
l95 = "#e7f1f2" l95 = "#e7f1f2"
l96 = "#eaf4f5" l96 = "#eaf4f5"
l97 = "#edf7f9" l97 = "#edf7f9"
l98 = "#f0fbfc" l98 = "#f2fafb"
[tundra] [tundra]
l10 = "#020306" l10 = "#020306"
@@ -726,7 +726,7 @@ l94 = "#ede9f1"
l95 = "#f0edf4" l95 = "#f0edf4"
l96 = "#f3f0f8" l96 = "#f3f0f8"
l97 = "#f7f3fb" l97 = "#f7f3fb"
l98 = "#faf7ff" l98 = "#faf7fe"
[moorland] [moorland]
l10 = "#050204" l10 = "#050204"
@@ -817,7 +817,7 @@ l94 = "#f1e8ec"
l95 = "#f5ecef" l95 = "#f5ecef"
l96 = "#f8eff3" l96 = "#f8eff3"
l97 = "#fbf2f6" l97 = "#fbf2f6"
l98 = "#fff6f9" l98 = "#fef6f9"
[red] [red]
l10 = "#0d0000" l10 = "#0d0000"
@@ -1443,18 +1443,18 @@ l83 = "#d6b8f7"
l84 = "#d8bcf8" l84 = "#d8bcf8"
l85 = "#dbc1f9" l85 = "#dbc1f9"
l86 = "#ddc5f9" l86 = "#ddc5f9"
l87 = "#e0c9fa" l87 = "#dfc9f9"
l88 = "#e2cdfb" l88 = "#e2cdfa"
l89 = "#e5d1fb" l89 = "#e4d2fa"
l90 = "#e7d5fc" l90 = "#e6d6fa"
l91 = "#e9d9fc" l91 = "#e9dafb"
l92 = "#ecdefd" l92 = "#ebdefb"
l93 = "#eee2fd" l93 = "#eee2fb"
l94 = "#f1e6fe" l94 = "#f0e6fc"
l95 = "#f3eafe" l95 = "#f2ebfc"
l96 = "#f5eefe" l96 = "#f5effd"
l97 = "#f8f2fe" l97 = "#f7f3fd"
l98 = "#faf7ff" l98 = "#faf7fe"
[magenta] [magenta]
l10 = "#080104" l10 = "#080104"
@@ -1540,9 +1540,9 @@ l89 = "#f7cde0"
l90 = "#f8d2e2" l90 = "#f8d2e2"
l91 = "#f9d6e5" l91 = "#f9d6e5"
l92 = "#fadbe8" l92 = "#fadbe8"
l93 = "#fbdfeb" l93 = "#fadfeb"
l94 = "#fce4ee" l94 = "#fbe4ee"
l95 = "#fce8f1" l95 = "#fce8f1"
l96 = "#fdedf4" l96 = "#fdedf3"
l97 = "#fef1f6" l97 = "#fdf1f6"
l98 = "#fef6f9" l98 = "#fef6f9"

View File

@@ -1,4 +1,4 @@
version = "1.5.0" version = "1.5.2"
[alpine] [alpine]
l10 = "oklch(10.0% 0.0000 0.0)" 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)" l95 = "oklch(95.0% 0.0110 29.0)"
l96 = "oklch(96.0% 0.0110 29.0)" l96 = "oklch(96.0% 0.0110 29.0)"
l97 = "oklch(97.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] [chaparral]
l10 = "oklch(10.0% 0.0110 62.5)" 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)" l95 = "oklch(95.0% 0.0110 62.5)"
l96 = "oklch(96.0% 0.0110 62.5)" l96 = "oklch(96.0% 0.0110 62.5)"
l97 = "oklch(97.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] [savanna]
l10 = "oklch(10.0% 0.0110 104.0)" 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)" l95 = "oklch(95.0% 0.0110 104.0)"
l96 = "oklch(96.0% 0.0110 104.0)" l96 = "oklch(96.0% 0.0110 104.0)"
l97 = "oklch(97.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] [grassland]
l10 = "oklch(10.0% 0.0110 148.0)" 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)" l95 = "oklch(95.0% 0.0110 148.0)"
l96 = "oklch(96.0% 0.0110 148.0)" l96 = "oklch(96.0% 0.0110 148.0)"
l97 = "oklch(97.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] [reef]
l10 = "oklch(10.0% 0.0110 205.0)" 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)" l95 = "oklch(95.0% 0.0110 205.0)"
l96 = "oklch(96.0% 0.0110 205.0)" l96 = "oklch(96.0% 0.0110 205.0)"
l97 = "oklch(97.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] [tundra]
l10 = "oklch(10.0% 0.0110 262.0)" 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)" l95 = "oklch(95.0% 0.0110 262.0)"
l96 = "oklch(96.0% 0.0110 262.0)" l96 = "oklch(96.0% 0.0110 262.0)"
l97 = "oklch(97.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] [heathland]
l10 = "oklch(10.0% 0.0110 306.0)" 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)" l95 = "oklch(95.0% 0.0110 306.0)"
l96 = "oklch(96.0% 0.0110 306.0)" l96 = "oklch(96.0% 0.0110 306.0)"
l97 = "oklch(97.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] [moorland]
l10 = "oklch(10.0% 0.0110 350.0)" 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)" l95 = "oklch(95.0% 0.0110 350.0)"
l96 = "oklch(96.0% 0.0110 350.0)" l96 = "oklch(96.0% 0.0110 350.0)"
l97 = "oklch(97.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] [red]
l10 = "oklch(10.0% 0.0406 29.0)" 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)" l92 = "oklch(92.0% 0.0360 104.0)"
l93 = "oklch(93.0% 0.0317 104.0)" l93 = "oklch(93.0% 0.0317 104.0)"
l94 = "oklch(94.0% 0.0273 104.0)" l94 = "oklch(94.0% 0.0273 104.0)"
l95 = "oklch(95.0% 0.0229 104.0)" l95 = "oklch(95.0% 0.0228 104.0)"
l96 = "oklch(96.0% 0.0184 104.0)" l96 = "oklch(96.0% 0.0183 104.0)"
l97 = "oklch(97.0% 0.0138 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] [green]
l10 = "oklch(10.0% 0.0178 148.0)" 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)" l83 = "oklch(83.0% 0.0915 306.0)"
l84 = "oklch(84.0% 0.0865 306.0)" l84 = "oklch(84.0% 0.0865 306.0)"
l85 = "oklch(85.0% 0.0815 306.0)" l85 = "oklch(85.0% 0.0815 306.0)"
l86 = "oklch(86.0% 0.0764 306.0)" l86 = "oklch(86.0% 0.0758 306.0)"
l87 = "oklch(87.0% 0.0712 306.0)" l87 = "oklch(87.0% 0.0697 306.0)"
l88 = "oklch(88.0% 0.0660 306.0)" l88 = "oklch(88.0% 0.0638 306.0)"
l89 = "oklch(89.0% 0.0607 306.0)" l89 = "oklch(89.0% 0.0579 306.0)"
l90 = "oklch(90.0% 0.0554 306.0)" l90 = "oklch(90.0% 0.0522 306.0)"
l91 = "oklch(91.0% 0.0500 306.0)" l91 = "oklch(91.0% 0.0466 306.0)"
l92 = "oklch(92.0% 0.0446 306.0)" l92 = "oklch(92.0% 0.0410 306.0)"
l93 = "oklch(93.0% 0.0391 306.0)" l93 = "oklch(93.0% 0.0356 306.0)"
l94 = "oklch(94.0% 0.0336 306.0)" l94 = "oklch(94.0% 0.0302 306.0)"
l95 = "oklch(95.0% 0.0281 306.0)" l95 = "oklch(95.0% 0.0250 306.0)"
l96 = "oklch(96.0% 0.0225 306.0)" l96 = "oklch(96.0% 0.0198 306.0)"
l97 = "oklch(97.0% 0.0169 306.0)" l97 = "oklch(97.0% 0.0147 306.0)"
l98 = "oklch(98.0% 0.0113 306.0)" l98 = "oklch(98.0% 0.0097 306.0)"
[magenta] [magenta]
l10 = "oklch(10.0% 0.0242 350.0)" 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)" l88 = "oklch(88.0% 0.0574 350.0)"
l89 = "oklch(89.0% 0.0528 350.0)" l89 = "oklch(89.0% 0.0528 350.0)"
l90 = "oklch(90.0% 0.0481 350.0)" l90 = "oklch(90.0% 0.0481 350.0)"
l91 = "oklch(91.0% 0.0435 350.0)" l91 = "oklch(91.0% 0.0433 350.0)"
l92 = "oklch(92.0% 0.0387 350.0)" l92 = "oklch(92.0% 0.0384 350.0)"
l93 = "oklch(93.0% 0.0340 350.0)" l93 = "oklch(93.0% 0.0334 350.0)"
l94 = "oklch(94.0% 0.0292 350.0)" l94 = "oklch(94.0% 0.0285 350.0)"
l95 = "oklch(95.0% 0.0244 350.0)" l95 = "oklch(95.0% 0.0237 350.0)"
l96 = "oklch(96.0% 0.0196 350.0)" l96 = "oklch(96.0% 0.0189 350.0)"
l97 = "oklch(97.0% 0.0147 350.0)" l97 = "oklch(97.0% 0.0141 350.0)"
l98 = "oklch(98.0% 0.0098 350.0)" l98 = "oklch(98.0% 0.0094 350.0)"

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

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

@@ -17,7 +17,7 @@ def register_parser(subparsers: _SubparserType) -> None:
type=str, type=str,
default="hex", default="hex",
choices=["hex", "oklch"], choices=["hex", "oklch"],
help="Color notation to export (either hex or oklch)", help="color notation to export (either hex or oklch)",
) )
parser.add_argument( parser.add_argument(
"-f", "-f",
@@ -25,13 +25,13 @@ def register_parser(subparsers: _SubparserType) -> None:
type=str, type=str,
default="toml", default="toml",
choices=["json", "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( parser.add_argument(
"-o", "-o",
"--output", "--output",
type=str, type=str,
help="Output file to write palette content", help="output file to write palette content",
) )
parser.set_defaults(func=handle_palette) parser.set_defaults(func=handle_palette)

View File

@@ -16,13 +16,13 @@ def register_parser(subparsers: _SubparserType) -> None:
"mode", "mode",
type=str, type=str,
choices=["dark", "light"], choices=["dark", "light"],
help="Scheme mode (light or dark)" help="scheme mode (light or dark)"
) )
parser.add_argument( parser.add_argument(
"biome", "biome",
type=str, type=str,
choices=list(monotone_h_map.keys()), choices=list(monotone_h_map.keys()),
help="Biome setting for scheme." help="biome setting for scheme"
) )
parser.add_argument( parser.add_argument(
"-m", "-m",
@@ -30,7 +30,7 @@ def register_parser(subparsers: _SubparserType) -> None:
type=str, type=str,
default="oklch", default="oklch",
choices=["wcag", "oklch", "lightness"], 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 # e.g., wcag=4.5; oklch=0.40; lightness=40
@@ -39,13 +39,13 @@ def register_parser(subparsers: _SubparserType) -> None:
"--distance", "--distance",
type=float, type=float,
default=0.40, default=0.40,
help="Distance threshold for specified metric", help="distance threshold for specified metric",
) )
parser.add_argument( parser.add_argument(
"-o", "-o",
"--output", "--output",
type=str, 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 # these params remain rooted in lightness; no need to accommodate metric
@@ -58,13 +58,13 @@ def register_parser(subparsers: _SubparserType) -> None:
"--l-base", "--l-base",
type=int, type=int,
default=20, default=20,
help="Minimum lightness level (default: 20)", help="minimum lightness level (default: 20)",
) )
parser.add_argument( parser.add_argument(
"--l-step", "--l-step",
type=int, type=int,
default=5, default=5,
help="Lightness step size (default: 5)", help="lightness step size (default: 5)",
) )
# gaps # gaps
@@ -72,19 +72,19 @@ def register_parser(subparsers: _SubparserType) -> None:
"--fg-gap", "--fg-gap",
type=int, type=int,
default=50, default=50,
help="Foreground lightness gap (default: 50)", help="foreground lightness gap (default: 50)",
) )
parser.add_argument( parser.add_argument(
"--grey-gap", "--grey-gap",
type=int, type=int,
default=30, default=30,
help="Grey lightness gap (default: 30)", help="grey lightness gap (default: 30)",
) )
parser.add_argument( parser.add_argument(
"--term-fg-gap", "--term-fg-gap",
type=int, type=int,
default=65, default=65,
help="Terminal foreground lightness gap (default: 60)", help="terminal foreground lightness gap (default: 60)",
) )
parser.set_defaults(func=handle_scheme) parser.set_defaults(func=handle_scheme)

View File

@@ -121,3 +121,21 @@ for h_str, L_points_C in Lpoints_Cqbr_Hmap.items():
max(0, min(_C, l_maxC_h(_L, _h))) max(0, min(_C, l_maxC_h(_L, _h)))
for _L, _C in zip(L_points, L_points_C, strict=True) 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])
]

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

@@ -1,7 +1,11 @@
from importlib.metadata import version
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from coloraide import Color 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.palette import compute_hlc_map
from monobiome.constants import ( from monobiome.constants import (
h_map, h_map,
@@ -10,11 +14,13 @@ from monobiome.constants import (
accent_h_map, accent_h_map,
monotone_h_map, monotone_h_map,
Lspace_Cmax_Hmap, Lspace_Cmax_Hmap,
max_Cstar_Horder,
Lpoints_Cstar_Hmap, 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 = {} name_h_map = {}
ax_h_map = {} ax_h_map = {}
fig, axes = plt.subplots( fig, axes = plt.subplots(
@@ -63,43 +69,56 @@ def plot_hue_chroma_bounds() -> None:
ncol=3 ncol=3
) )
plt.suptitle("$C^*$ curves for hue groups") plt.suptitle(f"$C^*$ curves for hue groups (v{VERSION})")
plt.show()
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)) 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()
#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: if h_str not in accent_h_map or h_str not in colors:
continue 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] _h = h_map[h_str]
n = int(0.45*len(x)) h_colors = [
ax.text(x[n], y[n]-0.01, h_str, rotation=10, va='center', ha='left') 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_xlabel("Lightness (%)")
ax.set_xticks([L_points[0], 45, 50, 55, 60, 65, 70, L_points[-1]]) 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( 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)
@@ -108,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 = []
@@ -117,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]],
@@ -133,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
@@ -143,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.0" version = "1.5.2"
description = "Monobiome color palette" description = "Monobiome color palette"
requires-python = ">=3.12" requires-python = ">=3.12"
authors = [ authors = [

View File

@@ -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}}"

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# note: this script is not portable; to be run in the monobiome scripts/ # note: this script is not portable; script to be placed in the monobiome
# directory (notice the `rm` invocations) # scripts/ directory and run from the repo root (notice the `rm` invocations)
# clean existing config # clean existing config
rm -rf app-config/* rm -rf app-config/*
@@ -37,6 +37,9 @@ for biome in "${biomes[@]}"; do
done done
done done
# remove lingering scheme file
rm scheme.toml
cd app-config/firefox cd app-config/firefox
shopt -s nullglob shopt -s nullglob
for f in *; do for f in *; do

24
scripts/plots.py Normal file
View 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)

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.0" version = "1.5.2"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "coloraide" }, { name = "coloraide" },