Skip to main content

mandelbruhst_cli/
palette.rs

1use crate::opts::Interval;
2use anyhow::{anyhow, Result};
3use image::Rgb;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, Serialize, Deserialize)]
7pub struct ColorPalette {
8    pub color_vals: Vec<ConfigRGB>,
9}
10
11#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
12pub struct ConfigRGB {
13    pub value: f64,
14    // NOTE: this makes the yaml code really verbose, ideally would like to change this into
15    // [red, green, blue] in a vec or hex code
16    pub red: u8,
17    pub green: u8,
18    pub blue: u8,
19}
20
21impl ConfigRGB {
22    pub fn to_rgb(&self) -> Rgb<u8> {
23        Rgb([self.red, self.green, self.blue])
24    }
25
26    pub fn lerp(&self, o: &Self, value: f64) -> Rgb<u8> {
27        let r_interval = Interval {
28            lower: self.red as f64,
29            upper: o.red as f64,
30        };
31        let g_interval = Interval {
32            lower: self.green as f64,
33            upper: o.green as f64,
34        };
35        let b_interval = Interval {
36            lower: self.blue as f64,
37            upper: o.blue as f64,
38        };
39        let frac = (value - self.value) / (o.value - self.value);
40
41        Rgb([
42            r_interval.lerp(frac) as u8,
43            g_interval.lerp(frac) as u8,
44            b_interval.lerp(frac) as u8,
45        ])
46    }
47}
48
49impl ColorPalette {
50    // TODO: switch config to use this instead to enforce constraints
51    pub fn new(color_vals: Vec<ConfigRGB>) -> Result<ColorPalette> {
52        if color_vals.len() < 2 {
53            return Err(anyhow!("need color vals"));
54        }
55
56        let mut sorted_colors = color_vals;
57        sorted_colors.sort_by(|a, b| a.value.partial_cmp(&b.value).unwrap());
58
59        let first = sorted_colors
60            .first()
61            .expect("more than 2 vals is an assertion")
62            .value;
63        let last = sorted_colors.last().unwrap().value;
64
65        if first != 0.0 || last != 1.0 {
66            return Err(anyhow!("need vals for 0.0 and 1.0"));
67        }
68
69        Ok(ColorPalette {
70            color_vals: sorted_colors,
71        })
72    }
73
74    pub fn value(&self, value: f64) -> Rgb<u8> {
75        if value > 1. {
76            return self.color_vals.last().unwrap().to_rgb();
77        }
78
79        match self
80            .color_vals
81            .binary_search_by(|&color| color.value.partial_cmp(&value).unwrap())
82        {
83            Ok(i) => self.color_vals[i].to_rgb(),
84            Err(i) => {
85                let c1 = self.color_vals[i - 1];
86                let c2 = self.color_vals[i];
87                c1.lerp(&c2, value)
88            }
89        }
90    }
91}