1use crate::{HSL, HSV, RGB, RGBA};
2
3pub trait ToRgba: Copy {
23 fn to_rgba(self) -> RGBA;
25}
26
27pub trait FromRgba: Sized {
31 fn from_rgba(rgba: RGBA) -> Self;
32}
33
34impl ToRgba for RGBA {
35 fn to_rgba(self) -> RGBA { self }
36}
37
38impl FromRgba for RGBA {
39 fn from_rgba(rgba: RGBA) -> Self { rgba }
40}
41
42impl ToRgba for RGB {
43 fn to_rgba(self) -> RGBA { RGBA(self.0, self.1, self.2, 1.0) }
44}
45
46impl FromRgba for RGB {
47 fn from_rgba(rgba: RGBA) -> Self { RGB(rgba.0, rgba.1, rgba.2) }
48}
49
50impl ToRgba for HSV {
51 fn to_rgba(self) -> RGBA { hsv_to_rgba(self) }
52}
53
54impl FromRgba for HSV {
55 fn from_rgba(rgba: RGBA) -> Self { rgba_to_hsv(rgba) }
56}
57
58impl ToRgba for HSL {
59 fn to_rgba(self) -> RGBA { hsl_to_rgba(self) }
60}
61
62impl FromRgba for HSL {
63 fn from_rgba(rgba: RGBA) -> Self { rgba_to_hsl(rgba) }
64}
65
66impl From<RGB> for RGBA { fn from(rgb: RGB) -> Self { rgb.to_rgba() } }
67impl From<HSV> for RGBA { fn from(hsv: HSV) -> Self { hsv.to_rgba() } }
68impl From<HSL> for RGBA { fn from(hsl: HSL) -> Self { hsl.to_rgba() } }
69impl From<RGBA> for RGB { fn from(rgba: RGBA) -> Self { RGB::from_rgba(rgba) } }
70impl From<RGBA> for HSV { fn from(rgba: RGBA) -> Self { HSV::from_rgba(rgba) } }
71impl From<RGBA> for HSL { fn from(rgba: RGBA) -> Self { HSL::from_rgba(rgba) } }
72
73pub(crate) fn hsv_to_rgba(hsv: HSV) -> RGBA {
74 let h = hsv.h / 60.0;
75 let s = hsv.s;
76 let v = hsv.v;
77 let i = h.floor() as i32;
78 let f = h - h.floor();
79 let p = v * (1.0 - s);
80 let q = v * (1.0 - s * f);
81 let t = v * (1.0 - s * (1.0 - f));
82 let (r, g, b) = match i % 6 {
83 0 => (v, t, p),
84 1 => (q, v, p),
85 2 => (p, v, t),
86 3 => (p, q, v),
87 4 => (t, p, v),
88 5 => (v, p, q),
89 _ => (v, p, q),
90 };
91 RGBA(r, g, b, 1.0)
92}
93
94pub(crate) fn rgba_to_hsv(rgba: RGBA) -> HSV {
95 let r = rgba.0;
96 let g = rgba.1;
97 let b = rgba.2;
98 let mx = r.max(g).max(b);
99 let mn = r.min(g).min(b);
100 let d = mx - mn;
101 let h = if d == 0.0 {
102 0.0
103 } else if mx == r {
104 60.0 * (((g - b) / d) % 6.0)
105 } else if mx == g {
106 60.0 * (((b - r) / d) + 2.0)
107 } else {
108 60.0 * (((r - g) / d) + 4.0)
109 };
110 let h = if h < 0.0 { h + 360.0 } else { h };
111 let s = if mx == 0.0 { 0.0 } else { d / mx };
112 HSV { h: h.clamp(0.0, 360.0), s: s.clamp(0.0, 1.0), v: mx.clamp(0.0, 1.0) }
113}
114
115pub(crate) fn hsl_to_rgba(hsl: HSL) -> RGBA {
116 let h = hsl.h / 360.0;
117 let s = hsl.s;
118 let l = hsl.l;
119 if s == 0.0 {
120 return RGBA(l, l, l, 1.0);
121 }
122 fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
123 if t < 0.0 { t += 1.0; }
124 if t > 1.0 { t -= 1.0; }
125 if t < 1.0 / 6.0 { p + (q - p) * 6.0 * t }
126 else if t < 1.0 / 2.0 { q }
127 else if t < 2.0 / 3.0 { p + (q - p) * (2.0 / 3.0 - t) * 6.0 }
128 else { p }
129 }
130 let q = if l < 0.5 { l * (1.0 + s) } else { l + s - l * s };
131 let p = 2.0 * l - q;
132 let r = hue_to_rgb(p, q, h + 1.0 / 3.0);
133 let g = hue_to_rgb(p, q, h);
134 let b = hue_to_rgb(p, q, h - 1.0 / 3.0);
135 RGBA(r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0), 1.0)
136}
137
138pub(crate) fn rgba_to_hsl(rgba: RGBA) -> HSL {
139 let r = rgba.0;
140 let g = rgba.1;
141 let b = rgba.2;
142 let mx = r.max(g).max(b);
143 let mn = r.min(g).min(b);
144 let d = mx - mn;
145 let l = (mx + mn) / 2.0;
146 let s = if d == 0.0 { 0.0 } else { d / (1.0 - (2.0 * l - 1.0).abs()) };
147 let h = if d == 0.0 {
148 0.0
149 } else if mx == r {
150 60.0 * (((g - b) / d) % 6.0)
151 } else if mx == g {
152 60.0 * (((b - r) / d) + 2.0)
153 } else {
154 60.0 * (((r - g) / d) + 4.0)
155 };
156 let h = if h < 0.0 { h + 360.0 } else { h };
157 HSL { h: h.clamp(0.0, 360.0), s: s.clamp(0.0, 1.0), l: l.clamp(0.0, 1.0) }
158}
159
160pub trait ColorInfo: ToRgba {
174 fn luminance(self) -> f32 {
178 let c = self.to_rgba();
179 0.2126 * c.0 + 0.7152 * c.1 + 0.0722 * c.2
180 }
181
182 fn is_light(self) -> bool { self.luminance() > 0.5 }
184
185 fn contrast_ratio(self, other: impl ToRgba) -> f32 {
190 let l1 = self.luminance();
191 let l2 = other.luminance();
192 let (lighter, darker) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
193 (lighter + 0.05) / (darker + 0.05)
194 }
195
196 fn to_hex(self) -> String {
198 let (r, g, b, a) = self.to_bytes();
199 format!("#{r:02x}{g:02x}{b:02x}{a:02x}")
200 }
201
202 fn to_bytes(self) -> (u8, u8, u8, u8) {
204 let c = self.to_rgba();
205 let r = (c.0.clamp(0.0, 1.0) * 255.0).round() as u8;
206 let g = (c.1.clamp(0.0, 1.0) * 255.0).round() as u8;
207 let b = (c.2.clamp(0.0, 1.0) * 255.0).round() as u8;
208 let a = (c.3.clamp(0.0, 1.0) * 255.0).round() as u8;
209 (r, g, b, a)
210 }
211}
212
213impl<T: ToRgba> ColorInfo for T {}