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#[must_use]
41#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
42#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43pub struct Color {
44    /// Red component
45    pub r: u8,
46    /// Green component
47    pub g: u8,
48    /// Blue component
49    pub b: u8,
50    /// Alpha component
51    pub a: u8,
52}
53
54impl Color {
55    /// White (FFFFFF)
56    pub const WHITE: Self = Self {
57        r: 0xFF,
58        g: 0xFF,
59        b: 0xFF,
60        a: 0xFF,
61    };
62
63    /// Black (000000)
64    pub const BLACK: Self = Self {
65        r: 0,
66        g: 0,
67        b: 0,
68        a: 0xFF,
69    };
70
71    /// Red (FF0000)
72    pub const RED: Self = Self {
73        r: 0xFF,
74        g: 0,
75        b: 0,
76        a: 0xFF,
77    };
78
79    /// Green (00FF00)
80    pub const GREEN: Self = Self {
81        r: 0,
82        g: 0xFF,
83        b: 0,
84        a: 0xFF,
85    };
86
87    /// Blue (0000FF)
88    pub const BLUE: Self = Self {
89        r: 0,
90        g: 0,
91        b: 0xFF,
92        a: 0xFF,
93    };
94
95    /// Cyan (00FFFF)
96    pub const CYAN: Self = Self {
97        r: 0,
98        g: 0xFF,
99        b: 0xFF,
100        a: 0xFF,
101    };
102
103    /// Magenta (FF00FF)
104    pub const MAGENTA: Self = Self {
105        r: 0xFF,
106        g: 0,
107        b: 0xFF,
108        a: 0xFF,
109    };
110
111    /// Yellow (FFFF00)
112    pub const YELLOW: Self = Self {
113        r: 0xFF,
114        g: 0xFF,
115        b: 0,
116        a: 0xFF,
117    };
118
119    /// Create a grayscale color from a single intensity value
120    ///
121    /// # Arguments
122    ///
123    /// * `shade` - Gray intensity
124    ///
125    /// # Example
126    ///
127    /// ```
128    /// use simple_color::Color;
129    ///
130    /// let gray = Color::gray(0x80);
131    /// let dark_gray = Color::gray(0x40);
132    /// let light_gray = Color::gray(0xC0);
133    /// let black = Color::gray(0);
134    /// let white = Color::gray(0xFF);
135    ///
136    /// assert_eq!(gray, Color::rgb(0x80, 0x80, 0x80));
137    /// assert_eq!(black, Color::BLACK);
138    /// assert_eq!(white, Color::WHITE);
139    /// ```
140    pub const fn gray(shade: u8) -> Self {
141        Self {
142            r: shade,
143            g: shade,
144            b: shade,
145            a: 0xFF,
146        }
147    }
148
149    /// Create an opaque RGB color
150    ///
151    /// # Arguments
152    ///
153    /// * `r` - Red component
154    /// * `g` - Green component
155    /// * `b` - Blue component
156    ///
157    /// # Example
158    ///
159    /// ```
160    /// use simple_color::Color;
161    ///
162    /// let cyan = Color::rgb(0, 0xFF, 0xFF);
163    /// assert_eq!(cyan, Color::CYAN);
164    /// ```
165    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
166        Self { r, g, b, a: 0xFF }
167    }
168
169    /// Create a translucent RGBA color
170    ///
171    /// # Arguments
172    ///
173    /// * `r` - Red component
174    /// * `g` - Green component
175    /// * `b` - Blue component
176    /// * `a` - Alpha component
177    ///
178    /// # Example
179    ///
180    /// ```
181    /// use simple_color::Color;
182    ///
183    /// let semi_transparent_red = Color::rgba(0xFF, 0x00, 0x00, 0x80);
184    /// assert_eq!(semi_transparent_red, Color::RED.a(0x80));
185    /// ```
186    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
187        Self { r, g, b, a }
188    }
189
190    /// Create a copy with modified red component
191    ///
192    /// # Arguments
193    ///
194    /// * `r` - New red value
195    ///
196    /// # Example
197    /// ```
198    /// use simple_color::Color;
199    ///
200    /// let dark_red = Color::BLACK.r(0x80);
201    /// assert_eq!(dark_red, Color::rgb(0x80, 0, 0));
202    /// ```
203    pub const fn r(self, r: u8) -> Self {
204        Self { r, ..self }
205    }
206
207    /// Create a copy with modified green component
208    ///
209    /// # Arguments
210    ///
211    /// * `g` - New green value
212    ///
213    /// # Example
214    ///
215    /// ```
216    /// use simple_color::Color;
217    ///
218    /// let yellow_green = Color::RED.g(0x80);
219    /// assert_eq!(yellow_green, Color::rgb(0xFF, 0x80, 0));
220    /// ```
221    pub const fn g(self, g: u8) -> Self {
222        Self { g, ..self }
223    }
224
225    /// Create a copy with modified blue component
226    ///
227    /// # Arguments
228    ///
229    /// * `b` - New blue value
230    ///
231    /// # Example
232    ///
233    /// ```
234    /// use simple_color::Color;
235    ///
236    /// let light_yellow = Color::WHITE.b(0x80);
237    /// assert_eq!(light_yellow, Color::rgb(0xFF, 0xFF, 0x80));
238    /// ```
239    pub const fn b(self, b: u8) -> Self {
240        Self { b, ..self }
241    }
242
243    /// Create a copy with modified alpha component
244    ///
245    /// # Arguments
246    ///
247    /// * `a` - New alpha value
248    ///
249    /// # Example
250    ///
251    /// ```
252    /// use simple_color::Color;
253    ///
254    /// let semi_transparent_blue = Color::BLUE.a(0x80);
255    /// assert_eq!(semi_transparent_blue, Color::rgba(0x00, 0x00, 0xFF, 0x80));
256    /// ```
257    pub const fn a(self, a: u8) -> Self {
258        Self { a, ..self }
259    }
260}
261
262const fn hex_from_byte(byte: u8) -> Option<u8> {
263    Some(match byte {
264        b'0'..=b'9' => byte - b'0',
265        b'a'..=b'f' => byte - b'a' + 10,
266        b'A'..=b'F' => byte - b'A' + 10,
267        _ => return None,
268    })
269}
270
271const fn component_from_byte(byte: u8) -> Result<u8, ColorParsingError> {
272    component_from_bytes(byte, byte)
273}
274
275const fn component_from_bytes(high: u8, low: u8) -> Result<u8, ColorParsingError> {
276    match [hex_from_byte(high), hex_from_byte(low)] {
277        [Some(h), Some(l)] => Ok(h * 0x10 + l),
278        [None, _] => Err(ColorParsingError::InvalidChar(high as char)),
279        [_, _] => Err(ColorParsingError::InvalidChar(low as char)),
280    }
281}
282
283impl FromStr for Color {
284    type Err = ColorParsingError;
285
286    /// Parse color from hexadecimal string.
287    ///
288    /// Supported formats:
289    /// - Grayscale - 1 or 2 characters representing brightness
290    /// - `RGB` - "RGB" or "RRGGBB"
291    /// - `RGBA` - "RGBA" or "RRGGBBAA"
292    ///
293    /// # Example
294    ///
295    /// ```
296    /// use simple_color::{Color, ColorParsingError};
297    ///
298    /// let red: Color = "FF0000".parse().unwrap();
299    /// assert_eq!(red, Color::RED);
300    ///
301    /// let semi_transparent_orange: Color = "ff800080".parse().unwrap();
302    /// assert_eq!(semi_transparent_orange, Color::rgba(0xFF, 0x80, 0, 0x80));
303    ///
304    /// let gray: Color = "80".parse().unwrap();
305    /// assert_eq!(gray, Color::gray(0x80));
306    ///
307    /// let invalid_char: Result<Color, _> = "80FG00".parse();
308    /// assert_eq!(invalid_char, Err(ColorParsingError::InvalidChar('G')));
309    ///
310    /// let invalid_length: Result<Color, _> = "FF80000".parse();
311    /// assert_eq!(invalid_length, Err(ColorParsingError::InvalidLength(7)));
312    /// ```
313    fn from_str(name: &str) -> Result<Self, Self::Err> {
314        let bytes = name.as_bytes();
315        Ok(match *bytes {
316            [g] => Self::gray(component_from_byte(g)?),
317            [gh, gl] => Self::gray(component_from_bytes(gh, gl)?),
318            [r, g, b] => Self::rgb(
319                component_from_byte(r)?,
320                component_from_byte(g)?,
321                component_from_byte(b)?,
322            ),
323            [r, g, b, a] => Self::rgba(
324                component_from_byte(r)?,
325                component_from_byte(g)?,
326                component_from_byte(b)?,
327                component_from_byte(a)?,
328            ),
329            [rh, rl, gh, gl, bh, bl] => Self::rgb(
330                component_from_bytes(rh, rl)?,
331                component_from_bytes(gh, gl)?,
332                component_from_bytes(bh, bl)?,
333            ),
334            [rh, rl, gh, gl, bh, bl, ah, al] => Self::rgba(
335                component_from_bytes(rh, rl)?,
336                component_from_bytes(gh, gl)?,
337                component_from_bytes(bh, bl)?,
338                component_from_bytes(ah, al)?,
339            ),
340            _ => return Err(ColorParsingError::InvalidLength(bytes.len())),
341        })
342    }
343}
344
345#[cfg(feature = "data-stream")]
346mod data_stream {
347    use crate::Color;
348
349    use data_stream::{FromStream, ToStream, from_stream, numbers::EndianSettings, to_stream};
350
351    use std::io::{Read, Result, Write};
352
353    impl<S: EndianSettings> ToStream<S> for Color {
354        fn to_stream<W: Write>(&self, writer: &mut W) -> Result<()> {
355            to_stream::<S, _, _>(&self.r, writer)?;
356            to_stream::<S, _, _>(&self.g, writer)?;
357            to_stream::<S, _, _>(&self.b, writer)?;
358            to_stream::<S, _, _>(&self.a, writer)?;
359
360            Ok(())
361        }
362    }
363
364    impl<S: EndianSettings> FromStream<S> for Color {
365        fn from_stream<R: Read>(reader: &mut R) -> Result<Self> {
366            Ok(Self {
367                r: from_stream::<S, _, _>(reader)?,
368                g: from_stream::<S, _, _>(reader)?,
369                b: from_stream::<S, _, _>(reader)?,
370                a: from_stream::<S, _, _>(reader)?,
371            })
372        }
373    }
374}
375
376#[cfg(feature = "parser")]
377mod parser {
378    use crate::Color;
379    use token_parser::{Context, ErrorKind, Parsable, Parser, Result, Span};
380    impl<C: Context> Parsable<C> for Color {
381        fn parse_symbol(name: Box<str>, _span: Span, _context: &C) -> Result<Self> {
382            name.parse()
383                .map_err(|_| ErrorKind::StringParsing("Color").into())
384        }
385
386        fn parse_list(parser: &mut Parser, context: &C) -> Result<Self> {
387            let args: Vec<u8> = parser.parse_rest(context)?;
388            Ok(match args.len() {
389                0 => return Err(ErrorKind::NotEnoughElements(1).into()),
390                1 => Self::gray(args[0]),
391                2 => return Err(ErrorKind::NotEnoughElements(2).into()),
392                3 => Self::rgb(args[0], args[1], args[2]),
393                4 => Self::rgba(args[0], args[1], args[2], args[3]),
394                n => return Err(ErrorKind::TooManyElements(n - 4).into()),
395            })
396        }
397    }
398}