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)]
21pub struct Color {
23 pub r: f32,
25 pub g: f32,
27 pub b: f32,
29 pub a: f32,
31}
32
33impl Color {
34 pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
41 Self { r, g, b, a }
42 }
43
44 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 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 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 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 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 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 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 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 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 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 pub fn from_html<S: AsRef<str>>(s: S) -> Result<Self, ParseColorError> {
185 parse(s.as_ref())
186 }
187
188 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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#[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#[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#[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#[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}