termwiz/
color.rs

1//! Colors for attributes
2// for FromPrimitive
3#![allow(clippy::useless_attribute)]
4
5use num_derive::*;
6#[cfg(feature = "use_serde")]
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8pub use wezterm_color_types::{LinearRgba, SrgbaTuple};
9use wezterm_dynamic::{FromDynamic, FromDynamicOptions, ToDynamic, Value};
10
11#[derive(Debug, Clone, Copy, FromPrimitive, PartialEq, Eq, FromDynamic, ToDynamic)]
12#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
13#[repr(u8)]
14/// These correspond to the classic ANSI color indices and are
15/// used for convenience/readability in code
16pub enum AnsiColor {
17    /// "Dark" black
18    Black = 0,
19    /// Dark red
20    Maroon,
21    /// Dark green
22    Green,
23    /// "Dark" yellow
24    Olive,
25    /// Dark blue
26    Navy,
27    /// Dark purple
28    Purple,
29    /// "Dark" cyan
30    Teal,
31    /// "Dark" white
32    Silver,
33    /// "Bright" black
34    Grey,
35    /// Bright red
36    Red,
37    /// Bright green
38    Lime,
39    /// Bright yellow
40    Yellow,
41    /// Bright blue
42    Blue,
43    /// Bright purple
44    Fuchsia,
45    /// Bright Cyan/Aqua
46    Aqua,
47    /// Bright white
48    White,
49}
50
51impl From<AnsiColor> for u8 {
52    fn from(col: AnsiColor) -> u8 {
53        col as u8
54    }
55}
56
57/// Describes a color in the SRGB colorspace using red, green and blue
58/// components in the range 0-255.
59#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash)]
60pub struct RgbColor {
61    bits: u32,
62}
63
64impl Into<SrgbaTuple> for RgbColor {
65    fn into(self) -> SrgbaTuple {
66        self.to_tuple_rgba()
67    }
68}
69
70impl RgbColor {
71    /// Construct a color from discrete red, green, blue values
72    /// in the range 0-255.
73    pub const fn new_8bpc(red: u8, green: u8, blue: u8) -> Self {
74        Self {
75            bits: ((red as u32) << 16) | ((green as u32) << 8) | blue as u32,
76        }
77    }
78
79    /// Construct a color from discrete red, green, blue values
80    /// in the range 0.0-1.0 in the sRGB colorspace.
81    pub fn new_f32(red: f32, green: f32, blue: f32) -> Self {
82        let red = (red * 255.) as u8;
83        let green = (green * 255.) as u8;
84        let blue = (blue * 255.) as u8;
85        Self::new_8bpc(red, green, blue)
86    }
87
88    /// Returns red, green, blue as 8bpc values.
89    /// Will convert from 10bpc if that is the internal storage.
90    pub fn to_tuple_rgb8(self) -> (u8, u8, u8) {
91        (
92            (self.bits >> 16) as u8,
93            (self.bits >> 8) as u8,
94            self.bits as u8,
95        )
96    }
97
98    /// Returns red, green, blue as floating point values in the range 0.0-1.0.
99    /// An alpha channel with the value of 1.0 is included.
100    /// The values are in the sRGB colorspace.
101    pub fn to_tuple_rgba(self) -> SrgbaTuple {
102        SrgbaTuple(
103            (self.bits >> 16) as u8 as f32 / 255.0,
104            (self.bits >> 8) as u8 as f32 / 255.0,
105            self.bits as u8 as f32 / 255.0,
106            1.0,
107        )
108    }
109
110    /// Returns red, green, blue as floating point values in the range 0.0-1.0.
111    /// An alpha channel with the value of 1.0 is included.
112    /// The values are converted from sRGB to linear colorspace.
113    pub fn to_linear_tuple_rgba(self) -> LinearRgba {
114        self.to_tuple_rgba().to_linear()
115    }
116
117    /// Construct a color from an X11/SVG/CSS3 color name.
118    /// Returns None if the supplied name is not recognized.
119    /// The list of names can be found here:
120    /// <https://en.wikipedia.org/wiki/X11_color_names>
121    pub fn from_named(name: &str) -> Option<RgbColor> {
122        Some(SrgbaTuple::from_named(name)?.into())
123    }
124
125    /// Returns a string of the form `#RRGGBB`
126    pub fn to_rgb_string(self) -> String {
127        let (red, green, blue) = self.to_tuple_rgb8();
128        format!("#{:02x}{:02x}{:02x}", red, green, blue)
129    }
130
131    /// Returns a string of the form `rgb:RRRR/GGGG/BBBB`
132    pub fn to_x11_16bit_rgb_string(self) -> String {
133        let (red, green, blue) = self.to_tuple_rgb8();
134        format!(
135            "rgb:{:02x}{:02x}/{:02x}{:02x}/{:02x}{:02x}",
136            red, red, green, green, blue, blue
137        )
138    }
139
140    /// Construct a color from a string of the form `#RRGGBB` where
141    /// R, G and B are all hex digits.
142    /// `hsl:hue sat light` is also accepted, and allows specifying a color
143    /// in the HSL color space, where `hue` is measure in degrees and has
144    /// a range of 0-360, and both `sat` and `light` are specified in percentage
145    /// in the range 0-100.
146    pub fn from_rgb_str(s: &str) -> Option<RgbColor> {
147        let srgb: SrgbaTuple = s.parse().ok()?;
148        Some(srgb.into())
149    }
150
151    /// Construct a color from an SVG/CSS3 color name.
152    /// or from a string of the form `#RRGGBB` where
153    /// R, G and B are all hex digits.
154    /// `hsl:hue sat light` is also accepted, and allows specifying a color
155    /// in the HSL color space, where `hue` is measure in degrees and has
156    /// a range of 0-360, and both `sat` and `light` are specified in percentage
157    /// in the range 0-100.
158    /// Returns None if the supplied name is not recognized.
159    /// The list of names can be found here:
160    /// <https://ogeon.github.io/docs/palette/master/palette/named/index.html>
161    pub fn from_named_or_rgb_string(s: &str) -> Option<Self> {
162        RgbColor::from_rgb_str(&s).or_else(|| RgbColor::from_named(&s))
163    }
164}
165
166impl From<SrgbaTuple> for RgbColor {
167    fn from(srgb: SrgbaTuple) -> RgbColor {
168        let SrgbaTuple(r, g, b, _) = srgb;
169        Self::new_f32(r, g, b)
170    }
171}
172
173/// This is mildly unfortunate: in order to round trip RgbColor with serde
174/// we need to provide a Serialize impl equivalent to the Deserialize impl
175/// below.  We use the impl below to allow more flexible specification of
176/// color strings in the config file.  A side effect of doing it this way
177/// is that we have to serialize RgbColor as a 7-byte string when we could
178/// otherwise serialize it as a 3-byte array.  There's probably a way
179/// to make this work more efficiently, but for now this will do.
180#[cfg(feature = "use_serde")]
181impl Serialize for RgbColor {
182    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
183    where
184        S: Serializer,
185    {
186        let s = self.to_rgb_string();
187        s.serialize(serializer)
188    }
189}
190
191#[cfg(feature = "use_serde")]
192impl<'de> Deserialize<'de> for RgbColor {
193    fn deserialize<D>(deserializer: D) -> Result<RgbColor, D::Error>
194    where
195        D: Deserializer<'de>,
196    {
197        let s = String::deserialize(deserializer)?;
198        RgbColor::from_named_or_rgb_string(&s)
199            .ok_or_else(|| format!("unknown color name: {}", s))
200            .map_err(serde::de::Error::custom)
201    }
202}
203
204impl ToDynamic for RgbColor {
205    fn to_dynamic(&self) -> Value {
206        self.to_rgb_string().to_dynamic()
207    }
208}
209
210impl FromDynamic for RgbColor {
211    fn from_dynamic(
212        value: &Value,
213        options: FromDynamicOptions,
214    ) -> Result<Self, wezterm_dynamic::Error> {
215        let s = String::from_dynamic(value, options)?;
216        Ok(RgbColor::from_named_or_rgb_string(&s)
217            .ok_or_else(|| format!("unknown color name: {}", s))?)
218    }
219}
220
221/// An index into the fixed color palette.
222pub type PaletteIndex = u8;
223
224/// Specifies the color to be used when rendering a cell.
225/// This differs from `ColorAttribute` in that this type can only
226/// specify one of the possible color types at once, whereas the
227/// `ColorAttribute` type can specify a TrueColor value and a fallback.
228#[derive(Debug, Clone, Copy, Eq, PartialEq)]
229pub enum ColorSpec {
230    Default,
231    /// Use either a raw number, or use values from the `AnsiColor` enum
232    PaletteIndex(PaletteIndex),
233    TrueColor(SrgbaTuple),
234}
235
236impl Default for ColorSpec {
237    fn default() -> Self {
238        ColorSpec::Default
239    }
240}
241
242impl From<AnsiColor> for ColorSpec {
243    fn from(col: AnsiColor) -> Self {
244        ColorSpec::PaletteIndex(col as u8)
245    }
246}
247
248impl From<RgbColor> for ColorSpec {
249    fn from(col: RgbColor) -> Self {
250        ColorSpec::TrueColor(col.into())
251    }
252}
253
254impl From<SrgbaTuple> for ColorSpec {
255    fn from(col: SrgbaTuple) -> Self {
256        ColorSpec::TrueColor(col)
257    }
258}
259
260/// Specifies the color to be used when rendering a cell.  This is the
261/// type used in the `CellAttributes` struct and can specify an optional
262/// TrueColor value, allowing a fallback to a more traditional palette
263/// index if TrueColor is not available.
264#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
265#[derive(Debug, Clone, Copy, Eq, PartialEq, FromDynamic, ToDynamic, Hash)]
266pub enum ColorAttribute {
267    /// Use RgbColor when supported, falling back to the specified PaletteIndex.
268    TrueColorWithPaletteFallback(SrgbaTuple, PaletteIndex),
269    /// Use RgbColor when supported, falling back to the default color
270    TrueColorWithDefaultFallback(SrgbaTuple),
271    /// Use the specified PaletteIndex
272    PaletteIndex(PaletteIndex),
273    /// Use the default color
274    Default,
275}
276
277impl Default for ColorAttribute {
278    fn default() -> Self {
279        ColorAttribute::Default
280    }
281}
282
283impl From<AnsiColor> for ColorAttribute {
284    fn from(col: AnsiColor) -> Self {
285        ColorAttribute::PaletteIndex(col as u8)
286    }
287}
288
289impl From<ColorSpec> for ColorAttribute {
290    fn from(spec: ColorSpec) -> Self {
291        match spec {
292            ColorSpec::Default => ColorAttribute::Default,
293            ColorSpec::PaletteIndex(idx) => ColorAttribute::PaletteIndex(idx),
294            ColorSpec::TrueColor(color) => ColorAttribute::TrueColorWithDefaultFallback(color),
295        }
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302    #[test]
303    fn from_hsl() {
304        let foo = RgbColor::from_rgb_str("hsl:235 100  50").unwrap();
305        assert_eq!(foo.to_rgb_string(), "#0015ff");
306    }
307
308    #[test]
309    fn from_rgb() {
310        assert!(RgbColor::from_rgb_str("").is_none());
311        assert!(RgbColor::from_rgb_str("#xyxyxy").is_none());
312
313        let black = RgbColor::from_rgb_str("#FFF").unwrap();
314        assert_eq!(black.to_tuple_rgb8(), (0xf0, 0xf0, 0xf0));
315
316        let black = RgbColor::from_rgb_str("#000000").unwrap();
317        assert_eq!(black.to_tuple_rgb8(), (0, 0, 0));
318
319        let grey = RgbColor::from_rgb_str("rgb:D6/D6/D6").unwrap();
320        assert_eq!(grey.to_tuple_rgb8(), (0xd6, 0xd6, 0xd6));
321
322        let grey = RgbColor::from_rgb_str("rgb:f0f0/f0f0/f0f0").unwrap();
323        assert_eq!(grey.to_tuple_rgb8(), (0xf0, 0xf0, 0xf0));
324    }
325
326    #[cfg(feature = "use_serde")]
327    #[test]
328    fn roundtrip_rgbcolor() {
329        let data = varbincode::serialize(&RgbColor::from_named("DarkGreen").unwrap()).unwrap();
330        eprintln!("serialized as {:?}", data);
331        let _decoded: RgbColor = varbincode::deserialize(data.as_slice()).unwrap();
332    }
333}