#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Tile {
pub index: usize,
pub core_row: usize,
pub core_col: usize,
pub core_rows: usize,
pub core_cols: usize,
pub read_row: usize,
pub read_col: usize,
pub read_rows: usize,
pub read_cols: usize,
}
impl Tile {
pub fn core_offset_row(&self) -> usize {
self.core_row - self.read_row
}
pub fn core_offset_col(&self) -> usize {
self.core_col - self.read_col
}
}
#[derive(Debug, Clone)]
pub struct TileGrid {
rows: usize,
cols: usize,
tile_size: usize,
overlap: usize,
tiles_down: usize,
tiles_across: usize,
next: usize,
}
impl TileGrid {
pub fn new(rows: usize, cols: usize, tile_size: usize, overlap: usize) -> Self {
let tile_size = tile_size.max(1);
let tiles_down = rows.div_ceil(tile_size);
let tiles_across = cols.div_ceil(tile_size);
Self {
rows,
cols,
tile_size,
overlap,
tiles_down,
tiles_across,
next: 0,
}
}
pub fn len(&self) -> usize {
self.tiles_down * self.tiles_across
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, index: usize) -> Option<Tile> {
if index >= self.len() {
return None;
}
let trow = index / self.tiles_across;
let tcol = index % self.tiles_across;
let core_row = trow * self.tile_size;
let core_col = tcol * self.tile_size;
let core_rows = self.tile_size.min(self.rows - core_row);
let core_cols = self.tile_size.min(self.cols - core_col);
let read_row = core_row.saturating_sub(self.overlap);
let read_col = core_col.saturating_sub(self.overlap);
let read_end_row = (core_row + core_rows + self.overlap).min(self.rows);
let read_end_col = (core_col + core_cols + self.overlap).min(self.cols);
Some(Tile {
index,
core_row,
core_col,
core_rows,
core_cols,
read_row,
read_col,
read_rows: read_end_row - read_row,
read_cols: read_end_col - read_col,
})
}
}
impl Iterator for TileGrid {
type Item = Tile;
fn next(&mut self) -> Option<Tile> {
let tile = self.get(self.next)?;
self.next += 1;
Some(tile)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.len().saturating_sub(self.next);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for TileGrid {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cores_cover_grid_exactly() {
let (rows, cols) = (1000, 1500);
let mut covered = vec![false; rows * cols];
for tile in TileGrid::new(rows, cols, 256, 16) {
for r in tile.core_row..tile.core_row + tile.core_rows {
for c in tile.core_col..tile.core_col + tile.core_cols {
assert!(!covered[r * cols + c], "core overlap at ({}, {})", r, c);
covered[r * cols + c] = true;
}
}
}
assert!(covered.iter().all(|&v| v), "gap in core coverage");
}
#[test]
fn read_extent_clamps_at_borders() {
let grid = TileGrid::new(100, 100, 50, 10);
let tiles: Vec<Tile> = grid.collect();
assert_eq!(tiles.len(), 4);
let tl = tiles[0];
assert_eq!((tl.read_row, tl.read_col), (0, 0));
assert_eq!((tl.read_rows, tl.read_cols), (60, 60));
assert_eq!((tl.core_offset_row(), tl.core_offset_col()), (0, 0));
let br = tiles[3];
assert_eq!((br.read_row, br.read_col), (40, 40));
assert_eq!((br.read_rows, br.read_cols), (60, 60));
assert_eq!((br.core_offset_row(), br.core_offset_col()), (10, 10));
}
#[test]
fn interior_tile_has_full_halo() {
let grid = TileGrid::new(300, 300, 100, 8);
let center = grid.get(4).unwrap(); assert_eq!((center.core_row, center.core_col), (100, 100));
assert_eq!((center.read_row, center.read_col), (92, 92));
assert_eq!((center.read_rows, center.read_cols), (116, 116));
}
#[test]
fn ragged_last_tiles() {
let grid = TileGrid::new(70, 130, 64, 4);
assert_eq!(grid.len(), 6); let last = grid.get(5).unwrap();
assert_eq!((last.core_rows, last.core_cols), (6, 2));
assert_eq!(
last.read_row + last.read_rows,
70,
"read extent must clamp to grid"
);
assert_eq!(last.read_col + last.read_cols, 130);
}
#[test]
fn zero_overlap_read_equals_core() {
for tile in TileGrid::new(128, 128, 32, 0) {
assert_eq!(tile.read_row, tile.core_row);
assert_eq!(tile.read_rows, tile.core_rows);
assert_eq!(tile.read_cols, tile.core_cols);
}
}
#[test]
fn empty_grid_yields_nothing() {
assert_eq!(TileGrid::new(0, 100, 64, 4).count(), 0);
assert!(TileGrid::new(0, 0, 64, 0).is_empty());
}
#[test]
fn exact_size_iterator() {
let mut grid = TileGrid::new(512, 512, 128, 16);
assert_eq!(grid.len(), 16);
grid.next();
assert_eq!(grid.size_hint(), (15, Some(15)));
}
}