render.rs

2.8 kB · rust · 99 lines

1use super::field::Field;2use crate::core::error::{value_error, Result};3use crate::core::ramp::Colorizer;45/// Quantizes a field into colored levels and encodes PNG bytes.6///7/// # Errors8///9/// Errors at scale zero.10pub fn render(11    field: &Field,12    colorizer: &Colorizer,13    levels: usize,14    symmetric: bool,15    invert: bool,16    scale: usize,17) -> Result<Vec<u8>> {18    if scale < 1 {19        return value_error("scale must be at least 1.");20    }21    let levels = levels.max(2);22    let size = field.size;23    let norm = field.normalized(symmetric);24    let max_val = levels - 1;25    let mut rgba = vec![[0u8; 4]; size * size];26    for (i, &v) in norm.iter().enumerate() {27        let t = if invert { 1.0 - v } else { v };28        let bucket = ((t * max_val as f32).round() as usize).min(max_val);29        let c = colorizer.color(bucket + 1, levels);30        rgba[i] = [c.r, c.g, c.b, 255];31    }32    crate::core::png(&rgba, size, size, scale)33}3435#[cfg(test)]36mod tests {37    use super::*;38    use crate::math::moire::{stack, Combine, Lattice, Spec};39    #[test]40    fn renders_png_bytes() {41        let f = stack(42            Spec::new(7, 2, 2),43            &[1, 3, 5],44            Combine::Sum,45            1,46            Lattice::Square,47            32,48            &[],49        )50        .unwrap();51        let png = render(&f, &Colorizer::fire(), 64, false, false, 2).unwrap();52        assert_eq!(&png[1..4], b"PNG");53    }54    #[test]55    fn png_pixels_stay_pinned() {56        let f = stack(57            Spec::new(7, 2, 2),58            &[1, 3, 5],59            Combine::Sum,60            1,61            Lattice::Square,62            32,63            &[],64        )65        .unwrap();66        let cases = [67            (68                render(&f, &Colorizer::fire(), 64, false, false, 2).unwrap(),69                64,70                3_230_592,71                [255, 232, 128, 255],72                [205, 91, 41, 255],73            ),74            (75                render(&f, &Colorizer::heat(), 32, true, true, 3).unwrap(),76                96,77                9_073_080,78                [255, 255, 255, 255],79                [214, 214, 214, 255],80            ),81            (82                render(&f, &Colorizer::diverge(), 8, true, false, 1).unwrap(),83                32,84                684_184,85                [255, 61, 64, 255],86                [255, 119, 121, 255],87            ),88        ];89        for (bytes, side, sum, corner, centre) in &cases {90            let (w, h, pixels) = crate::core::unpng(bytes).unwrap();91            assert_eq!((w, h), (*side, *side));92            let bytes: u64 = pixels.iter().flatten().map(|&b| u64::from(b)).sum();93            assert_eq!(bytes, *sum);94            assert_eq!(pixels[0], *corner);95            assert_eq!(pixels[side * side - 1], *corner);96            assert_eq!(pixels[(side / 2) * side + side / 2], *centre);97        }98    }99}