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)]
enum Label {
Inactive,
LinearH,
LinearV,
Matrix,
}
#[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,
pub reads_horizontal: bool,
}
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];
let w = grid.width;
for y in 0..grid.height {
let ty = y / tile;
let base = ty * cols;
let row = &grid.dark[y * w..(y + 1) * w];
let prev = (y >= 1).then(|| &grid.dark[(y - 1) * w..y * w]);
for (x, &d) in row.iter().enumerate() {
let idx = base + x / tile;
area[idx] += 1;
if x >= 1 && d != row[x - 1] {
htrans[idx] += 1;
}
if let Some(prev) = prev
&& d != prev[x]
{
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 label: Vec<Label> = (0..cols * rows)
.map(|i| {
let a = stats.area[i];
if a == 0 {
return Label::Inactive;
}
let h = stats.htrans[i];
let v = stats.vtrans[i];
let total = h + v;
if total as f32 / (a as f32) < edge_density {
return Label::Inactive;
}
let anisotropy = (h as f32 - v as f32).abs() / total as f32;
if anisotropy >= aniso {
if h >= v {
Label::LinearH
} else {
Label::LinearV
}
} else {
Label::Matrix
}
})
.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;
let seed = label[start];
if seed == Label::Inactive || visited[start] {
continue;
}
visited[start] = true;
stack.push((sx, sy));
let reach = if seed == Label::Matrix { 1 } else { 2 };
let mut min_tx = sx;
let mut max_tx = sx;
let mut min_ty = sy;
let mut max_ty = sy;
let mut tiles = 0usize;
while let Some((cx, cy)) = stack.pop() {
tiles += 1;
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(reach);
let x1 = (cx + reach).min(cols - 1);
let y0 = cy.saturating_sub(reach);
let y1 = (cy + reach).min(rows - 1);
for ny in y0..=y1 {
for nx in x0..=x1 {
let nidx = ny * cols + nx;
if !visited[nidx] && label[nidx] == seed {
visited[nidx] = true;
stack.push((nx, ny));
}
}
}
}
if tiles < min_tiles {
continue;
}
let family = match seed {
Label::LinearH | Label::LinearV => Family::Linear,
_ => Family::Matrix,
};
if family == Family::Matrix && (max_tx - min_tx < 1 || max_ty - min_ty < 1) {
continue;
}
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,
reads_horizontal: seed != Label::LinearV,
});
}
}
out
}