libsixel_rs/
color.rs

1//! Types and functions for Sixel colors.
2
3/// Represents a Sixel color name.
4#[repr(u32)]
5#[derive(Clone, Copy, Debug, Default, PartialEq)]
6pub enum ColorName {
7    /// Reserved color variant for undefined RGB values.
8    #[default]
9    Reserved,
10    /// Unnamed [Color] variant.
11    Rgb(u32),
12    /// Named [Color] variant.
13    Variant(ColorVariant),
14}
15
16impl ColorName {
17    /// Creates a new [ColorName].
18    pub const fn new() -> ColorName {
19        ColorName::Reserved
20    }
21}
22
23/// Represents a named [Color] variant.
24#[repr(u32)]
25#[derive(Clone, Copy, Debug, Default, PartialEq)]
26pub enum ColorVariant {
27    #[default]
28    Black = 0x000000,
29}
30
31impl From<ColorName> for u32 {
32    fn from(val: ColorName) -> Self {
33        match val {
34            ColorName::Reserved => 0,
35            ColorName::Rgb(color) => color,
36            ColorName::Variant(color) => color as u32,
37        }
38    }
39}
40
41impl From<&str> for ColorName {
42    fn from(val: &str) -> Self {
43        if val.starts_with("rgb:") && val.len() >= 10 {
44            if let Ok(rgb) = u32::from_str_radix(&val[4..].to_lowercase()[..6], 16) {
45                Self::Rgb(rgb)
46            } else {
47                Self::Reserved
48            }
49        } else if val.starts_with('#') && val.len() >= 7 {
50            if let Ok(rgb) = u32::from_str_radix(&val[1..].to_lowercase()[..6], 16) {
51                Self::Rgb(rgb)
52            } else {
53                Self::Reserved
54            }
55        } else {
56            match val {
57                "black" => Self::Variant(ColorVariant::Black),
58                _ => Self::Reserved,
59            }
60        }
61    }
62}
63
64/// Represents the red, green, and blue components of a [Color].
65#[repr(C)]
66#[derive(Clone, Copy, Debug, Default, PartialEq)]
67pub struct Rgb {
68    r: u8,
69    g: u8,
70    b: u8,
71}
72
73impl Rgb {
74    /// Creates a new [Rgb].
75    pub const fn new() -> Self {
76        Self { r: 0, g: 0, b: 0 }
77    }
78
79    /// Creates a new [Rgb] from the given values.
80    pub const fn create(r: u8, g: u8, b: u8) -> Self {
81        Self { r, g, b }
82    }
83
84    /// Gets the red [Rgb] value.
85    pub const fn r(&self) -> u8 {
86        self.r
87    }
88
89    /// Sets the red [Rgb] value.
90    pub fn set_r(&mut self, r: u8) {
91        self.r = r;
92    }
93
94    /// Gets the green [Rgb] value.
95    pub const fn g(&self) -> u8 {
96        self.g
97    }
98
99    /// Sets the green [Rgb] value.
100    pub fn set_g(&mut self, g: u8) {
101        self.g = g;
102    }
103
104    /// Gets the blue [Rgb] value.
105    pub const fn b(&self) -> u8 {
106        self.b
107    }
108
109    /// Sets the blue [Rgb] value.
110    pub fn set_b(&mut self, b: u8) {
111        self.b = b;
112    }
113}
114
115impl From<ColorName> for Rgb {
116    fn from(val: ColorName) -> Self {
117        let rgb = u32::from(val);
118
119        Self {
120            r: ((rgb & 0xff0000) >> 16) as u8,
121            g: ((rgb & 0xff00) >> 8) as u8,
122            b: (rgb & 0xff) as u8,
123        }
124    }
125}
126
127/// Represents a Sixel color.
128#[repr(C)]
129#[derive(Clone, Copy, Debug, Default, PartialEq)]
130pub struct Color {
131    pub name: ColorName,
132    pub rgb: Rgb,
133}
134
135impl Color {
136    /// Creates a new [Color].
137    pub const fn new() -> Self {
138        Self {
139            name: ColorName::new(),
140            rgb: Rgb::new(),
141        }
142    }
143}
144
145impl From<&str> for Color {
146    fn from(val: &str) -> Self {
147        let name = ColorName::from(val);
148        let rgb = Rgb::from(name);
149
150        Self { name, rgb }
151    }
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::Result;
158
159    #[test]
160    fn test_color_parsing() -> Result<()> {
161        let color_str = "black";
162        let rgb0_str = "rgb:AB1122";
163        let rgb1_str = "#A1c022";
164        let invalid_str = "not a RGB color";
165        let too_short0_str = "rgb:1";
166        let too_short1_str = "#1";
167        let rgb_trailing_str = "#A1c022whatisallthistrailgarbage";
168
169        assert_eq!(
170            ColorName::from(color_str),
171            ColorName::Variant(ColorVariant::Black)
172        );
173        assert_eq!(ColorName::from(rgb0_str), ColorName::Rgb(0xab1122));
174        assert_eq!(ColorName::from(rgb1_str), ColorName::Rgb(0xa1c022));
175        assert_eq!(ColorName::from(invalid_str), ColorName::Reserved);
176        assert_eq!(ColorName::from(rgb_trailing_str), ColorName::Rgb(0xa1c022));
177        assert_eq!(ColorName::from(too_short0_str), ColorName::Reserved);
178        assert_eq!(ColorName::from(too_short1_str), ColorName::Reserved);
179
180        Ok(())
181    }
182}