Skip to main content

tihu/
color.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::str::FromStr;
4
5/**
6 * 颜色分量最大值是1.0,不是255
7 */
8#[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/**
16 * 颜色分量最大值是1.0,不是255
17 */
18#[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
212/**
213 * 颜色分量最大值是1.0,不是255
214 */
215fn 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}