zng_color/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Color and gradient types, functions and macros, [`Rgba`], [`filter`], [`hex!`] and more.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11#![recursion_limit = "256"]
12
13use std::{fmt, ops, sync::Arc};
14use zng_app_context::context_local;
15
16use zng_layout::unit::{AngleDegree, EQ_GRANULARITY, EQ_GRANULARITY_100, Factor, FactorUnits, about_eq, about_eq_hash, about_eq_ord};
17use zng_var::{
18    IntoVar, Var, VarValue,
19    animation::{Transition, Transitionable, easing::EasingStep},
20    context_var, expr_var, impl_from_and_into_var,
21};
22
23pub use zng_view_api::config::ColorScheme;
24
25#[doc(hidden)]
26pub use zng_color_proc_macros::hex_color;
27
28pub use zng_layout::unit::{Rgba, RgbaComponent};
29
30pub mod colors;
31pub mod filter;
32pub mod gradient;
33pub mod web_colors;
34
35mod mix;
36pub use mix::*;
37
38/// Hexadecimal color literal.
39///
40/// # Syntax
41///
42/// `[#|0x]RRGGBB[AA]` or `[#|0x]RGB[A]`.
43///
44/// An optional prefix `#` or `0x` is supported, after the prefix a hexadecimal integer literal is expected. The literal can be
45/// separated using `_`. No integer type suffix is allowed.
46///
47/// The literal is a sequence of 3 or 4 bytes (red, green, blue and alpha). If the sequence is in pairs each pair is a byte `[00..=FF]`.
48/// If the sequence is in single characters this is a shorthand that repeats the character for each byte, e.g. `#012F` equals `#001122FF`.
49///
50/// # Examples
51///
52/// ```
53/// # use zng_color::hex;
54/// let red = hex!(#FF0000);
55/// let green = hex!(#00FF00);
56/// let blue = hex!(#0000FF);
57/// let red_half_transparent = hex!(#FF00007F);
58///
59/// assert_eq!(red, hex!(#F00));
60/// assert_eq!(red, hex!(0xFF_00_00));
61/// assert_eq!(red, hex!(FF_00_00));
62/// ```
63///
64#[macro_export]
65macro_rules! hex {
66    ($($tt:tt)+) => {
67        $crate::hex_color!{$crate, $($tt)*}
68    };
69}
70
71fn lerp_rgba_linear(mut from: Rgba, to: Rgba, factor: Factor) -> Rgba {
72    from.red = from.red.lerp(&to.red, factor);
73    from.green = from.green.lerp(&to.green, factor);
74    from.blue = from.blue.lerp(&to.blue, factor);
75    from.alpha = from.alpha.lerp(&to.alpha, factor);
76    from
77}
78
79/// Default implementation of lerp for [`Rgba`] in apps.
80///
81/// Implements [`lerp_space`] dependent transition.
82///
83/// Apps set this as the default implementation on init.
84pub fn lerp_rgba(from: Rgba, to: Rgba, factor: Factor) -> Rgba {
85    match lerp_space() {
86        LerpSpace::HslaChromatic => Hsla::from(from).slerp_chromatic(to.into(), factor).into(),
87        LerpSpace::Rgba => lerp_rgba_linear(from, to, factor),
88        LerpSpace::Hsla => Hsla::from(from).slerp(to.into(), factor).into(),
89        LerpSpace::HslaLinear => Hsla::from(from).lerp_hsla(to.into(), factor).into(),
90    }
91}
92
93/// Pre-multiplied RGB + alpha.
94///
95/// Use from/into conversion to create.
96#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
97pub struct PreMulRgba {
98    /// [`Rgba::red`] multiplied by `alpha`.
99    pub red: f32,
100    /// [`Rgba::green`] multiplied by `alpha`.
101    pub green: f32,
102    /// [`Rgba::blue`] multiplied by `alpha`.
103    pub blue: f32,
104    /// Alpha channel value, in the `[0.0..=1.0]` range.
105    pub alpha: f32,
106}
107impl PartialEq for PreMulRgba {
108    fn eq(&self, other: &Self) -> bool {
109        about_eq(self.red, other.red, EQ_GRANULARITY)
110            && about_eq(self.green, other.green, EQ_GRANULARITY)
111            && about_eq(self.blue, other.blue, EQ_GRANULARITY)
112            && about_eq(self.alpha, other.alpha, EQ_GRANULARITY)
113    }
114}
115impl Eq for PreMulRgba {}
116impl std::hash::Hash for PreMulRgba {
117    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
118        about_eq_hash(self.red, EQ_GRANULARITY, state);
119        about_eq_hash(self.green, EQ_GRANULARITY, state);
120        about_eq_hash(self.blue, EQ_GRANULARITY, state);
121        about_eq_hash(self.alpha, EQ_GRANULARITY, state);
122    }
123}
124impl PartialOrd for PreMulRgba {
125    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
126        Some(self.cmp(other))
127    }
128}
129impl Ord for PreMulRgba {
130    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
131        about_eq_ord(self.red, other.red, EQ_GRANULARITY)
132            .cmp(&about_eq_ord(self.green, other.green, EQ_GRANULARITY))
133            .cmp(&about_eq_ord(self.blue, other.blue, EQ_GRANULARITY))
134            .cmp(&about_eq_ord(self.alpha, other.alpha, EQ_GRANULARITY))
135    }
136}
137
138impl_from_and_into_var! {
139    fn from(c: Rgba) -> PreMulRgba {
140        PreMulRgba {
141            red: c.red * c.alpha,
142            green: c.green * c.alpha,
143            blue: c.blue * c.alpha,
144            alpha: c.alpha,
145        }
146    }
147
148    fn from(c: PreMulRgba) -> Rgba {
149        Rgba {
150            red: c.red / c.alpha,
151            green: c.green / c.alpha,
152            blue: c.blue / c.alpha,
153            alpha: c.alpha,
154        }
155    }
156
157    fn from(c: Hsla) -> PreMulRgba {
158        Rgba::from(c).into()
159    }
160
161    fn from(c: PreMulRgba) -> Hsla {
162        Rgba::from(c).into()
163    }
164
165    fn from(c: Hsva) -> PreMulRgba {
166        Rgba::from(c).into()
167    }
168
169    fn from(c: PreMulRgba) -> Hsva {
170        Rgba::from(c).into()
171    }
172}
173
174/// HSL + alpha.
175///
176/// # Equality
177///
178/// Equality is determined using [`about_eq`] with `0.001` granularity for [`hue`](Hsla::hue)
179/// and `0.00001` granularity for the others.
180///
181/// [`about_eq`]: zng_layout::unit::about_eq
182#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
183pub struct Hsla {
184    /// Hue color angle in the `[0.0..=360.0]` range.
185    pub hue: f32,
186    /// Saturation amount in the `[0.0..=1.0]` range, zero is gray, one is full color.
187    pub saturation: f32,
188    /// Lightness amount in the `[0.0..=1.0]` range, zero is black, one is white.
189    pub lightness: f32,
190    /// Alpha channel in the `[0.0..=1.0]` range, zero is invisible, one is opaque.
191    pub alpha: f32,
192}
193impl PartialEq for Hsla {
194    fn eq(&self, other: &Self) -> bool {
195        about_eq(self.hue, other.hue, EQ_GRANULARITY_100)
196            && about_eq(self.saturation, other.saturation, EQ_GRANULARITY)
197            && about_eq(self.lightness, other.lightness, EQ_GRANULARITY)
198            && about_eq(self.alpha, other.alpha, EQ_GRANULARITY)
199    }
200}
201impl Eq for Hsla {}
202impl PartialOrd for Hsla {
203    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
204        Some(self.cmp(other))
205    }
206}
207impl Ord for Hsla {
208    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
209        about_eq_ord(self.hue, other.hue, EQ_GRANULARITY_100)
210            .cmp(&about_eq_ord(self.saturation, other.saturation, EQ_GRANULARITY))
211            .cmp(&about_eq_ord(self.lightness, other.lightness, EQ_GRANULARITY))
212            .cmp(&about_eq_ord(self.alpha, other.alpha, EQ_GRANULARITY))
213    }
214}
215impl std::hash::Hash for Hsla {
216    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
217        about_eq_hash(self.hue, EQ_GRANULARITY_100, state);
218        about_eq_hash(self.saturation, EQ_GRANULARITY, state);
219        about_eq_hash(self.lightness, EQ_GRANULARITY, state);
220        about_eq_hash(self.alpha, EQ_GRANULARITY, state);
221    }
222}
223impl Hsla {
224    /// Sets the [`hue`](Self::hue) color angle.
225    ///
226    /// The value is normalized to be in the `[0.0..=360.0]` range, that is `362.deg()` becomes `2.0`.
227    pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
228        self.hue = hue.into().modulo().0
229    }
230
231    /// Sets the [`lightness`](Self::lightness) value.
232    pub fn set_lightness<L: Into<Factor>>(&mut self, lightness: L) {
233        self.lightness = lightness.into().0;
234    }
235
236    /// Sets the [`saturation`](Self::saturation) value.
237    pub fn set_saturation<S: Into<Factor>>(&mut self, saturation: S) {
238        self.saturation = saturation.into().0;
239    }
240
241    /// Sets the [`alpha`](Self::alpha) value.
242    pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
243        self.alpha = alpha.into().0
244    }
245
246    /// Returns a copy of this color with a new `hue`.
247    pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
248        self.set_hue(hue);
249        self
250    }
251
252    /// Returns a copy of this color with a new `lightness`.
253    pub fn with_lightness<L: Into<Factor>>(mut self, lightness: L) -> Self {
254        self.set_lightness(lightness);
255        self
256    }
257
258    /// Returns a copy of this color with a new `saturation`.
259    pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
260        self.set_saturation(saturation);
261        self
262    }
263
264    /// Returns a copy of this color with a new `alpha`.
265    pub fn with_alpha<A: Into<Factor>>(mut self, alpha: A) -> Self {
266        self.set_alpha(alpha);
267        self
268    }
269
270    fn lerp_sla(mut self, to: Hsla, factor: Factor) -> Self {
271        self.saturation = self.saturation.lerp(&to.saturation, factor);
272        self.lightness = self.lightness.lerp(&to.lightness, factor);
273        self.alpha = self.alpha.lerp(&to.alpha, factor);
274        self
275    }
276
277    /// Interpolate in the [`LerpSpace::Hsla`] mode.
278    pub fn slerp(mut self, to: Self, factor: Factor) -> Self {
279        self = self.lerp_sla(to, factor);
280        self.hue = AngleDegree(self.hue).slerp(AngleDegree(to.hue), factor).0;
281        self
282    }
283
284    /// If the saturation is more than zero.
285    ///
286    /// If `false` the color is achromatic, the hue value does not affect the color.
287    pub fn is_chromatic(self) -> bool {
288        self.saturation > 0.0001
289    }
290
291    /// Interpolate in the [`LerpSpace::HslaChromatic`] mode.
292    pub fn slerp_chromatic(mut self, to: Self, factor: Factor) -> Self {
293        if self.is_chromatic() && to.is_chromatic() {
294            self.slerp(to, factor)
295        } else {
296            self = self.lerp_sla(to, factor);
297            if to.is_chromatic() {
298                self.hue = to.hue;
299            }
300            self
301        }
302    }
303
304    fn lerp_hsla(mut self, to: Self, factor: Factor) -> Self {
305        self = self.lerp_sla(to, factor);
306        self.hue = self.hue.lerp(&to.hue, factor);
307        self
308    }
309
310    fn lerp(self, to: Self, factor: Factor) -> Self {
311        match lerp_space() {
312            LerpSpace::HslaChromatic => self.slerp_chromatic(to, factor),
313            LerpSpace::Rgba => lerp_rgba_linear(self.into(), to.into(), factor).into(),
314            LerpSpace::Hsla => self.slerp(to, factor),
315            LerpSpace::HslaLinear => self.lerp_hsla(to, factor),
316        }
317    }
318}
319impl fmt::Debug for Hsla {
320    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321        if f.alternate() {
322            f.debug_struct("Hsla")
323                .field("hue", &self.hue)
324                .field("saturation", &self.saturation)
325                .field("lightness", &self.lightness)
326                .field("alpha", &self.alpha)
327                .finish()
328        } else {
329            fn p(n: f32) -> f32 {
330                clamp_normal(n) * 100.0
331            }
332            let a = p(self.alpha);
333            let h = AngleDegree(self.hue).modulo().0.round();
334            if (a - 100.0).abs() <= EQ_GRANULARITY {
335                write!(f, "hsl({h}.deg(), {}.pct(), {}.pct())", p(self.saturation), p(self.lightness))
336            } else {
337                write!(
338                    f,
339                    "hsla({h}.deg(), {}.pct(), {}.pct(), {}.pct())",
340                    p(self.saturation),
341                    p(self.lightness),
342                    a
343                )
344            }
345        }
346    }
347}
348impl fmt::Display for Hsla {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        fn p(n: f32) -> f32 {
351            clamp_normal(n) * 100.0
352        }
353        let a = p(self.alpha);
354        let h = AngleDegree(self.hue).modulo().0.round();
355        if (a - 100.0).abs() <= EQ_GRANULARITY {
356            write!(f, "hsl({h}º, {}%, {}%)", p(self.saturation), p(self.lightness))
357        } else {
358            write!(f, "hsla({h}º, {}%, {}%, {}%)", p(self.saturation), p(self.lightness), a)
359        }
360    }
361}
362impl Transitionable for Hsla {
363    fn lerp(self, to: &Self, step: EasingStep) -> Self {
364        self.lerp(*to, step)
365    }
366}
367
368/// HSV + alpha
369///
370/// # Equality
371///
372/// Equality is determined using [`about_eq`] with `0.001` granularity for [`hue`](Hsva::hue)
373/// and `0.00001` granularity for the others.
374///
375/// [`about_eq`]: zng_layout::unit::about_eq
376#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
377pub struct Hsva {
378    /// Hue color angle in the `[0.0..=360.0]` range.
379    pub hue: f32,
380    /// Saturation amount in the `[0.0..=1.0]` range, zero is gray, one is full color.
381    pub saturation: f32,
382    /// Brightness amount in the `[0.0..=1.0]` range, zero is black, one is white.
383    pub value: f32,
384    /// Alpha channel in the `[0.0..=1.0]` range, zero is invisible, one is opaque.
385    pub alpha: f32,
386}
387impl PartialEq for Hsva {
388    fn eq(&self, other: &Self) -> bool {
389        about_eq(self.hue, other.hue, EQ_GRANULARITY_100)
390            && about_eq(self.saturation, other.saturation, EQ_GRANULARITY)
391            && about_eq(self.value, other.value, EQ_GRANULARITY)
392            && about_eq(self.alpha, other.alpha, EQ_GRANULARITY)
393    }
394}
395impl Eq for Hsva {}
396impl PartialOrd for Hsva {
397    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
398        Some(self.cmp(other))
399    }
400}
401impl Ord for Hsva {
402    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
403        about_eq_ord(self.hue, other.hue, EQ_GRANULARITY_100)
404            .cmp(&about_eq_ord(self.saturation, other.saturation, EQ_GRANULARITY))
405            .cmp(&about_eq_ord(self.value, other.value, EQ_GRANULARITY))
406            .cmp(&about_eq_ord(self.alpha, other.alpha, EQ_GRANULARITY))
407    }
408}
409impl std::hash::Hash for Hsva {
410    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
411        about_eq_hash(self.hue, EQ_GRANULARITY_100, state);
412        about_eq_hash(self.saturation, EQ_GRANULARITY, state);
413        about_eq_hash(self.value, EQ_GRANULARITY, state);
414        about_eq_hash(self.alpha, EQ_GRANULARITY, state);
415    }
416}
417impl Hsva {
418    /// Sets the [`hue`](Self::hue) color angle.
419    ///
420    /// The value is normalized to be in the `[0.0..=360.0]` range, that is `362.deg()` becomes `2.0`.
421    pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
422        self.hue = hue.into().modulo().0
423    }
424
425    /// Sets the [`value`](Self::value).
426    pub fn set_value<L: Into<Factor>>(&mut self, value: L) {
427        self.value = value.into().0;
428    }
429
430    /// Sets the [`saturation`](Self::saturation) value.
431    pub fn set_saturation<L: Into<Factor>>(&mut self, saturation: L) {
432        self.saturation = saturation.into().0;
433    }
434
435    /// Sets the [`alpha`](Self::alpha) value.
436    pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
437        self.alpha = alpha.into().0
438    }
439
440    /// Returns a copy of this color with a new `hue`.
441    pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
442        self.set_hue(hue);
443        self
444    }
445
446    /// Returns a copy of this color with a new `value`.
447    pub fn with_value<V: Into<Factor>>(mut self, value: V) -> Self {
448        self.set_value(value);
449        self
450    }
451
452    /// Returns a copy of this color with a new `saturation`.
453    pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
454        self.set_saturation(saturation);
455        self
456    }
457
458    /// Returns a copy of this color with a new `alpha`.
459    pub fn with_alpha<A: Into<Factor>>(mut self, alpha: A) -> Self {
460        self.set_alpha(alpha);
461        self
462    }
463}
464impl fmt::Debug for Hsva {
465    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466        if f.alternate() {
467            f.debug_struct("Hsla")
468                .field("hue", &self.hue)
469                .field("saturation", &self.saturation)
470                .field("value", &self.value)
471                .field("alpha", &self.alpha)
472                .finish()
473        } else {
474            fn p(n: f32) -> f32 {
475                clamp_normal(n) * 100.0
476            }
477            let a = p(self.alpha);
478            let h = AngleDegree(self.hue).modulo().0.round();
479            if (a - 100.0).abs() <= EQ_GRANULARITY {
480                write!(f, "hsv({h}.deg(), {}.pct(), {}.pct())", p(self.saturation), p(self.value))
481            } else {
482                write!(
483                    f,
484                    "hsva({h}.deg(), {}.pct(), {}.pct(), {}.pct())",
485                    p(self.saturation),
486                    p(self.value),
487                    a
488                )
489            }
490        }
491    }
492}
493impl fmt::Display for Hsva {
494    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495        fn p(n: f32) -> f32 {
496            clamp_normal(n) * 100.0
497        }
498        let a = p(self.alpha);
499        let h = AngleDegree(self.hue).modulo().0.round();
500        if (a - 100.0).abs() <= EQ_GRANULARITY {
501            write!(f, "hsv({h}º, {}%, {}%)", p(self.saturation), p(self.value))
502        } else {
503            write!(f, "hsva({h}º, {}%, {}%, {}%)", p(self.saturation), p(self.value), a)
504        }
505    }
506}
507impl_from_and_into_var! {
508    fn from(hsla: Hsla) -> Hsva {
509        let lightness = clamp_normal(hsla.lightness);
510        let saturation = clamp_normal(hsla.saturation);
511
512        let value = lightness + saturation * lightness.min(1.0 - lightness);
513        let saturation = if value <= EQ_GRANULARITY {
514            0.0
515        } else {
516            2.0 * (1.0 - lightness / value)
517        };
518
519        Hsva {
520            hue: hsla.hue,
521            saturation,
522            value,
523            alpha: hsla.alpha,
524        }
525    }
526
527    fn from(hsva: Hsva) -> Hsla {
528        let saturation = clamp_normal(hsva.saturation);
529        let value = clamp_normal(hsva.value);
530
531        let lightness = value * (1.0 - saturation / 2.0);
532        let saturation = if lightness <= EQ_GRANULARITY || lightness >= 1.0 - EQ_GRANULARITY {
533            0.0
534        } else {
535            2.0 * (1.0 * lightness / value)
536        };
537
538        Hsla {
539            hue: hsva.hue,
540            saturation,
541            lightness,
542            alpha: hsva.alpha,
543        }
544    }
545
546    fn from(hsva: Hsva) -> Rgba {
547        let hue = AngleDegree(hsva.hue).modulo().0;
548        let saturation = clamp_normal(hsva.saturation);
549        let value = clamp_normal(hsva.value);
550
551        let c = value * saturation;
552        let hue = hue / 60.0;
553        let x = c * (1.0 - (hue.rem_euclid(2.0) - 1.0).abs());
554
555        let (red, green, blue) = if hue <= 1.0 {
556            (c, x, 0.0)
557        } else if hue <= 2.0 {
558            (x, c, 0.0)
559        } else if hue <= 3.0 {
560            (0.0, c, x)
561        } else if hue <= 4.0 {
562            (0.0, x, c)
563        } else if hue <= 5.0 {
564            (x, 0.0, c)
565        } else if hue <= 6.0 {
566            (c, 0.0, x)
567        } else {
568            (0.0, 0.0, 0.0)
569        };
570
571        let m = value - c;
572
573        let f = |n: f32| ((n + m) * 255.0).round() / 255.0;
574
575        Rgba {
576            red: f(red),
577            green: f(green),
578            blue: f(blue),
579            alpha: hsva.alpha,
580        }
581    }
582
583    fn from(hsla: Hsla) -> Rgba {
584        if hsla.saturation <= EQ_GRANULARITY {
585            return rgba(hsla.lightness, hsla.lightness, hsla.lightness, hsla.alpha);
586        }
587
588        let hue = AngleDegree(hsla.hue).modulo().0;
589        let saturation = clamp_normal(hsla.saturation);
590        let lightness = clamp_normal(hsla.lightness);
591
592        let c = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
593        let hp = hue / 60.0;
594        let x = c * (1.0 - ((hp % 2.0) - 1.0).abs());
595        let (red, green, blue) = if hp <= 1.0 {
596            (c, x, 0.0)
597        } else if hp <= 2.0 {
598            (x, c, 0.0)
599        } else if hp <= 3.0 {
600            (0.0, c, x)
601        } else if hp <= 4.0 {
602            (0.0, x, c)
603        } else if hp <= 5.0 {
604            (x, 0.0, c)
605        } else if hp <= 6.0 {
606            (c, 0.0, x)
607        } else {
608            (0.0, 0.0, 0.0)
609        };
610        let m = lightness - c * 0.5;
611
612        let f = |i: f32| ((i + m) * 255.0).round() / 255.0;
613
614        Rgba {
615            red: f(red),
616            green: f(green),
617            blue: f(blue),
618            alpha: hsla.alpha,
619        }
620    }
621}
622impl Transitionable for Hsva {
623    fn lerp(self, to: &Self, step: EasingStep) -> Self {
624        match lerp_space() {
625            LerpSpace::HslaChromatic => Hsla::from(self).slerp_chromatic((*to).into(), step).into(),
626            LerpSpace::Rgba => lerp_rgba_linear(self.into(), (*to).into(), step).into(),
627            LerpSpace::Hsla => Hsla::from(self).slerp((*to).into(), step).into(),
628            LerpSpace::HslaLinear => Hsla::from(self).lerp_hsla((*to).into(), step).into(),
629        }
630    }
631}
632
633macro_rules! cylindrical_color {
634    ($rgba:ident -> ($min:ident, $max:ident, $delta:ident, $hue:ident)) => {
635        fn sanitize(i: f32) -> f32 {
636            clamp_normal((i * 255.0).round() / 255.0)
637        }
638
639        let r = sanitize($rgba.red);
640        let g = sanitize($rgba.green);
641        let b = sanitize($rgba.blue);
642
643        let $min = r.min(g).min(b);
644        let $max = r.max(g).max(b);
645
646        fn about_eq(a: f32, b: f32) -> bool {
647            (a - b) <= EQ_GRANULARITY
648        }
649
650        let $delta = $max - $min;
651
652        let $hue = if $delta <= EQ_GRANULARITY {
653            0.0
654        } else {
655            60.0 * if about_eq($max, r) {
656                ((g - b) / $delta).rem_euclid(6.0)
657            } else if about_eq($max, g) {
658                (b - r) / $delta + 2.0
659            } else {
660                debug_assert!(about_eq($max, b));
661                (r - g) / $delta + 4.0
662            }
663        };
664    };
665}
666
667impl_from_and_into_var! {
668    fn from(rgba: Rgba) -> Hsva {
669        cylindrical_color!(rgba -> (min, max, delta, hue));
670
671        let saturation = if max <= EQ_GRANULARITY { 0.0 } else { delta / max };
672
673        let value = max;
674
675        Hsva {
676            hue,
677            saturation,
678            value,
679            alpha: rgba.alpha,
680        }
681    }
682
683    fn from(rgba: Rgba) -> Hsla {
684        cylindrical_color!(rgba -> (min, max, delta, hue));
685
686        let lightness = (max + min) / 2.0;
687
688        let saturation = if delta <= EQ_GRANULARITY {
689            0.0
690        } else {
691            delta / (1.0 - (2.0 * lightness - 1.0).abs())
692        };
693
694        Hsla {
695            hue,
696            lightness,
697            saturation,
698            alpha: rgba.alpha,
699        }
700    }
701}
702
703// Util
704fn clamp_normal(i: f32) -> f32 {
705    i.clamp(0.0, 1.0)
706}
707
708/// RGB color, opaque, alpha is set to `1.0`.
709///
710/// # Arguments
711///
712/// The arguments can either be [`f32`] in the `0.0..=1.0` range or
713/// [`u8`] in the `0..=255` range or a [percentage](zng_layout::unit::FactorPercent).
714///
715/// # Examples
716///
717/// ```
718/// use zng_color::rgb;
719///
720/// let red = rgb(1.0, 0.0, 0.0);
721/// let green = rgb(0, 255, 0);
722/// ```
723pub fn rgb<C: Into<RgbaComponent>>(red: C, green: C, blue: C) -> Rgba {
724    rgba(red, green, blue, 1.0)
725}
726
727/// RGBA color.
728///
729/// # Arguments
730///
731/// The arguments can either be `f32` in the `0.0..=1.0` range or
732/// `u8` in the `0..=255` range or a [percentage](zng_layout::unit::FactorPercent).
733///
734/// The rgb arguments must be of the same type, the alpha argument can be of a different type.
735///
736/// # Examples
737///
738/// ```
739/// use zng_color::rgba;
740///
741/// let half_red = rgba(255, 0, 0, 0.5);
742/// let green = rgba(0.0, 1.0, 0.0, 1.0);
743/// let transparent = rgba(0, 0, 0, 0);
744/// ```
745pub fn rgba<C: Into<RgbaComponent>, A: Into<RgbaComponent>>(red: C, green: C, blue: C, alpha: A) -> Rgba {
746    Rgba {
747        red: red.into().0,
748        green: green.into().0,
749        blue: blue.into().0,
750        alpha: alpha.into().0,
751    }
752}
753
754/// HSL color, opaque, alpha is set to `1.0`.
755///
756/// # Arguments
757///
758/// The first argument `hue` can be any [angle unit]. The other two arguments can be [`f32`] in the `0.0..=1.0`
759/// range or a [percentage](zng_layout::unit::FactorPercent).
760///
761/// The `saturation` and `lightness` arguments must be of the same type.
762///
763/// # Examples
764///
765/// ```
766/// use zng_color::hsl;
767/// use zng_layout::unit::*;
768///
769/// let red = hsl(0.deg(), 100.pct(), 50.pct());
770/// let green = hsl(115.deg(), 1.0, 0.5);
771/// ```
772///
773/// [angle unit]: trait@zng_layout::unit::AngleUnits
774pub fn hsl<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, lightness: N) -> Hsla {
775    hsla(hue, saturation, lightness, 1.0)
776}
777
778/// HSLA color.
779///
780/// # Arguments
781///
782/// The first argument `hue` can be any [angle unit]. The other two arguments can be [`f32`] in the `0.0..=1.0`
783/// range or a [percentage](zng_layout::unit::FactorPercent).
784///
785/// The `saturation` and `lightness` arguments must be of the same type.
786///
787/// # Examples
788///
789/// ```
790/// use zng_color::hsla;
791/// use zng_layout::unit::*;
792///
793/// let red = hsla(0.deg(), 100.pct(), 50.pct(), 1.0);
794/// let green = hsla(115.deg(), 1.0, 0.5, 100.pct());
795/// let transparent = hsla(0.deg(), 1.0, 0.5, 0.0);
796/// ```
797///
798/// [angle unit]: trait@zng_layout::unit::AngleUnits
799pub fn hsla<H: Into<AngleDegree>, N: Into<Factor>, A: Into<Factor>>(hue: H, saturation: N, lightness: N, alpha: A) -> Hsla {
800    Hsla {
801        hue: hue.into().0,
802        saturation: saturation.into().0,
803        lightness: lightness.into().0,
804        alpha: alpha.into().0,
805    }
806}
807
808/// HSV color, opaque, alpha is set to `1.0`.
809///
810/// # Arguments
811///
812/// The first argument `hue` can be any [angle unit]. The other two arguments can be [`f32`] in the `0.0..=1.0`
813/// range or a [percentage](zng_layout::unit::FactorPercent).
814///
815/// The `saturation` and `value` arguments must be of the same type.
816///
817/// # Examples
818///
819/// ```
820/// use zng_color::hsv;
821/// use zng_layout::unit::*;
822///
823/// let red = hsv(0.deg(), 100.pct(), 50.pct());
824/// let green = hsv(115.deg(), 1.0, 0.5);
825/// ```
826///
827/// [angle unit]: trait@zng_layout::unit::AngleUnits
828pub fn hsv<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, value: N) -> Hsva {
829    hsva(hue, saturation, value, 1.0)
830}
831
832/// HSVA color.
833///
834/// # Arguments
835///
836/// The first argument `hue` can be any [angle unit]. The other two arguments can be [`f32`] in the `0.0..=1.0`
837/// range or a [percentage](zng_layout::unit::FactorPercent).
838///
839/// The `saturation` and `value` arguments must be of the same type.
840///
841/// # Examples
842///
843/// ```
844/// use zng_color::hsva;
845/// use zng_layout::unit::*;
846///
847/// let red = hsva(0.deg(), 100.pct(), 50.pct(), 1.0);
848/// let green = hsva(115.deg(), 1.0, 0.5, 100.pct());
849/// let transparent = hsva(0.deg(), 1.0, 0.5, 0.0);
850/// ```
851///
852/// [angle unit]: trait@zng_layout::unit::AngleUnits
853pub fn hsva<H: Into<AngleDegree>, N: Into<Factor>, A: Into<Factor>>(hue: H, saturation: N, value: N, alpha: A) -> Hsva {
854    Hsva {
855        hue: hue.into().0,
856        saturation: saturation.into().0,
857        value: value.into().0,
858        alpha: alpha.into().0,
859    }
860}
861
862context_var! {
863    /// Defines the preferred color scheme in a context.
864    pub static COLOR_SCHEME_VAR: ColorScheme = ColorScheme::default();
865}
866
867/// RGBA color pair.
868///
869/// # Arguments
870///
871/// The arguments can be any color type that converts to [`Rgba`]. The first color
872/// is used in [`ColorScheme::Dark`] contexts, the second color is used in [`ColorScheme::Light`] contexts.
873///
874/// Note that [`LightDark`] converts `IntoVar<Rgba>` with a contextual var that selects the color, so you
875/// can just set color properties directly with a color pair .
876pub fn light_dark(light: impl Into<Rgba>, dark: impl Into<Rgba>) -> LightDark {
877    LightDark {
878        light: light.into(),
879        dark: dark.into(),
880    }
881}
882
883/// Represents a dark and light *color*.
884///
885///
886/// Note that this struct converts `IntoVar<Rgba>` with a contextual var that selects the color, so you
887/// can just set color properties directly with a color pair.
888#[derive(Debug, Clone, Copy, PartialEq, Hash, serde::Serialize, serde::Deserialize, Transitionable)]
889pub struct LightDark {
890    /// Color used when [`ColorScheme::Dark`].
891    pub dark: Rgba,
892    /// Color used when [`ColorScheme::Light`].
893    pub light: Rgba,
894}
895impl_from_and_into_var! {
896    /// From `(light, dark)` tuple.
897    fn from<L: Into<Rgba>, D: Into<Rgba>>((light, dark): (L, D)) -> LightDark {
898        LightDark {
899            light: light.into(),
900            dark: dark.into(),
901        }
902    }
903
904    /// From same color to both.
905    fn from(color: Rgba) -> LightDark {
906        LightDark { dark: color, light: color }
907    }
908
909    /// From same color to both.
910    fn from(color: Hsva) -> LightDark {
911        Rgba::from(color).into()
912    }
913
914    /// From same color to both.
915    fn from(color: Hsla) -> LightDark {
916        Rgba::from(color).into()
917    }
918
919    fn from(color: LightDark) -> Option<LightDark>;
920}
921impl IntoVar<Rgba> for LightDark {
922    fn into_var(self) -> Var<Rgba> {
923        COLOR_SCHEME_VAR.map(move |s| match s {
924            ColorScheme::Light => self.light,
925            ColorScheme::Dark => self.dark,
926            _ => self.light,
927        })
928    }
929}
930impl LightDark {
931    /// New from light, dark colors.
932    pub fn new(light: impl Into<Rgba>, dark: impl Into<Rgba>) -> Self {
933        Self {
934            light: light.into(),
935            dark: dark.into(),
936        }
937    }
938
939    /// Overlay WHITE/BLACK to the dark/light color depending on the `factor`, negative factor inverts overlay.
940    pub fn shade_fct(mut self, factor: impl Into<Factor>) -> Self {
941        let mut factor = factor.into();
942        let (dark_overlay, light_overlay) = if factor > 0.fct() {
943            (colors::WHITE, colors::BLACK)
944        } else {
945            factor = factor.abs();
946            (colors::BLACK, colors::WHITE)
947        };
948        self.dark = dark_overlay.with_alpha(factor).mix_normal(self.dark);
949        self.light = light_overlay.with_alpha(factor).mix_normal(self.light);
950        self
951    }
952
953    /// Shade at 8% increments.
954    ///
955    /// Common usage: 1=hovered, 2=pressed.
956    pub fn shade(self, shade: i8) -> Self {
957        self.shade_fct(shade as f32 * 0.08)
958    }
959
960    /// Gets a contextual `Rgba` var that selects the color for the context scheme.
961    ///
962    /// Also see [`LightDarkVarExt`] for mapping from vars.
963    pub fn rgba(self) -> Var<Rgba> {
964        IntoVar::<Rgba>::into_var(self)
965    }
966
967    /// Gets a contextual `Rgba` var that selects the color for the context scheme and `map` it.
968    pub fn rgba_map<T: VarValue>(self, mut map: impl FnMut(Rgba) -> T + Send + 'static) -> Var<T> {
969        COLOR_SCHEME_VAR.map(move |s| match s {
970            ColorScheme::Light => map(self.light),
971            ColorScheme::Dark => map(self.dark),
972            _ => map(self.light),
973        })
974    }
975
976    /// Gets a contextual `Rgba` var that selects the color for the context scheme and converts it to `T`.
977    pub fn rgba_into<T: VarValue + From<Rgba>>(self) -> Var<T> {
978        self.rgba_map(T::from)
979    }
980}
981impl ops::Index<ColorScheme> for LightDark {
982    type Output = Rgba;
983
984    fn index(&self, index: ColorScheme) -> &Self::Output {
985        match index {
986            ColorScheme::Light => &self.light,
987            ColorScheme::Dark => &self.dark,
988            _ => &self.light,
989        }
990    }
991}
992impl ops::IndexMut<ColorScheme> for LightDark {
993    fn index_mut(&mut self, index: ColorScheme) -> &mut Self::Output {
994        match index {
995            ColorScheme::Light => &mut self.light,
996            ColorScheme::Dark => &mut self.dark,
997            _ => &mut self.light,
998        }
999    }
1000}
1001
1002/// Extension methods for `Var<LightDark>`.
1003pub trait LightDarkVarExt {
1004    /// Gets a contextualized var that maps to [`LightDark::rgba`].
1005    fn rgba(&self) -> Var<Rgba>;
1006    /// Gets a contextualized var that maps to [`LightDark::rgba`] and `map`.
1007    fn rgba_map<T: VarValue>(&self, map: impl FnMut(Rgba) -> T + Send + 'static) -> Var<T>;
1008    /// Gets a contextualized var that maps to [`LightDark::rgba`] converted into `T`.
1009    fn rgba_into<T: VarValue + From<Rgba>>(&self) -> Var<T>;
1010
1011    /// Gets a contextualized var that maps using `map` and then to [`LightDark::rgba`].
1012    fn map_rgba(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<Rgba>;
1013    /// Gets a contextualized var that maps using `map` and then into `T`.
1014    fn map_rgba_into<T: VarValue + From<Rgba>>(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<T>;
1015
1016    /// Gets a contextualized var that maps to [`LightDark::shade_fct`] and then to [`LightDark::rgba`].
1017    fn shade_fct(&self, fct: impl Into<Factor>) -> Var<Rgba>;
1018    /// Gets a contextualized var that maps to [`LightDark::shade_fct`] and then to [`LightDark::rgba`] and then into `T`.
1019    fn shade_fct_into<T: VarValue + From<Rgba>>(&self, fct: impl Into<Factor>) -> Var<T>;
1020
1021    /// Gets a contextualized var that maps to [`LightDark::shade`] and then to [`LightDark::rgba`].   
1022    ///
1023    /// * +1 - Hovered.
1024    /// * +2 - Pressed.
1025    fn shade(&self, shade: i8) -> Var<Rgba>;
1026    /// Gets a contextualized var that maps to [`LightDark::shade`] and then to [`LightDark::rgba`] and then into `T`.
1027    fn shade_into<T: VarValue + From<Rgba>>(&self, shade: i8) -> Var<T>;
1028}
1029impl LightDarkVarExt for Var<LightDark> {
1030    fn rgba(&self) -> Var<Rgba> {
1031        expr_var! {
1032            let c = #{self.clone()};
1033            match *#{COLOR_SCHEME_VAR} {
1034                ColorScheme::Light => c.light,
1035                ColorScheme::Dark => c.dark,
1036                _ => c.light,
1037            }
1038        }
1039    }
1040
1041    fn rgba_map<T: VarValue>(&self, mut map: impl FnMut(Rgba) -> T + Send + 'static) -> Var<T> {
1042        expr_var! {
1043            let c = #{self.clone()};
1044            match *#{COLOR_SCHEME_VAR} {
1045                ColorScheme::Light => map(c.light),
1046                ColorScheme::Dark => map(c.dark),
1047                _ => map(c.light),
1048            }
1049        }
1050    }
1051
1052    fn rgba_into<T: VarValue + From<Rgba>>(&self) -> Var<T> {
1053        self.rgba_map(Into::into)
1054    }
1055
1056    fn map_rgba(&self, mut map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<Rgba> {
1057        expr_var! {
1058            let c = map(*#{self.clone()});
1059            match *#{COLOR_SCHEME_VAR} {
1060                ColorScheme::Light => c.light,
1061                ColorScheme::Dark => c.dark,
1062                _ => c.light,
1063            }
1064        }
1065    }
1066
1067    fn map_rgba_into<T: VarValue + From<Rgba>>(&self, mut map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<T> {
1068        expr_var! {
1069            let c = map(*#{self.clone()});
1070            match *#{COLOR_SCHEME_VAR} {
1071                ColorScheme::Light => T::from(c.light),
1072                ColorScheme::Dark => T::from(c.dark),
1073                _ => T::from(c.light),
1074            }
1075        }
1076    }
1077
1078    fn shade_fct(&self, fct: impl Into<Factor>) -> Var<Rgba> {
1079        let fct = fct.into();
1080        expr_var! {
1081            let c = #{self.clone()}.shade_fct(fct);
1082            match *#{COLOR_SCHEME_VAR} {
1083                ColorScheme::Light => c.light,
1084                ColorScheme::Dark => c.dark,
1085                _ => c.light,
1086            }
1087        }
1088    }
1089
1090    fn shade(&self, shade: i8) -> Var<Rgba> {
1091        expr_var! {
1092            let c = #{self.clone()}.shade(shade);
1093            match *#{COLOR_SCHEME_VAR} {
1094                ColorScheme::Light => c.light,
1095                ColorScheme::Dark => c.dark,
1096                _ => c.light,
1097            }
1098        }
1099    }
1100
1101    fn shade_fct_into<T: VarValue + From<Rgba>>(&self, fct: impl Into<Factor>) -> Var<T> {
1102        let fct = fct.into();
1103        expr_var! {
1104            let c = #{self.clone()}.shade_fct(fct);
1105            match *#{COLOR_SCHEME_VAR} {
1106                ColorScheme::Light => T::from(c.light),
1107                ColorScheme::Dark => T::from(c.dark),
1108                _ => T::from(c.light),
1109            }
1110        }
1111    }
1112
1113    fn shade_into<T: VarValue + From<Rgba>>(&self, shade: i8) -> Var<T> {
1114        expr_var! {
1115            let c = #{self.clone()}.shade(shade);
1116            match *#{COLOR_SCHEME_VAR} {
1117                ColorScheme::Light => T::from(c.light),
1118                ColorScheme::Dark => T::from(c.dark),
1119                _ => T::from(c.light),
1120            }
1121        }
1122    }
1123}
1124
1125/// Defines the color space for color interpolation.
1126///
1127/// See [`with_lerp_space`] for more details.
1128#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1129pub enum LerpSpace {
1130    /// Linear interpolation in each RGBA component.
1131    Rgba,
1132    /// Spherical linear interpolation in Hue (shorter path), linear interpolation in SLA.
1133    Hsla,
1134    /// Linear interpolate SLA, spherical linear interpolation in Hue (short) if both colors are chromatic (S>0) or
1135    /// jumps to the chromatic hue from the start.
1136    #[default]
1137    HslaChromatic,
1138    /// Linear interpolation in each HSLA component.
1139    HslaLinear,
1140}
1141
1142/// Gets the lerp space used for color interpolation.
1143///
1144/// Must be called only inside the [`with_lerp_space`] closure or in the lerp implementation of a variable animating
1145/// with [`rgba_sampler`] or [`hsla_linear_sampler`].
1146pub fn lerp_space() -> LerpSpace {
1147    LERP_SPACE.get_clone()
1148}
1149
1150/// Calls `f` with [`lerp_space`] set to `space`.
1151///
1152/// See [`rgba_sampler`] and [`hsla_linear_sampler`] for a way to set the space in animations.
1153pub fn with_lerp_space<R>(space: LerpSpace, f: impl FnOnce() -> R) -> R {
1154    LERP_SPACE.with_context(&mut Some(Arc::new(space)), f)
1155}
1156
1157/// Animation sampler that sets the [`lerp_space`] to [`LerpSpace::Rgba`].
1158///
1159/// Samplers can be set in animations using the [`Var::easing_with`] method.
1160///
1161/// [`Var::easing_with`]: zng_var::Var::easing_with
1162pub fn rgba_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1163    with_lerp_space(LerpSpace::Rgba, || t.sample(step))
1164}
1165
1166/// Animation sampler that sets the [`lerp_space`] to [`LerpSpace::Hsla`].
1167///
1168/// Note that this is already the default.
1169pub fn hsla_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1170    with_lerp_space(LerpSpace::Hsla, || t.sample(step))
1171}
1172
1173/// Animation sampler that sets the [`lerp_space`] to [`LerpSpace::HslaLinear`].
1174///
1175/// Samplers can be set in animations using the [`Var::easing_with`] method.
1176///
1177/// [`Var::easing_with`]: zng_var::Var::easing_with
1178pub fn hsla_linear_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1179    with_lerp_space(LerpSpace::HslaLinear, || t.sample(step))
1180}
1181
1182context_local! {
1183    static LERP_SPACE: LerpSpace = LerpSpace::default();
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188    use super::*;
1189    use zng_layout::unit::AngleUnits as _;
1190
1191    #[test]
1192    fn hsl_red() {
1193        assert_eq!(Rgba::from(hsl(0.0.deg(), 100.pct(), 50.pct())), rgb(1.0, 0.0, 0.0))
1194    }
1195
1196    #[test]
1197    fn hsl_color() {
1198        assert_eq!(Rgba::from(hsl(91.0.deg(), 1.0, 0.5)), rgb(123, 255, 0))
1199    }
1200
1201    #[test]
1202    fn rgb_to_hsl() {
1203        let color = rgba(0, 100, 200, 0.2);
1204        let a = format!("{color:?}");
1205        let b = format!("{:?}", Rgba::from(Hsla::from(color)));
1206        assert_eq!(a, b)
1207    }
1208
1209    #[test]
1210    fn rgb_to_hsv() {
1211        let color = rgba(0, 100, 200, 0.2);
1212        let a = format!("{color:?}");
1213        let b = format!("{:?}", Rgba::from(Hsva::from(color)));
1214        assert_eq!(a, b)
1215    }
1216
1217    #[test]
1218    fn rgba_display() {
1219        macro_rules! test {
1220            ($($tt:tt)+) => {
1221                let expected = stringify!($($tt)+).replace(" ", "");
1222                let actual = hex!($($tt)+).to_string();
1223                assert_eq!(expected, actual);
1224            }
1225        }
1226
1227        test!(#AABBCC);
1228        test!(#123456);
1229        test!(#000000);
1230        test!(#FFFFFF);
1231
1232        test!(#AABBCCDD);
1233        test!(#12345678);
1234        test!(#00000000);
1235        test!(#FFFFFF00);
1236    }
1237
1238    #[test]
1239    fn test_hex_color() {
1240        fn f(n: u8) -> f32 {
1241            n as f32 / 255.0
1242        }
1243        assert_eq!(Rgba::new(f(0x11), f(0x22), f(0x33), f(0x44)), hex!(0x11223344));
1244
1245        assert_eq!(colors::BLACK, hex!(0x00_00_00_FF));
1246        assert_eq!(colors::WHITE, hex!(0xFF_FF_FF_FF));
1247        assert_eq!(colors::WHITE, hex!(0xFF_FF_FF));
1248        assert_eq!(colors::WHITE, hex!(0xFFFFFF));
1249        assert_eq!(colors::WHITE, hex!(#FFFFFF));
1250        assert_eq!(colors::WHITE, hex!(FFFFFF));
1251        assert_eq!(colors::WHITE, hex!(0xFFFF));
1252        assert_eq!(colors::BLACK, hex!(0x000));
1253        assert_eq!(colors::BLACK, hex!(#000));
1254        assert_eq!(colors::BLACK, hex!(000));
1255    }
1256
1257    // #[test]
1258    // fn rgb_to_hsv_all() {
1259    //     for r in 0..=255 {
1260    //         println!("{r}");
1261    //         for g in 0..=255 {
1262    //             for b in 0..=255 {
1263    //                 let color = rgb(r, g, b);
1264    //                 let a = color.to_string();
1265    //                 let b = color.to_hsva().to_rgba().to_string();
1266    //                 assert_eq!(a, b)
1267    //             }
1268    //         }
1269    //     }
1270    // }
1271}