use super::grid::DownGrid;
struct TileStats {
cols: usize,
rows: usize,
tile: usize,
htrans: Vec<u32>,
vtrans: Vec<u32>,
area: Vec<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Family {
Matrix,
Linear,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Region {
pub x0: usize,
pub y0: usize,
pub x1: usize,
pub y1: usize,
pub family: Family,
}
impl Region {
pub fn area(&self) -> usize {
(self.x1 - self.x0) * (self.y1 - self.y0)
}
}
impl TileStats {
fn build(grid: &DownGrid, tile: usize) -> TileStats {
let tile = tile.max(1);
let cols = grid.width.div_ceil(tile);
let rows = grid.height.div_ceil(tile);
let mut htrans = vec![0u32; cols * rows];
let mut vtrans = vec![0u32; cols * rows];
let mut area = vec![0u32; cols * rows];
for y in 0..grid.height {
let ty = y / tile;
let base = ty * cols;
for x in 0..grid.width {
let tx = x / tile;
let idx = base + tx;
area[idx] += 1;
let d = grid.dark(x, y);
if x >= 1 && d != grid.dark(x - 1, y) {
htrans[idx] += 1;
}
if y >= 1 && d != grid.dark(x, y - 1) {
vtrans[idx] += 1;
}
}
}
TileStats {
cols,
rows,
tile,
htrans,
vtrans,
area,
}
}
}
pub(crate) fn regions(
grid: &DownGrid,
tile: usize,
edge_density: f32,
min_tiles: usize,
aniso: f32,
) -> Vec<Region> {
let stats = TileStats::build(grid, tile);
let cols = stats.cols;
let rows = stats.rows;
let active: Vec<bool> = (0..cols * rows)
.map(|i| {
let a = stats.area[i];
if a == 0 {
return false;
}
let density = (stats.htrans[i] + stats.vtrans[i]) as f32 / a as f32;
density >= edge_density
})
.collect();
let mut visited = vec![false; cols * rows];
let mut stack: Vec<(usize, usize)> = Vec::new();
let mut out = Vec::new();
for sy in 0..rows {
for sx in 0..cols {
let start = sy * cols + sx;
if !active[start] || visited[start] {
continue;
}
visited[start] = true;
stack.push((sx, sy));
let mut min_tx = sx;
let mut max_tx = sx;
let mut min_ty = sy;
let mut max_ty = sy;
let mut tiles = 0usize;
let mut h_sum = 0u64;
let mut v_sum = 0u64;
while let Some((cx, cy)) = stack.pop() {
let idx = cy * cols + cx;
tiles += 1;
h_sum += u64::from(stats.htrans[idx]);
v_sum += u64::from(stats.vtrans[idx]);
min_tx = min_tx.min(cx);
max_tx = max_tx.max(cx);
min_ty = min_ty.min(cy);
max_ty = max_ty.max(cy);
let x0 = cx.saturating_sub(1);
let x1 = (cx + 1).min(cols - 1);
let y0 = cy.saturating_sub(1);
let y1 = (cy + 1).min(rows - 1);
for ny in y0..=y1 {
for nx in x0..=x1 {
let nidx = ny * cols + nx;
if active[nidx] && !visited[nidx] {
visited[nidx] = true;
stack.push((nx, ny));
}
}
}
}
if tiles < min_tiles {
continue;
}
let total = (h_sum + v_sum) as f32;
let anisotropy = if total > 0.0 {
(h_sum as f32 - v_sum as f32).abs() / total
} else {
0.0
};
let family = if anisotropy >= aniso {
Family::Linear
} else {
Family::Matrix
};
let t = stats.tile;
out.push(Region {
x0: min_tx * t,
y0: min_ty * t,
x1: ((max_tx + 1) * t).min(grid.width),
y1: ((max_ty + 1) * t).min(grid.height),
family,
});
}
}
out
}