Skip to main content

xuko_misc/
color.rs

1//! Color types
2
3/// An error that occur when creating a [`RGBAColor`]
4#[derive(Debug, thiserror::Error)]
5pub enum ColorCreationError {
6    /// Parse error
7    #[error("parse error: {0}")]
8    ParseError(String),
9}
10
11/// RGBA Color
12///
13/// # Examples
14///
15/// ```
16/// use xuko_misc::color::RGBAColor;
17/// let color = RGBAColor::from(0xFF33AA);
18///
19/// assert_eq!(color.r, 255);
20/// assert_eq!(color.g, 51);
21/// assert_eq!(color.b, 170);
22/// assert_eq!(color.a, 255);
23/// ```
24#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
25pub struct RGBAColor {
26    /// The red component of the [RGBAColor]
27    pub r: u8,
28    /// The green component of the [RGBAColor]
29    pub g: u8,
30    /// The blue component of the [RGBAColor]
31    pub b: u8,
32    /// The alpha component of the [RGBAColor]
33    pub a: u8,
34}
35
36impl RGBAColor {
37    /// Create a new [`RGBAColor`] from anything that implements [`Into`]<[`RGBAColor`]>
38    pub fn new(c: impl Into<RGBAColor>) -> Self {
39        c.into()
40    }
41
42    /// Create a new [`RGBAColor`] from anything that implements
43    /// [`TryInto`]<[`RGBAColor`], Error = [`ColorCreationError`]>
44    pub fn try_new(
45        c: impl TryInto<RGBAColor, Error = ColorCreationError>,
46    ) -> Result<Self, ColorCreationError> {
47        c.try_into()
48    }
49
50    /// Add a color with another.
51    pub const fn add(&self, other: &Self) -> Self {
52        Self {
53            r: self.r.saturating_add(other.r),
54            g: self.g.saturating_add(other.g),
55            b: self.b.saturating_add(other.b),
56            a: self.a.saturating_add(other.a),
57        }
58    }
59
60    /// Subtract a color from another.
61    pub const fn sub(&self, other: &Self) -> Self {
62        Self {
63            r: self.r.saturating_sub(other.r),
64            g: self.g.saturating_sub(other.g),
65            b: self.b.saturating_sub(other.b),
66            a: self.a.saturating_sub(other.a),
67        }
68    }
69
70    /// Multiply a color with another.
71    pub const fn mul(&self, other: &Self) -> Self {
72        Self {
73            r: self.r.saturating_mul(other.r),
74            g: self.g.saturating_mul(other.g),
75            b: self.b.saturating_mul(other.b),
76            a: self.a.saturating_mul(other.a),
77        }
78    }
79
80    /// Colorize color to another color.
81    pub const fn colorize(&self, other: &Self) -> Self {
82        let red = 255u8.wrapping_sub(other.r);
83        let green = 255u8.wrapping_sub(other.g);
84        let blue = 255u8.wrapping_sub(other.b);
85
86        Self {
87            r: self.r.saturating_sub(red),
88            g: self.r.saturating_sub(green),
89            b: self.r.saturating_sub(blue),
90            a: self.a,
91        }
92    }
93}
94
95impl Default for RGBAColor {
96    fn default() -> Self {
97        Self {
98            r: 0,
99            g: 0,
100            b: 0,
101            a: 255,
102        }
103    }
104}
105
106impl From<u32> for RGBAColor {
107    fn from(value: u32) -> Self {
108        // color must be atleast triple hex
109        // assert!(value >= 0x000000_u32);
110
111        if value > 0xFFFFFF {
112            Self {
113                r: ((value >> 24) & 0xFF) as u8,
114                g: ((value >> 16) & 0xFF) as u8,
115                b: ((value >> 8) & 0xFF) as u8,
116                a: (value & 0xFF) as u8,
117            }
118        } else {
119            Self {
120                r: ((value >> 16) & 0xFF) as u8,
121                g: ((value >> 8) & 0xFF) as u8,
122                b: (value & 0xFF) as u8,
123                a: 255,
124            }
125        }
126    }
127}
128
129impl TryFrom<&str> for RGBAColor {
130    type Error = ColorCreationError;
131
132    fn try_from(value: &str) -> Result<Self, Self::Error> {
133        let s = value.strip_prefix('#').unwrap_or(value);
134
135        u32::from_str_radix(s, 16)
136            .map_err(|e| ColorCreationError::ParseError(e.to_string()))
137            .map(|v| v.into())
138    }
139}
140
141impl TryFrom<String> for RGBAColor {
142    type Error = ColorCreationError;
143
144    fn try_from(value: String) -> Result<Self, Self::Error> {
145        Self::try_new(&*value)
146    }
147}
148
149impl From<[u8; 3]> for RGBAColor {
150    fn from(value: [u8; 3]) -> Self {
151        Self {
152            r: value[0],
153            g: value[1],
154            b: value[2],
155            a: 255,
156        }
157    }
158}
159
160impl From<[u8; 4]> for RGBAColor {
161    fn from(value: [u8; 4]) -> Self {
162        Self {
163            r: value[0],
164            g: value[1],
165            b: value[2],
166            a: value[3],
167        }
168    }
169}
170
171impl From<(u8, u8, u8)> for RGBAColor {
172    fn from(value: (u8, u8, u8)) -> Self {
173        Self {
174            r: value.0,
175            g: value.1,
176            b: value.2,
177            a: 255,
178        }
179    }
180}
181
182impl From<(u8, u8, u8, u8)> for RGBAColor {
183    fn from(value: (u8, u8, u8, u8)) -> Self {
184        Self {
185            r: value.0,
186            g: value.1,
187            b: value.2,
188            a: value.3,
189        }
190    }
191}