1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::str::FromStr;
4
5#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
9pub struct RgbColor {
10 pub red: f64,
11 pub green: f64,
12 pub blue: f64,
13}
14
15#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
19pub struct RgbaColor {
20 pub red: f64,
21 pub green: f64,
22 pub blue: f64,
23 pub alpha: f64,
24}
25
26#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
27pub struct HslColor {
28 pub hue: f64,
29 pub saturation: f64,
30 pub lightness: f64,
31}
32
33#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
34pub struct HslaColor {
35 pub hue: f64,
36 pub saturation: f64,
37 pub lightness: f64,
38 pub alpha: f64,
39}
40
41fn normalize_degree(num: f64) -> f64 {
42 let max = 360.0;
43 let mut num = num % max;
44 if 0.0 > num {
45 num += max;
46 }
47 return num;
48}
49
50impl HslaColor {
51 pub fn add_hue(&self, addon: f64) -> HslaColor {
52 return HslaColor {
53 hue: normalize_degree(self.hue + addon),
54 saturation: self.saturation,
55 lightness: self.lightness,
56 alpha: self.alpha,
57 };
58 }
59 pub fn add_saturation(&self, addon: f64) -> HslaColor {
60 return HslaColor {
61 hue: self.hue,
62 saturation: (self.saturation + addon).max(0.0).min(1.0),
63 lightness: self.lightness,
64 alpha: self.alpha,
65 };
66 }
67 pub fn add_lightness(&self, addon: f64) -> HslaColor {
68 return HslaColor {
69 hue: self.hue,
70 saturation: self.saturation,
71 lightness: (self.lightness + addon).max(0.0).min(1.0),
72 alpha: self.alpha,
73 };
74 }
75 pub fn to_css(&self) -> String {
76 let saturation = (self.saturation * 10000.0).round() / 100.0;
77 let lightness = (self.lightness * 10000.0).round() / 100.0;
78 return format!(
79 "hsla({}, {}%, {}%, {})",
80 self.hue, saturation, lightness, self.alpha
81 );
82 }
83}
84
85#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
86pub enum Color {
87 Rgb(RgbColor),
88 Rgba(RgbaColor),
89 Hsl(HslColor),
90 Hsla(HslaColor),
91}
92
93fn parse_css_fn(color: &str) -> Option<(&str, Vec<&str>)> {
94 if let Some(start) = color.find("(") {
95 let fn_name = color.split_at(start).0;
96 let rest = color.split_at(start + 1).1;
97 if let Some(end) = rest.find(")") {
98 let args = rest.split_at(end).0;
99 return Some((
100 fn_name.trim(),
101 args.split(",").map(|item| item.trim()).collect(),
102 ));
103 }
104 }
105 return None;
106}
107
108fn parse_css_number(number: &str, max: Option<f64>) -> Option<f64> {
109 if number.ends_with("%") {
110 let number = number.split_at(number.len() - 1).0;
111 f64::from_str(number).map(|number| number / 100.0).ok()
112 } else {
113 f64::from_str(number)
114 .map(|number| number / max.unwrap_or(1.0))
115 .ok()
116 }
117}
118
119const MAX_RGB: f64 = 255.0;
120
121fn parse_hex_color(color: &str) -> Option<Color> {
122 let items: Vec<_> = color.chars().collect();
123 let seeds = "0123456789abcdef";
124 let data: [usize; 6] = if 4 == items.len() {
125 [
126 seeds.find(*items.get(1)?)?,
127 seeds.find(*items.get(1)?)?,
128 seeds.find(*items.get(2)?)?,
129 seeds.find(*items.get(2)?)?,
130 seeds.find(*items.get(3)?)?,
131 seeds.find(*items.get(3)?)?,
132 ]
133 } else {
134 [
135 seeds.find(*items.get(1)?)?,
136 seeds.find(*items.get(2)?)?,
137 seeds.find(*items.get(3)?)?,
138 seeds.find(*items.get(4)?)?,
139 seeds.find(*items.get(5)?)?,
140 seeds.find(*items.get(6)?)?,
141 ]
142 };
143 let red = 16 * data[0] + data[1];
144 let green = 16 * data[2] + data[3];
145 let blue = 16 * data[4] + data[5];
146 return Some(Color::Rgb(RgbColor {
147 red: red as f64 / MAX_RGB,
148 green: green as f64 / MAX_RGB,
149 blue: blue as f64 / MAX_RGB,
150 }));
151}
152
153pub fn parse_css_color(color: &str) -> Option<Color> {
154 let color = color.trim().to_lowercase();
155 if color.starts_with("#") {
156 return parse_hex_color(&color);
157 } else {
158 if let Some((fn_name, args)) = parse_css_fn(&color) {
159 match fn_name {
160 "rgb" => {
161 let red = parse_css_number(args.get(0)?, Some(MAX_RGB))?;
162 let green = parse_css_number(args.get(1)?, Some(MAX_RGB))?;
163 let blue = parse_css_number(args.get(2)?, Some(MAX_RGB))?;
164 return Some(Color::Rgb(RgbColor { red, green, blue }));
165 }
166 "rgba" => {
167 let red = parse_css_number(args.get(0)?, Some(MAX_RGB))?;
168 let green = parse_css_number(args.get(1)?, Some(MAX_RGB))?;
169 let blue = parse_css_number(args.get(2)?, Some(MAX_RGB))?;
170 let alpha = parse_css_number(args.get(3)?, None)?;
171 return Some(Color::Rgba(RgbaColor {
172 red,
173 green,
174 blue,
175 alpha,
176 }));
177 }
178 "hsl" => {
179 let hue = parse_css_number(args.get(0)?, None)?;
180 let saturation = parse_css_number(args.get(1)?, Some(100.0))?;
181 let lightness = parse_css_number(args.get(2)?, Some(100.0))?;
182 return Some(Color::Hsl(HslColor {
183 hue,
184 saturation,
185 lightness,
186 }));
187 }
188 "hsla" => {
189 let hue = parse_css_number(args.get(0)?, None)?;
190 let saturation = parse_css_number(args.get(1)?, Some(100.0))?;
191 let lightness = parse_css_number(args.get(2)?, Some(100.0))?;
192 let alpha = parse_css_number(args.get(3)?, None)?;
193 return Some(Color::Hsla(HslaColor {
194 hue,
195 saturation,
196 lightness,
197 alpha,
198 }));
199 }
200 _ => {
201 return None;
202 }
203 };
204 } else {
205 let named_colors = get_named_colors();
206 let color = named_colors.get(color.as_str())?;
207 return parse_hex_color(color);
208 }
209 }
210}
211
212fn rgb_to_hsl(red: f64, green: f64, blue: f64, alpha: f64) -> HslaColor {
216 let max = red.max(green).max(blue);
217 let min = red.min(green).min(blue);
218 let hue = {
219 if max == min {
220 0.0
221 } else {
222 if max == green {
223 60.0 * (blue - red) / (max - min) + 120.0
224 } else if max == blue {
225 60.0 * (red - green) / (max - min) + 240.0
226 } else {
227 if green >= blue {
228 60.0 * (green - blue) / (max - min)
229 } else {
230 60.0 * (green - blue) / (max - min) + 360.0
231 }
232 }
233 }
234 };
235 let lightness = 0.5 * (max + min);
236 let saturation = {
237 if 0.0 == lightness || max == min {
238 0.0
239 } else if 0.0 < lightness && 0.5 >= lightness {
240 (max - min) / (max + min)
241 } else {
242 (max - min) / (2.0 - max - min)
243 }
244 };
245 return HslaColor {
246 hue,
247 saturation,
248 lightness,
249 alpha,
250 };
251}
252
253fn get_named_colors() -> HashMap<&'static str, &'static str> {
254 return vec![
255 ("aliceblue", "#f0f8ff"),
256 ("antiquewhite", "#faebd7"),
257 ("aqua", "#00ffff"),
258 ("aquamarine", "#7fffd4"),
259 ("azure", "#f0ffff"),
260 ("beige", "#f5f5dc"),
261 ("bisque", "#ffe4c4"),
262 ("black", "#000000"),
263 ("blanchedalmond", "#ffebcd"),
264 ("blue", "#0000ff"),
265 ("blueviolet", "#8a2be2"),
266 ("brown", "#a52a2a"),
267 ("burlywood", "#deb887"),
268 ("cadetblue", "#5f9ea0"),
269 ("chartreuse", "#7fff00"),
270 ("chocolate", "#d2691e"),
271 ("coral", "#ff7f50"),
272 ("cornflowerblue", "#6495ed"),
273 ("cornsilk", "#fff8dc"),
274 ("crimson", "#dc143c"),
275 ("cyan", "#00ffff"),
276 ("darkblue", "#00008b"),
277 ("darkcyan", "#008b8b"),
278 ("darkgoldenrod", "#b8860b"),
279 ("darkgray", "#a9a9a9"),
280 ("darkgreen", "#006400"),
281 ("darkgrey", "#a9a9a9"),
282 ("darkkhaki", "#bdb76b"),
283 ("darkmagenta", "#8b008b"),
284 ("darkolivegreen", "#556b2f"),
285 ("darkorange", "#ff8c00"),
286 ("darkorchid", "#9932cc"),
287 ("darkred", "#8b0000"),
288 ("darksalmon", "#e9967a"),
289 ("darkseagreen", "#8fbc8f"),
290 ("darkslateblue", "#483d8b"),
291 ("darkslategray", "#2f4f4f"),
292 ("darkslategrey", "#2f4f4f"),
293 ("darkturquoise", "#00ced1"),
294 ("darkviolet", "#9400d3"),
295 ("deeppink", "#ff1493"),
296 ("deepskyblue", "#00bfff"),
297 ("dimgray", "#696969"),
298 ("dimgrey", "#696969"),
299 ("dodgerblue", "#1e90ff"),
300 ("firebrick", "#b22222"),
301 ("floralwhite", "#fffaf0"),
302 ("forestgreen", "#228b22"),
303 ("fuchsia", "#ff00ff"),
304 ("gainsboro", "#dcdcdc"),
305 ("ghostwhite", "#f8f8ff"),
306 ("gold", "#ffd700"),
307 ("goldenrod", "#daa520"),
308 ("gray", "#808080"),
309 ("green", "#008000"),
310 ("greenyellow", "#adff2f"),
311 ("grey", "#808080"),
312 ("honeydew", "#f0fff0"),
313 ("hotpink", "#ff69b4"),
314 ("indianred", "#cd5c5c"),
315 ("indigo", "#4b0082"),
316 ("ivory", "#fffff0"),
317 ("khaki", "#f0e68c"),
318 ("lavender", "#e6e6fa"),
319 ("lavenderblush", "#fff0f5"),
320 ("lawngreen", "#7cfc00"),
321 ("lemonchiffon", "#fffacd"),
322 ("lightblue", "#add8e6"),
323 ("lightcoral", "#f08080"),
324 ("lightcyan", "#e0ffff"),
325 ("lightgoldenrodyellow", "#fafad2"),
326 ("lightgray", "#d3d3d3"),
327 ("lightgreen", "#90ee90"),
328 ("lightgrey", "#d3d3d3"),
329 ("lightpink", "#ffb6c1"),
330 ("lightsalmon", "#ffa07a"),
331 ("lightseagreen", "#20b2aa"),
332 ("lightskyblue", "#87cefa"),
333 ("lightslategray", "#778899"),
334 ("lightslategrey", "#778899"),
335 ("lightsteelblue", "#b0c4de"),
336 ("lightyellow", "#ffffe0"),
337 ("lime", "#00ff00"),
338 ("limegreen", "#32cd32"),
339 ("linen", "#faf0e6"),
340 ("magenta", "#ff00ff"),
341 ("maroon", "#800000"),
342 ("mediumaquamarine", "#66cdaa"),
343 ("mediumblue", "#0000cd"),
344 ("mediumorchid", "#ba55d3"),
345 ("mediumpurple", "#9370db"),
346 ("mediumseagreen", "#3cb371"),
347 ("mediumslateblue", "#7b68ee"),
348 ("mediumspringgreen", "#00fa9a"),
349 ("mediumturquoise", "#48d1cc"),
350 ("mediumvioletred", "#c71585"),
351 ("midnightblue", "#191970"),
352 ("mintcream", "#f5fffa"),
353 ("mistyrose", "#ffe4e1"),
354 ("moccasin", "#ffe4b5"),
355 ("navajowhite", "#ffdead"),
356 ("navy", "#000080"),
357 ("oldlace", "#fdf5e6"),
358 ("olive", "#808000"),
359 ("olivedrab", "#6b8e23"),
360 ("orange", "#ffa500"),
361 ("orangered", "#ff4500"),
362 ("orchid", "#da70d6"),
363 ("palegoldenrod", "#eee8aa"),
364 ("palegreen", "#98fb98"),
365 ("paleturquoise", "#afeeee"),
366 ("palevioletred", "#db7093"),
367 ("papayawhip", "#ffefd5"),
368 ("peachpuff", "#ffdab9"),
369 ("peru", "#cd853f"),
370 ("pink", "#ffc0cb"),
371 ("plum", "#dda0dd"),
372 ("powderblue", "#b0e0e6"),
373 ("purple", "#800080"),
374 ("rebeccapurple", "#663399"),
375 ("red", "#ff0000"),
376 ("rosybrown", "#bc8f8f"),
377 ("royalblue", "#4169e1"),
378 ("saddlebrown", "#8b4513"),
379 ("salmon", "#fa8072"),
380 ("sandybrown", "#f4a460"),
381 ("seagreen", "#2e8b57"),
382 ("seashell", "#fff5ee"),
383 ("sienna", "#a0522d"),
384 ("silver", "#c0c0c0"),
385 ("skyblue", "#87ceeb"),
386 ("slateblue", "#6a5acd"),
387 ("slategray", "#708090"),
388 ("slategrey", "#708090"),
389 ("snow", "#fffafa"),
390 ("springgreen", "#00ff7f"),
391 ("steelblue", "#4682b4"),
392 ("tan", "#d2b48c"),
393 ("teal", "#008080"),
394 ("thistle", "#d8bfd8"),
395 ("tomato", "#ff6347"),
396 ("turquoise", "#40e0d0"),
397 ("violet", "#ee82ee"),
398 ("wheat", "#f5deb3"),
399 ("white", "#ffffff"),
400 ("whitesmoke", "#f5f5f5"),
401 ("yellow", "#ffff00"),
402 ("yellowgreen", "#9acd32"),
403 ]
404 .into_iter()
405 .collect();
406}
407
408pub fn calc_hsla_color(color: &str) -> Option<HslaColor> {
409 let color = parse_css_color(color)?;
410 let hsla = match color {
411 Color::Rgb(color) => rgb_to_hsl(color.red, color.green, color.blue, 1.0),
412 Color::Rgba(color) => rgb_to_hsl(color.red, color.green, color.blue, color.alpha),
413 Color::Hsl(color) => HslaColor {
414 hue: color.hue,
415 saturation: color.saturation,
416 lightness: color.lightness,
417 alpha: 1.0,
418 },
419 Color::Hsla(color) => color,
420 };
421 return Some(hsla);
422}