use crate::detect::roi::TileMask;
use crate::frame::{RawFrame, Rect};
#[derive(Debug, Clone)]
pub struct WorkingFrame {
pub cols: u16,
pub rows: u16,
pub luma: Box<[u8]>,
}
impl WorkingFrame {
pub fn from_raw(frame: &RawFrame, cols: u16, rows: u16) -> Self {
let cols = cols.max(1);
let rows = rows.max(1);
let w = frame.width.max(1);
let h = frame.height.max(1);
let mut luma = vec![0u8; cols as usize * rows as usize];
for ty in 0..rows as u32 {
let y0 = (ty * h) / rows as u32;
let y1 = (((ty + 1) * h) / rows as u32).max(y0 + 1).min(h);
for tx in 0..cols as u32 {
let x0 = (tx * w) / cols as u32;
let x1 = (((tx + 1) * w) / cols as u32).max(x0 + 1).min(w);
let mut sum: u64 = 0;
let mut count: u64 = 0;
let step_x = ((x1 - x0) / 16).max(1);
let step_y = ((y1 - y0) / 16).max(1);
let mut py = y0;
while py < y1 {
let row_off = py as usize * frame.stride as usize;
let mut px = x0;
while px < x1 {
let off = row_off + px as usize * 4;
let b = frame.buffer.get(off).copied().unwrap_or(0) as u64;
let g = frame.buffer.get(off + 1).copied().unwrap_or(0) as u64;
let r = frame.buffer.get(off + 2).copied().unwrap_or(0) as u64;
sum += (54 * r + 183 * g + 19 * b) >> 8;
count += 1;
px += step_x;
}
py += step_y;
}
let mean = if count == 0 { 0 } else { (sum / count) as u8 };
luma[(ty * cols as u32 + tx) as usize] = mean;
}
}
Self {
cols,
rows,
luma: luma.into_boxed_slice(),
}
}
pub fn len(&self) -> usize {
self.luma.len()
}
pub fn is_empty(&self) -> bool {
self.luma.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct TileDiff {
pub changed: Box<[bool]>,
pub changed_count: u32,
pub area_ratio: f32,
pub bboxes: Vec<Rect>,
pub cols: u16,
pub rows: u16,
}
impl TileDiff {
pub fn empty(cols: u16, rows: u16) -> Self {
Self {
changed: vec![false; cols as usize * rows as usize].into_boxed_slice(),
changed_count: 0,
area_ratio: 0.0,
bboxes: Vec::new(),
cols,
rows,
}
}
}
pub fn diff(
prev: &WorkingFrame,
cur: &WorkingFrame,
threshold: u8,
ignore: &TileMask,
frame_w: u32,
frame_h: u32,
) -> TileDiff {
let cols = cur.cols;
let rows = cur.rows;
let len = cur.luma.len();
let mut changed = vec![false; len].into_boxed_slice();
let mut count = 0u32;
if prev.luma.len() == len {
for i in 0..len {
if ignore.get(i) {
continue;
}
let d = prev.luma[i].abs_diff(cur.luma[i]);
if d > threshold {
changed[i] = true;
count += 1;
}
}
}
let total = len.max(1) as f32;
let bboxes = connected_bboxes(&changed, cols, rows, frame_w, frame_h);
TileDiff {
changed,
changed_count: count,
area_ratio: count as f32 / total,
bboxes,
cols,
rows,
}
}
fn connected_bboxes(
changed: &[bool],
cols: u16,
rows: u16,
frame_w: u32,
frame_h: u32,
) -> Vec<Rect> {
let cols = cols as usize;
let rows = rows as usize;
let mut visited = vec![false; changed.len()];
let mut boxes = Vec::new();
let mut stack: Vec<usize> = Vec::new();
for start in 0..changed.len() {
if !changed[start] || visited[start] {
continue;
}
let (mut min_c, mut min_r, mut max_c, mut max_r) = (usize::MAX, usize::MAX, 0usize, 0usize);
stack.clear();
stack.push(start);
visited[start] = true;
while let Some(idx) = stack.pop() {
let c = idx % cols;
let r = idx / cols;
min_c = min_c.min(c);
min_r = min_r.min(r);
max_c = max_c.max(c);
max_r = max_r.max(r);
if c > 0 {
let n = idx - 1;
if changed[n] && !visited[n] {
visited[n] = true;
stack.push(n);
}
}
if c + 1 < cols {
let n = idx + 1;
if changed[n] && !visited[n] {
visited[n] = true;
stack.push(n);
}
}
if r > 0 {
let n = idx - cols;
if changed[n] && !visited[n] {
visited[n] = true;
stack.push(n);
}
}
if r + 1 < rows {
let n = idx + cols;
if changed[n] && !visited[n] {
visited[n] = true;
stack.push(n);
}
}
}
let px0 = (min_c as u32 * frame_w) / cols as u32;
let py0 = (min_r as u32 * frame_h) / rows as u32;
let px1 = ((max_c as u32 + 1) * frame_w) / cols as u32;
let py1 = ((max_r as u32 + 1) * frame_h) / rows as u32;
boxes.push(Rect::new(
px0 as i32,
py0 as i32,
px1.saturating_sub(px0),
py1.saturating_sub(py0),
));
}
boxes
}