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}