inku/
lib.rs

1#![allow(clippy::many_single_char_names)]
2#![warn(missing_docs)]
3
4//! An RGBA [`Color`] backed by a `u32`.
5//!
6//! # Examples
7//!
8//! ```
9//! type RGBA = inku::Color<inku::RGBA>;
10//!
11//! let color = RGBA::new(0x000000ff);
12//! let new_color = color
13//!     // Lighten the color by 10%
14//!     .lighten(0.1)
15//!     // Saturate the color by 30%
16//!     .saturate(0.3);
17//!
18//! assert_eq!(new_color.to_u32(), 0x201111ff);
19//!
20//! // 4 bytes
21//! assert_eq!(4, std::mem::size_of::<RGBA>());
22//! ```
23//!
24//! # Storage Formats
25//!
26//! An RGBA color backed by a `u32`.
27//!
28//! There are multiple storage formats to choose from, [`ZRGB`] and [`RGBA`]. These determine how
29//! the underlying `u32` is laid out.
30//!
31//! ```
32//! type RGBA = inku::Color<inku::RGBA>;
33//! type ZRGB = inku::Color<inku::ZRGB>;
34//!
35//! assert_eq!(RGBA::new(0xfacadeff).to_u32(), 0xfacadeff);
36//!
37//! // NOTE: The high byte is zeroed out
38//! assert_eq!(ZRGB::new(0xfffacade).to_u32(), 0x00facade);
39//! ```
40//!
41//! # Manipulations are lossy
42//!
43//! Because we're representing the colour with a `u32`, manipulations are not reversible.
44//! Consider the following:
45//!
46//! ```
47//! type RGBA = inku::Color<inku::RGBA>;
48//! let color = RGBA::new(0xfacadeff);
49//!
50//! // We convert the RGB values to HSL and desaturated the color
51//! let desaturated_color = color.desaturate(0.1);
52//! assert_eq!(0xf7ccdeff, desaturated_color.to_u32());
53//!
54//! // We don't know what our original hue was, so we can't get back to the original color
55//! let resaturated_color = desaturated_color.saturate(0.1);
56//! assert_eq!(0xf9c9ddff, resaturated_color.to_u32());
57//! ```
58
59use std::fmt;
60use std::fmt::Write;
61use std::marker::PhantomData;
62
63/// An RGBA color backed by a `u32`.
64///
65/// There are multiple storage formats to choose from, see the [crate level documentation][crate]
66/// for more info.
67///
68/// # Examples
69///
70/// ```
71/// type RGBA = inku::Color<inku::RGBA>;
72/// assert_eq!(RGBA::new(0xfacadeff).to_u32(), 0xfacadeff);
73///
74/// // 4 bytes
75/// assert_eq!(4, std::mem::size_of::<RGBA>());
76/// ```
77#[derive(Copy, Clone, PartialEq, Default, Hash)]
78pub struct Color<T: Storage>(u32, PhantomData<T>);
79
80#[doc(hidden)]
81pub trait Storage: PartialEq + Copy + Clone + private::Sealed {
82    fn init(color: u32) -> u32 {
83        color
84    }
85    fn decode(color: u32) -> (u8, u8, u8, u8);
86    fn encode(r: u8, g: u8, b: u8, a: u8) -> u32;
87    fn write_hex(w: &mut dyn Write, color: u32) -> fmt::Result {
88        write!(w, "{:#010x}", color)
89    }
90}
91
92mod private {
93    // https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
94    pub trait Sealed {}
95
96    impl Sealed for super::ZRGB {}
97    impl Sealed for super::RGBA {}
98}
99
100fn decode(color: u32) -> (u8, u8, u8, u8) {
101    let b1 = (color >> 24) & 0xff;
102    let b2 = (color >> 16) & 0xff;
103    let b3 = (color >> 8) & 0xff;
104    let b4 = color & 0xff;
105    (b1 as u8, b2 as u8, b3 as u8, b4 as u8)
106}
107
108fn encode(b1: u8, b2: u8, b3: u8, b4: u8) -> u32 {
109    let b1 = b1 as u32;
110    let b2 = b2 as u32;
111    let b3 = b3 as u32;
112    let b4 = b4 as u32;
113    (b1 << 24) | (b2 << 16) | (b3 << 8) | b4
114}
115
116/// ZRGB (0RGB) storage format.
117///
118/// ```text
119/// 0x00000000
120///   ^^ ignored (zeroed out)
121///     ^^ red
122///       ^^ green
123///         ^^ blue
124/// ```
125#[derive(PartialEq, Copy, Clone)]
126#[allow(clippy::upper_case_acronyms)]
127pub struct ZRGB;
128
129impl Storage for ZRGB {
130    fn init(color: u32) -> u32 {
131        color & 0xffffff
132    }
133
134    fn decode(color: u32) -> (u8, u8, u8, u8) {
135        let (_, r, g, b) = decode(color);
136        (r, g, b, 0)
137    }
138
139    fn encode(r: u8, g: u8, b: u8, _a: u8) -> u32 {
140        encode(0, r, g, b)
141    }
142
143    fn write_hex(w: &mut dyn Write, color: u32) -> fmt::Result {
144        // The high byte is ignored
145        write!(w, "{:#08x}", color & 0xffffff)
146    }
147}
148
149/// RGBA storage format.
150///
151/// ```text
152/// 0x00000000
153///   ^^ red
154///     ^^ green
155///       ^^ blue
156///         ^^ alpha
157/// ```
158#[derive(PartialEq, Copy, Clone)]
159#[allow(clippy::upper_case_acronyms)]
160pub struct RGBA;
161
162impl Storage for RGBA {
163    fn decode(color: u32) -> (u8, u8, u8, u8) {
164        decode(color)
165    }
166
167    fn encode(r: u8, g: u8, b: u8, a: u8) -> u32 {
168        encode(r, g, b, a)
169    }
170}
171
172impl<T: Storage> Color<T> {
173    /// Initializes a new `Color` from a `u32`.
174    ///
175    /// # Examples
176    ///
177    /// ```
178    /// type Color = inku::Color<inku::ZRGB>;
179    /// let color = Color::new(0x000000);
180    /// ```
181    ///
182    /// Using `ZRGB`, the `u32` is treated as follows:
183    ///
184    /// ```text
185    /// 0x00000000
186    ///   ^^ ignored (zeroed out)
187    ///     ^^ red
188    ///       ^^ green
189    ///         ^^ blue
190    /// ```
191    pub fn new(color: u32) -> Self {
192        Self(T::init(color), PhantomData)
193    }
194
195    /// Lightens the color by translating to HSL color space then adjusting the lightness value.
196    ///
197    /// # Panics
198    ///
199    /// Panics if `percent` is not between `0.0` and `1.0`
200    #[must_use]
201    pub fn lighten(self, percent: f64) -> Self {
202        assert_percent(percent);
203        self.map_hsla(|h, s, mut l, a| {
204            // Increase the lightness and ensure we don't go over 1.0
205            l = (l + percent).min(1.0);
206            (h, s, l, a)
207        })
208    }
209
210    /// Darkens the color by translating to HSL color space then adjusting the lightness value.
211    ///
212    /// # Panics
213    ///
214    /// Panics if `percent` is not between `0.0` and `1.0`
215    #[must_use]
216    pub fn darken(self, percent: f64) -> Self {
217        assert_percent(percent);
218        self.map_hsla(|h, s, mut l, a| {
219            // Decrease the lightness and ensure we don't go below 0.0
220            l = (l - percent).max(0.0);
221            (h, s, l, a)
222        })
223    }
224
225    /// Increases saturation of the color by translating to HSL color space then adjusting the
226    /// saturation value.
227    ///
228    /// # Panics
229    ///
230    /// Panics if `percent` is not between `0.0` and `1.0`
231    #[must_use]
232    pub fn saturate(self, percent: f64) -> Self {
233        assert_percent(percent);
234        self.map_hsla(|h, mut s, l, a| {
235            // Increase the saturation and ensure we don't go over 1.0
236            s = (s + percent).min(1.0);
237            (h, s, l, a)
238        })
239    }
240
241    /// Decreases saturation of the color by translating to HSL color space then adjusting the
242    /// saturation value.
243    ///
244    /// # Panics
245    ///
246    /// Panics if `percent` is not between `0.0` and `1.0`
247    #[must_use]
248    pub fn desaturate(self, percent: f64) -> Self {
249        assert_percent(percent);
250        self.map_hsla(|h, mut s, l, a| {
251            // Decrease the saturation and ensure we don't go below 0.0
252            s = (s - percent).max(0.0);
253            (h, s, l, a)
254        })
255    }
256
257    /// Rotate the hue by translating to HSL color space then adjusting the hue value. Takes a
258    /// value between `0.0` and `360.0`.
259    #[must_use]
260    pub fn rotate_hue(self, amount: f64) -> Self {
261        self.map_hsla(|mut h, s, l, a| {
262            // Add the amount and ensure the value is a positive number between 0.0 and 360.0
263            h = ((h + amount) % 360.0 + 360.0) % 360.0;
264            (h, s, l, a)
265        })
266    }
267
268    /// Returns the underlying `u32`.
269    pub fn to_u32(self) -> u32 {
270        self.0
271    }
272
273    /// The [percieved brightness](https://www.w3.org/TR/AERT#color-contrast) of the color (a
274    /// number between `0.0` and `1.0`).
275    pub fn brightness(self) -> f64 {
276        let (r, g, b, _a) = self.to_rgba();
277        let r = r as f64 / 255.0;
278        let g = g as f64 / 255.0;
279        let b = b as f64 / 255.0;
280        (299.0 * r + 587.0 * g + 114.0 * b) / 1_000.0
281    }
282
283    /// Determine whether a color is perceived as a light color ([percieved
284    /// brightness](https://www.w3.org/TR/AERT#color-contrast) is greater than `0.5`).
285    pub fn is_light(self) -> bool {
286        self.brightness() > 0.5
287    }
288
289    /// Maps `(r1, g1, b1, a1)` to `(r2, g2, b2, a2)` by applying a function to the channels.
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// # use inku::{Color, RGBA};
295    /// let color = Color::<RGBA>::new(0x00000011);
296    /// assert_eq!(
297    ///     color.map(|r, g, b, a| (r, g, b, a + 0x22)).to_u32(),
298    ///     0x00000033
299    /// );
300    /// ```
301    ///
302    /// ```
303    /// # use inku::{Color,  ZRGB};
304    /// const F: fn(u8, u8, u8, u8) -> (u8, u8, u8, u8) = |r, g, b, a| {
305    ///     assert_eq!(r, 0x22);
306    ///     assert_eq!(g, 0x33);
307    ///     assert_eq!(b, 0x44);
308    ///     assert_eq!(a, 0x00);
309    ///     (1, 2, 3, 4)
310    /// };
311    /// let color = Color::<ZRGB>::new(0x11223344);
312    /// assert_eq!(color.map(F).to_u32(), 0x00010203);
313    /// ```
314    pub fn map<F>(&self, f: F) -> Self
315    where
316        F: Fn(u8, u8, u8, u8) -> (u8, u8, u8, u8),
317    {
318        let (r, g, b, a) = self.to_rgba();
319        let (r, g, b, a) = f(r, g, b, a);
320        Self::from_rgba(r, g, b, a)
321    }
322
323    fn map_hsla<F>(&self, f: F) -> Self
324    where
325        F: Fn(f64, f64, f64, f64) -> (f64, f64, f64, f64),
326    {
327        let (h, s, l, a) = self.to_hsla();
328        let (h, s, l, a) = f(h, s, l, a);
329        Color::from_hsla(h, s, l, a)
330    }
331
332    fn to_rgba(self) -> (u8, u8, u8, u8) {
333        let (r, g, b, a) = T::decode(self.0);
334        (r, g, b, a)
335    }
336
337    fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
338        Self::new(T::encode(r, g, b, a))
339    }
340
341    // NOTE: These {to,from}_hsla functions are private because if you're already dealing with
342    // colors in HSLA you're probably better off keeping your colors in HSLA so you don't lose
343    // fidelity.
344
345    fn to_hsla(self) -> (f64, f64, f64, f64) {
346        let (r, g, b, a) = self.to_rgba();
347        let (h, s, l, a) = rgba_to_hsla(r, g, b, a);
348        (h, s, l, a)
349    }
350
351    fn from_hsla(h: f64, s: f64, l: f64, a: f64) -> Self {
352        let (r, g, b, a) = hsla_to_rgba(h, s, l, a);
353        Self::from_rgba(r as u8, g as u8, b as u8, a as u8)
354    }
355}
356
357#[cfg(not(any(test, feature = "color_debug")))]
358impl<T: Storage> fmt::Debug for Color<T> {
359    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360        let storage = format!("{}", std::any::type_name::<T>())
361            .split("::")
362            .last()
363            .expect("no type name")
364            .to_owned();
365        write!(f, "Color<{}>(", storage)?;
366        T::write_hex(f, self.0)?;
367        write!(f, ")")
368    }
369}
370
371#[cfg(any(test, feature = "color_debug"))]
372impl<T: Storage> fmt::Debug for Color<T> {
373    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374        use crossterm::style;
375
376        let fg = style::SetForegroundColor(if self.is_light() {
377            style::Color::Black
378        } else {
379            style::Color::White
380        });
381        let (r, g, b, _a) = self.to_rgba();
382        let bg = style::SetBackgroundColor(style::Color::Rgb { r, g, b });
383
384        let storage = format!("{}", std::any::type_name::<T>())
385            .split("::")
386            .last()
387            .expect("no type name")
388            .to_owned();
389
390        write!(f, "Color<{}>({}{}", storage, fg, bg)?;
391        T::write_hex(f, self.0)?;
392        write!(f, "{})", style::ResetColor)
393    }
394}
395
396impl<T: Storage> From<u32> for Color<T> {
397    fn from(color: u32) -> Self {
398        Self::new(color)
399    }
400}
401
402impl<T: Storage> From<(u8, u8, u8)> for Color<T> {
403    fn from(rgb: (u8, u8, u8)) -> Self {
404        let (r, g, b) = rgb;
405        Self::from_rgba(r, g, b, 0)
406    }
407}
408
409impl<T: Storage> From<(u8, u8, u8, u8)> for Color<T> {
410    fn from(rgba: (u8, u8, u8, u8)) -> Self {
411        let (r, g, b, a) = rgba;
412        Self::from_rgba(r, g, b, a)
413    }
414}
415
416impl<T: Storage> From<Color<T>> for u32 {
417    fn from(color: Color<T>) -> u32 {
418        color.to_u32()
419    }
420}
421
422impl<T: Storage> From<Color<T>> for (u8, u8, u8) {
423    fn from(color: Color<T>) -> (u8, u8, u8) {
424        let (r, g, b, _a) = color.to_rgba();
425        (r, g, b)
426    }
427}
428
429impl<T: Storage> From<Color<T>> for (u8, u8, u8, u8) {
430    fn from(color: Color<T>) -> (u8, u8, u8, u8) {
431        color.to_rgba()
432    }
433}
434
435fn assert_percent(percent: f64) {
436    assert!(
437        (0.0..=1.0).contains(&percent),
438        "percent ({:?}) must be between 0.0 and 1.0",
439        percent
440    );
441}
442
443// https://css-tricks.com/converting-color-spaces-in-javascript/#hsl-to-rgb
444fn hsla_to_rgba(h: f64, s: f64, l: f64, mut a: f64) -> (u8, u8, u8, u8) {
445    debug_assert!(
446        (0.0..=360.0).contains(&h),
447        "h must be between 0.0 and 360.0"
448    );
449    debug_assert!((0.0..=1.0).contains(&s), "s must be between 0.0 and 1.0");
450    debug_assert!((0.0..=1.0).contains(&l), "l must be between 0.0 and 1.0");
451
452    // Next, we find chroma (c), which is color intensity
453    let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
454
455    // Then we use x for the second largest component (first being chroma)
456    let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
457
458    // Then the amount to add to each channel to match the lightness (m)
459    let m = l - c / 2.0;
460
461    // The hue will determine what the red, green, and blue should be depending on which 60 degree
462    // sector of the color wheel it lies.
463    let (mut r, mut g, mut b) = if (0.0..60.0).contains(&h) {
464        (c, x, 0.0)
465    } else if (60.0..120.0).contains(&h) {
466        (x, c, 0.0)
467    } else if (120.0..180.0).contains(&h) {
468        (0.0, c, x)
469    } else if (180.0..240.0).contains(&h) {
470        (0.0, x, c)
471    } else if (240.0..300.0).contains(&h) {
472        (x, 0.0, c)
473    } else if (300.0..360.0).contains(&h) {
474        (c, 0.0, x)
475    } else {
476        unreachable!();
477    };
478
479    // To get the final RGBA value, we add m to each channel, multiply it by 255
480    r = (r + m) * 255.0;
481    g = (g + m) * 255.0;
482    b = (b + m) * 255.0;
483    a = a * 255.0;
484
485    (r as u8, g as u8, b as u8, a as u8)
486}
487
488// https://css-tricks.com/converting-color-spaces-in-javascript/#rgb-to-hsl
489fn rgba_to_hsla(r: u8, g: u8, b: u8, a: u8) -> (f64, f64, f64, f64) {
490    // First, we must divide the red, green, and blue, and alpha by 255 to use values between 0.0
491    // and 1.0.
492    let r = r as f64 / 255.0;
493    let g = g as f64 / 255.0;
494    let b = b as f64 / 255.0;
495    let a = a as f64 / 255.0;
496
497    // Then we find the minimum and maximum of those values (c_min and c_max) as well as the
498    // difference between them (delta).
499    let c_min = r.min(g.min(b));
500    let c_max = r.max(g.max(b));
501    let delta = c_max - c_min;
502
503    let error_margin = std::f64::EPSILON;
504
505    // Calculate the hue
506    let mut h = if delta == 0.0 {
507        0.0
508    } else if (c_max - r).abs() < error_margin {
509        // Red is max
510        ((g - b) / delta) % 6.0
511    } else if (c_max - g).abs() < error_margin {
512        // Green is max
513        (b - r) / delta + 2.0
514    } else {
515        // Blue is max
516        (r - g) / delta + 4.0
517    };
518
519    h *= 60.0;
520
521    // Make negative hues positive behind 360 degrees
522    if h < 0.0 {
523        h += 360.0;
524    }
525
526    // Calculate lightness
527    let l = (c_max + c_min) / 2.0;
528
529    // Calculate saturation
530    let s = if delta == 0.0 {
531        0.0
532    } else {
533        // For rounding issues we need to ensure we stay below 1.0
534        (delta / (1.0 - (2.0 * l - 1.0).abs())).min(1.0)
535    };
536
537    debug_assert!(s <= 1.0);
538    debug_assert!(l <= 1.0);
539
540    (h, s, l, a)
541}
542
543#[cfg(test)]
544mod tests {
545    use super::*;
546
547    #[test]
548    fn lighten() {
549        let color = Color::<ZRGB>::new(0x000000);
550
551        for percent in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].iter() {
552            eprintln!("{:?}", color.lighten(*percent));
553        }
554
555        assert_eq!(Color::<ZRGB>::new(0x191919), color.lighten(0.1));
556        assert_eq!(Color::<ZRGB>::new(0x7f7f7f), color.lighten(0.5));
557        assert_eq!(Color::<ZRGB>::new(0xffffff), color.lighten(1.0));
558
559        assert_eq!(
560            Color::<ZRGB>::new(0xffffff),
561            Color::<ZRGB>::new(0xffffff).lighten(1.0)
562        );
563    }
564
565    #[test]
566    fn darken() {
567        let color = Color::<ZRGB>::new(0xffffff);
568
569        for percent in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].iter() {
570            eprintln!("{:?}", color.darken(*percent));
571        }
572
573        assert_eq!(Color::<ZRGB>::new(0xe5e5e5), color.darken(0.1));
574        assert_eq!(Color::<ZRGB>::new(0x7f7f7f), color.darken(0.5));
575        assert_eq!(Color::<ZRGB>::new(0x000000), color.darken(1.0));
576
577        assert_eq!(
578            Color::<ZRGB>::new(0x000000),
579            Color::<ZRGB>::new(0x000000).darken(1.0)
580        );
581    }
582
583    #[test]
584    fn saturate() {
585        let color = Color::<ZRGB>::new(0xe2e2e2);
586
587        for percent in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].iter() {
588            eprintln!("{:?}", color.saturate(*percent));
589        }
590
591        assert_eq!(Color::<ZRGB>::new(0xe4dfdf), color.saturate(0.1));
592        assert_eq!(Color::<ZRGB>::new(0xf0d3d3), color.saturate(0.5));
593        assert_eq!(Color::<ZRGB>::new(0xffc4c4), color.saturate(1.0));
594    }
595
596    #[test]
597    fn desaturate() {
598        let color = Color::<ZRGB>::new(0xffc4c4);
599
600        for percent in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0].iter() {
601            eprintln!("{:?}", color.desaturate(*percent));
602        }
603
604        assert_eq!(Color::<ZRGB>::new(0xfcc6c6), color.desaturate(0.1),);
605        assert_eq!(Color::<ZRGB>::new(0xf0d2d2), color.desaturate(0.5));
606        assert_eq!(Color::<ZRGB>::new(0xe1e1e1), color.desaturate(1.0));
607    }
608
609    #[test]
610    fn rotate_hue() {
611        let color = Color::<ZRGB>::new(0xffc4c4);
612
613        for n in (0..360).into_iter().step_by(10) {
614            eprintln!("{:?}", color.rotate_hue(n as f64));
615        }
616
617        assert_eq!(color.rotate_hue(100.0), Color::<ZRGB>::new(0xd7fec3));
618        assert_eq!(color.rotate_hue(200.0), Color::<ZRGB>::new(0xc3ebfe));
619        assert_eq!(color.rotate_hue(300.0), Color::<ZRGB>::new(0xfec3fe));
620    }
621
622    #[test]
623    fn to_rgba() {
624        assert_eq!(
625            (0xbb, 0xcc, 0xdd, 0x00),
626            Color::<ZRGB>::new(0xaabbccdd).to_rgba()
627        );
628    }
629
630    #[test]
631    fn from_rgba() {
632        assert_eq!(
633            Color::<ZRGB>::new(0xaabbccdd),
634            Color::<ZRGB>::from_rgba(0xbb, 0xcc, 0xdd, 0x00)
635        );
636    }
637
638    #[test]
639    fn to_hsla() {
640        let color = Color::<RGBA>::new(0x96643233);
641        let (h, s, l, a) = color.to_hsla();
642
643        let assert_float = |a: f64, b: f64| {
644            let error_margin = std::f64::EPSILON;
645            assert!((a - b).abs() < error_margin, "{:?} == {:?}", a, b)
646        };
647
648        assert_float(29.999999999999996, h);
649        assert_float(0.50000000000000014, s);
650        assert_float(0.39215686274509810, l);
651        assert_float(0.2, a);
652
653        // Ensure saturation is within 0.0 and 1.0
654        Color::<ZRGB>::new(0xff2009).to_hsla();
655    }
656
657    #[test]
658    fn brightness() {
659        assert_eq!(0.0, Color::<ZRGB>::new(0x000000).brightness());
660        assert_eq!(1.0, Color::<ZRGB>::new(0xffffff).brightness());
661    }
662
663    #[test]
664    fn is_light() {
665        let light = Color::<ZRGB>::new(0xffffff);
666        let dark = Color::<ZRGB>::new(0x000000);
667
668        assert!(light.is_light());
669        assert!(!dark.is_light());
670    }
671
672    #[test]
673    fn map() {
674        let color = Color::<ZRGB>::new(0x00000000);
675        assert_eq!(
676            color
677                .map(|r, g, b, a| (r + 1, g + 2, b + 3, a + 4))
678                .to_u32(),
679            0x00010203
680        );
681
682        let color = Color::<RGBA>::new(0x00000000);
683        assert_eq!(
684            color
685                .map(|r, g, b, a| (r + 1, g + 2, b + 3, a + 4))
686                .to_u32(),
687            0x01020304
688        );
689
690        const RED: fn(u8, u8, u8, u8) -> (u8, u8, u8, u8) = |_r, g, b, a| (255, g, b, a);
691        assert_eq!(color.map(RED).to_u32(), 0xff000000);
692    }
693
694    #[test]
695    fn storage() {
696        assert_eq!(0x00bbccdd, ZRGB::init(0xaabbccdd));
697        assert_eq!((0xbb, 0xcc, 0xdd, 0x00), ZRGB::decode(0xaabbccdd));
698        assert_eq!(0x00bbccdd, ZRGB::encode(0xbb, 0xcc, 0xdd, 0xaa));
699
700        assert_eq!(0xaabbccdd, RGBA::init(0xaabbccdd));
701        assert_eq!((0xaa, 0xbb, 0xcc, 0xdd), RGBA::decode(0xaabbccdd));
702        assert_eq!(0xaabbccdd, RGBA::encode(0xaa, 0xbb, 0xcc, 0xdd));
703
704        // For sanity checking; cargo test storage -- --nocapture
705        eprintln!("{:?}", Color::<ZRGB>::new(0xff_facade));
706        eprintln!("{:?}", Color::<RGBA>::new(0xfacade_ff));
707    }
708
709    #[test]
710    fn test_rgba_to_hsla() {
711        let (h, s, l, a) = rgba_to_hsla(1, 2, 3, 4);
712        let (r, g, b, a) = hsla_to_rgba(h, s, l, a);
713
714        assert_eq!(r, 1);
715        assert_eq!(g, 2);
716        assert_eq!(b, 3);
717        assert_eq!(a, 4);
718    }
719}