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}