render.rs

2.8 kB · rust · 95 lines

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