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