1use alloc::vec::Vec;
2
3#[cfg(feature = "libm")]
4use num_traits::MulAdd;
5
6pub type Color = [u8; 4];
7
8#[derive(Clone, Copy, Debug, Default)]
9struct GradientPoint {
10 pos: f64,
11 color: Color,
12}
13
14#[derive(Clone, Copy, Debug, Default)]
15struct GradientDomain {
16 min: f64,
17 max: f64,
18}
19
20impl GradientDomain {
21 pub fn new(min: f64, max: f64) -> Self {
22 Self { min, max }
23 }
24
25 pub fn set_min(&mut self, min: f64) {
26 self.min = min;
27 }
28
29 pub fn set_max(&mut self, max: f64) {
30 self.max = max;
31 }
32}
33
34#[derive(Clone, Debug, Default)]
35pub struct ColorGradient {
36 gradient_points: Vec<GradientPoint>,
37 domain: GradientDomain,
38}
39
40impl ColorGradient {
41 pub fn new() -> Self {
42 let gradient = Self {
43 gradient_points: Vec::new(),
44 domain: GradientDomain::new(0.0, 1.0),
45 };
46
47 gradient.build_grayscale_gradient()
48 }
49
50 pub fn add_gradient_point(mut self, pos: f64, color: Color) -> Self {
51 let new_point = GradientPoint { pos, color };
52
53 if self.domain.min > pos {
56 self.domain.set_min(pos);
57 self.gradient_points.insert(0, new_point);
60 } else if self.domain.max < pos {
61 self.domain.set_max(pos);
62
63 self.gradient_points.push(new_point)
65 } else if !self .gradient_points
68 .iter()
69 .any(|&x| (x.pos - pos).abs() < f64::EPSILON)
70 {
71 let insertion_point = self.find_insertion_point(pos);
74
75 self.gradient_points.insert(insertion_point, new_point);
77 }
78
79 self
80 }
81
82 fn find_insertion_point(&self, pos: f64) -> usize {
83 self.gradient_points
84 .iter()
85 .position(|x| x.pos >= pos)
86 .unwrap_or(self.gradient_points.len())
87 }
88
89 pub fn clear_gradient(mut self) -> Self {
90 self.gradient_points.clear();
91 self.domain = GradientDomain::new(0.0, 0.0);
92
93 self
94 }
95
96 pub fn build_grayscale_gradient(self) -> Self {
97 self.clear_gradient()
98 .add_gradient_point(-1.0, [0, 0, 0, 255])
99 .add_gradient_point(1.0, [255, 255, 255, 255])
100 }
101
102 #[rustfmt::skip]
103 pub fn build_terrain_gradient(self) -> Self {
104 self.clear_gradient()
105 .add_gradient_point(-1.00, [ 0, 0, 0, 255])
106 .add_gradient_point(-256.0 / 16384.0, [ 6, 58, 127, 255])
107 .add_gradient_point(-1.0 / 16384.0, [ 14, 112, 192, 255])
108 .add_gradient_point(0.0, [ 70, 120, 60, 255])
109 .add_gradient_point(1024.0 / 16384.0, [110, 140, 75, 255])
110 .add_gradient_point(2048.0 / 16384.0, [160, 140, 111, 255])
111 .add_gradient_point(3072.0 / 16384.0, [184, 163, 141, 255])
112 .add_gradient_point(4096.0 / 16384.0, [128, 128, 128, 255])
113 .add_gradient_point(5632.0 / 16384.0, [128, 128, 128, 255])
114 .add_gradient_point(6144.0 / 16384.0, [250, 250, 250, 255])
115 .add_gradient_point(1.0, [255, 255, 255, 255])
116 }
117
118 #[rustfmt::skip]
119 pub fn build_rainbow_gradient(self) -> Self {
120 self.clear_gradient()
121 .add_gradient_point(-1.0, [255, 0, 0, 255])
122 .add_gradient_point(-0.7, [255, 255, 0, 255])
123 .add_gradient_point(-0.4, [ 0, 255, 0, 255])
124 .add_gradient_point( 0.0, [ 0, 255, 255, 255])
125 .add_gradient_point( 0.3, [ 0, 0, 255, 255])
126 .add_gradient_point( 0.6, [255, 0, 255, 255])
127 .add_gradient_point( 1.0, [255, 0, 0, 255])
128 }
129
130 pub fn get_color(&self, pos: f64) -> Color {
131 let mut color = Color::default();
132
133 if !self.gradient_points.is_empty() {
135 match () {
136 _ if pos < self.domain.min => color = self.gradient_points.first().unwrap().color,
137 _ if pos > self.domain.max => color = self.gradient_points.last().unwrap().color,
138 _ => {
139 for points in self.gradient_points.windows(2) {
140 if (points[0].pos <= pos) && (points[1].pos > pos) {
141 let alpha = (pos - points[0].pos) / (points[1].pos - points[0].pos);
143
144 color = interpolate_color(points[0].color, points[1].color, alpha)
146 }
147 }
148 }
149 };
150 };
151
152 color
153 }
154}
155
156fn interpolate_color(color0: Color, color1: Color, alpha: f64) -> Color {
157 fn blend_channel(channel0: u8, channel1: u8, alpha: f64) -> u8 {
158 let c0 = (f64::from(channel0)) / 255.0;
159 let c1 = (f64::from(channel1)) / 255.0;
160
161 ((c1 - c0).mul_add(alpha, c0) * 255.0) as u8
162 }
163
164 let mut color = Color::default();
165
166 for i in 0..color.len() {
167 color[i] = blend_channel(color0[i], color1[i], alpha);
168 }
169
170 color
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn linerp_color_1() {
179 assert_eq!(
180 [0, 127, 255, 0],
181 interpolate_color([0, 0, 255, 0], [0, 255, 255, 0], 0.5)
182 );
183 }
184
185 #[test]
186 fn color_gradient_1() {
187 let gradient = ColorGradient::new();
188
189 let gradient = gradient
190 .clear_gradient()
191 .add_gradient_point(0.0, [0, 0, 0, 0])
192 .add_gradient_point(1.0, [255, 255, 255, 255]);
193
194 assert_eq!([127, 127, 127, 127], gradient.get_color(0.5));
195 }
196}