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