csscolorparser/
color.rs

1use std::convert::TryFrom;
2use std::fmt;
3use std::str::FromStr;
4
5#[cfg(feature = "rust-rgb")]
6use rgb::{RGB, RGBA};
7
8#[cfg(feature = "serde")]
9use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
10
11#[cfg(feature = "lab")]
12use crate::lab::{lab_to_linear_rgb, linear_rgb_to_lab};
13
14use crate::utils::*;
15use crate::{parse, ParseColorError};
16
17#[cfg(feature = "named-colors")]
18use crate::NAMED_COLORS;
19
20#[derive(Debug, Clone, PartialEq, PartialOrd)]
21/// The color
22pub struct Color {
23    /// Red
24    pub r: f32,
25    /// Green
26    pub g: f32,
27    /// Blue
28    pub b: f32,
29    /// Alpha
30    pub a: f32,
31}
32
33impl Color {
34    /// Arguments:
35    ///
36    /// * `r`: Red value [0..1]
37    /// * `g`: Green value [0..1]
38    /// * `b`: Blue value [0..1]
39    /// * `a`: Alpha value [0..1]
40    pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
41        Self { r, g, b, a }
42    }
43
44    /// Arguments:
45    ///
46    /// * `r`: Red value [0..255]
47    /// * `g`: Green value [0..255]
48    /// * `b`: Blue value [0..255]
49    /// * `a`: Alpha value [0..255]
50    pub fn from_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
51        Self {
52            r: r as f32 / 255.0,
53            g: g as f32 / 255.0,
54            b: b as f32 / 255.0,
55            a: a as f32 / 255.0,
56        }
57    }
58
59    /// Arguments:
60    ///
61    /// * `r`: Red value [0..1]
62    /// * `g`: Green value [0..1]
63    /// * `b`: Blue value [0..1]
64    /// * `a`: Alpha value [0..1]
65    pub fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
66        fn from_linear(x: f32) -> f32 {
67            if x >= 0.0031308 {
68                return 1.055 * x.powf(1.0 / 2.4) - 0.055;
69            }
70            12.92 * x
71        }
72        Self::new(from_linear(r), from_linear(g), from_linear(b), a)
73    }
74
75    /// Arguments:
76    ///
77    /// * `r`: Red value [0..255]
78    /// * `g`: Green value [0..255]
79    /// * `b`: Blue value [0..255]
80    /// * `a`: Alpha value [0..255]
81    pub fn from_linear_rgba8(r: u8, g: u8, b: u8, a: u8) -> Self {
82        Self::from_linear_rgba(
83            r as f32 / 255.0,
84            g as f32 / 255.0,
85            b as f32 / 255.0,
86            a as f32 / 255.0,
87        )
88    }
89
90    /// Arguments:
91    ///
92    /// * `h`: Hue angle [0..360]
93    /// * `s`: Saturation [0..1]
94    /// * `v`: Value [0..1]
95    /// * `a`: Alpha [0..1]
96    pub fn from_hsva(h: f32, s: f32, v: f32, a: f32) -> Self {
97        let [r, g, b] = hsv_to_rgb(normalize_angle(h), s.clamp(0.0, 1.0), v.clamp(0.0, 1.0));
98        Self::new(r, g, b, a)
99    }
100
101    /// Arguments:
102    ///
103    /// * `h`: Hue angle [0..360]
104    /// * `s`: Saturation [0..1]
105    /// * `l`: Lightness [0..1]
106    /// * `a`: Alpha [0..1]
107    pub fn from_hsla(h: f32, s: f32, l: f32, a: f32) -> Self {
108        let [r, g, b] = hsl_to_rgb(normalize_angle(h), s.clamp(0.0, 1.0), l.clamp(0.0, 1.0));
109        Self::new(r, g, b, a)
110    }
111
112    /// Arguments:
113    ///
114    /// * `h`: Hue angle [0..360]
115    /// * `w`: Whiteness [0..1]
116    /// * `b`: Blackness [0..1]
117    /// * `a`: Alpha [0..1]
118    pub fn from_hwba(h: f32, w: f32, b: f32, a: f32) -> Self {
119        let [r, g, b] = hwb_to_rgb(normalize_angle(h), w.clamp(0.0, 1.0), b.clamp(0.0, 1.0));
120        Self::new(r, g, b, a)
121    }
122
123    /// Arguments:
124    ///
125    /// * `l`: Perceived lightness
126    /// * `a`: How green/red the color is
127    /// * `b`: How blue/yellow the color is
128    /// * `alpha`: Alpha [0..1]
129    pub fn from_oklaba(l: f32, a: f32, b: f32, alpha: f32) -> Self {
130        let [r, g, b] = oklab_to_linear_rgb(l, a, b);
131        Self::from_linear_rgba(r, g, b, alpha)
132    }
133
134    /// Arguments:
135    ///
136    /// * `l`: Perceived lightness
137    /// * `c`: Chroma
138    /// * `h`: Hue angle in radians
139    /// * `alpha`: Alpha [0..1]
140    pub fn from_oklcha(l: f32, c: f32, h: f32, alpha: f32) -> Self {
141        Self::from_oklaba(l, c * h.cos(), c * h.sin(), alpha)
142    }
143
144    #[cfg(feature = "lab")]
145    /// Arguments:
146    ///
147    /// * `l`: Lightness
148    /// * `a`: Distance along the `a` axis
149    /// * `b`: Distance along the `b` axis
150    /// * `alpha`: Alpha [0..1]
151    pub fn from_laba(l: f32, a: f32, b: f32, alpha: f32) -> Self {
152        let [r, g, b] = lab_to_linear_rgb(l, a, b);
153        Self::from_linear_rgba(r, g, b, alpha)
154    }
155
156    #[cfg(feature = "lab")]
157    /// Arguments:
158    ///
159    /// * `l`: Lightness
160    /// * `c`: Chroma
161    /// * `h`: Hue angle in radians
162    /// * `alpha`: Alpha [0..1]
163    pub fn from_lcha(l: f32, c: f32, h: f32, alpha: f32) -> Self {
164        Self::from_laba(l, c * h.cos(), c * h.sin(), alpha)
165    }
166
167    /// Create color from CSS color string.
168    ///
169    /// # Examples
170    /// ```
171    /// use csscolorparser::Color;
172    /// # use std::error::Error;
173    /// # fn main() -> Result<(), Box<dyn Error>> {
174    ///
175    /// let c = Color::from_html("rgb(255,0,0)")?;
176    ///
177    /// assert_eq!(c.to_array(), [1.0, 0.0, 0.0, 1.0]);
178    /// assert_eq!(c.to_rgba8(), [255, 0, 0, 255]);
179    /// assert_eq!(c.to_css_hex(), "#ff0000");
180    /// assert_eq!(c.to_css_rgb(), "rgb(255 0 0)");
181    /// # Ok(())
182    /// # }
183    /// ```
184    pub fn from_html<S: AsRef<str>>(s: S) -> Result<Self, ParseColorError> {
185        parse(s.as_ref())
186    }
187
188    /// Restricts R, G, B, A values to the range [0..1].
189    pub fn clamp(&self) -> Self {
190        Self {
191            r: self.r.clamp(0.0, 1.0),
192            g: self.g.clamp(0.0, 1.0),
193            b: self.b.clamp(0.0, 1.0),
194            a: self.a.clamp(0.0, 1.0),
195        }
196    }
197
198    /// Returns name if there is a name for this color.
199    ///
200    /// **Note:** It ignores transparency (alpha value).
201    ///
202    /// ```
203    /// use csscolorparser::Color;
204    ///
205    /// assert_eq!(Color::from_rgba8(255, 0, 0, 255).name(), Some("red"));
206    /// assert_eq!(Color::from_rgba8(238, 130, 238, 255).name(), Some("violet"));
207    /// assert_eq!(Color::from_rgba8(90, 150, 200, 255).name(), None);
208    /// ```
209    #[cfg(feature = "named-colors")]
210    pub fn name(&self) -> Option<&'static str> {
211        let rgb = &self.to_rgba8()[0..3];
212        for (&k, &v) in NAMED_COLORS.entries() {
213            if v == rgb {
214                return Some(k);
215            }
216        }
217        None
218    }
219
220    /// Returns: `[r, g, b, a]`
221    ///
222    /// * Red, green, blue and alpha in the range [0..1]
223    pub fn to_array(&self) -> [f32; 4] {
224        [
225            self.r.clamp(0.0, 1.0),
226            self.g.clamp(0.0, 1.0),
227            self.b.clamp(0.0, 1.0),
228            self.a.clamp(0.0, 1.0),
229        ]
230    }
231
232    /// Returns: `[r, g, b, a]`
233    ///
234    /// * Red, green, blue and alpha in the range [0..255]
235    pub fn to_rgba8(&self) -> [u8; 4] {
236        [
237            (self.r * 255.0 + 0.5) as u8,
238            (self.g * 255.0 + 0.5) as u8,
239            (self.b * 255.0 + 0.5) as u8,
240            (self.a * 255.0 + 0.5) as u8,
241        ]
242    }
243
244    /// Returns: `[r, g, b, a]`
245    ///
246    /// * Red, green, blue and alpha in the range [0..65535]
247    pub fn to_rgba16(&self) -> [u16; 4] {
248        [
249            (self.r * 65535.0 + 0.5) as u16,
250            (self.g * 65535.0 + 0.5) as u16,
251            (self.b * 65535.0 + 0.5) as u16,
252            (self.a * 65535.0 + 0.5) as u16,
253        ]
254    }
255
256    /// Returns: `[h, s, v, a]`
257    ///
258    /// * `h`: Hue angle [0..360]
259    /// * `s`: Saturation [0..1]
260    /// * `v`: Value [0..1]
261    /// * `a`: Alpha [0..1]
262    pub fn to_hsva(&self) -> [f32; 4] {
263        let [h, s, v] = rgb_to_hsv(
264            self.r.clamp(0.0, 1.0),
265            self.g.clamp(0.0, 1.0),
266            self.b.clamp(0.0, 1.0),
267        );
268        [
269            h,
270            s.clamp(0.0, 1.0),
271            v.clamp(0.0, 1.0),
272            self.a.clamp(0.0, 1.0),
273        ]
274    }
275
276    /// Returns: `[h, s, l, a]`
277    ///
278    /// * `h`: Hue angle [0..360]
279    /// * `s`: Saturation [0..1]
280    /// * `l`: Lightness [0..1]
281    /// * `a`: Alpha [0..1]
282    pub fn to_hsla(&self) -> [f32; 4] {
283        let [h, s, l] = rgb_to_hsl(
284            self.r.clamp(0.0, 1.0),
285            self.g.clamp(0.0, 1.0),
286            self.b.clamp(0.0, 1.0),
287        );
288        [
289            h,
290            s.clamp(0.0, 1.0),
291            l.clamp(0.0, 1.0),
292            self.a.clamp(0.0, 1.0),
293        ]
294    }
295
296    /// Returns: `[h, w, b, a]`
297    ///
298    /// * `h`: Hue angle [0..360]
299    /// * `w`: Whiteness [0..1]
300    /// * `b`: Blackness [0..1]
301    /// * `a`: Alpha [0..1]
302    pub fn to_hwba(&self) -> [f32; 4] {
303        let [h, w, b] = rgb_to_hwb(
304            self.r.clamp(0.0, 1.0),
305            self.g.clamp(0.0, 1.0),
306            self.b.clamp(0.0, 1.0),
307        );
308        [
309            h,
310            w.clamp(0.0, 1.0),
311            b.clamp(0.0, 1.0),
312            self.a.clamp(0.0, 1.0),
313        ]
314    }
315
316    /// Returns: `[r, g, b, a]`
317    ///
318    /// * Red, green, blue and alpha in the range [0..1]
319    pub fn to_linear_rgba(&self) -> [f32; 4] {
320        fn to_linear(x: f32) -> f32 {
321            if x >= 0.04045 {
322                return ((x + 0.055) / 1.055).powf(2.4);
323            }
324            x / 12.92
325        }
326        [
327            to_linear(self.r),
328            to_linear(self.g),
329            to_linear(self.b),
330            self.a,
331        ]
332    }
333
334    /// Returns: `[r, g, b, a]`
335    ///
336    /// * Red, green, blue and alpha in the range [0..255]
337    pub fn to_linear_rgba_u8(&self) -> [u8; 4] {
338        let [r, g, b, a] = self.to_linear_rgba();
339        [
340            (r * 255.0).round() as u8,
341            (g * 255.0).round() as u8,
342            (b * 255.0).round() as u8,
343            (a * 255.0).round() as u8,
344        ]
345    }
346
347    /// Returns: `[l, a, b, alpha]`
348    pub fn to_oklaba(&self) -> [f32; 4] {
349        let [r, g, b, _] = self.to_linear_rgba();
350        let [l, a, b] = linear_rgb_to_oklab(r, g, b);
351        [l, a, b, self.a.clamp(0.0, 1.0)]
352    }
353
354    /// Returns: `[l, c, h, alpha]`
355    pub fn to_oklcha(&self) -> [f32; 4] {
356        let [l, a, b, alpha] = self.to_oklaba();
357        let c = (a * a + b * b).sqrt();
358        let h = b.atan2(a);
359        [l, c, h, alpha]
360    }
361
362    #[cfg(feature = "lab")]
363    /// Returns: `[l, a, b, alpha]`
364    pub fn to_laba(&self) -> [f32; 4] {
365        let [r, g, b, alpha] = self.to_linear_rgba();
366        let [l, a, b] = linear_rgb_to_lab(r, g, b);
367        [l, a, b, alpha.clamp(0.0, 1.0)]
368    }
369
370    #[cfg(feature = "lab")]
371    /// Returns: `[l, c, h, alpha]`
372    pub fn to_lcha(&self) -> [f32; 4] {
373        let [l, a, b, alpha] = self.to_laba();
374        let c = (a * a + b * b).sqrt();
375        let h = b.atan2(a);
376        [l, c, h, alpha.clamp(0.0, 1.0)]
377    }
378
379    /// Get CSS RGB hexadecimal color representation
380    pub fn to_css_hex(&self) -> String {
381        let [r, g, b, a] = self.to_rgba8();
382        if a < 255 {
383            format!("#{r:02x}{g:02x}{b:02x}{a:02x}")
384        } else {
385            format!("#{r:02x}{g:02x}{b:02x}")
386        }
387    }
388
389    /// Get CSS `rgb()` color representation
390    pub fn to_css_rgb(&self) -> String {
391        let [r, g, b, _] = self.to_rgba8();
392        format!("rgb({r} {g} {b}{})", fmt_alpha(self.a))
393    }
394
395    /// Get CSS `hsl()` color representation
396    pub fn to_css_hsl(&self) -> String {
397        let [h, s, l, alpha] = self.to_hsla();
398        let h = if h.is_nan() {
399            "none".into()
400        } else {
401            fmt_float(h, 2)
402        };
403        let s = (s * 100.0 + 0.5).floor();
404        let l = (l * 100.0 + 0.5).floor();
405        format!("hsl({h} {s}% {l}%{})", fmt_alpha(alpha))
406    }
407
408    /// Get CSS `hwb()` color representation
409    pub fn to_css_hwb(&self) -> String {
410        let [h, w, b, alpha] = self.to_hwba();
411        let h = if h.is_nan() {
412            "none".into()
413        } else {
414            fmt_float(h, 2)
415        };
416        let w = (w * 100.0 + 0.5).floor();
417        let b = (b * 100.0 + 0.5).floor();
418        format!("hwb({h} {w}% {b}%{})", fmt_alpha(alpha))
419    }
420
421    /// Get CSS `oklab()` color representation
422    pub fn to_css_oklab(&self) -> String {
423        let [l, a, b, alpha] = self.to_oklaba();
424        let l = fmt_float(l, 3);
425        let a = fmt_float(a, 3);
426        let b = fmt_float(b, 3);
427        format!("oklab({l} {a} {b}{})", fmt_alpha(alpha))
428    }
429
430    /// Get CSS `oklch()` color representation
431    pub fn to_css_oklch(&self) -> String {
432        let [l, c, h, alpha] = self.to_oklcha();
433        let l = fmt_float(l, 3);
434        let c = fmt_float(c, 3);
435        let h = fmt_float(normalize_angle(h.to_degrees()), 2);
436        format!("oklch({l} {c} {h}{})", fmt_alpha(alpha))
437    }
438
439    #[cfg(feature = "lab")]
440    /// Get CSS `lab()` color representation
441    pub fn to_css_lab(&self) -> String {
442        let [l, a, b, alpha] = self.to_laba();
443        let l = fmt_float(l, 2);
444        let a = fmt_float(a, 2);
445        let b = fmt_float(b, 2);
446        format!("lab({l} {a} {b}{})", fmt_alpha(alpha))
447    }
448
449    #[cfg(feature = "lab")]
450    /// Get CSS `lch()` color representation
451    pub fn to_css_lch(&self) -> String {
452        use std::f32::consts::PI;
453
454        fn to_degrees(t: f32) -> f32 {
455            if t > 0.0 {
456                t / PI * 180.0
457            } else {
458                360.0 - (t.abs() / PI) * 180.0
459            }
460        }
461
462        let [l, c, h, alpha] = self.to_lcha();
463        let l = fmt_float(l, 2);
464        let c = fmt_float(c, 2);
465        let h = fmt_float(to_degrees(h), 2);
466        format!("lch({l} {c} {h}{})", fmt_alpha(alpha))
467    }
468
469    /// Blend this color with the other one, in the RGB color-space. `t` in the range [0..1].
470    pub fn interpolate_rgb(&self, other: &Color, t: f32) -> Self {
471        Self {
472            r: self.r + t * (other.r - self.r),
473            g: self.g + t * (other.g - self.g),
474            b: self.b + t * (other.b - self.b),
475            a: self.a + t * (other.a - self.a),
476        }
477    }
478
479    /// Blend this color with the other one, in the linear RGB color-space. `t` in the range [0..1].
480    pub fn interpolate_linear_rgb(&self, other: &Color, t: f32) -> Self {
481        let [r1, g1, b1, a1] = self.to_linear_rgba();
482        let [r2, g2, b2, a2] = other.to_linear_rgba();
483        Self::from_linear_rgba(
484            r1 + t * (r2 - r1),
485            g1 + t * (g2 - g1),
486            b1 + t * (b2 - b1),
487            a1 + t * (a2 - a1),
488        )
489    }
490
491    /// Blend this color with the other one, in the HSV color-space. `t` in the range [0..1].
492    pub fn interpolate_hsv(&self, other: &Color, t: f32) -> Self {
493        let [h1, s1, v1, a1] = self.to_hsva();
494        let [h2, s2, v2, a2] = other.to_hsva();
495        Self::from_hsva(
496            interp_angle(h1, h2, t),
497            s1 + t * (s2 - s1),
498            v1 + t * (v2 - v1),
499            a1 + t * (a2 - a1),
500        )
501    }
502
503    /// Blend this color with the other one, in the [Oklab](https://bottosson.github.io/posts/oklab/) color-space. `t` in the range [0..1].
504    pub fn interpolate_oklab(&self, other: &Color, t: f32) -> Self {
505        let [l1, a1, b1, alpha1] = self.to_oklaba();
506        let [l2, a2, b2, alpha2] = other.to_oklaba();
507        Self::from_oklaba(
508            l1 + t * (l2 - l1),
509            a1 + t * (a2 - a1),
510            b1 + t * (b2 - b1),
511            alpha1 + t * (alpha2 - alpha1),
512        )
513    }
514
515    #[cfg(feature = "lab")]
516    /// Blend this color with the other one, in the Lab color-space. `t` in the range [0..1].
517    pub fn interpolate_lab(&self, other: &Color, t: f32) -> Self {
518        let [l1, a1, b1, alpha1] = self.to_laba();
519        let [l2, a2, b2, alpha2] = other.to_laba();
520        Self::from_laba(
521            l1 + t * (l2 - l1),
522            a1 + t * (a2 - a1),
523            b1 + t * (b2 - b1),
524            alpha1 + t * (alpha2 - alpha1),
525        )
526    }
527
528    #[cfg(feature = "lab")]
529    /// Blend this color with the other one, in the LCH color-space. `t` in the range [0..1].
530    pub fn interpolate_lch(&self, other: &Color, t: f32) -> Self {
531        let [l1, c1, h1, alpha1] = self.to_lcha();
532        let [l2, c2, h2, alpha2] = other.to_lcha();
533        Self::from_lcha(
534            l1 + t * (l2 - l1),
535            c1 + t * (c2 - c1),
536            interp_angle_rad(h1, h2, t),
537            alpha1 + t * (alpha2 - alpha1),
538        )
539    }
540}
541
542impl Default for Color {
543    fn default() -> Self {
544        Self {
545            r: 0.0,
546            g: 0.0,
547            b: 0.0,
548            a: 1.0,
549        }
550    }
551}
552
553impl fmt::Display for Color {
554    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
555        write!(f, "RGBA({},{},{},{})", self.r, self.g, self.b, self.a)
556    }
557}
558
559impl FromStr for Color {
560    type Err = ParseColorError;
561
562    fn from_str(s: &str) -> Result<Self, Self::Err> {
563        parse(s)
564    }
565}
566
567impl TryFrom<&str> for Color {
568    type Error = ParseColorError;
569
570    fn try_from(s: &str) -> Result<Self, Self::Error> {
571        parse(s)
572    }
573}
574
575impl From<(f32, f32, f32, f32)> for Color {
576    fn from((r, g, b, a): (f32, f32, f32, f32)) -> Self {
577        Self { r, g, b, a }
578    }
579}
580
581impl From<(f32, f32, f32)> for Color {
582    fn from((r, g, b): (f32, f32, f32)) -> Self {
583        Self { r, g, b, a: 1.0 }
584    }
585}
586
587impl From<[f32; 4]> for Color {
588    fn from([r, g, b, a]: [f32; 4]) -> Self {
589        Self { r, g, b, a }
590    }
591}
592
593impl From<[f32; 3]> for Color {
594    fn from([r, g, b]: [f32; 3]) -> Self {
595        Self { r, g, b, a: 1.0 }
596    }
597}
598
599impl From<[f64; 4]> for Color {
600    fn from([r, g, b, a]: [f64; 4]) -> Self {
601        Self {
602            r: r as f32,
603            g: g as f32,
604            b: b as f32,
605            a: a as f32,
606        }
607    }
608}
609
610impl From<[f64; 3]> for Color {
611    fn from([r, g, b]: [f64; 3]) -> Self {
612        Self {
613            r: r as f32,
614            g: g as f32,
615            b: b as f32,
616            a: 1.0,
617        }
618    }
619}
620
621impl From<(u8, u8, u8, u8)> for Color {
622    fn from((r, g, b, a): (u8, u8, u8, u8)) -> Self {
623        Self::from_rgba8(r, g, b, a)
624    }
625}
626
627impl From<(u8, u8, u8)> for Color {
628    fn from((r, g, b): (u8, u8, u8)) -> Self {
629        Self::from_rgba8(r, g, b, 255)
630    }
631}
632
633impl From<[u8; 4]> for Color {
634    fn from([r, g, b, a]: [u8; 4]) -> Self {
635        Self::from_rgba8(r, g, b, a)
636    }
637}
638
639impl From<[u8; 3]> for Color {
640    fn from([r, g, b]: [u8; 3]) -> Self {
641        Self::from_rgba8(r, g, b, 255)
642    }
643}
644
645/// Convert rust-rgb's `RGB<f32>` type into `Color`.
646#[cfg(feature = "rust-rgb")]
647impl From<RGB<f32>> for Color {
648    fn from(item: RGB<f32>) -> Self {
649        Self::new(item.r, item.g, item.b, 1.0)
650    }
651}
652
653/// Convert rust-rgb's `RGBA<f32>` type into `Color`.
654#[cfg(feature = "rust-rgb")]
655impl From<RGBA<f32>> for Color {
656    fn from(item: RGBA<f32>) -> Self {
657        Self::new(item.r, item.g, item.b, item.a)
658    }
659}
660
661/// Implement Serde serialization into HEX string
662#[cfg(feature = "serde")]
663impl Serialize for Color {
664    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
665        serializer.serialize_str(&self.to_css_hex())
666    }
667}
668
669/// Implement Serde deserialization from string
670#[cfg(feature = "serde")]
671impl<'de> Deserialize<'de> for Color {
672    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
673        deserializer.deserialize_str(ColorVisitor)
674    }
675}
676
677#[cfg(feature = "serde")]
678struct ColorVisitor;
679
680#[cfg(feature = "serde")]
681impl Visitor<'_> for ColorVisitor {
682    type Value = Color;
683
684    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
685        f.write_str("a valid css color")
686    }
687
688    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
689    where
690        E: serde::de::Error,
691    {
692        Color::from_str(v).map_err(serde::de::Error::custom)
693    }
694}
695
696fn fmt_float(t: f32, precision: usize) -> String {
697    let s = format!("{:.1$}", t, precision);
698    s.trim_end_matches('0').trim_end_matches('.').to_string()
699}
700
701fn fmt_alpha(alpha: f32) -> String {
702    if alpha < 1.0 {
703        format!(" / {}%", (alpha.max(0.0) * 100.0 + 0.5).floor())
704    } else {
705        "".into()
706    }
707}
708
709#[cfg(test)]
710mod tests {
711    #[cfg(any(feature = "serde", feature = "rust-rgb"))]
712    use super::*;
713
714    #[cfg(feature = "rust-rgb")]
715    #[test]
716    fn test_convert_rust_rgb_to_color() {
717        let rgb = RGB::new(0.0, 0.5, 1.0);
718        assert_eq!(Color::new(0.0, 0.5, 1.0, 1.0), Color::from(rgb));
719
720        let rgba = RGBA::new(1.0, 0.5, 0.0, 0.5);
721        assert_eq!(Color::new(1.0, 0.5, 0.0, 0.5), Color::from(rgba));
722    }
723
724    #[cfg(feature = "serde")]
725    #[test]
726    fn test_serde_serialize_to_hex() {
727        let color = Color::new(1.0, 1.0, 0.5, 0.5);
728        serde_test::assert_ser_tokens(&color, &[serde_test::Token::Str("#ffff8080")]);
729    }
730
731    #[cfg(all(feature = "serde", feature = "named-colors"))]
732    #[test]
733    fn test_serde_deserialize_from_string() {
734        let named = Color::new(1.0, 1.0, 0.0, 1.0);
735        serde_test::assert_de_tokens(&named, &[serde_test::Token::Str("yellow")]);
736
737        let hex = Color::new(0.0, 1.0, 0.0, 1.0);
738        serde_test::assert_de_tokens(&hex, &[serde_test::Token::Str("#00ff00ff")]);
739
740        let rgb = Color::new(0.0, 1.0, 0.0, 1.0);
741        serde_test::assert_de_tokens(&rgb, &[serde_test::Token::Str("rgba(0,255,0,1)")]);
742    }
743}