widgetry/
color.rs

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    /// Once uploaded, textures are addressed by their id, starting from 1, from left to right, top
31    /// to bottom, like so:
32    ///
33    ///   ┌─┬─┬─┐
34    ///   │1│2│3│
35    ///   ├─┼─┼─┤
36    ///   │4│5│6│
37    ///   ├─┼─┼─┤
38    ///   │7│8│9│
39    ///   └─┴─┴─┘
40    ///
41    /// Texture(0) is reserved for a pure white (no-op) texture.
42    Texture(Texture),
43
44    /// The `color` parameter is multiplied by any color baked into the texture, so typically this
45    /// only makes sense for grayscale textures.
46    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    // Note all of these are based on the default built-in spritesheet
56    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    // TODO Won't this confuse the shader?
73    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    // TODO should assert stuff about the inputs
86
87    // TODO Once f32 can be used in const fn, make these const fn too and clean up call sites
88    // dividing by 255.0. https://github.com/rust-lang/rust/issues/57241
89    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    /// Note this is incorrect for `Color::CLEAR`. Can't fix in a const fn.
115    pub const fn alpha(&self, a: f32) -> Color {
116        Color::rgba_f(self.r, self.g, self.b, a)
117    }
118
119    /// Multiply the color's current alpha by the `factor`, returning a new color.
120    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        // Skip the leading '#'
126        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    // Mix the color with black
151    pub fn shade(self, black_ratio: f64) -> Color {
152        self.lerp(Color::BLACK, black_ratio)
153    }
154
155    // Mix the color with white
156    pub fn tint(self, white_ratio: f64) -> Color {
157        self.lerp(Color::WHITE, white_ratio)
158    }
159
160    // A theme agnostic way to get a more/less intense color without affecting alpha.
161    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// https://developer.mozilla.org/en-US/docs/Web/CSS/linear-gradient is the best reference I've
171// found, even though it's technically for CSS, not SVG. Ah, and
172// https://www.w3.org/TR/SVG11/pservers.html
173#[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        // In between two
207        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}