yt_tools/
colormaps.rs

1extern crate wasm_bindgen;
2use wasm_bindgen::prelude::*;
3
4use std::collections::HashMap;
5use std::f64;
6
7pub fn get_normalizer(name: String) -> (fn(f64) -> f64) {
8    let f: fn(f64) -> f64 = match name.to_lowercase().as_ref() {
9        "log" => |f| f.log10(),
10        "linear" => |f| f,
11        _ => |f| f,
12    };
13    f
14}
15
16#[wasm_bindgen]
17#[derive(Clone, Debug)]
18pub struct RGBAValue {
19    red: u8,
20    green: u8,
21    blue: u8,
22    alpha: u8,
23}
24
25#[wasm_bindgen]
26#[derive(Clone, Debug)]
27pub struct Colormap {
28    table: Vec<RGBAValue>,
29}
30
31#[wasm_bindgen]
32#[derive(Clone, Debug)]
33pub struct ColormapCollection {
34    color_maps: HashMap<String, Colormap>,
35}
36
37impl RGBAValue {
38    pub fn new(red: u8, green: u8, blue: u8, alpha: u8) -> RGBAValue {
39        RGBAValue { red, green, blue, alpha }
40    }
41}
42
43#[wasm_bindgen]
44impl Colormap {
45    #[wasm_bindgen(constructor)]
46    pub fn new(
47        rgba: Vec<u8>,
48    ) -> Colormap {
49        if rgba.len() % 4 != 0 {
50            panic!("Needs RGBA flattened.");
51        }
52        let mut table: Vec<RGBAValue> = Vec::new();
53        for i in 0..rgba.len()/4 {
54            table.push( RGBAValue::new(
55                rgba[i * 4 + 0],
56                rgba[i * 4 + 1],
57                rgba[i * 4 + 2],
58                rgba[i * 4 + 3]
59            ));
60        }
61        Colormap {
62            table: table,
63        }
64    }
65}
66
67#[wasm_bindgen]
68impl ColormapCollection {
69    #[wasm_bindgen(constructor)]
70    pub fn new() -> ColormapCollection {
71        let mut color_maps = HashMap::new();
72        let mut default_cmap: Vec<RGBAValue> = Vec::new();
73        for i in 0..256 {
74            default_cmap.push(RGBAValue::new(i as u8, i as u8, i as u8, 255));
75        }
76        color_maps.insert(String::from("default"), 
77            Colormap {
78                table: default_cmap,
79            }
80        );
81        ColormapCollection { color_maps }
82    }
83
84    pub fn add_colormap(&mut self, name: String, table: Vec<u8>) {
85        self.color_maps.insert(name.clone(), Colormap::new(table));
86    }
87
88    pub fn normalize(
89        &mut self,
90        name: String,
91        buffer: Vec<f64>,
92        image: &mut [u8],
93        min_val: Option<f64>,
94        max_val: Option<f64>,
95        take_log: bool,
96    ) {
97        let f = match take_log {
98            true => get_normalizer("log".to_string()),
99            false => get_normalizer("linear".to_string()),
100        };
101        let mut cmin_val: f64 = 0.0;
102        let mut cmax_val: f64 = 0.0;
103        if min_val == None || max_val == None {
104            cmin_val = f64::MAX;
105            cmax_val = f64::MIN;
106            for v in &buffer {
107                cmin_val = cmin_val.min(*v);
108                cmax_val = cmax_val.max(*v);
109            }
110        }
111        cmin_val = match min_val {
112            Some(v) => v,
113            None => cmin_val,
114        };
115        cmax_val = match max_val {
116            Some(v) => v,
117            None => cmax_val,
118        };
119        let cmap = match self.color_maps.get(&name) {
120            Some(cmap) => cmap,
121            None => panic!("Colormap {:?} does not exist.", name),
122        };
123        cmin_val = f(cmin_val);
124        cmax_val = f(cmax_val);
125        let tsize = cmap.table.len();
126        for (i, &x) in buffer.iter().enumerate() {
127            let scaled = ((f(x) - cmin_val) / (cmax_val - cmin_val))
128                .min(1.0)
129                .max(0.0);
130            let bin_id = ((scaled * (tsize as f64)) as usize)
131                          .max(0).min(tsize - 1);
132            image[i * 4 + 0] = cmap.table[bin_id].red;
133            image[i * 4 + 1] = cmap.table[bin_id].green;
134            image[i * 4 + 2] = cmap.table[bin_id].blue;
135            image[i * 4 + 3] = cmap.table[bin_id].alpha;
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142
143    use super::*;
144
145    fn linear_ramp_cmap() -> Vec<u8> {
146        let mut rgba_map: Vec<u8> = Vec::new();
147        for i in 0..256 {
148            rgba_map.push(i as u8);
149            rgba_map.push(0);
150            rgba_map.push(0);
151            rgba_map.push(255);
152        }
153        rgba_map
154    }
155
156    #[test]
157    fn create_colormap() {
158        // We will make a vector. This is in R, G, B, A order.
159        // We will make it with linearly ramping R, 255 A, and 0 elsewhere.
160        let rgba_map = linear_ramp_cmap();
161        let _cm = Colormap::new(rgba_map);
162        // Test that our pixels are in the right order.
163        for (i, rgba) in _cm.table.iter().enumerate() {
164            assert_eq!(i as u8, rgba.red);
165            assert_eq!(0, rgba.green);
166            assert_eq!(0, rgba.blue);
167            assert_eq!(255, rgba.alpha);
168        }
169    }
170
171    #[test]
172    #[should_panic]
173    fn create_bad_colormap() {
174        let mut rgba_map = linear_ramp_cmap().clone();
175        rgba_map.pop();
176        let _cm = Colormap::new(rgba_map);
177    }
178
179    #[test]
180    fn create_colormap_collection() {
181        let mut cmap_collection = ColormapCollection::new();
182        cmap_collection.add_colormap("simple".to_string(), linear_ramp_cmap());
183
184        // Create a normalized f64 buffer
185        let mut ibuf: Vec<f64> = Vec::new();
186        for i in 0..256 {
187            ibuf.push( (i as f64) / 256.0);
188        }
189
190        // Our output image
191        let mut obuf: Vec<u8> = Vec::new();
192        obuf.resize(256 * 4, 0);
193
194        cmap_collection.normalize("default".to_string(), ibuf.clone(), obuf.as_mut_slice(), None, None, false);
195
196        for (i, rgba) in obuf.chunks_exact(4).enumerate() {
197            assert_eq!(rgba[0], i as u8);
198            assert_eq!(rgba[1], i as u8);
199            assert_eq!(rgba[2], i as u8);
200            assert_eq!(rgba[3], 255);
201        }
202
203        cmap_collection.normalize("simple".to_string(), ibuf.clone(), obuf.as_mut_slice(), None, None, false);
204
205        for (i, rgba) in obuf.chunks_exact(4).enumerate() {
206            assert_eq!(rgba[0], i as u8);
207            assert_eq!(rgba[1], 0);
208            assert_eq!(rgba[2], 0);
209            assert_eq!(rgba[3], 255);
210        }
211        
212        // Create a normalized f64 buffer
213        ibuf.resize(0, 0.0);
214
215        for i in 0..256 {
216            ibuf.push( 10_f64.powf((i as f64) / 256.0));
217        }
218
219        cmap_collection.normalize("simple".to_string(), ibuf.clone(), obuf.as_mut_slice(), None, None, true);
220        for (i, rgba) in obuf.chunks_exact(4).enumerate() {
221            assert_eq!(rgba[0], i as u8);
222            assert_eq!(rgba[1], 0);
223            assert_eq!(rgba[2], 0);
224            assert_eq!(rgba[3], 255);
225        }
226    }
227}