1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use geom::{Line, Pt2D};
6
7#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
8pub struct Color {
9 pub r: f32,
10 pub g: f32,
11 pub b: f32,
12 pub a: f32,
13}
14
15impl fmt::Display for Color {
16 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17 write!(
18 f,
19 "Color(r={}, g={}, b={}, a={})",
20 self.r, self.g, self.b, self.a
21 )
22 }
23}
24
25#[derive(Debug, Clone, PartialEq)]
26pub enum Fill {
27 Color(Color),
28 LinearGradient(LinearGradient),
29
30 Texture(Texture),
43
44 ColoredTexture(Color, Texture),
47}
48
49#[derive(Debug, Copy, Clone, PartialEq)]
50pub struct Texture(pub u32);
51
52#[allow(dead_code)]
53impl Texture {
54 pub const NOOP: Texture = Texture(0);
55 pub const GRASS: Texture = Texture(1);
57 pub const STILL_WATER: Texture = Texture(2);
58 pub const RUNNING_WATER: Texture = Texture(3);
59 pub const CONCRETE: Texture = Texture(4);
60 pub const SAND: Texture = Texture(5);
61 pub const DIRT: Texture = Texture(6);
62 pub const SNOW: Texture = Texture(7);
63 pub const TREE: Texture = Texture(15);
64 pub const PINE_TREE: Texture = Texture(16);
65 pub const CACTUS: Texture = Texture(17);
66 pub const SHRUB: Texture = Texture(18);
67 pub const CROSS_HATCH: Texture = Texture(19);
68 pub const SNOW_PERSON: Texture = Texture(29);
69}
70
71impl Color {
72 pub const CLEAR: Color = Color::rgba_f(1.0, 0.0, 0.0, 0.0);
74 pub const BLACK: Color = Color::rgb_f(0.0, 0.0, 0.0);
75 pub const WHITE: Color = Color::rgb_f(1.0, 1.0, 1.0);
76 pub const RED: Color = Color::rgb_f(1.0, 0.0, 0.0);
77 pub const GREEN: Color = Color::rgb_f(0.0, 1.0, 0.0);
78 pub const BLUE: Color = Color::rgb_f(0.0, 0.0, 1.0);
79 pub const CYAN: Color = Color::rgb_f(0.0, 1.0, 1.0);
80 pub const YELLOW: Color = Color::rgb_f(1.0, 1.0, 0.0);
81 pub const PURPLE: Color = Color::rgb_f(0.5, 0.0, 0.5);
82 pub const PINK: Color = Color::rgb_f(1.0, 0.41, 0.71);
83 pub const ORANGE: Color = Color::rgb_f(1.0, 0.55, 0.0);
84
85 pub fn rgb(r: usize, g: usize, b: usize) -> Color {
90 Color::rgba(r, g, b, 1.0)
91 }
92
93 pub const fn rgb_f(r: f32, g: f32, b: f32) -> Color {
94 Color { r, g, b, a: 1.0 }
95 }
96
97 pub fn rgba(r: usize, g: usize, b: usize, a: f32) -> Color {
98 Color {
99 r: (r as f32) / 255.0,
100 g: (g as f32) / 255.0,
101 b: (b as f32) / 255.0,
102 a,
103 }
104 }
105
106 pub const fn rgba_f(r: f32, g: f32, b: f32, a: f32) -> Color {
107 Color { r, g, b, a }
108 }
109
110 pub const fn grey(f: f32) -> Color {
111 Color::rgb_f(f, f, f)
112 }
113
114 pub const fn alpha(&self, a: f32) -> Color {
116 Color::rgba_f(self.r, self.g, self.b, a)
117 }
118
119 pub fn multiply_alpha(&self, factor: f32) -> Color {
121 Color::rgba_f(self.r, self.g, self.b, self.a * factor)
122 }
123
124 pub fn hex(raw: &str) -> Color {
125 let r = usize::from_str_radix(&raw[1..3], 16).unwrap();
127 let g = usize::from_str_radix(&raw[3..5], 16).unwrap();
128 let b = usize::from_str_radix(&raw[5..7], 16).unwrap();
129 Color::rgb(r, g, b)
130 }
131
132 pub fn as_hex(&self) -> String {
133 format!(
134 "#{:02X}{:02X}{:02X}",
135 (self.r * 255.0) as usize,
136 (self.g * 255.0) as usize,
137 (self.b * 255.0) as usize
138 )
139 }
140
141 pub fn lerp(self, other: Color, pct: f64) -> Color {
142 Color::rgba_f(
143 lerp(pct, (self.r, other.r)),
144 lerp(pct, (self.g, other.g)),
145 lerp(pct, (self.b, other.b)),
146 lerp(pct, (self.a, other.a)),
147 )
148 }
149
150 pub fn shade(self, black_ratio: f64) -> Color {
152 self.lerp(Color::BLACK, black_ratio)
153 }
154
155 pub fn tint(self, white_ratio: f64) -> Color {
157 self.lerp(Color::WHITE, white_ratio)
158 }
159
160 pub fn dull(self, ratio: f64) -> Color {
162 self.lerp(Color::grey(0.5), ratio)
163 }
164
165 pub fn invert(self) -> Color {
166 Color::rgba_f(1.0 - self.r, 1.0 - self.g, 1.0 - self.b, self.a)
167 }
168}
169
170#[derive(Debug, Clone, PartialEq)]
174pub struct LinearGradient {
175 pub line: Line,
176 pub stops: Vec<(f64, Color)>,
177}
178
179impl LinearGradient {
180 pub(crate) fn new_fill(lg: &usvg::LinearGradient) -> Fill {
181 let line = Line::must_new(Pt2D::new(lg.x1, lg.y1), Pt2D::new(lg.x2, lg.y2));
182 let mut stops = Vec::new();
183 for stop in &lg.stops {
184 let color = Color::rgba(
185 stop.color.red as usize,
186 stop.color.green as usize,
187 stop.color.blue as usize,
188 stop.opacity.get() as f32,
189 );
190 stops.push((stop.offset.get(), color));
191 }
192 Fill::LinearGradient(LinearGradient { line, stops })
193 }
194
195 fn interp(&self, pt: Pt2D) -> Color {
196 let pct = self
197 .line
198 .percent_along_of_point(self.line.to_polyline().project_pt(pt))
199 .unwrap();
200 if pct < self.stops[0].0 {
201 return self.stops[0].1;
202 }
203 if pct > self.stops.last().unwrap().0 {
204 return self.stops.last().unwrap().1;
205 }
206 for ((pct1, c1), (pct2, c2)) in self.stops.iter().zip(self.stops.iter().skip(1)) {
208 if pct >= *pct1 && pct <= *pct2 {
209 return c1.lerp(*c2, to_pct(pct, (*pct1, *pct2)));
210 }
211 }
212 unreachable!()
213 }
214}
215
216fn to_pct(value: f64, (low, high): (f64, f64)) -> f64 {
217 assert!(low <= high);
218 assert!(value >= low);
219 assert!(value <= high);
220 (value - low) / (high - low)
221}
222
223fn lerp(pct: f64, (x1, x2): (f32, f32)) -> f32 {
224 x1 + (pct as f32) * (x2 - x1)
225}
226
227impl Fill {
228 pub(crate) fn shader_style(&self, pt: Pt2D) -> [f32; 5] {
229 match self {
230 Fill::Color(c) => [c.r, c.g, c.b, c.a, 0.0],
231 Fill::LinearGradient(ref lg) => {
232 let c = lg.interp(pt);
233 [c.r, c.g, c.b, c.a, 0.0]
234 }
235 Fill::Texture(texture) => [1.0, 1.0, 1.0, 1.0, texture.0 as f32],
236 Fill::ColoredTexture(color, texture) => {
237 [color.r, color.g, color.b, color.a, texture.0 as f32]
238 }
239 }
240 }
241}
242
243impl std::convert::From<Color> for Fill {
244 fn from(color: Color) -> Fill {
245 Fill::Color(color)
246 }
247}
248
249impl std::convert::From<Texture> for Fill {
250 fn from(texture: Texture) -> Fill {
251 Fill::Texture(texture)
252 }
253}