1use crate::colour::{Colour, RgbaComponents};
6use ndarray::Array;
7use palette::{
8 encoding::{Linear, Srgb},
9 rgb::Rgb,
10 Alpha, Gradient, LinSrgba,
11};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::convert::TryInto;
15
16pub type GradientLinearRGBA = Gradient<Alpha<Rgb<Linear<Srgb>, f64>, f64>>;
18
19const VIRIDIS7: [Colour; 7] = [
20 Colour::Seq((0.267004, 0.004874, 0.329415, 1.)),
21 Colour::Seq((0.267968, 0.223549, 0.512008, 1.)),
22 Colour::Seq((0.190631, 0.407061, 0.556089, 1.)),
23 Colour::Seq((0.127568, 0.566949, 0.550556, 1.)),
24 Colour::Seq((0.20803, 0.718701, 0.472873, 1.)),
25 Colour::Seq((0.565498, 0.84243, 0.262877, 1.)),
26 Colour::Seq((0.993248, 0.906157, 0.143936, 1.)),
27];
28
29const INFERNO7: [Colour; 7] = [
30 Colour::Seq((0.001462, 0.000466, 0.013866, 1.)),
31 Colour::Seq((0.197297, 0.0384, 0.367535, 1.)),
32 Colour::Seq((0.472328, 0.110547, 0.428334, 1.)),
33 Colour::Seq((0.735683, 0.215906, 0.330245, 1.)),
34 Colour::Seq((0.929644, 0.411479, 0.145367, 1.)),
35 Colour::Seq((0.986175, 0.713153, 0.103863, 1.)),
36 Colour::Seq((0.988362, 0.998364, 0.644924, 1.)),
37];
38
39macro_rules! gen_cmap_fn {
41 ($(#[$attr:meta])* => ($name:ident, $colours:expr)) => {
42 $(#[$attr])*
43 pub fn $name( vmin: f64, vmax: f64) -> GradientLinearRGBA {
44 make_gradient(vmin, vmax, &$colours)
45 }
46 }
47}
48
49gen_cmap_fn! {
50=> (viridis, VIRIDIS7)
52}
53
54gen_cmap_fn! {
55=> (inferno, INFERNO7)
57}
58
59fn make_gradient(vmin: f64, vmax: f64, rgba: &[Colour]) -> GradientLinearRGBA {
69 let nums = Array::linspace(vmin, vmax, rgba.len());
70 let cols = nums
71 .iter()
72 .zip(rgba)
73 .map(|(v, comps)| {
74 (
75 *v,
76 LinSrgba::from_components(Into::<(f64, f64, f64, f64)>::into(comps.clone())),
77 )
78 })
79 .collect();
80 Gradient::with_domain(cols)
81}
82
83fn make_gradient_with_breaks(nums: &[(f64, Colour)]) -> GradientLinearRGBA {
84 let cols = nums
85 .iter()
86 .map(|(v, comps)| {
87 (
88 *v,
89 LinSrgba::from_components(Into::<(f64, f64, f64, f64)>::into(comps.clone())),
90 )
91 })
92 .collect();
93 Gradient::with_domain(cols)
94}
95
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
98#[serde(untagged)]
99pub enum ColourDefinition {
100 Discrete(Vec<(isize, Colour)>),
102 Colours(Vec<Colour>),
104 ColoursAndBreaks(Vec<(f64, Colour)>),
106 RGB([f64; 3], [f64; 3]),
108}
109
110#[derive(Debug, Clone)]
112pub struct Composite {
113 vmin: Option<Vec<f64>>,
114 vmax: Option<Vec<f64>>,
115 gradient: Option<GradientLinearRGBA>,
116 hashmap: Option<HashMap<isize, RgbaComponents>>,
117 display: Option<String>,
118 colour_definition: ColourDefinition,
119 len: usize,
120}
121
122impl Default for Composite {
123 fn default() -> Self {
124 let grad: Gradient<Alpha<Rgb<Linear<Srgb>, f64>, f64>> = viridis(0.0, 1.0);
125 Self {
126 vmin: Some(vec![0.0]),
127 vmax: Some(vec![1.0]),
128 gradient: Some(grad),
129 hashmap: None,
130 display: Some("Gradient".to_string()),
131 colour_definition: ColourDefinition::Colours(vec![
132 (0.0, 0.0, 0.0, 0.0).into(),
133 (1.0, 1.0, 1.0, 1.0).into(),
134 ]),
135 len: 1,
136 }
137 }
138}
139
140impl std::fmt::Display for Composite {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
142 write!(f, "{}", self.display.as_ref().unwrap_or(&"".to_string()))
143 }
144}
145
146impl Composite {
147 pub fn new_rgb(vmin: Vec<f64>, vmax: Vec<f64>) -> Self {
157 Self {
158 vmin: Some(vmin.clone()),
159 vmax: Some(vmax.clone()),
160 display: Some("RGBComposite".to_string()),
161 gradient: None,
162 colour_definition: ColourDefinition::RGB(
163 vmin.try_into().unwrap_or_else(|v: Vec<_>| {
164 panic!("Expected a Vec of length {} but it was {}", 3, v.len())
165 }),
166 vmax.try_into().unwrap_or_else(|v: Vec<_>| {
167 panic!("Expected a Vec of length {} but it was {}", 3, v.len())
168 }),
169 ),
170 len: 3,
171 ..Default::default()
172 }
173 }
174
175 pub fn new_gradient(
186 vmin: f64,
187 vmax: f64,
188 cmap_f: &'static dyn Fn(f64, f64) -> GradientLinearRGBA,
189 ) -> Self {
190 let grad = cmap_f(vmin, vmax);
191 Self {
192 vmin: Some(vec![vmin]),
193 vmax: Some(vec![vmax]),
194 gradient: Some(grad),
195 display: Some("Gradient".to_string()),
196 len: 1,
197 ..Default::default()
198 }
199 }
200
201 pub fn new_custom_gradient(vmin: f64, vmax: f64, colours: Vec<Colour>) -> Self {
216 let grad = make_gradient(vmin, vmax, &colours);
217 Self {
218 vmin: Some(vec![vmin]),
219 vmax: Some(vec![vmax]),
220 gradient: Some(grad),
221 display: Some("Gradient".to_string()),
222 colour_definition: ColourDefinition::Colours(colours),
223 len: 1,
224 ..Default::default()
225 }
226 }
227
228 pub fn new_gradient_with_breaks(cols_and_breaks: Vec<(f64, Colour)>) -> Self {
244 let grad = make_gradient_with_breaks(&cols_and_breaks);
245 Self {
246 gradient: Some(grad),
247 display: Some("GradientWithBreaks".to_string()),
248 colour_definition: ColourDefinition::ColoursAndBreaks(cols_and_breaks),
249 len: 1,
250 ..Default::default()
251 }
252 }
253
254 pub fn new_discrete_palette(cols_and_breaks: Vec<(isize, Colour)>) -> Self {
271 let hashmap = cols_and_breaks
272 .clone()
273 .into_iter()
274 .map(|(b, c)| (b, c.into()))
275 .collect();
276
277 Self {
278 display: Some("DiscretePalette".to_string()),
279 hashmap: Some(hashmap),
280 colour_definition: ColourDefinition::Discrete(cols_and_breaks),
281 len: 1,
282 ..Default::default()
283 }
284 }
285
286 pub(crate) fn is_contiguous(&self) -> bool {
287 !matches!(self.colour_definition, ColourDefinition::RGB(_, _))
288 }
289
290 pub fn n_bands(&self) -> usize {
295 self.len
296 }
297}
298
299fn gradient_handle(comp: &Composite, values: &[f64], no_data_values: Option<&[f64]>) -> [u8; 4] {
300 let grad = comp.gradient.as_ref().unwrap();
301 let col = grad.get(values[0]);
302 let (r, g, b, a) = col.into_components();
303 let a = if let Some(ndv) = no_data_values {
304 assert!(
305 ndv.len() == 1,
306 "To use a {} style you need to provide 1 `no_data` value",
307 comp
308 );
309 if (values[0] - ndv[0]).abs() < f64::EPSILON {
310 0u8
311 } else {
312 (a * 255.0) as u8
313 }
314 } else {
315 (a * 255.0) as u8
316 };
317 [(r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8, a]
318}
319
320fn rgb_handle(comp: &Composite, values: &[f64], no_data_values: Option<&[f64]>) -> [u8; 4] {
321 let norm: Vec<f64> = values
322 .iter()
323 .enumerate()
324 .map(|(i, v)| {
325 if v > &comp.vmax.as_ref().unwrap()[i] {
326 1.0
327 } else if v < &comp.vmin.as_ref().unwrap()[i] {
328 0.0
329 } else {
330 (v - comp.vmin.as_ref().unwrap()[i])
331 / (comp.vmax.as_ref().unwrap()[i] - comp.vmin.as_ref().unwrap()[i])
332 }
333 })
334 .collect();
335 let (r, g, b, a) = (norm[0], norm[1], norm[2], 1.0);
336 let a = if let Some(no_data_values) = no_data_values {
337 assert!(
338 no_data_values.len() == 3,
339 "To use a {} style you need to provide 3 `no_data` values",
340 comp
341 );
342 if no_data_values
343 .iter()
344 .zip(values)
345 .any(|(ndv, v)| (v - ndv).abs() < f64::EPSILON)
346 {
347 0u8
348 } else {
349 (a * 255.0) as u8
350 }
351 } else {
352 (a * 255.0) as u8
353 };
354 [(r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8, a]
355}
356
357fn hashmap_handle(comp: &Composite, values: &[f64]) -> [u8; 4] {
358 let val = values[0];
359 let hash = comp.hashmap.as_ref().unwrap();
360 let (r, g, b, a) = hash
361 .get(&(val.trunc() as isize))
362 .unwrap_or(&(0.0, 0.0, 0.0, 0.0));
363 [
364 (r * 255.0) as u8,
365 (g * 255.0) as u8,
366 (b * 255.0) as u8,
367 (a * 255.0) as u8,
368 ]
369}
370
371pub trait HandleGet {
373 fn get(&self, values: &[f64], no_data_values: Option<&[f64]>) -> [u8; 4];
380}
381
382impl HandleGet for Composite {
383 fn get(&self, values: &[f64], no_data_values: std::option::Option<&[f64]>) -> [u8; 4] {
384 match &self.colour_definition {
385 ColourDefinition::Discrete(_) => hashmap_handle(self, values),
386 ColourDefinition::Colours(_) | ColourDefinition::ColoursAndBreaks(_) => {
387 gradient_handle(self, values, no_data_values)
388 }
389 ColourDefinition::RGB(_, _) => rgb_handle(self, values, no_data_values),
390 }
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 macro_rules! test_cmap {
399 ($test_name:ident, $fn:expr, $colours:expr) => {
400 #[test]
401 fn $test_name() {
402 let len = $colours.len();
403 let grad = $fn(0.0, (len - 1) as f64);
404 let expected = $colours;
405 let from_grad: Vec<Colour> = (0..len)
406 .into_iter()
407 .map(|i| grad.get(i as f64).into())
408 .collect();
409 assert_eq!(from_grad, expected)
410 }
411 };
412 }
413
414 test_cmap!(test_viridis, viridis, VIRIDIS7);
415 test_cmap!(test_inferno, inferno, INFERNO7);
416
417 #[test]
418 fn test_colour_definition_is_deserialized() {
419 let expected_col_def = ColourDefinition::Colours(vec![
420 (1., 0., 0., 1.).into(),
421 (0., 1., 0., 1.).into(),
422 (0., 0., 1., 1.).into(),
423 ]);
424
425 let s = r#"[
426 [1.0, 0.0, 0.0, 1.0],
427 [0.0, 1.0, 0.0, 1.0],
428 [0.0, 0.0, 1.0, 1.0]
429 ]"#;
430 let col_def: ColourDefinition = serde_json::from_str(s).unwrap();
431 assert_eq!(col_def, expected_col_def);
432 let s = r#"[
433 "ff0000ff",
434 "00ff00ff",
435 "0000ffff"
436 ]"#;
437 let col_def: ColourDefinition = serde_json::from_str(s).unwrap();
438 assert_eq!(col_def, expected_col_def);
439 }
440}