pix_engine/color/
conversion.rs

1//! [Color] conversion functions.
2//!
3//! Provides methods for converting [Color]s:
4//!
5//! - [`Color::from_str`]: Parse from a hexadecimal string (e.g. "#FF0", or "#00FF00").
6//! - [`Color::from_slice`]: Parse from an RGB/A slice (e.g. [0, 125, 0] or [255, 255, 0, 125]).
7//! - [`Color::from_hex`]: Parse from a `u32` RGBA hexadecimal value (e.g. 0x00FF00FF).
8//! - [`Color::as_hex`]: Convert back to a `u32` RGBA hexadecimal value.
9//! - [`Color::inverted`]: Invert the RGB colors channel-wise, ignoring the alpha channel.
10//! - [`Color::lerp`]: Linear interpolate between two colors, channel-wise, including the alpha
11//!   channel.
12//!
13//! Examples
14//!
15//! ```no_run
16//! # // no_run because these tests are run on their respective functions
17//! # use pix_engine::prelude::*;
18//! use std::str::FromStr;
19//!
20//! # fn main() -> PixResult<()> {
21//! let c = Color::from_str("#F0F5BF")?;
22//! assert_eq!(c.channels(), [240, 245, 191, 255]);
23//!
24//! let vals = [128.0, 64.0, 0.0];
25//! let c = Color::from_slice(ColorMode::Rgb, &vals)?;
26//! assert_eq!(c.channels(), [128, 64, 0, 255]);
27//!
28//! let c = Color::from_hex(0xF0FF_00FF);
29//! assert_eq!(c.channels(), [240, 255, 0, 255]);
30//!
31//! let c = Color::rgba(255, 0, 255, 125);
32//! assert_eq!(c.inverted().as_hex(), 0xFF00_FF7D);
33//!
34//! let from = rgb!(255, 0, 0);
35//! let to = rgb!(0, 100, 255);
36//! let lerped = from.lerp(to, 0.5);
37//! assert_eq!(lerped.channels(), [128, 50, 128, 255]);
38//! # Ok(())
39//! # }
40//! ```
41
42use super::Mode::{Hsb, Hsl, Rgb};
43use crate::prelude::*;
44use std::{convert::TryFrom, result, str::FromStr};
45
46impl Color {
47    /// Constructs a `Color` from a [slice] of 1-4 values. The number of values
48    /// provided alter how they are interpreted similar to the [color!], [rgb!], [hsb!], and
49    /// [hsl!] macros.
50    ///
51    /// # Errors
52    ///
53    /// If the [slice] is empty or has more than 4 values, an error is returned.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// # use pix_engine::prelude::*;
59    /// # fn main() -> PixResult<()> {
60    /// let vals: Vec<f64> = vec![128.0, 64.0, 0.0];
61    /// let c = Color::from_slice(ColorMode::Rgb, &vals)?; // RGB Vec
62    /// assert_eq!(c.channels(), [128, 64, 0, 255]);
63    ///
64    /// let vals: [f64; 4] = [128.0, 64.0, 0.0, 128.0];
65    /// let c = Color::from_slice(ColorMode::Rgb, &vals[..])?; // RGBA slice
66    /// assert_eq!(c.channels(), [128, 64, 0, 128]);
67    /// # Ok(())
68    /// # }
69    /// ```
70    pub fn from_slice<T, S>(mode: ColorMode, slice: S) -> PixResult<Self>
71    where
72        T: Copy + Into<f64>,
73        S: AsRef<[T]>,
74    {
75        let slice = slice.as_ref();
76        let result = match *slice {
77            [gray] => Self::with_mode(mode, gray, gray, gray),
78            [gray, a] => Self::with_mode_alpha(mode, gray, gray, gray, a),
79            [v1, v2, v3] => Self::with_mode(mode, v1, v2, v3),
80            [v1, v2, v3, a] => Self::with_mode_alpha(mode, v1, v2, v3, a),
81            _ => return Err(PixError::InvalidColorSlice.into()),
82        };
83        Ok(result)
84    }
85
86    /// Constructs a `Color` from a [u32] RGB hexadecimal value with max alpha.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// # use pix_engine::prelude::*;
92    /// let c = Color::from_hex(0xF0FF00);
93    /// assert_eq!(c.channels(), [240, 255, 0, 255]);
94    /// ```
95    #[inline]
96    pub const fn from_hex(hex: u32) -> Self {
97        let [_, r, g, b] = hex.to_be_bytes();
98        Self::rgba(r, g, b, 255)
99    }
100
101    /// Constructs a `Color` from a [u32] RGBA hexadecimal value.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// # use pix_engine::prelude::*;
107    /// let c = Color::from_hex_alpha(0xF0FF_00FF);
108    /// assert_eq!(c.channels(), [240, 255, 0, 255]);
109    ///
110    /// let c = Color::from_hex_alpha(0xF0FF_0080);
111    /// assert_eq!(c.channels(), [240, 255, 0, 128]);
112    /// ```
113    #[inline]
114    pub const fn from_hex_alpha(hex: u32) -> Self {
115        let [r, g, b, a] = hex.to_be_bytes();
116        Self::rgba(r, g, b, a)
117    }
118
119    /// Constructs a `Color` by inverting the RGBA values.
120    ///
121    /// # Example
122    ///
123    /// ```
124    /// # use pix_engine::prelude::*;
125    /// let c = Color::from_hex(0xF0FF00);
126    /// assert_eq!(c.inverted().as_hex(), 0x0F00FF);
127    /// ```
128    #[inline]
129    pub const fn inverted(&self) -> Self {
130        let hex = self.as_hex();
131        Self::from_hex(0x00FF_FFFF ^ hex)
132    }
133
134    /// Constructs an opaque `Color` blended over a given background, using an alpha value.
135    pub fn blended<A>(&self, bg: Color, alpha: A) -> Self
136    where
137        A: Into<f64>,
138    {
139        let a = alpha.into().clamp(0.0, 1.0);
140        let [v1, v2, v3, _] = convert_levels(bg.levels(), bg.mode(), self.mode());
141        let [bv1, bv2, bv3, _] = self.levels();
142
143        let blended = |bg: f64, fg: f64, alpha: f64| bg.mul_add(1.0 - alpha, fg * alpha);
144        let levels = clamp_levels([
145            blended(v1, bv1, a),
146            blended(v2, bv2, a),
147            blended(v3, bv3, a),
148            1.0,
149        ]);
150        Self {
151            mode: self.mode,
152            channels: calculate_channels(levels),
153        }
154    }
155
156    /// Constructs a `Color` by linear interpolating between two `Color`s by a given amount between
157    /// `0.0` and `1.0`.
158    ///
159    /// # Examples
160    ///
161    /// ```
162    /// # use pix_engine::prelude::*;
163    /// let from = rgb!(255, 0, 0);
164    /// let to = rgb!(0, 100, 255);
165    /// let lerped = from.lerp(to, 0.5);
166    /// assert_eq!(lerped.channels(), [128, 50, 128, 255]);
167    ///
168    /// let from = rgb!(255, 0, 0);
169    /// let to = hsb!(120.0, 80.0, 100.0, 0.5);
170    /// let lerped = from.lerp(to, 0.25); // `to` is implicity converted to RGB
171    /// assert_eq!(lerped.channels(), [204, 64, 13, 223]);
172    /// ```
173    pub fn lerp<A>(&self, other: Color, amt: A) -> Self
174    where
175        A: Into<f64>,
176    {
177        let lerp = |start: f64, stop: f64, amt: f64| amt.mul_add(stop - start, start);
178
179        let amt = amt.into().clamp(0.0, 1.0);
180        let [v1, v2, v3, a] = self.levels();
181        let [ov1, ov2, ov3, oa] = convert_levels(other.levels(), other.mode(), self.mode());
182        let levels = clamp_levels([
183            lerp(v1, ov1, amt),
184            lerp(v2, ov2, amt),
185            lerp(v3, ov3, amt),
186            lerp(a, oa, amt),
187        ]);
188        Self {
189            mode: self.mode,
190            channels: calculate_channels(levels),
191        }
192    }
193}
194
195impl FromStr for Color {
196    type Err = PixError;
197
198    /// Converts to [Color] from a hexadecimal string.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// # use pix_engine::prelude::*;
204    /// use std::str::FromStr;
205    ///
206    /// # fn main() -> PixResult<()> {
207    /// let c = Color::from_str("#F0F")?; // 3-digit Hex string
208    /// assert_eq!(c.channels(), [255, 0, 255, 255]);
209    ///
210    /// let c = Color::from_str("#F0F5")?; // 4-digit Hex string
211    /// assert_eq![c.channels(), [255, 0, 255, 85]];
212    ///
213    /// let c = Color::from_str("#F0F5BF")?; // 6-digit Hex string
214    /// assert_eq!(c.channels(), [240, 245, 191, 255]);
215    ///
216    /// let c = Color::from_str("#F0F5BF5F")?; // 8-digit Hex string
217    /// assert_eq!(c.channels(), [240, 245, 191, 95]);
218    /// # Ok(())
219    /// # }
220    /// ```
221    fn from_str(string: &str) -> result::Result<Self, Self::Err> {
222        if !string.starts_with('#') {
223            return Err(PixError::ParseColorError);
224        }
225
226        let mut channels: [u8; 4] = [0, 0, 0, 255];
227        let parse_hex =
228            |hex: &str| u8::from_str_radix(hex, 16).map_err(|_| PixError::ParseColorError);
229
230        let string = string.trim().to_lowercase();
231        match string.len() - 1 {
232            3 | 4 => {
233                for (i, _) in string[1..].char_indices() {
234                    let hex = parse_hex(&string[i + 1..i + 2])?;
235                    channels[i] = (hex << 4) | hex;
236                }
237            }
238            6 | 8 => {
239                for (i, _) in string[1..].char_indices().step_by(2) {
240                    channels[i / 2] = parse_hex(&string[i + 1..i + 3])?;
241                }
242            }
243            _ => return Err(PixError::ParseColorError),
244        }
245
246        let [r, g, b, a] = channels;
247        Ok(Self::rgba(r, g, b, a))
248    }
249}
250
251impl TryFrom<&str> for Color {
252    type Error = PixError;
253    /// Try to create a `Color` from a hexadecimal string.
254    fn try_from(s: &str) -> result::Result<Self, Self::Error> {
255        Self::from_str(s)
256    }
257}
258
259/// Return the max value for each [`ColorMode`].
260pub(crate) const fn maxes(mode: ColorMode) -> [f64; 4] {
261    match mode {
262        Rgb => [255.0; 4],
263        Hsb | Hsl => [360.0, 100.0, 100.0, 1.0],
264    }
265}
266
267/// Clamp levels to `0.0..=1.0`.
268pub(crate) fn clamp_levels(levels: [f64; 4]) -> [f64; 4] {
269    [
270        levels[0].clamp(0.0, 1.0),
271        levels[1].clamp(0.0, 1.0),
272        levels[2].clamp(0.0, 1.0),
273        levels[3].clamp(0.0, 1.0),
274    ]
275}
276
277/// Converts levels from one [`ColorMode`] to another.
278pub(crate) fn convert_levels(levels: [f64; 4], from: ColorMode, to: ColorMode) -> [f64; 4] {
279    match (from, to) {
280        (Hsb, Rgb) => hsb_to_rgb(levels),
281        (Hsl, Rgb) => hsl_to_rgb(levels),
282        (Rgb, Hsb) => rgb_to_hsb(levels),
283        (Rgb, Hsl) => rgb_to_hsl(levels),
284        (Hsb, Hsl) => hsb_to_hsl(levels),
285        (Hsl, Hsb) => hsl_to_hsb(levels),
286        (_, _) => levels,
287    }
288}
289
290/// Converts to [Rgb] to [Hsb] format.
291#[allow(clippy::many_single_char_names)]
292pub(crate) fn rgb_to_hsb([r, g, b, a]: [f64; 4]) -> [f64; 4] {
293    let c_max = r.max(g).max(b);
294    let c_min = r.min(g).min(b);
295    let chr = c_max - c_min;
296    if chr.abs() < f64::EPSILON {
297        [0.0, 0.0, c_max, a]
298    } else {
299        let mut h = if (r - c_max).abs() < f64::EPSILON {
300            // Magenta to yellow
301            (g - b) / chr
302        } else if (g - c_max).abs() < f64::EPSILON {
303            // Yellow to cyan
304            2.0 + (b - r) / chr
305        } else {
306            // Cyan to magenta
307            4.0 + (r - g) / chr
308        };
309        if h < 0.0 {
310            h += 6.0;
311        } else if h >= 6.0 {
312            h -= 6.0;
313        }
314        let s = chr / c_max;
315        [h / 6.0, s, c_max, a]
316    }
317}
318
319/// Converts to [Rgb] to [Hsl] format.
320#[allow(clippy::many_single_char_names)]
321pub(crate) fn rgb_to_hsl([r, g, b, a]: [f64; 4]) -> [f64; 4] {
322    let c_max = r.max(g).max(b);
323    let c_min = r.min(g).min(b);
324    let l = c_max + c_min;
325    let chr = c_max - c_min;
326    if chr.abs() < f64::EPSILON {
327        [0.0, 0.0, l / 2.0, a]
328    } else {
329        let mut h = if (r - c_max).abs() < f64::EPSILON {
330            // Magenta to yellow
331            (g - b) / chr
332        } else if (g - c_max).abs() < f64::EPSILON {
333            // Yellow to cyan
334            2.0 + (b - r) / chr
335        } else {
336            // Cyan to magenta
337            4.0 + (r - g) / chr
338        };
339        if h < 0.0 {
340            h += 6.0;
341        } else if h >= 6.0 {
342            h -= 6.0;
343        }
344        let s = if l < 1.0 { chr / l } else { chr / (2.0 - l) };
345        [h / 6.0, s, l / 2.0, a]
346    }
347}
348
349/// Converts to [Hsb] to [Rgb] format.
350#[allow(clippy::many_single_char_names)]
351pub(crate) fn hsb_to_rgb([h, s, b, a]: [f64; 4]) -> [f64; 4] {
352    if b.abs() < f64::EPSILON {
353        [0.0, 0.0, 0.0, a]
354    } else if s.abs() < f64::EPSILON {
355        [b, b, b, a]
356    } else {
357        let h = h * 6.0;
358        let sector = h.floor().clamp(0.0, 2160.0) as usize;
359        let tint1 = b * (1.0 - s);
360        let tint2 = b * (1.0 - s * (h - sector as f64));
361        let tint3 = b * (1.0 - s * (1.0 + sector as f64 - h));
362        let (r, g, b) = match sector {
363            // Yellow to green
364            1 => (tint2, b, tint1),
365            // Green to cyan
366            2 => (tint1, b, tint3),
367            // Cyan to blue
368            3 => (tint1, tint2, b),
369            // Blue to magenta
370            4 => (tint3, tint1, b),
371            // Magenta to red
372            5 => (b, tint1, tint2),
373            // Red to yellow (sector is 0 or 6)
374            _ => (b, tint3, tint1),
375        };
376        [r, g, b, a]
377    }
378}
379
380/// Converts to [Hsl] to [Rgb] format.
381#[allow(clippy::many_single_char_names)]
382pub(crate) fn hsl_to_rgb([h, s, l, a]: [f64; 4]) -> [f64; 4] {
383    if s.abs() < f64::EPSILON {
384        [l, l, l, a]
385    } else {
386        let h = h * 6.0;
387        let b = if l < 0.5 {
388            (1.0 + s) * l
389        } else {
390            l + s - l * s
391        };
392        let zest = 2.0 * l - b;
393        let hzb_to_rgb = |h: f64, z: f64, b: f64| -> f64 {
394            let h = if h < 0.0 {
395                h + 6.0
396            } else if h >= 6.0 {
397                h - 6.0
398            } else {
399                h
400            };
401            match h {
402                // Red to yellow (increasing green)
403                _ if h < 1.0 => (b - z).mul_add(h, z),
404                // Yellow to cyan (greatest green)
405                _ if h < 3.0 => b,
406                // Cyan to blue (decreasing green)
407                _ if h < 4.0 => (b - z).mul_add(4.0 - h, z),
408                // Blue to red (least green)
409                _ => z,
410            }
411        };
412        [
413            hzb_to_rgb(h + 2.0, zest, b),
414            hzb_to_rgb(h, zest, b),
415            hzb_to_rgb(h - 2.0, zest, b),
416            a,
417        ]
418    }
419}
420
421/// Converts to [Hsl] to [Hsb] format.
422#[allow(clippy::many_single_char_names)]
423pub(crate) fn hsl_to_hsb([h, s, l, a]: [f64; 4]) -> [f64; 4] {
424    let b = if l < 0.5 {
425        (1.0 + s) * l
426    } else {
427        l + s - l * s
428    };
429    let s = 2.0 * (b - l) / b;
430    [h, s, b, a]
431}
432
433/// Converts to [Hsb] to [Hsl] format.
434#[allow(clippy::many_single_char_names)]
435pub(crate) fn hsb_to_hsl([h, s, b, a]: [f64; 4]) -> [f64; 4] {
436    let l = (2.0 - s) * b / 2.0;
437    let s = match l {
438        _ if (l - 1.0).abs() < f64::EPSILON => 0.0,
439        _ if l < 0.5 => s / 2.0 - s,
440        _ => s * b / (2.0 - l * 2.0),
441    };
442    [h, s, l, a]
443}
444
445/// Converts levels to [u8] RGBA channels.
446pub(crate) fn calculate_channels(levels: [f64; 4]) -> [u8; 4] {
447    let [r, g, b, a] = levels;
448    let [r_max, g_max, b_max, a_max] = maxes(Rgb);
449    [
450        (r * r_max).round().clamp(0.0, 255.0) as u8,
451        (g * g_max).round().clamp(0.0, 255.0) as u8,
452        (b * b_max).round().clamp(0.0, 255.0) as u8,
453        (a * a_max).round().clamp(0.0, 255.0) as u8,
454    ]
455}
456
457impl Color {
458    /// Update RGB channels by calculating them from the current levels.
459    pub(crate) fn update_channels(&mut self, levels: [f64; 4], mode: ColorMode) {
460        let levels = convert_levels(levels, mode, Rgb);
461        self.channels = calculate_channels(levels);
462    }
463}
464
465macro_rules! impl_from {
466    ($($source: ty),*) => {
467        $(
468            impl From<$source> for Color {
469                #[doc = concat!("Convert [", stringify!($source), "] to grayscale `Color`")]
470                fn from(gray: $source) -> Self {
471                    let gray = f64::from(gray);
472                    Self::with_mode(Rgb, gray, gray, gray)
473                }
474            }
475
476            impl From<[$source; 1]> for Color {
477                #[doc = concat!("Convert [", stringify!($source), "] to grayscale `Color`")]
478                fn from([gray]: [$source; 1]) -> Self {
479                    let gray = f64::from(gray);
480                    Self::with_mode(Rgb, gray, gray, gray)
481                }
482            }
483
484            impl From<[$source; 2]> for Color {
485                #[doc = concat!("Convert `[", stringify!($source), "; 2]` to grayscale `Color` with alpha")]
486                fn from([gray, alpha]: [$source; 2]) -> Self {
487                    let gray = f64::from(gray);
488                    let alpha = f64::from(alpha);
489                    Self::with_mode_alpha(Rgb, gray, gray, gray, alpha)
490                }
491            }
492
493            impl From<[$source; 3]> for Color {
494                #[doc = concat!("Convert `[", stringify!($source), "; 3]` to `Color` with max alpha")]
495                fn from([r, g, b]: [$source; 3]) -> Self {
496                    Self::with_mode(Rgb, f64::from(r), f64::from(g), f64::from(b))
497                }
498            }
499
500            impl From<[$source; 4]> for Color {
501                #[doc = concat!("Convert `[", stringify!($source), "; 4]` to `Color`")]
502                fn from([r, g, b, a]: [$source; 4]) -> Self {
503                    Self::with_mode_alpha(Rgb, f64::from(r), f64::from(g), f64::from(b), f64::from(a))
504                }
505            }
506        )*
507    };
508}
509
510impl_from!(i8, u8, i16, u16, f32);
511impl_from!(i32, u32, f64);
512
513#[cfg(test)]
514mod tests {
515    use crate::prelude::{hsb, hsl, rgb, Color};
516
517    macro_rules! assert_color_eq {
518        ($c1:expr, $c2:expr) => {
519            assert_eq!($c1.channels(), $c2.channels());
520        };
521    }
522
523    #[test]
524    fn test_slice_conversions() {
525        let _: Color = 50u8.into();
526        let _: Color = 50i8.into();
527        let _: Color = 50u16.into();
528        let _: Color = 50i16.into();
529        let _: Color = 50u32.into();
530        let _: Color = 50i32.into();
531        let _: Color = 50.0f32.into();
532        let _: Color = 50.0f64.into();
533
534        let _: Color = [50u8].into();
535        let _: Color = [50i8].into();
536        let _: Color = [50u16].into();
537        let _: Color = [50i16].into();
538        let _: Color = [50u32].into();
539        let _: Color = [50i32].into();
540        let _: Color = [50.0f32].into();
541        let _: Color = [50.0f64].into();
542
543        let _: Color = [50u8, 100].into();
544        let _: Color = [50i8, 100].into();
545        let _: Color = [50u16, 100].into();
546        let _: Color = [50i16, 100].into();
547        let _: Color = [50u32, 100].into();
548        let _: Color = [50i32, 100].into();
549        let _: Color = [50.0f32, 100.0].into();
550        let _: Color = [50.0f64, 100.0].into();
551
552        let _: Color = [50u8, 100, 55].into();
553        let _: Color = [50i8, 100, 55].into();
554        let _: Color = [50u16, 100, 55].into();
555        let _: Color = [50i16, 100, 55].into();
556        let _: Color = [50u32, 100, 55].into();
557        let _: Color = [50i32, 100, 55].into();
558        let _: Color = [50.0f32, 100.0, 55.0].into();
559        let _: Color = [50.0f64, 100.0, 55.0].into();
560
561        let _: Color = [50u8, 100, 55, 100].into();
562        let _: Color = [50i8, 100, 55, 100].into();
563        let _: Color = [50u16, 100, 55, 100].into();
564        let _: Color = [50i16, 100, 55, 100].into();
565        let _: Color = [50u32, 100, 55, 100].into();
566        let _: Color = [50i32, 100, 55, 100].into();
567        let _: Color = [50.0f32, 100.0, 55.0, 100.0].into();
568        let _: Color = [50.0f64, 100.0, 55.0, 100.0].into();
569    }
570
571    #[test]
572    fn test_hsb_to_rgb() {
573        assert_color_eq!(hsb!(0.0, 0.0, 0.0), rgb!(0, 0, 0));
574        assert_color_eq!(hsb!(0.0, 0.0, 100.0), rgb!(255, 255, 255));
575        assert_color_eq!(hsb!(0.0, 100.0, 100.0), rgb!(255, 0, 0));
576        assert_color_eq!(hsb!(120.0, 100.0, 100.0), rgb!(0, 255, 0));
577        assert_color_eq!(hsb!(240.0, 100.0, 100.0), rgb!(0, 0, 255));
578        assert_color_eq!(hsb!(60.0, 100.0, 100.0), rgb!(255, 255, 0));
579        assert_color_eq!(hsb!(180.0, 100.0, 100.0), rgb!(0, 255, 255));
580        assert_color_eq!(hsb!(300.0, 100.0, 100.0), rgb!(255, 0, 255));
581        assert_color_eq!(hsb!(0.0, 0.0, 75.0), rgb!(191, 191, 191));
582        assert_color_eq!(hsb!(0.0, 0.0, 50.0), rgb!(128, 128, 128));
583        assert_color_eq!(hsb!(0.0, 100.0, 50.0), rgb!(128, 0, 0));
584        assert_color_eq!(hsb!(60.0, 100.0, 50.0), rgb!(128, 128, 0));
585        assert_color_eq!(hsb!(120.0, 100.0, 50.0), rgb!(0, 128, 0));
586        assert_color_eq!(hsb!(300.0, 100.0, 50.0), rgb!(128, 0, 128));
587        assert_color_eq!(hsb!(180.0, 100.0, 50.0), rgb!(0, 128, 128));
588        assert_color_eq!(hsb!(240.0, 100.0, 50.0), rgb!(0, 0, 128));
589    }
590
591    #[test]
592    fn test_hsb_to_hsl() {
593        assert_color_eq!(hsb!(0.0, 0.0, 0.0), hsl!(0.0, 0.0, 0.0));
594        assert_color_eq!(hsb!(0.0, 0.0, 100.0), hsl!(0.0, 0.0, 100.0));
595        assert_color_eq!(hsb!(0.0, 100.0, 100.0), hsl!(0.0, 100.0, 50.0));
596        assert_color_eq!(hsb!(120.0, 100.0, 100.0), hsl!(120.0, 100.0, 50.0));
597        assert_color_eq!(hsb!(240.0, 100.0, 100.0), hsl!(240.0, 100.0, 50.0));
598        assert_color_eq!(hsb!(60.0, 100.0, 100.0), hsl!(60.0, 100.0, 50.0));
599        assert_color_eq!(hsb!(180.0, 100.0, 100.0), hsl!(180.0, 100.0, 50.0));
600        assert_color_eq!(hsb!(300.0, 100.0, 100.0), hsl!(300.0, 100.0, 50.0));
601        assert_color_eq!(hsb!(0.0, 0.0, 75.0), hsl!(0.0, 0.0, 75.0));
602        assert_color_eq!(hsb!(0.0, 0.0, 50.0), hsl!(0.0, 0.0, 50.0));
603        assert_color_eq!(hsb!(0.0, 100.0, 50.0), hsl!(0.0, 100.0, 25.0));
604        assert_color_eq!(hsb!(60.0, 100.0, 50.0), hsl!(60.0, 100.0, 25.0));
605        assert_color_eq!(hsb!(120.0, 100.0, 50.0), hsl!(120.0, 100.0, 25.0));
606        assert_color_eq!(hsb!(300.0, 100.0, 50.0), hsl!(300.0, 100.0, 25.0));
607        assert_color_eq!(hsb!(180.0, 100.0, 50.0), hsl!(180.0, 100.0, 25.0));
608        assert_color_eq!(hsb!(240.0, 100.0, 50.0), hsl!(240.0, 100.0, 25.0));
609    }
610
611    #[test]
612    fn test_hsl_to_rgb() {
613        assert_color_eq!(hsl!(0.0, 0.0, 0.0), rgb!(0, 0, 0));
614        assert_color_eq!(hsl!(0.0, 0.0, 100.0), rgb!(255, 255, 255));
615        assert_color_eq!(hsl!(0.0, 100.0, 100.0), rgb!(255, 255, 255));
616        assert_color_eq!(hsl!(120.0, 100.0, 100.0), rgb!(255, 255, 255));
617        assert_color_eq!(hsl!(240.0, 100.0, 100.0), rgb!(255, 255, 255));
618        assert_color_eq!(hsl!(60.0, 100.0, 100.0), rgb!(255, 255, 255));
619        assert_color_eq!(hsl!(180.0, 100.0, 100.0), rgb!(255, 255, 255));
620        assert_color_eq!(hsl!(300.0, 100.0, 100.0), rgb!(255, 255, 255));
621        assert_color_eq!(hsl!(0.0, 0.0, 75.0), rgb!(191, 191, 191));
622        assert_color_eq!(hsl!(0.0, 0.0, 50.0), rgb!(128, 128, 128));
623        assert_color_eq!(hsl!(0.0, 100.0, 50.0), rgb!(255, 0, 0));
624        assert_color_eq!(hsl!(60.0, 100.0, 50.0), rgb!(255, 255, 0));
625        assert_color_eq!(hsl!(120.0, 100.0, 50.0), rgb!(0, 255, 0));
626        assert_color_eq!(hsl!(300.0, 100.0, 50.0), rgb!(255, 0, 255));
627        assert_color_eq!(hsl!(180.0, 100.0, 50.0), rgb!(0, 255, 255));
628        assert_color_eq!(hsl!(240.0, 100.0, 50.0), rgb!(0, 0, 255));
629    }
630
631    #[test]
632    fn test_hsl_to_hsb() {
633        assert_color_eq!(hsl!(0.0, 0.0, 0.0), hsb!(0.0, 0.0, 0.0));
634        assert_color_eq!(hsl!(0.0, 0.0, 100.0), hsb!(0.0, 0.0, 100.0));
635        assert_color_eq!(hsl!(0.0, 100.0, 100.0), hsb!(0.0, 0.0, 100.0));
636        assert_color_eq!(hsl!(120.0, 100.0, 100.0), hsb!(120.0, 0.0, 100.0));
637        assert_color_eq!(hsl!(240.0, 100.0, 100.0), hsb!(240.0, 0.0, 100.0));
638        assert_color_eq!(hsl!(60.0, 100.0, 100.0), hsb!(60.0, 0.0, 100.0));
639        assert_color_eq!(hsl!(180.0, 100.0, 100.0), hsb!(180.0, 0.0, 100.0));
640        assert_color_eq!(hsl!(300.0, 100.0, 100.0), hsb!(300.0, 0.0, 100.0));
641        assert_color_eq!(hsl!(0.0, 0.0, 75.0), hsb!(0.0, 0.0, 75.0));
642        assert_color_eq!(hsl!(0.0, 0.0, 50.0), hsb!(0.0, 0.0, 50.0));
643        assert_color_eq!(hsl!(0.0, 100.0, 50.0), hsb!(0.0, 100.0, 100.0));
644        assert_color_eq!(hsl!(60.0, 100.0, 50.0), hsb!(60.0, 100.0, 100.0));
645        assert_color_eq!(hsl!(120.0, 100.0, 50.0), hsb!(120.0, 100.0, 100.0));
646        assert_color_eq!(hsl!(300.0, 100.0, 50.0), hsb!(300.0, 100.0, 100.0));
647        assert_color_eq!(hsl!(180.0, 100.0, 50.0), hsb!(180.0, 100.0, 100.0));
648        assert_color_eq!(hsl!(240.0, 100.0, 50.0), hsb!(240.0, 100.0, 100.0));
649    }
650
651    #[test]
652    fn test_rgb_to_hsb() {
653        assert_color_eq!(rgb!(0, 0, 0), hsb!(0.0, 0.0, 0.0));
654        assert_color_eq!(rgb!(255, 255, 255), hsb!(0.0, 0.0, 100.0));
655        assert_color_eq!(rgb!(255, 0, 0), hsb!(0.0, 100.0, 100.0));
656        assert_color_eq!(rgb!(0, 255, 0), hsb!(120.0, 100.0, 100.0));
657        assert_color_eq!(rgb!(0, 0, 255), hsb!(240.0, 100.0, 100.0));
658        assert_color_eq!(rgb!(255, 255, 0), hsb!(60.0, 100.0, 100.0));
659        assert_color_eq!(rgb!(0, 255, 255), hsb!(180.0, 100.0, 100.0));
660        assert_color_eq!(rgb!(255, 0, 255), hsb!(300.0, 100.0, 100.0));
661        assert_color_eq!(rgb!(191, 191, 191), hsb!(0.0, 0.0, 75.0));
662        assert_color_eq!(rgb!(128, 128, 128), hsb!(0.0, 0.0, 50.0));
663        assert_color_eq!(rgb!(128, 0, 0), hsb!(0.0, 100.0, 50.0));
664        assert_color_eq!(rgb!(128, 128, 0), hsb!(60.0, 100.0, 50.0));
665        assert_color_eq!(rgb!(0, 128, 0), hsb!(120.0, 100.0, 50.0));
666        assert_color_eq!(rgb!(128, 0, 128), hsb!(300.0, 100.0, 50.0));
667        assert_color_eq!(rgb!(0, 128, 128), hsb!(180.0, 100.0, 50.0));
668        assert_color_eq!(rgb!(0, 0, 128), hsb!(240.0, 100.0, 50.0));
669    }
670
671    #[test]
672    fn test_rgb_to_hsl() {
673        assert_color_eq!(rgb!(0, 0, 0), hsl!(0.0, 0.0, 0.0));
674        assert_color_eq!(rgb!(255, 255, 255), hsl!(0.0, 0.0, 100.0));
675        assert_color_eq!(rgb!(255, 0, 0), hsl!(0.0, 100.0, 50.0));
676        assert_color_eq!(rgb!(0, 255, 0), hsl!(120.0, 100.0, 50.0));
677        assert_color_eq!(rgb!(0, 0, 255), hsl!(240.0, 100.0, 50.0));
678        assert_color_eq!(rgb!(255, 255, 0), hsl!(60.0, 100.0, 50.0));
679        assert_color_eq!(rgb!(0, 255, 255), hsl!(180.0, 100.0, 50.0));
680        assert_color_eq!(rgb!(255, 0, 255), hsl!(300.0, 100.0, 50.0));
681        assert_color_eq!(rgb!(191, 191, 191), hsl!(0.0, 0.0, 75.0));
682        assert_color_eq!(rgb!(128, 128, 128), hsl!(0.0, 0.0, 50.0));
683        assert_color_eq!(rgb!(128, 0, 0), hsl!(0.0, 100.0, 25.0));
684        assert_color_eq!(rgb!(128, 128, 0), hsl!(60.0, 100.0, 25.0));
685        assert_color_eq!(rgb!(0, 128, 0), hsl!(120.0, 100.0, 25.0));
686        assert_color_eq!(rgb!(128, 0, 128), hsl!(300.0, 100.0, 25.0));
687        assert_color_eq!(rgb!(0, 128, 128), hsl!(180.0, 100.0, 25.0));
688        assert_color_eq!(rgb!(0, 0, 128), hsl!(240.0, 100.0, 25.0));
689    }
690}