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}