1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-22 22:42:48 +03:00

add wezterm.color module for working with colors

wezterm.color.parse() returns a color object that can be assigned in the
wezterm color config, and that can be used to adjust hue, saturation and
lightness, as well as calculate harmonizing colors (complements, triads,
squares) from the RGB/HSL color wheel.
This commit is contained in:
Wez Furlong 2022-07-10 23:41:18 -07:00
parent 4161e67f60
commit 463ca2fa29
11 changed files with 373 additions and 0 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@
/docs/config/lua/mux-events/index.md
/docs/config/lua/mux-window/index.md
/docs/config/lua/pane/index.md
/docs/config/lua/wezterm.color/index.md
/docs/config/lua/wezterm.gui/index.md
/docs/config/lua/wezterm.mux/index.md
/docs/config/lua/wezterm/index.md

18
Cargo.lock generated
View File

@ -607,6 +607,16 @@ dependencies = [
"zstd",
]
[[package]]
name = "color-funcs"
version = "0.1.0"
dependencies = [
"anyhow",
"config",
"csscolorparser",
"luahelper",
]
[[package]]
name = "color_quant"
version = "1.1.0"
@ -851,6 +861,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841475b11553e394b89eebb5cdfe8ca0b960005ad464bca369ade02a62da053e"
dependencies = [
"lab",
"phf 0.10.1",
]
@ -1092,6 +1103,7 @@ dependencies = [
"battery",
"chrono",
"cocoa",
"color-funcs",
"config",
"dirs-next",
"env_logger",
@ -1898,6 +1910,12 @@ dependencies = [
"log",
]
[[package]]
name = "lab"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
[[package]]
name = "lazy_static"
version = "1.4.0"

View File

@ -316,6 +316,10 @@ TOC = [
"module: wezterm",
"config/lua/wezterm",
),
Gen(
"module: wezterm.color",
"config/lua/wezterm.color",
),
Gen(
"module: wezterm.gui",
"config/lua/wezterm.gui",

View File

@ -333,6 +333,129 @@ impl SrgbaTuple {
(self.2 * 65535.) as u16
)
}
pub fn to_hsla(self) -> (f64, f64, f64, f64) {
csscolorparser::Color::from_rgba(self.0.into(), self.1.into(), self.2.into(), self.3.into())
.to_hsla()
}
pub fn from_hsla(h: f64, s: f64, l: f64, a: f64) -> Self {
let (r, g, b, a) = csscolorparser::Color::from_hsla(h, s, l, a).rgba();
Self(r as f32, g as f32, b as f32, a as f32)
}
/// Scale the color towards the maximum saturation by factor, a value ranging from 0.0 to 1.0.
pub fn saturate(&self, factor: f64) -> Self {
let (h, s, l, a) = self.to_hsla();
let s = apply_scale(s, factor);
Self::from_hsla(h, s, l, a)
}
/// Increase the saturation by amount, a value ranging from 0.0 to 1.0.
pub fn saturate_fixed(&self, amount: f64) -> Self {
let (h, s, l, a) = self.to_hsla();
let s = apply_fixed(s, amount);
Self::from_hsla(h, s, l, a)
}
/// Scale the color towards the maximum lightness by factor, a value ranging from 0.0 to 1.0
pub fn lighten(&self, factor: f64) -> Self {
let (h, s, l, a) = self.to_hsla();
let l = apply_scale(l, factor);
Self::from_hsla(h, s, l, a)
}
/// Lighten the color by amount, a value ranging from 0.0 to 1.0
pub fn lighten_fixed(&self, amount: f64) -> Self {
let (h, s, l, a) = self.to_hsla();
let l = apply_fixed(l, amount);
Self::from_hsla(h, s, l, a)
}
/// Find the complementary color: the one opposite it on the color wheel
pub fn adjust_hue_fixed(&self, amount: f64) -> Self {
let (h, s, l, a) = self.to_hsla();
let h = normalize_angle(h + amount);
Self::from_hsla(h, s, l, a)
}
pub fn complement(&self) -> Self {
self.adjust_hue_fixed(180.)
}
pub fn triad(&self) -> (Self, Self) {
(self.adjust_hue_fixed(120.), self.adjust_hue_fixed(-120.))
}
pub fn square(&self) -> (Self, Self, Self) {
(
self.adjust_hue_fixed(90.),
self.adjust_hue_fixed(270.),
self.adjust_hue_fixed(180.),
)
}
}
/// From "Paint Inspired Color Compositing" by Gosset and Chen
/// <https://stackoverflow.com/a/14116553/149111 >has a python
/// implementation
/// <https://bahamas10.github.io/ryb/> has a copy of the paper
/// itself at <https://bahamas10.github.io/ryb/assets/ryb.pdf>
pub fn ryb_to_rgb(r: f32, y: f32, b: f32) -> (f32, f32, f32) {
fn cubic(t: f32, a: f32, b: f32) -> f32 {
let weight = t * t * (3. - 2. * t);
a + weight * (b - a)
}
let red = {
let x0 = cubic(b, 1.0, 0.163);
let x1 = cubic(b, 1.0, 0.0);
let x2 = cubic(b, 1.0, 0.5);
let x3 = cubic(b, 1.0, 0.2);
let y0 = cubic(y, x0, x1);
let y1 = cubic(y, x2, x3);
cubic(r, y0, y1)
};
let green = {
let x0 = cubic(b, 1.0, 0.373);
let x1 = cubic(b, 1.0, 0.66);
let x2 = cubic(b, 0.0, 0.0);
let x3 = cubic(b, 0.5, 0.094);
let y0 = cubic(y, x0, x1);
let y1 = cubic(y, x2, x3);
cubic(r, y0, y1)
};
let blue = {
let x0 = cubic(b, 1.0, 0.6);
let x1 = cubic(b, 0.0, 0.2);
let x2 = cubic(b, 0.0, 0.5);
let x3 = cubic(b, 0.0, 0.0);
let y0 = cubic(y, x0, x1);
let y1 = cubic(y, x2, x3);
cubic(r, y0, y1)
};
(red, green, blue)
}
fn normalize_angle(t: f64) -> f64 {
let mut t = t % 360.0;
if t < 0.0 {
t += 360.0;
}
t
}
fn apply_scale(current: f64, factor: f64) -> f64 {
let difference = if factor >= 0. { 1.0 - current } else { current };
let delta = difference.max(0.) * factor;
(current + delta).max(0.)
}
fn apply_fixed(current: f64, amount: f64) -> f64 {
(current + amount).max(0.)
}
impl Hash for SrgbaTuple {

View File

@ -18,6 +18,7 @@ As features stabilize some brief notes about them will accumulate here.
* [window:set_position](config/lua/window/set_position.md) method for controlling window position.
* [window:maximize](config/lua/window/maximize.md) and [window:restore](config/lua/window/restore.md) methods for controlling window maximization state.
* [window:get_selection_escapes_for_pane](config/lua/window/get_selection_escapes_for_pane.md) method for getting the current selection including escape sequences. [#2223](https://github.com/wez/wezterm/issues/2223)
* New [wezterm.color](config/lua/wezterm.color/index.md) module for working with colors.
* New [wezterm.gui](config/lua/wezterm.gui/index.md) module and [mux_window:gui_window](config/lua/mux-window/gui_window.md) method.
* New [wezterm.gui.screens()](config/lua/wezterm.gui/screens.md) function for getting information about the available screens/monitors/displays
* You may now use [wezterm.format](config/lua/wezterm/format.md) (or otherwise use strings with escape sequences) in the labels of the [Launcher Menu](config/launch.md#the-launcher-menu).

View File

@ -0,0 +1,6 @@
*Since: nightly builds only*
The `wezterm.color` module exposes functions that work with colors.
## Available functions, constants

View File

@ -0,0 +1,117 @@
# wezterm.color.parse(string)
*Since: nightly builds only*
Parses the passed color and returns an `RgbaColor` object.
`RgbaColor` objects evaluate as strings but have a number of methods
that allow transforming colors.
```
> wezterm.color.parse("black")
#000000
```
This example picks a foreground color, computes its complement
and darkens it to use it as a background color:
```lua
local wezterm = require 'wezterm'
local fg = wezterm.color.parse("yellow")
local bg = fg:complement():darken(0.2)
return {
colors = {
foreground = fg,
background = bg,
}
}
```
## `color:complement()`
*Since: nightly builds only*
Returns the complement of the color. The complement is computed
by converting to HSL, rotating by 180 degrees and converting back
to RGBA.
## `color:triad()`
*Since: nightly builds only*
Returns the other two colors that form a triad. The other colors
are at +/- 120 degrees in the HSL color wheel.
```lua
local a, b = wezterm:color.parse("yellow"):triad()
```
## `color:square()`
*Since: nightly builds only*
Returns the other three colors that form a square. The other colors
are 90 degrees apart on the HSL color wheel.
```lua
local a, b, = wezterm:color.parse("yellow"):square()
```
## `color:saturate(factor)`
*Since: nightly builds only*
Scales the color towards the maximum saturation by the provided factor, which
should be in the range `0.0` through `1.0`.
## `color:saturate_fixed(amount)`
*Since: nightly builds only*
Increase the saturation by amount, a value ranging from `0.0` to `1.0`.
## `color:desaturate(factor)`
*Since: nightly builds only*
Scales the color towards the minimum saturation by the provided factor, which
should be in the range `0.0` through `1.0`.
## `color:desaturate_fixed(amount)`
*Since: nightly builds only*
Decrease the saturation by amount, a value ranging from `0.0` to `1.0`.
## `color:lighten(factor)`
*Since: nightly builds only*
Scales the color towards the maximum lightness by the provided factor, which
should be in the range `0.0` through `1.0`.
## `color:lighten_fixed(amount)`
*Since: nightly builds only*
Increase the lightness by amount, a value ranging from `0.0` to `1.0`.
## `color:darken(factor)`
*Since: nightly builds only*
Scales the color towards the minimum lightness by the provided factor, which
should be in the range `0.0` through `1.0`.
## `color:darken_fixed(amount)`
*Since: nightly builds only*
Decrease the lightness by amount, a value ranging from `0.0` to `1.0`.
## `color:adjust_hue_fixed(degrees)`
*Since: nightly builds only*
Adjust the hue angle by the specified number of degrees.

View File

@ -17,6 +17,7 @@ log = "0.4"
env_logger = "0.9"
termwiz = { path = "../termwiz" }
battery = { path = "../lua-api-crates/battery" }
color-funcs = { path = "../lua-api-crates/color-funcs" }
termwiz-funcs = { path = "../lua-api-crates/termwiz-funcs" }
logging = { path = "../lua-api-crates/logging" }
mux-lua = { path = "../lua-api-crates/mux" }

View File

@ -161,6 +161,7 @@ fn register_panic_hook() {
fn register_lua_modules() {
for func in [
battery::register,
color_funcs::register,
termwiz_funcs::register,
logging::register,
mux_lua::register,

View File

@ -0,0 +1,12 @@
[package]
name = "color-funcs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0"
config = { path = "../../config" }
csscolorparser = {version="0.6", features=["lab"]}
luahelper = { path = "../../luahelper" }

View File

@ -0,0 +1,89 @@
use config::lua::get_or_create_sub_module;
use config::lua::mlua::{self, Lua, MetaMethod, UserData, UserDataMethods};
use config::RgbaColor;
#[derive(Clone)]
struct ColorWrap(RgbaColor);
impl ColorWrap {
pub fn complement(&self) -> Self {
Self(self.0.complement().into())
}
pub fn triad(&self) -> (Self, Self) {
let (a, b) = self.0.triad();
(Self(a.into()), Self(b.into()))
}
pub fn square(&self) -> (Self, Self, Self) {
let (a, b, c) = self.0.square();
(Self(a.into()), Self(b.into()), Self(c.into()))
}
pub fn saturate(&self, factor: f64) -> Self {
Self(self.0.saturate(factor).into())
}
pub fn saturate_fixed(&self, amount: f64) -> Self {
Self(self.0.saturate_fixed(amount).into())
}
pub fn lighten(&self, factor: f64) -> Self {
Self(self.0.lighten(factor).into())
}
pub fn lighten_fixed(&self, amount: f64) -> Self {
Self(self.0.lighten_fixed(amount).into())
}
pub fn adjust_hue_fixed(&self, amount: f64) -> Self {
Self(self.0.adjust_hue_fixed(amount).into())
}
}
impl UserData for ColorWrap {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
let s: String = this.0.into();
Ok(s)
});
methods.add_meta_method(MetaMethod::Eq, |_, this, other: ColorWrap| {
Ok(this.0 == other.0)
});
methods.add_method("complement", |_, this, _: ()| Ok(this.complement()));
methods.add_method("triad", |_, this, _: ()| Ok(this.triad()));
methods.add_method("square", |_, this, _: ()| Ok(this.square()));
methods.add_method("saturate", |_, this, factor: f64| Ok(this.saturate(factor)));
methods.add_method("desaturate", |_, this, factor: f64| {
Ok(this.saturate(-factor))
});
methods.add_method("saturate_fixed", |_, this, amount: f64| {
Ok(this.saturate_fixed(amount))
});
methods.add_method("desaturate_fixed", |_, this, amount: f64| {
Ok(this.saturate_fixed(-amount))
});
methods.add_method("lighten", |_, this, factor: f64| Ok(this.lighten(factor)));
methods.add_method("darken", |_, this, factor: f64| Ok(this.lighten(-factor)));
methods.add_method("lighten_fixed", |_, this, amount: f64| {
Ok(this.lighten_fixed(amount))
});
methods.add_method("darken_fixed", |_, this, amount: f64| {
Ok(this.lighten_fixed(-amount))
});
methods.add_method("adjust_hue_fixed", |_, this, amount: f64| {
Ok(this.adjust_hue_fixed(amount))
});
}
}
pub fn register(lua: &Lua) -> anyhow::Result<()> {
let color = get_or_create_sub_module(lua, "color")?;
color.set("parse", lua.create_function(parse_color)?)?;
Ok(())
}
fn parse_color<'lua>(_: &'lua Lua, spec: String) -> mlua::Result<ColorWrap> {
let color =
RgbaColor::try_from(spec).map_err(|err| mlua::Error::external(format!("{err:#}")))?;
Ok(ColorWrap(color))
}