Skip to main content

simple_color/
lib.rs

1#![deny(missing_docs)]
2
3//! A minimal color handling library.
4//!
5//! Parsing and serialization are optionally supported.
6//! All color constructors support `const` evaluation.
7//!
8//! # Basic Usage
9//!
10//! ```rust
11//! use simple_color::Color;
12//!
13//! // Predefined colors
14//! let white = Color::WHITE;
15//! let red = Color::RED;
16//!
17//! // Custom colors
18//! let yellow = Color::rgb(0xFF, 0xFF, 0);
19//! let transparent_blue = Color::rgba(0, 0, 0xFF, 0x80);
20//! ```
21
22#[cfg(feature = "serde")]
23use serde::{Deserialize, Serialize};
24use std::str::FromStr;
25use thiserror::Error;
26
27/// Error type for color parsing failures
28#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)]
29pub enum ColorParsingError {
30    /// Contains the invalid character found during parsing
31    #[error("Invalid hex character '{0}'")]
32    InvalidChar(char),
33
34    /// Contains the unexpected input length
35    #[error("Invalid length {0} - expected 1, 2, 3, 4, 6 or 8")]
36    InvalidLength(usize),
37}
38
39/// RGBA color representation with 8-bit components
40#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
42pub struct Color {
43    /// Red component
44    pub r: u8,
45    /// Green component
46    pub g: u8,
47    /// Blue component
48    pub b: u8,
49    /// Alpha component
50    pub a: u8,
51}
52
53impl Color {
54    /// White (FFFFFF)
55    pub const WHITE: Self = Self {
56        r: 0xFF,
57        g: 0xFF,
58        b: 0xFF,
59        a: 0xFF,
60    };
61
62    /// Black (000000)
63    pub const BLACK: Self = Self {
64        r: 0,
65        g: 0,
66        b: 0,
67        a: 0xFF,
68    };
69
70    /// Red (FF0000)
71    pub const RED: Self = Self {
72        r: 0xFF,
73        g: 0,
74        b: 0,
75        a: 0xFF,
76    };
77
78    /// Green (00FF00)
79    pub const GREEN: Self = Self {
80        r: 0,
81        g: 0xFF,
82        b: 0,
83        a: 0xFF,
84    };
85
86    /// Blue (0000FF)
87    pub const BLUE: Self = Self {
88        r: 0,
89        g: 0,
90        b: 0xFF,
91        a: 0xFF,
92    };
93
94    /// Cyan (00FFFF)
95    pub const CYAN: Self = Self {
96        r: 0,
97        g: 0xFF,
98        b: 0xFF,
99        a: 0xFF,
100    };
101
102    /// Magenta (FF00FF)
103    pub const MAGENTA: Self = Self {
104        r: 0xFF,
105        g: 0,
106        b: 0xFF,
107        a: 0xFF,
108    };
109
110    /// Yellow (FFFF00)
111    pub const YELLOW: Self = Self {
112        r: 0xFF,
113        g: 0xFF,
114        b: 0,
115        a: 0xFF,
116    };
117
118    /// Create a grayscale color from a single intensity value
119    ///
120    /// # Arguments
121    ///
122    /// * `shade` - Gray intensity
123    ///
124    /// # Example
125    ///
126    /// ```
127    /// use simple_color::Color;
128    ///
129    /// let gray = Color::gray(0x80);
130    /// let dark_gray = Color::gray(0x40);
131    /// let light_gray = Color::gray(0xC0);
132    /// let black = Color::gray(0);
133    /// let white = Color::gray(0xFF);
134    ///
135    /// assert_eq!(gray, Color::rgb(0x80, 0x80, 0x80));
136    /// assert_eq!(black, Color::BLACK);
137    /// assert_eq!(white, Color::WHITE);
138    /// ```
139    pub const fn gray(shade: u8) -> Self {
140        Self {
141            r: shade,
142            g: shade,
143            b: shade,
144            a: 0xFF,
145        }
146    }
147
148    /// Create an opaque RGB color
149    ///
150    /// # Arguments
151    ///
152    /// * `r` - Red component
153    /// * `g` - Green component
154    /// * `b` - Blue component
155    ///
156    /// # Example
157    ///
158    /// ```
159    /// use simple_color::Color;
160    ///
161    /// let cyan = Color::rgb(0, 0xFF, 0xFF);
162    /// assert_eq!(cyan, Color::CYAN);
163    /// ```
164    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
165        Self { r, g, b, a: 0xFF }
166    }
167
168    /// Create a translucent RGBA color
169    ///
170    /// # Arguments
171    ///
172    /// * `r` - Red component
173    /// * `g` - Green component
174    /// * `b` - Blue component
175    /// * `a` - Alpha component
176    ///
177    /// # Example
178    ///
179    /// ```
180    /// use simple_color::Color;
181    ///
182    /// let semi_transparent_red = Color::rgba(0xFF, 0x00, 0x00, 0x80);
183    /// assert_eq!(semi_transparent_red, Color::RED.a(0x80));
184    /// ```
185    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
186        Self { r, g, b, a }
187    }
188
189    /// Create a copy with modified red component
190    ///
191    /// # Arguments
192    ///
193    /// * `r` - New red value
194    ///
195    /// # Example
196    /// ```
197    /// use simple_color::Color;
198    ///
199    /// let dark_red = Color::BLACK.r(0x80);
200    /// assert_eq!(dark_red, Color::rgb(0x80, 0, 0));
201    /// ```
202    pub const fn r(self, r: u8) -> Self {
203        Self { r, ..self }
204    }
205
206    /// Create a copy with modified green component
207    ///
208    /// # Arguments
209    ///
210    /// * `g` - New green value
211    ///
212    /// # Example
213    ///
214    /// ```
215    /// use simple_color::Color;
216    ///
217    /// let yellow_green = Color::RED.g(0x80);
218    /// assert_eq!(yellow_green, Color::rgb(0xFF, 0x80, 0));
219    /// ```
220    pub const fn g(self, g: u8) -> Self {
221        Self { g, ..self }
222    }
223
224    /// Create a copy with modified blue component
225    ///
226    /// # Arguments
227    ///
228    /// * `b` - New blue value
229    ///
230    /// # Example
231    ///
232    /// ```
233    /// use simple_color::Color;
234    ///
235    /// let light_yellow = Color::WHITE.b(0x80);
236    /// assert_eq!(light_yellow, Color::rgb(0xFF, 0xFF, 0x80));
237    /// ```
238    pub const fn b(self, b: u8) -> Self {
239        Self { b, ..self }
240    }
241
242    /// Create a copy with modified alpha component
243    ///
244    /// # Arguments
245    ///
246    /// * `a` - New alpha value
247    ///
248    /// # Example
249    ///
250    /// ```
251    /// use simple_color::Color;
252    ///
253    /// let semi_transparent_blue = Color::BLUE.a(0x80);
254    /// assert_eq!(semi_transparent_blue, Color::rgba(0x00, 0x00, 0xFF, 0x80));
255    /// ```
256    pub const fn a(self, a: u8) -> Self {
257        Self { a, ..self }
258    }
259}
260
261const fn hex_from_char(c: char) -> Option<u8> {
262    Some(match c {
263        '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => c as u8 - b'0',
264        'a' | 'b' | 'c' | 'd' | 'e' | 'f' => c as u8 - b'a' + 10,
265        'A' | 'B' | 'C' | 'D' | 'E' | 'F' => c as u8 - b'A' + 10,
266        _ => return None,
267    })
268}
269
270#[inline]
271const fn component_from_char(c: char) -> Result<u8, ColorParsingError> {
272    component_from_chars(c, c)
273}
274
275const fn component_from_chars(cc: char, c: char) -> Result<u8, ColorParsingError> {
276    match [hex_from_char(cc), hex_from_char(c)] {
277        [Some(nn), Some(n)] => Ok(nn * 0x10 + n),
278        [f, _] => Err(ColorParsingError::InvalidChar(if f.is_none() {
279            cc
280        } else {
281            c
282        })),
283    }
284}
285
286impl FromStr for Color {
287    type Err = ColorParsingError;
288
289    /// Parse color from hexadecimal string.
290    ///
291    /// Supported formats:
292    /// - Grayscale - 1 or 2 characters representing brightness
293    /// - `RGB` - "RGB" or "RRGGBB"
294    /// - `RGBA` - "RGBA" or "RRGGBBAA"
295    ///
296    /// # Example
297    ///
298    /// ```
299    /// use simple_color::{Color, ColorParsingError};
300    ///
301    /// let red: Color = "FF0000".parse().unwrap();
302    /// assert_eq!(red, Color::RED);
303    ///
304    /// let semi_transparent_orange: Color = "ff800080".parse().unwrap();
305    /// assert_eq!(semi_transparent_orange, Color::rgba(0xFF, 0x80, 0, 0x80));
306    ///
307    /// let gray: Color = "80".parse().unwrap();
308    /// assert_eq!(gray, Color::gray(0x80));
309    ///
310    /// let invalid_char: Result<Color, _> = "80FG00".parse();
311    /// assert_eq!(invalid_char, Err(ColorParsingError::InvalidChar('G')));
312    ///
313    /// let invalid_length: Result<Color, _> = "FF80000".parse();
314    /// assert_eq!(invalid_length, Err(ColorParsingError::InvalidLength(7)));
315    /// ```
316    fn from_str(name: &str) -> Result<Self, Self::Err> {
317        let len = name.len();
318        let mut chars = name.chars();
319        Ok(unsafe {
320            match len {
321                1 => Self::gray(component_from_char(chars.next().unwrap_unchecked())?),
322                2 => Self::gray(component_from_chars(
323                    chars.next().unwrap_unchecked(),
324                    chars.next().unwrap_unchecked(),
325                )?),
326                3 => Self::rgb(
327                    component_from_char(chars.next().unwrap_unchecked())?,
328                    component_from_char(chars.next().unwrap_unchecked())?,
329                    component_from_char(chars.next().unwrap_unchecked())?,
330                ),
331                4 => Self::rgba(
332                    component_from_char(chars.next().unwrap_unchecked())?,
333                    component_from_char(chars.next().unwrap_unchecked())?,
334                    component_from_char(chars.next().unwrap_unchecked())?,
335                    component_from_char(chars.next().unwrap_unchecked())?,
336                ),
337                6 => Self::rgb(
338                    component_from_chars(
339                        chars.next().unwrap_unchecked(),
340                        chars.next().unwrap_unchecked(),
341                    )?,
342                    component_from_chars(
343                        chars.next().unwrap_unchecked(),
344                        chars.next().unwrap_unchecked(),
345                    )?,
346                    component_from_chars(
347                        chars.next().unwrap_unchecked(),
348                        chars.next().unwrap_unchecked(),
349                    )?,
350                ),
351                8 => Self::rgba(
352                    component_from_chars(
353                        chars.next().unwrap_unchecked(),
354                        chars.next().unwrap_unchecked(),
355                    )?,
356                    component_from_chars(
357                        chars.next().unwrap_unchecked(),
358                        chars.next().unwrap_unchecked(),
359                    )?,
360                    component_from_chars(
361                        chars.next().unwrap_unchecked(),
362                        chars.next().unwrap_unchecked(),
363                    )?,
364                    component_from_chars(
365                        chars.next().unwrap_unchecked(),
366                        chars.next().unwrap_unchecked(),
367                    )?,
368                ),
369                _ => return Err(ColorParsingError::InvalidLength(len)),
370            }
371        })
372    }
373}
374
375#[cfg(feature = "data-stream")]
376mod data_stream {
377    use crate::Color;
378
379    use data_stream::{FromStream, ToStream, from_stream, numbers::EndianSettings, to_stream};
380
381    use std::io::{Read, Result, Write};
382
383    impl<S: EndianSettings> ToStream<S> for Color {
384        fn to_stream<W: Write>(&self, writer: &mut W) -> Result<()> {
385            to_stream::<S, _, _>(&self.r, writer)?;
386            to_stream::<S, _, _>(&self.g, writer)?;
387            to_stream::<S, _, _>(&self.b, writer)?;
388            to_stream::<S, _, _>(&self.a, writer)?;
389
390            Ok(())
391        }
392    }
393
394    impl<S: EndianSettings> FromStream<S> for Color {
395        fn from_stream<R: Read>(reader: &mut R) -> Result<Self> {
396            Ok(Self {
397                r: from_stream::<S, _, _>(reader)?,
398                g: from_stream::<S, _, _>(reader)?,
399                b: from_stream::<S, _, _>(reader)?,
400                a: from_stream::<S, _, _>(reader)?,
401            })
402        }
403    }
404}
405
406#[cfg(feature = "parser")]
407mod parser {
408    use crate::Color;
409    use token_parser::{Context, ErrorKind, Parsable, Parser, Result, Span};
410    impl<C: Context> Parsable<C> for Color {
411        fn parse_symbol(name: Box<str>, _span: Span, _context: &C) -> Result<Self> {
412            if let Ok(color) = name.parse() {
413                Ok(color)
414            } else {
415                Err(ErrorKind::StringParsing.into())
416            }
417        }
418
419        fn parse_list(parser: &mut Parser, context: &C) -> Result<Self> {
420            let args: Vec<u8> = parser.parse_rest(context)?;
421            Ok(match args.len() {
422                0 => return Err(ErrorKind::NotEnoughElements(1).into()),
423                1 => Self::gray(args[0]),
424                2 => return Err(ErrorKind::NotEnoughElements(2).into()),
425                3 => Self::rgb(args[0], args[1], args[2]),
426                4 => Self::rgba(args[0], args[1], args[2], args[3]),
427                n => return Err(ErrorKind::TooManyElements(n - 4).into()),
428            })
429        }
430    }
431}