Skip to main content

blazediff_milo/
map.rs

1//! Rendering the per-pixel error map into an RGBA visualization.
2
3/// Paint `map`, one value per pixel of a `width` x `height` image, over an
4/// RGBA8 `output` buffer as an opaque grayscale image, bright where the
5/// perceived error is high. Values are clamped to `0..=1`, the range the
6/// reference's own colormap render assumes.
7///
8/// A no-op when `map` or `output` is too short for `width * height`.
9pub fn render_map(output: &mut [u8], width: usize, height: usize, map: &[f32]) {
10    let pixels = width * height;
11    if map.len() < pixels || output.len() < pixels * 4 {
12        return;
13    }
14    for (pixel, value) in output.chunks_exact_mut(4).zip(map) {
15        let gray = (value.clamp(0.0, 1.0) * 255.0) as u8;
16        pixel[0] = gray;
17        pixel[1] = gray;
18        pixel[2] = gray;
19        pixel[3] = 255;
20    }
21}
22
23#[cfg(test)]
24mod tests {
25    use super::*;
26
27    #[test]
28    fn no_error_paints_opaque_black() {
29        let mut output = vec![7u8; 2 * 2 * 4];
30        render_map(&mut output, 2, 2, &[0.0; 4]);
31        for pixel in output.chunks_exact(4) {
32            assert_eq!(pixel, [0, 0, 0, 255]);
33        }
34    }
35
36    #[test]
37    fn out_of_range_values_are_clamped() {
38        let mut output = vec![0u8; 2 * 4];
39        render_map(&mut output, 2, 1, &[-3.0, 7.0]);
40        assert_eq!(&output[..4], &[0, 0, 0, 255]);
41        assert_eq!(&output[4..], &[255, 255, 255, 255]);
42    }
43
44    #[test]
45    fn a_short_map_leaves_the_output_alone() {
46        let mut output = vec![9u8; 2 * 4];
47        render_map(&mut output, 2, 1, &[0.5]);
48        assert!(output.iter().all(|byte| *byte == 9));
49    }
50}