1use std::fmt::{Debug, Formatter};
7
8#[derive(Clone, Copy, PartialEq, Eq, Hash)]
14#[non_exhaustive]
15pub enum Color {
16 #[doc(hidden)]
17 Rgba32(u32),
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum ColorParseError {
23 WrongSize(usize),
25 #[allow(missing_docs)]
28 NotHex { idx: usize, byte: u8 },
29}
30
31impl Color {
32 pub const fn rgb8(r: u8, g: u8, b: u8) -> Color {
34 Color::from_rgba32_u32(((r as u32) << 24) | ((g as u32) << 16) | ((b as u32) << 8) | 0xff)
35 }
36
37 pub const fn rgba8(r: u8, g: u8, b: u8, a: u8) -> Color {
39 Color::from_rgba32_u32(
40 ((r as u32) << 24) | ((g as u32) << 16) | ((b as u32) << 8) | (a as u32),
41 )
42 }
43
44 pub const fn from_rgba32_u32(rgba: u32) -> Color {
46 Color::Rgba32(rgba)
47 }
48
49 pub const fn from_hex_str(hex: &str) -> Result<Color, ColorParseError> {
61 match get_4bit_hex_channels(hex) {
63 Ok(channels) => Ok(color_from_4bit_hex(channels)),
64 Err(e) => Err(e),
65 }
66 }
67
68 pub const fn grey8(grey: u8) -> Color {
82 Color::rgb8(grey, grey, grey)
83 }
84
85 pub fn grey(grey: f64) -> Color {
87 Color::rgb(grey, grey, grey)
88 }
89
90 pub fn rgba(r: f64, g: f64, b: f64, a: f64) -> Color {
95 let r = (r.clamp(0.0, 1.0) * 255.0).round() as u32;
96 let g = (g.clamp(0.0, 1.0) * 255.0).round() as u32;
97 let b = (b.clamp(0.0, 1.0) * 255.0).round() as u32;
98 let a = (a.clamp(0.0, 1.0) * 255.0).round() as u32;
99 Color::from_rgba32_u32((r << 24) | (g << 16) | (b << 8) | a)
100 }
101
102 pub fn rgb(r: f64, g: f64, b: f64) -> Color {
107 let r = (r.clamp(0.0, 1.0) * 255.0).round() as u32;
108 let g = (g.clamp(0.0, 1.0) * 255.0).round() as u32;
109 let b = (b.clamp(0.0, 1.0) * 255.0).round() as u32;
110 Color::from_rgba32_u32((r << 24) | (g << 16) | (b << 8) | 0xff)
111 }
112
113 #[allow(non_snake_case)]
131 #[allow(clippy::many_single_char_names)]
132 #[allow(clippy::unreadable_literal)]
133 pub fn hlc(h: f64, L: f64, c: f64) -> Color {
134 fn f_inv(t: f64) -> f64 {
137 let d = 6. / 29.;
138 if t > d {
139 t.powi(3)
140 } else {
141 3. * d * d * (t - 4. / 29.)
142 }
143 }
144 let th = h * (std::f64::consts::PI / 180.);
145 let a = c * th.cos();
146 let b = c * th.sin();
147 let ll = (L + 16.) * (1. / 116.);
148 let X = f_inv(ll + a * (1. / 500.));
150 let Y = f_inv(ll);
151 let Z = f_inv(ll - b * (1. / 200.));
152 let r_lin = 3.02172918 * X - 1.61692294 * Y - 0.40480625 * Z;
166 let g_lin = -0.94339358 * X + 1.91584267 * Y + 0.02755094 * Z;
167 let b_lin = 0.06945666 * X - 0.22903204 * Y + 1.15957526 * Z;
168 fn gamma(u: f64) -> f64 {
169 if u <= 0.0031308 {
170 12.92 * u
171 } else {
172 1.055 * u.powf(1. / 2.4) - 0.055
173 }
174 }
175 Color::rgb(gamma(r_lin), gamma(g_lin), gamma(b_lin))
176 }
177
178 pub fn hlca(h: f64, l: f64, c: f64, a: f64) -> Color {
182 Color::hlc(h, c, l).with_alpha(a)
183 }
184
185 pub fn with_alpha(self, a: f64) -> Color {
189 let a = (a.clamp(0.0, 1.0) * 255.0).round() as u32;
190 Color::from_rgba32_u32((self.as_rgba_u32() & !0xff) | a)
191 }
192
193 pub const fn with_r8(self, r: u8) -> Color {
197 Color::from_rgba32_u32((self.as_rgba_u32() & !0xff000000) | (r as u32) << 24)
198 }
199
200 pub const fn with_g8(self, g: u8) -> Color {
204 Color::from_rgba32_u32((self.as_rgba_u32() & !0xff0000) | (g as u32) << 16)
205 }
206
207 pub const fn with_b8(self, b: u8) -> Color {
211 Color::from_rgba32_u32((self.as_rgba_u32() & !0xff00) | (b as u32) << 8)
212 }
213
214 pub const fn with_a8(self, a: u8) -> Color {
218 Color::from_rgba32_u32((self.as_rgba_u32() & !0xff) | a as u32)
219 }
220
221 pub const fn as_rgba_u32(self) -> u32 {
223 match self {
224 Color::Rgba32(rgba) => rgba,
225 }
226 }
227
228 pub fn as_rgba8(self) -> (u8, u8, u8, u8) {
230 let rgba = self.as_rgba_u32();
231 (
232 (rgba >> 24 & 255) as u8,
233 ((rgba >> 16) & 255) as u8,
234 ((rgba >> 8) & 255) as u8,
235 (rgba & 255) as u8,
236 )
237 }
238
239 pub fn as_rgba(self) -> (f64, f64, f64, f64) {
241 let rgba = self.as_rgba_u32();
242 (
243 (rgba >> 24) as f64 / 255.0,
244 ((rgba >> 16) & 255) as f64 / 255.0,
245 ((rgba >> 8) & 255) as f64 / 255.0,
246 (rgba & 255) as f64 / 255.0,
247 )
248 }
249
250 pub const AQUA: Color = Color::rgb8(0, 255, 255);
254
255 pub const BLACK: Color = Color::rgb8(0, 0, 0);
257
258 pub const BLUE: Color = Color::rgb8(0, 0, 255);
260
261 pub const FUCHSIA: Color = Color::rgb8(255, 0, 255);
263
264 pub const GRAY: Color = Color::grey8(128);
266
267 pub const GREEN: Color = Color::rgb8(0, 128, 0);
269
270 pub const LIME: Color = Color::rgb8(0, 255, 0);
272
273 pub const MAROON: Color = Color::rgb8(128, 0, 0);
275
276 pub const NAVY: Color = Color::rgb8(0, 0, 128);
278
279 pub const OLIVE: Color = Color::rgb8(128, 128, 0);
281
282 pub const PURPLE: Color = Color::rgb8(128, 0, 128);
284
285 pub const RED: Color = Color::rgb8(255, 0, 0);
287
288 pub const SILVER: Color = Color::grey8(192);
290
291 pub const TEAL: Color = Color::rgb8(0, 128, 128);
293
294 pub const TRANSPARENT: Color = Color::rgba8(0, 0, 0, 0);
296
297 pub const WHITE: Color = Color::grey8(255);
299
300 pub const YELLOW: Color = Color::rgb8(255, 255, 0);
302}
303
304const fn get_4bit_hex_channels(hex_str: &str) -> Result<[u8; 8], ColorParseError> {
305 let mut four_bit_channels = match hex_str.as_bytes() {
306 &[b'#', r, g, b] | &[r, g, b] => [r, r, g, g, b, b, b'f', b'f'],
307 &[b'#', r, g, b, a] | &[r, g, b, a] => [r, r, g, g, b, b, a, a],
308 &[b'#', r0, r1, g0, g1, b0, b1] | &[r0, r1, g0, g1, b0, b1] => {
309 [r0, r1, g0, g1, b0, b1, b'f', b'f']
310 }
311 &[b'#', r0, r1, g0, g1, b0, b1, a0, a1] | &[r0, r1, g0, g1, b0, b1, a0, a1] => {
312 [r0, r1, g0, g1, b0, b1, a0, a1]
313 }
314 other => return Err(ColorParseError::WrongSize(other.len())),
315 };
316
317 let mut i = 0;
320 while i < four_bit_channels.len() {
321 let ascii = four_bit_channels[i];
322 let as_hex = match hex_from_ascii_byte(ascii) {
323 Ok(hex) => hex,
324 Err(byte) => return Err(ColorParseError::NotHex { idx: i, byte }),
325 };
326 four_bit_channels[i] = as_hex;
327 i += 1;
328 }
329 Ok(four_bit_channels)
330}
331
332const fn color_from_4bit_hex(components: [u8; 8]) -> Color {
333 let [r0, r1, g0, g1, b0, b1, a0, a1] = components;
334 Color::rgba8(r0 << 4 | r1, g0 << 4 | g1, b0 << 4 | b1, a0 << 4 | a1)
335}
336
337const fn hex_from_ascii_byte(b: u8) -> Result<u8, u8> {
338 match b {
339 b'0'..=b'9' => Ok(b - b'0'),
340 b'A'..=b'F' => Ok(b - b'A' + 10),
341 b'a'..=b'f' => Ok(b - b'a' + 10),
342 _ => Err(b),
343 }
344}
345
346impl Debug for Color {
347 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
348 write!(f, "#{:08x}", self.as_rgba_u32())
349 }
350}
351
352impl std::fmt::Display for ColorParseError {
353 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
354 match self {
355 ColorParseError::WrongSize(n) => write!(f, "Input string has invalid length {n}"),
356 ColorParseError::NotHex { idx, byte } => {
357 write!(f, "byte {byte:X} at index {idx} is not valid hex digit")
358 }
359 }
360 }
361}
362
363impl std::error::Error for ColorParseError {}
364#[cfg(test)]
365mod tests {
366 use super::*;
367 #[test]
368 fn color_from_hex() {
369 assert_eq!(Color::from_hex_str("#BAD"), Color::from_hex_str("BBAADD"));
370 assert_eq!(
371 Color::from_hex_str("#BAD"),
372 Ok(Color::from_rgba32_u32(0xBBAADDFF))
373 );
374 assert_eq!(Color::from_hex_str("BAD"), Color::from_hex_str("BBAADD"));
375 assert_eq!(Color::from_hex_str("#BADF"), Color::from_hex_str("BAD"));
376 assert_eq!(Color::from_hex_str("#BBAADDFF"), Color::from_hex_str("BAD"));
377 assert_eq!(Color::from_hex_str("BBAADDFF"), Color::from_hex_str("BAD"));
378 assert_eq!(Color::from_hex_str("bBAadDfF"), Color::from_hex_str("BAD"));
379 assert_eq!(Color::from_hex_str("#0f6"), Ok(Color::rgb8(0, 0xff, 0x66)));
380 assert_eq!(
381 Color::from_hex_str("#0f6a"),
382 Ok(Color::rgba8(0, 0xff, 0x66, 0xaa))
383 );
384 assert!(Color::from_hex_str("#0f6aa").is_err());
385 assert!(Color::from_hex_str("#0f").is_err());
386 assert!(Color::from_hex_str("x0f").is_err());
387 assert!(Color::from_hex_str("#0afa1").is_err());
388 }
389
390 #[test]
391 fn change_subcolor_values() {
392 let color = Color::from_rgba32_u32(0x11aa22bb);
393
394 assert_eq!(color.with_r8(0xff), Color::from_rgba32_u32(0xffaa22bb));
395 assert_eq!(color.with_g8(0xff), Color::from_rgba32_u32(0x11ff22bb));
396 assert_eq!(color.with_b8(0xff), Color::from_rgba32_u32(0x11aaffbb));
397 assert_eq!(color.with_a8(0xff), Color::from_rgba32_u32(0x11aa22ff));
398 }
399}