bracket_color/
rgba.rs

1use crate::prelude::{HtmlColorConversionError, HSV, RGB};
2use std::convert::From;
3use std::ops;
4
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6#[derive(PartialEq, Copy, Clone, Default, Debug)]
7/// Represents an R/G/B triplet, in the range 0..1 (32-bit float)
8pub struct RGBA {
9    /// The red component (0..1)
10    pub r: f32,
11    /// The green component (0..1)
12    pub g: f32,
13    /// The blue component (0..1)
14    pub b: f32,
15    /// The alpha component (0..1), 0 is transparent, 1 is solid
16    pub a: f32,
17}
18
19// Implement operator overloading
20
21/// Support adding a float to a color. The result is clamped via the constructor.
22impl ops::Add<f32> for RGBA {
23    type Output = Self;
24    #[must_use]
25    fn add(mut self, rhs: f32) -> Self {
26        self.r += rhs;
27        self.g += rhs;
28        self.b += rhs;
29        self.a += rhs;
30        self
31    }
32}
33
34/// Support adding an RGB to a color. The result is clamped via the constructor.
35impl ops::Add<RGBA> for RGBA {
36    type Output = Self;
37    #[must_use]
38    fn add(mut self, rhs: Self) -> Self {
39        self.r += rhs.r;
40        self.g += rhs.g;
41        self.b += rhs.b;
42        self.a += rhs.a;
43        self
44    }
45}
46
47/// Support subtracting a float from a color. The result is clamped via the constructor.
48impl ops::Sub<f32> for RGBA {
49    type Output = Self;
50    #[must_use]
51    fn sub(mut self, rhs: f32) -> Self {
52        self.r -= rhs;
53        self.g -= rhs;
54        self.b -= rhs;
55        self.a -= rhs;
56        self
57    }
58}
59
60/// Support subtracting an RGB from a color. The result is clamped via the constructor.
61impl ops::Sub<RGBA> for RGBA {
62    type Output = Self;
63    #[must_use]
64    fn sub(mut self, rhs: Self) -> Self {
65        self.r -= rhs.r;
66        self.g -= rhs.g;
67        self.b -= rhs.b;
68        self.a -= rhs.a;
69        self
70    }
71}
72
73/// Support multiplying a color by a float. The result is clamped via the constructor.
74impl ops::Mul<f32> for RGBA {
75    type Output = Self;
76    #[must_use]
77    fn mul(mut self, rhs: f32) -> Self {
78        self.r *= rhs;
79        self.g *= rhs;
80        self.b *= rhs;
81        self.a *= rhs;
82        self
83    }
84}
85
86/// Support multiplying a color by another color. The result is clamped via the constructor.
87impl ops::Mul<RGBA> for RGBA {
88    type Output = Self;
89    #[must_use]
90    fn mul(mut self, rhs: Self) -> Self {
91        self.r *= rhs.r;
92        self.g *= rhs.g;
93        self.b *= rhs.b;
94        self.a *= rhs.a;
95        self
96    }
97}
98
99impl RGBA {
100    /// Constructs a new, zeroed (black) RGB triplet.
101    #[must_use]
102    pub fn new() -> Self {
103        Self {
104            r: 0.0,
105            g: 0.0,
106            b: 0.0,
107            a: 0.0,
108        }
109    }
110
111    /// Constructs a new RGB color, from 3 32-bit floats in the range 0..1
112    /// 
113    /// # Arguments
114    /// 
115    /// * `r` - the red component (0..1)
116    /// * `g` - the green component (0..1)
117    /// * `b` - the blue component (0..1)
118    /// * `a` - the alpha component (0..1). 0 is transparent, 1 is solid.
119    /// 
120    /// # Example
121    /// 
122    /// ```rust
123    /// use bracket_color::prelude::*;
124    /// let red = RGBA::from_f32(1.0, 0.0, 0.0, 1.0);
125    /// let green = RGBA::from_f32(0.0, 1.0, 0.0, 1.0);
126    /// let invisible = RGBA::from_f32(0.0, 0.0, 0.0, 0.0);
127    /// ```
128    #[inline]
129    #[must_use]
130    pub fn from_f32(r: f32, g: f32, b: f32, a: f32) -> Self {
131        let r_clamped = f32::min(1.0, f32::max(0.0, r));
132        let g_clamped = f32::min(1.0, f32::max(0.0, g));
133        let b_clamped = f32::min(1.0, f32::max(0.0, b));
134        let a_clamped = f32::min(1.0, f32::max(0.0, a));
135        Self {
136            r: r_clamped,
137            g: g_clamped,
138            b: b_clamped,
139            a: a_clamped,
140        }
141    }
142
143    /// Constructs a new RGB color, from 3 bytes in the range 0..255
144    /// 
145    /// # Arguments
146    /// 
147    /// * `r` - the red component, ranged from 0 to 255
148    /// * `g` - the green component, ranged from 0 to 255
149    /// * `b` - the blue component, ranged from 0 to 255
150    /// 
151    /// # Example
152    /// 
153    /// ```rust
154    /// use bracket_color::prelude::*;
155    /// let red = RGBA::from_u8(255, 0, 0, 255);
156    /// let green = RGBA::from_u8(0, 255, 0, 255);
157    /// let invisible = RGBA::from_u8(0, 0, 0, 0);
158    /// ```
159    #[inline]
160    #[must_use]
161    pub fn from_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
162        Self {
163            r: f32::from(r) / 255.0,
164            g: f32::from(g) / 255.0,
165            b: f32::from(b) / 255.0,
166            a: f32::from(a) / 255.0,
167        }
168    }
169
170    /// Construct an RGB color from a tuple of u8, or a named constant
171    /// 
172    /// # Arguments
173    /// 
174    /// * `col` a tuple of three `u8` values. See `from_u8`. These are usually provided from the `named` colors list.
175    /// 
176    /// # Example
177    /// 
178    /// ```rust
179    /// use bracket_color::prelude::*;
180    /// let red = RGBA::named(RED);
181    /// let green = RGBA::named((0, 255, 0));
182    /// ```
183    #[inline]
184    #[must_use]
185    pub fn named(col: (u8, u8, u8)) -> Self {
186        Self::from_u8(col.0, col.1, col.2, 255)
187    }
188
189    /// Constructs from an HTML color code (e.g. "#eeffeeff")
190    ///
191    /// # Arguments
192    /// 
193    /// * `code` - an HTML color notation (e.g. "#ffeeffff")
194    /// 
195    /// # Example
196    /// 
197    /// ```rust
198    /// use bracket_color::prelude::*;
199    /// let red = RGBA::from_hex("#FF0000FF");
200    /// let green = RGBA::from_hex("#00FF00FF");
201    /// ```
202    ///
203    /// # Errors
204    /// 
205    /// See `HtmlColorConversionError`
206    #[allow(clippy::cast_precision_loss)]
207    pub fn from_hex<S: AsRef<str>>(code: S) -> Result<Self, HtmlColorConversionError> {
208        let mut full_code = code.as_ref().chars();
209
210        if let Some(hash) = full_code.next() {
211            if hash != '#' {
212                return Err(HtmlColorConversionError::MissingHash);
213            }
214        } else {
215            return Err(HtmlColorConversionError::InvalidStringLength);
216        }
217
218        let red1 = match full_code.next() {
219            Some(red) => match red.to_digit(16) {
220                Some(red) => red * 16,
221                None => return Err(HtmlColorConversionError::InvalidCharacter),
222            },
223            None => return Err(HtmlColorConversionError::InvalidStringLength),
224        };
225        let red2 = match full_code.next() {
226            Some(red) => match red.to_digit(16) {
227                Some(red) => red,
228                None => return Err(HtmlColorConversionError::InvalidCharacter),
229            },
230            None => return Err(HtmlColorConversionError::InvalidStringLength),
231        };
232
233        let green1 = match full_code.next() {
234            Some(green) => match green.to_digit(16) {
235                Some(green) => green * 16,
236                None => return Err(HtmlColorConversionError::InvalidCharacter),
237            },
238            None => return Err(HtmlColorConversionError::InvalidStringLength),
239        };
240        let green2 = match full_code.next() {
241            Some(green) => match green.to_digit(16) {
242                Some(green) => green,
243                None => return Err(HtmlColorConversionError::InvalidCharacter),
244            },
245            None => return Err(HtmlColorConversionError::InvalidStringLength),
246        };
247
248        let blue1 = match full_code.next() {
249            Some(blue) => match blue.to_digit(16) {
250                Some(blue) => blue * 16,
251                None => return Err(HtmlColorConversionError::InvalidCharacter),
252            },
253            None => return Err(HtmlColorConversionError::InvalidStringLength),
254        };
255        let blue2 = match full_code.next() {
256            Some(blue) => match blue.to_digit(16) {
257                Some(blue) => blue,
258                None => return Err(HtmlColorConversionError::InvalidCharacter),
259            },
260            None => return Err(HtmlColorConversionError::InvalidStringLength),
261        };
262
263        let alpha1 = match full_code.next() {
264            Some(alpha) => match alpha.to_digit(16) {
265                Some(alpha) => alpha * 16,
266                None => return Err(HtmlColorConversionError::InvalidCharacter),
267            },
268            None => return Err(HtmlColorConversionError::InvalidStringLength),
269        };
270        let alpha2 = match full_code.next() {
271            Some(alpha) => match alpha.to_digit(16) {
272                Some(alpha) => alpha,
273                None => return Err(HtmlColorConversionError::InvalidCharacter),
274            },
275            None => return Err(HtmlColorConversionError::InvalidStringLength),
276        };
277
278        if full_code.next().is_some() {
279            return Err(HtmlColorConversionError::InvalidStringLength);
280        }
281
282        Ok(Self {
283            r: (red1 + red2) as f32 / 255.0,
284            g: (green1 + green2) as f32 / 255.0,
285            b: (blue1 + blue2) as f32 / 255.0,
286            a: (alpha1 + alpha2) as f32 / 255.0,
287        })
288    }
289
290    /// Converts to an RGB, dropping the alpha component
291    #[inline]
292    #[must_use]
293    pub fn to_rgb(&self) -> RGB {
294        RGB::from_f32(self.r, self.g, self.b)
295    }
296
297    /// Converts an RGBA to an array of `f32`, useful in Bevy.
298    #[cfg(feature = "bevy")]
299    #[must_use]
300    pub fn as_rgba_f32(&self) -> [f32; 4] {
301        [self.r, self.g, self.b, self.a]
302    }
303
304    /// Applies a quick grayscale conversion to the color
305    #[inline]
306    #[must_use]
307    pub fn to_greyscale(&self) -> Self {
308        let linear = (self.r * 0.2126) + (self.g * 0.7152) + (self.b * 0.0722);
309        Self::from_f32(linear, linear, linear, self.a)
310    }
311
312    /// Applies a lengthier desaturate (via HSV) to the color
313    #[inline]
314    #[must_use]
315    pub fn desaturate(&self) -> Self {
316        let mut hsv = self.to_rgb().to_hsv();
317        hsv.s = 0.0;
318        hsv.to_rgb().to_rgba(self.a)
319    }
320
321    /// Lerps by a specified percentage (from 0 to 1) between this color and another
322    #[inline]
323    #[must_use]
324    pub fn lerp(&self, color: Self, percent: f32) -> Self {
325        let range = (
326            color.r - self.r,
327            color.g - self.g,
328            color.b - self.b,
329            color.a - self.a,
330        );
331        Self {
332            r: self.r + range.0 * percent,
333            g: self.g + range.1 * percent,
334            b: self.b + range.2 * percent,
335            a: self.a + range.3 * percent,
336        }
337    }
338
339    /// Lerps only the alpha channel, by a specified percentage (from 0 to 1) between this color and another
340    #[inline]
341    #[must_use]
342    pub fn lerp_alpha(&self, color: Self, percent: f32) -> Self {
343        let range = color.a - self.a;
344        Self {
345            r: self.r,
346            g: self.g,
347            b: self.b,
348            a: self.a + range * percent,
349        }
350    }
351}
352
353/// Support conversion from RGB
354impl From<RGB> for RGBA {
355    fn from(item: RGB) -> Self {
356        Self::from_f32(item.r, item.g, item.b, 1.0)
357    }
358}
359
360/// Support conversion from HSV
361impl From<HSV> for RGBA {
362    fn from(item: HSV) -> Self {
363        item.to_rgba(1.0)
364    }
365}
366
367/// Support conversion from a color tuple
368impl From<(u8, u8, u8, u8)> for RGBA {
369    fn from(vals: (u8, u8, u8, u8)) -> Self {
370        Self::from_u8(vals.0, vals.1, vals.2, vals.3)
371    }
372}
373
374/// Support conversion from a color tuple
375impl From<(u8, u8, u8)> for RGBA {
376    fn from(vals: (u8, u8, u8)) -> Self {
377        Self::from_u8(vals.0, vals.1, vals.2, 255)
378    }
379}
380
381impl From<[f32; 4]> for RGBA {
382    fn from(item: [f32; 4]) -> Self {
383        Self::from_f32(item[0], item[1], item[2], item[3])
384    }
385}
386
387// Support conversion from Bevy
388#[cfg(feature = "bevy")]
389impl From<bevy::prelude::Color> for RGBA {
390    fn from(item: bevy::prelude::Color) -> Self {
391        Self::from_f32(item.r(), item.g(), item.b(), item.a())
392    }
393}
394
395#[cfg(feature = "bevy")]
396impl From<RGBA> for bevy::prelude::Color {
397    fn from(item: RGBA) -> Self {
398        Self::from([item.r, item.g, item.b, item.a])
399    }
400}
401
402#[cfg(feature = "crossterm")]
403mod crossterm_features {
404    use super::RGBA;
405    use crossterm::style::Color;
406    use std::convert::TryFrom;
407
408    impl TryFrom<RGBA> for Color {
409        type Error = &'static str;
410
411        fn try_from(rgb: RGBA) -> Result<Self, Self::Error> {
412            let (r, g, b) = (rgb.r, rgb.g, rgb.b);
413            for c in [r, g, b].iter() {
414                if *c < 0.0 {
415                    return Err("Value < 0.0 found!");
416                }
417                if *c > 1.0 {
418                    return Err("Value > 1.0 found!");
419                }
420            }
421            let (r, g, b) = ((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8);
422            let rgb = Color::Rgb { r, g, b };
423            Ok(rgb)
424        }
425    }
426
427    #[cfg(test)]
428    mod tests {
429        use crate::prelude::RGBA;
430        use crossterm::style::Color;
431        use std::convert::TryInto;
432
433        #[test]
434        fn basic_conversion() {
435            let rgb = RGBA {
436                r: 0.0,
437                g: 0.5,
438                b: 1.0,
439                a: 1.0,
440            };
441            let rgb: Color = rgb.try_into().unwrap();
442            match rgb {
443                Color::Rgb { r, g, b } => {
444                    assert_eq!(r, 0);
445                    assert_eq!(g, 127);
446                    assert_eq!(b, 255);
447                }
448                _ => unreachable!(),
449            }
450        }
451
452        #[test]
453        fn negative_rgb() {
454            let rgb = RGBA {
455                r: 0.0,
456                g: 0.5,
457                b: -1.0,
458                a: 1.0,
459            };
460            let rgb: Result<Color, _> = rgb.try_into();
461            assert!(rgb.is_err());
462        }
463
464        #[test]
465        fn too_large_rgb() {
466            let rgb = RGBA {
467                r: 0.0,
468                g: 0.5,
469                b: 1.1,
470                a: 1.0,
471            };
472            let rgb: Result<Color, _> = rgb.try_into();
473            assert!(rgb.is_err());
474        }
475    }
476}
477
478// Unit tests for the color system
479
480#[cfg(test)]
481mod tests {
482    use crate::prelude::*;
483
484    #[test]
485    // Tests that we make an RGB triplet at defaults and it is black.
486    fn make_rgba_minimal() {
487        let black = RGBA::new();
488        assert!(black.r < std::f32::EPSILON);
489        assert!(black.g < std::f32::EPSILON);
490        assert!(black.b < std::f32::EPSILON);
491        assert!(black.a < std::f32::EPSILON);
492    }
493
494    #[test]
495    // Tests that we make an HSV triplet at defaults and it is black.
496    fn convert_olive_to_rgb() {
497        let grey = HSV::from_f32(60.0 / 360.0, 1.0, 0.501_960_8);
498        let rgb = grey.to_rgba(1.0);
499        assert!(f32::abs(rgb.r - 128.0 / 255.0) < std::f32::EPSILON);
500        assert!(f32::abs(rgb.g - 128.0 / 255.0) < std::f32::EPSILON);
501        assert!(rgb.b < std::f32::EPSILON);
502        assert!((rgb.a - 1.0).abs() < std::f32::EPSILON);
503    }
504
505    #[test]
506    // Tests that we make an HSV triplet at defaults and it is black.
507    fn test_red_hex() {
508        let rgb = RGBA::from_hex("#FF0000FF").expect("Invalid hex string");
509        assert!(f32::abs(rgb.r - 1.0) < std::f32::EPSILON);
510        assert!(rgb.g < std::f32::EPSILON);
511        assert!(rgb.b < std::f32::EPSILON);
512        assert!((rgb.a - 1.0).abs() < std::f32::EPSILON);
513    }
514
515    #[test]
516    // Tests that we make an HSV triplet at defaults and it is black.
517    fn test_green_hex() {
518        let rgb = RGBA::from_hex("#00FF00FF").expect("Invalid hex string");
519        assert!(rgb.r < std::f32::EPSILON);
520        assert!(f32::abs(rgb.g - 1.0) < std::f32::EPSILON);
521        assert!(rgb.b < std::f32::EPSILON);
522        assert!((rgb.a - 1.0).abs() < std::f32::EPSILON);
523    }
524
525    #[test]
526    // Tests that we make an HSV triplet at defaults and it is black.
527    fn test_blue_hex() {
528        let rgb = RGBA::from_hex("#0000FFFF").expect("Invalid hex string");
529        assert!(rgb.r < std::f32::EPSILON);
530        assert!(rgb.g < std::f32::EPSILON);
531        assert!(f32::abs(rgb.b - 1.0) < std::f32::EPSILON);
532        assert!((rgb.a - 1.0).abs() < std::f32::EPSILON);
533    }
534
535    #[test]
536    // Tests that we make an HSV triplet at defaults and it is black.
537    fn test_blue_named() {
538        let rgb = RGBA::named(BLUE);
539        assert!(rgb.r < std::f32::EPSILON);
540        assert!(rgb.g < std::f32::EPSILON);
541        assert!(f32::abs(rgb.b - 1.0) < std::f32::EPSILON);
542        assert!((rgb.a - 1.0).abs() < std::f32::EPSILON);
543    }
544
545    #[test]
546    // Test the lerp function
547    fn test_lerp() {
548        let black = RGBA::named(BLACK);
549        let white = RGBA::named(WHITE);
550        assert!(black.lerp(white, 0.0) == black);
551        assert!(black.lerp(white, 1.0) == white);
552    }
553
554    #[test]
555    // Test the lerp function
556    fn test_lerp_alpha() {
557        let black = RGB::named(BLACK).to_rgba(0.0);
558        let white = RGB::named(WHITE).to_rgba(1.0);
559
560        let l0 = black.lerp_alpha(white, 0.0);
561        let l1 = black.lerp_alpha(white, 1.0);
562        assert!(l0.a < std::f32::EPSILON);
563        assert!((l1.a - 1.0).abs() < std::f32::EPSILON);
564
565        assert!(l0.to_rgb() == RGB::named(BLACK));
566        assert!(l1.to_rgb() == RGB::named(BLACK));
567    }
568}