1 use geom::{Line, Pt2D};
2 use serde::{Deserialize, Serialize};
3 use std::fmt;
4 
5 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
6 pub struct Color {
7     pub r: f32,
8     pub g: f32,
9     pub b: f32,
10     pub a: f32,
11 }
12 
13 impl fmt::Display for Color {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result14     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15         write!(
16             f,
17             "Color(r={}, g={}, b={}, a={})",
18             self.r, self.g, self.b, self.a
19         )
20     }
21 }
22 
23 // TODO Maybe needs a better name
24 #[derive(Debug, Clone, PartialEq)]
25 pub enum FancyColor {
26     RGBA(Color),
27     LinearGradient(LinearGradient),
28 }
29 
30 impl Color {
31     // TODO Won't this confuse the shader? :P
32     pub const INVISIBLE: Color = Color::rgba_f(1.0, 0.0, 0.0, 0.0);
33     pub const BLACK: Color = Color::rgb_f(0.0, 0.0, 0.0);
34     pub const WHITE: Color = Color::rgb_f(1.0, 1.0, 1.0);
35     pub const RED: Color = Color::rgb_f(1.0, 0.0, 0.0);
36     pub const GREEN: Color = Color::rgb_f(0.0, 1.0, 0.0);
37     pub const BLUE: Color = Color::rgb_f(0.0, 0.0, 1.0);
38     pub const CYAN: Color = Color::rgb_f(0.0, 1.0, 1.0);
39     pub const YELLOW: Color = Color::rgb_f(1.0, 1.0, 0.0);
40     pub const PURPLE: Color = Color::rgb_f(0.5, 0.0, 0.5);
41     pub const PINK: Color = Color::rgb_f(1.0, 0.41, 0.71);
42     pub const ORANGE: Color = Color::rgb_f(1.0, 0.55, 0.0);
43 
44     // TODO should assert stuff about the inputs
45 
46     // TODO Once f32 can be used in const fn, make these const fn too and clean up call sites
47     // dividing by 255.0. https://github.com/rust-lang/rust/issues/57241
rgb(r: usize, g: usize, b: usize) -> Color48     pub fn rgb(r: usize, g: usize, b: usize) -> Color {
49         Color::rgba(r, g, b, 1.0)
50     }
51 
rgb_f(r: f32, g: f32, b: f32) -> Color52     pub const fn rgb_f(r: f32, g: f32, b: f32) -> Color {
53         Color { r, g, b, a: 1.0 }
54     }
55 
rgba(r: usize, g: usize, b: usize, a: f32) -> Color56     pub fn rgba(r: usize, g: usize, b: usize, a: f32) -> Color {
57         Color {
58             r: (r as f32) / 255.0,
59             g: (g as f32) / 255.0,
60             b: (b as f32) / 255.0,
61             a,
62         }
63     }
64 
rgba_f(r: f32, g: f32, b: f32, a: f32) -> Color65     pub const fn rgba_f(r: f32, g: f32, b: f32, a: f32) -> Color {
66         Color { r, g, b, a }
67     }
68 
grey(f: f32) -> Color69     pub const fn grey(f: f32) -> Color {
70         Color::rgb_f(f, f, f)
71     }
72 
alpha(&self, a: f32) -> Color73     pub const fn alpha(&self, a: f32) -> Color {
74         Color::rgba_f(self.r, self.g, self.b, a)
75     }
76 
hex(raw: &str) -> Color77     pub fn hex(raw: &str) -> Color {
78         // Skip the leading '#'
79         let r = usize::from_str_radix(&raw[1..3], 16).unwrap();
80         let g = usize::from_str_radix(&raw[3..5], 16).unwrap();
81         let b = usize::from_str_radix(&raw[5..7], 16).unwrap();
82         Color::rgb(r, g, b)
83     }
84 
to_hex(&self) -> String85     pub fn to_hex(&self) -> String {
86         format!(
87             "#{:02X}{:02X}{:02X}",
88             (self.r * 255.0) as usize,
89             (self.g * 255.0) as usize,
90             (self.b * 255.0) as usize
91         )
92     }
93 
lerp(self, other: Color, pct: f64) -> Color94     pub fn lerp(self, other: Color, pct: f64) -> Color {
95         Color::rgba_f(
96             lerp(pct, (self.r, other.r)),
97             lerp(pct, (self.g, other.g)),
98             lerp(pct, (self.b, other.b)),
99             lerp(pct, (self.a, other.a)),
100         )
101     }
102 }
103 
104 // https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient is the best reference I've
105 // found, even though it's technically for CSS, not SVG. Ah, and
106 // https://www.w3.org/TR/SVG11/pservers.html
107 #[derive(Debug, Clone, PartialEq)]
108 pub struct LinearGradient {
109     pub line: Line,
110     pub stops: Vec<(f64, Color)>,
111 }
112 
113 impl LinearGradient {
new(lg: &usvg::LinearGradient) -> FancyColor114     pub(crate) fn new(lg: &usvg::LinearGradient) -> FancyColor {
115         let line = Line::must_new(Pt2D::new(lg.x1, lg.y1), Pt2D::new(lg.x2, lg.y2));
116         let mut stops = Vec::new();
117         for stop in &lg.stops {
118             let color = Color::rgba(
119                 stop.color.red as usize,
120                 stop.color.green as usize,
121                 stop.color.blue as usize,
122                 stop.opacity.value() as f32,
123             );
124             stops.push((stop.offset.value(), color));
125         }
126         FancyColor::LinearGradient(LinearGradient { line, stops })
127     }
128 
interp(&self, pt: Pt2D) -> Color129     fn interp(&self, pt: Pt2D) -> Color {
130         let pct = self
131             .line
132             .percent_along_of_point(self.line.project_pt(pt))
133             .unwrap();
134         if pct < self.stops[0].0 {
135             return self.stops[0].1;
136         }
137         if pct > self.stops.last().unwrap().0 {
138             return self.stops.last().unwrap().1;
139         }
140         // In between two
141         for ((pct1, c1), (pct2, c2)) in self.stops.iter().zip(self.stops.iter().skip(1)) {
142             if pct >= *pct1 && pct <= *pct2 {
143                 return c1.lerp(*c2, to_pct(pct, (*pct1, *pct2)));
144             }
145         }
146         unreachable!()
147     }
148 }
149 
to_pct(value: f64, (low, high): (f64, f64)) -> f64150 fn to_pct(value: f64, (low, high): (f64, f64)) -> f64 {
151     assert!(low <= high);
152     assert!(value >= low);
153     assert!(value <= high);
154     (value - low) / (high - low)
155 }
156 
lerp(pct: f64, (x1, x2): (f32, f32)) -> f32157 fn lerp(pct: f64, (x1, x2): (f32, f32)) -> f32 {
158     x1 + (pct as f32) * (x2 - x1)
159 }
160 
161 impl FancyColor {
style(&self, pt: Pt2D) -> [f32; 4]162     pub(crate) fn style(&self, pt: Pt2D) -> [f32; 4] {
163         match self {
164             FancyColor::RGBA(c) => [c.r, c.g, c.b, c.a],
165             FancyColor::LinearGradient(ref lg) => {
166                 let c = lg.interp(pt);
167                 [c.r, c.g, c.b, c.a]
168             }
169         }
170     }
171 }
172