crop.rs

3.6 kB · rust · 118 lines

1use crate::two::Cell2d;2use mrlycore::tensor::Tensor;34/// Crops a frame sequence to the centred square bounding every cell ever alive.5pub fn crop(grids: &[Cell2d]) -> Vec<Cell2d> {6    if grids.is_empty() {7        return Vec::new();8    }9    let shape = &grids[0].types().shape;10    let (h, w) = (shape[0], shape[1]);11    let mut any = vec![false; h * w];12    let mut found = false;13    for grid in grids {14        for (i, &v) in grid.types().bytes().iter().enumerate() {15            if v == 1 {16                any[i] = true;17                found = true;18            }19        }20    }21    if !found {22        return grids.to_vec();23    }24    let (mut rmin, mut rmax, mut cmin, mut cmax) = (h, 0usize, w, 0usize);25    for r in 0..h {26        for c in 0..w {27            if any[r * w + c] {28                rmin = rmin.min(r);29                rmax = rmax.max(r);30                cmin = cmin.min(c);31                cmax = cmax.max(c);32            }33        }34    }35    let bh = rmax - rmin + 1;36    let bw = cmax - cmin + 1;37    if bh == h && bw == w {38        return grids.to_vec();39    }40    let side = bh.max(bw);41    let pad_top = (side - bh) / 2;42    let pad_left = (side - bw) / 2;43    grids44        .iter()45        .map(|grid| {46            let src = grid.types();47            let mut out = Tensor::new(vec![side, side]);48            for r in 0..bh {49                for c in 0..bw {50                    let v = src.get(&[rmin + r, cmin + c]);51                    if v != 0 {52                        out.set(&[pad_top + r, pad_left + c], v);53                    }54                }55            }56            Cell2d::new(out)57        })58        .collect()59}6061/// Tiles every frame n by n to reach at least min_canvas a side, unchanged when already there.62pub fn tessellate(grids: &[Cell2d], min_canvas: usize) -> Vec<Cell2d> {63    if grids.is_empty() || min_canvas == 0 {64        return grids.to_vec();65    }66    let shape = &grids[0].types().shape;67    let side = shape[0].max(shape[1]);68    if side == 0 || side >= min_canvas {69        return grids.to_vec();70    }71    let n = min_canvas.div_ceil(side);72    grids.iter().map(|grid| grid.clone().tile(n, n)).collect()73}7475#[cfg(test)]76mod tests {77    use super::*;78    #[test]79    fn crops_to_bounding_square() {80        let mut t = Tensor::new(vec![5, 5]);81        t.set(&[2, 3], 1);82        let cropped = crop(&[Cell2d::new(t)]);83        assert_eq!(cropped[0].types().shape, vec![1, 1]);84        assert_eq!(cropped[0].types().get(&[0, 0]), 1);85    }86    #[test]87    fn empty_is_returned_unchanged() {88        let t = Tensor::new(vec![4, 4]);89        let cropped = crop(&[Cell2d::new(t)]);90        assert_eq!(cropped[0].types().shape, vec![4, 4]);91    }92    #[test]93    fn full_grid_is_unchanged() {94        let mut t = Tensor::new(vec![3, 3]);95        t.set(&[0, 0], 1);96        t.set(&[2, 2], 1);97        let cropped = crop(&[Cell2d::new(t)]);98        assert_eq!(cropped[0].types().shape, vec![3, 3]);99    }100    #[test]101    fn tessellate_tiles_small_runs_up_to_the_canvas() {102        let mut t = Tensor::new(vec![5, 5]);103        t.set(&[1, 2], 1);104        let grids = vec![Cell2d::new(t)];105        let tiled = tessellate(&grids, 12);106        assert_eq!(tiled[0].types().shape, vec![15, 15]);107        assert_eq!(tiled[0].types().get(&[1, 2]), 1);108        assert_eq!(tiled[0].types().get(&[6, 7]), 1);109        assert_eq!(tiled[0].types().sum(), 9);110    }111    #[test]112    fn tessellate_leaves_big_runs_alone() {113        let grids = vec![Cell2d::new(Tensor::new(vec![5, 5]))];114        assert_eq!(tessellate(&grids, 5)[0].types().shape, vec![5, 5]);115        assert_eq!(tessellate(&grids, 0)[0].types().shape, vec![5, 5]);116        assert!(tessellate(&[], 12).is_empty());117    }118}