1use super::{deg, percent, Angle, Color, Ratio, HSL, HSLA};
2use std::fmt;
3
4pub fn rgb(r: u8, g: u8, b: u8) -> RGB {
18 RGB {
19 r: Ratio::from_u8(r),
20 g: Ratio::from_u8(g),
21 b: Ratio::from_u8(b),
22 }
23}
24
25pub fn rgba(r: u8, g: u8, b: u8, a: f32) -> RGBA {
42 RGBA {
43 r: Ratio::from_u8(r),
44 g: Ratio::from_u8(g),
45 b: Ratio::from_u8(b),
46 a: Ratio::from_f32(a),
47 }
48}
49
50#[derive(Debug, Copy, Clone, PartialEq)]
51pub struct RGB {
57 pub r: Ratio,
59
60 pub g: Ratio,
62
63 pub b: Ratio,
65}
66
67impl fmt::Display for RGB {
68 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69 write!(
70 f,
71 "rgb({}, {}, {})",
72 self.r.as_u8(),
73 self.g.as_u8(),
74 self.b.as_u8()
75 )
76 }
77}
78
79impl Color for RGB {
80 type Alpha = RGBA;
81
82 fn to_css(self) -> String {
83 self.to_string()
84 }
85
86 fn to_rgb(self) -> RGB {
87 self
88 }
89
90 fn to_rgba(self) -> RGBA {
91 let RGB { r, g, b } = self;
92
93 RGBA {
94 r,
95 g,
96 b,
97 a: percent(100),
98 }
99 }
100
101 fn to_hsl(self) -> HSL {
104 self.to_rgba().to_hsl()
105 }
106
107 fn to_hsla(self) -> HSLA {
108 self.to_rgba().to_hsla()
109 }
110
111 fn saturate(self, amount: Ratio) -> Self {
112 self.to_rgba().saturate(amount).to_rgb()
113 }
114
115 fn desaturate(self, amount: Ratio) -> Self {
116 self.to_rgba().desaturate(amount).to_rgb()
117 }
118
119 fn lighten(self, amount: Ratio) -> Self {
120 self.to_rgba().lighten(amount).to_rgb()
121 }
122
123 fn darken(self, amount: Ratio) -> Self {
124 self.to_rgba().darken(amount).to_rgb()
125 }
126
127 fn fadein(self, amount: Ratio) -> RGBA {
128 self.to_rgba().fadein(amount)
129 }
130
131 fn fadeout(self, amount: Ratio) -> RGBA {
132 self.to_rgba().fadeout(amount)
133 }
134
135 fn fade(self, amount: Ratio) -> RGBA {
136 self.to_rgba().fade(amount)
137 }
138
139 fn spin(self, amount: Angle) -> Self {
140 self.to_rgba().spin(amount).to_rgb()
141 }
142
143 fn mix<T: Color>(self, other: T, weight: Ratio) -> RGBA {
144 self.to_rgba().mix(other, weight)
145 }
146
147 fn tint(self, weight: Ratio) -> Self {
148 self.to_rgba().tint(weight).to_rgb()
149 }
150
151 fn shade(self, weight: Ratio) -> Self {
152 self.to_rgba().shade(weight).to_rgb()
153 }
154
155 fn greyscale(self) -> Self {
156 self.to_rgba().greyscale().to_rgb()
157 }
158}
159
160#[derive(Debug, Copy, Clone, PartialEq)]
161pub struct RGBA {
169 pub r: Ratio,
171
172 pub g: Ratio,
174
175 pub b: Ratio,
177
178 pub a: Ratio,
180}
181
182impl fmt::Display for RGBA {
183 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184 write!(
185 f,
186 "rgba({}, {}, {}, {:.02})",
187 self.r.as_u8(),
188 self.g.as_u8(),
189 self.b.as_u8(),
190 self.a.as_f32()
191 )
192 }
193}
194
195impl Color for RGBA {
196 type Alpha = Self;
197
198 fn to_css(self) -> String {
199 self.to_string()
200 }
201
202 fn to_rgb(self) -> RGB {
203 let RGBA { r, g, b, .. } = self;
204 RGB { r, g, b }
205 }
206
207 fn to_rgba(self) -> RGBA {
208 self
209 }
210
211 fn to_hsl(self) -> HSL {
212 self.to_hsla().to_hsl()
213 }
214
215 fn to_hsla(self) -> HSLA {
216 let RGBA { r, g, b, a } = self;
217
218 if r == g && g == b {
222 return HSLA {
223 h: deg(0),
224 s: percent(0),
225 l: r,
226 a,
227 };
228 }
229
230 let r = self.r.as_f32();
234 let g = self.g.as_f32();
235 let b = self.b.as_f32();
236
237 let max = if r > g && r > b {
238 r
239 } else if g > b {
240 g
241 } else {
242 b
243 };
244
245 let min = if r < g && r < b {
246 r
247 } else if g < b {
248 g
249 } else {
250 b
251 };
252
253 let luminosity = (max + min) / 2.0;
254
255 let saturation = if max == min {
260 0.0
261 } else if luminosity < 0.5 {
262 (max - min) / (max + min)
263 } else {
264 (max - min) / (2.0 - (max + min))
265 };
266
267 let hue = if max == r {
274 60.0 * (g - b) / (max - min)
275 } else if max == g {
276 120.0 + 60.0 * (b - r) / (max - min)
277 } else {
278 240.0 + 60.0 * (r - g) / (max - min)
279 };
280
281 HSLA {
282 h: deg(hue.round() as i32),
283 s: Ratio::from_f32(saturation),
284 l: Ratio::from_f32(luminosity),
285 a,
286 }
287 }
288
289 fn saturate(self, amount: Ratio) -> Self {
290 self.to_hsla().saturate(amount).to_rgba()
291 }
292
293 fn desaturate(self, amount: Ratio) -> Self {
294 self.to_hsla().desaturate(amount).to_rgba()
295 }
296
297 fn lighten(self, amount: Ratio) -> Self {
298 self.to_hsla().lighten(amount).to_rgba()
299 }
300
301 fn darken(self, amount: Ratio) -> Self {
302 self.to_hsla().darken(amount).to_rgba()
303 }
304
305 fn fadein(self, amount: Ratio) -> Self {
306 self.fade(self.a + amount)
307 }
308
309 fn fadeout(self, amount: Ratio) -> Self {
310 self.fade(self.a - amount)
311 }
312
313 fn fade(self, amount: Ratio) -> Self {
314 let RGBA { r, g, b, .. } = self;
315 RGBA { r, g, b, a: amount }
316 }
317
318 fn spin(self, amount: Angle) -> Self {
319 self.to_hsla().spin(amount).to_rgba()
320 }
321
322 fn mix<T: Color>(self, other: T, weight: Ratio) -> Self {
327 let RGBA {
328 r: r_lhs,
329 g: g_lhs,
330 b: b_lhs,
331 a: a_lhs,
332 } = self;
333
334 let RGBA {
335 r: r_rhs,
336 g: g_rhs,
337 b: b_rhs,
338 a: a_rhs,
339 } = other.to_rgba();
340
341 let w = (weight.as_f32() * 2.0) - 1.0;
343
344 let a = a_lhs.as_f32() - a_rhs.as_f32();
346
347 let rgb_weight = if w * a == -1.0 {
349 w
350 } else {
351 (w + a) / (1.0 + w * a)
352 };
353
354 let rgb_weight = (rgb_weight + 1.0) / 2.0;
356
357 let rgb_weight_lhs = Ratio::from_f32(rgb_weight);
359 let rgb_weight_rhs = Ratio::from_f32(1.0) - rgb_weight_lhs;
360
361 let alpha_weight_lhs = weight;
362 let alpha_weight_rhs = Ratio::from_f32(1.0) - alpha_weight_lhs;
363
364 RGBA {
365 r: (r_lhs * rgb_weight_lhs) + (r_rhs * rgb_weight_rhs),
366 g: (g_lhs * rgb_weight_lhs) + (g_rhs * rgb_weight_rhs),
367 b: (b_lhs * rgb_weight_lhs) + (b_rhs * rgb_weight_rhs),
368 a: (a_lhs * alpha_weight_lhs) + (a_rhs * alpha_weight_rhs),
369 }
370 }
371
372 fn tint(self, weight: Ratio) -> Self {
373 self.mix(rgb(255, 255, 255), weight)
374 }
375
376 fn shade(self, weight: Ratio) -> Self {
377 self.mix(rgb(0, 0, 0), weight)
378 }
379
380 fn greyscale(self) -> Self {
381 self.to_hsla().greyscale().to_rgba()
382 }
383}