#[cfg(feature = "debug-rect")]
use std::sync::Mutex;
#[cfg(feature = "debug-rect")]
#[doc(hidden)]
pub static LOG: Mutex<Vec<String>> = Mutex::new(Vec::new());
#[cfg(feature = "debug-rect")]
#[macro_export]
macro_rules! debug_rect {
($stage:expr, $x:expr, $y:expr, $w:expr, $h:expr, $($arg:tt)*) => {{
let msg = format!($($arg)*);
let row = format!("{},{},{},{},{},{}", $stage, $x, $y, $w, $h, msg.replace(',', ";"));
if let Ok(mut buf) = $crate::debug_rect::LOG.lock() {
buf.push(row);
}
}};
}
#[cfg(not(feature = "debug-rect"))]
#[macro_export]
macro_rules! debug_rect {
($stage:expr, $x:expr, $y:expr, $w:expr, $h:expr, $($arg:tt)*) => {
if false {
let _ = ($stage, $x, $y, $w, $h);
let _ = format_args!($($arg)*);
}
};
}
#[cfg(feature = "debug-rect")]
pub fn clear() {
if let Ok(mut buf) = LOG.lock() {
buf.clear();
}
}
#[cfg(not(feature = "debug-rect"))]
pub fn clear() {}
#[cfg(feature = "debug-rect")]
pub fn flush(base_path: &str) {
let path = if base_path.is_empty() {
"debug_rect.csv".to_string()
} else {
format!("{base_path}.debug_rect.csv")
};
let rows = {
let Ok(buf) = LOG.lock() else { return };
buf.clone()
};
if rows.is_empty() {
return;
}
let mut out = String::with_capacity(rows.len() * 80);
out.push_str("stage,x,y,w,h,message\n");
for row in &rows {
out.push_str(row);
out.push('\n');
}
if let Err(e) = std::fs::write(&path, &out) {
eprintln!("debug_rect: failed to write {path}: {e}");
} else {
eprintln!("debug_rect: wrote {} rows to {path}", rows.len());
}
}
#[cfg(not(feature = "debug-rect"))]
pub fn flush(_base_path: &str) {}
#[cfg(feature = "debug-rect")]
pub fn query_overlapping(qx: i64, qy: i64, qw: i64, qh: i64) -> Vec<String> {
let Ok(buf) = LOG.lock() else {
return Vec::new();
};
let mut hits = Vec::new();
for row in buf.iter() {
let parts: Vec<&str> = row.splitn(6, ',').collect();
if parts.len() < 5 {
continue;
}
let Ok(rx) = parts[1].parse::<i64>() else {
continue;
};
let Ok(ry) = parts[2].parse::<i64>() else {
continue;
};
let Ok(rw) = parts[3].parse::<i64>() else {
continue;
};
let Ok(rh) = parts[4].parse::<i64>() else {
continue;
};
if rx < qx + qw && rx + rw > qx && ry < qy + qh && ry + rh > qy {
hits.push(row.clone());
}
}
hits
}
#[cfg(not(feature = "debug-rect"))]
pub fn query_overlapping(_qx: i64, _qy: i64, _qw: i64, _qh: i64) -> Vec<String> {
Vec::new()
}
#[cfg(feature = "debug-rect")]
pub fn find_worst_block(
img_a: &[u8],
img_b: &[u8],
width: usize,
height: usize,
channels: usize,
block_size: usize,
) -> (usize, usize, usize, usize, f64) {
assert_eq!(img_a.len(), img_b.len());
assert!(img_a.len() >= width * height * channels);
assert!(block_size > 0);
let mut worst_x = 0;
let mut worst_y = 0;
let mut worst_sad = 0.0_f64;
let mut by = 0;
while by < height {
let bh = block_size.min(height - by);
let mut bx = 0;
while bx < width {
let bw = block_size.min(width - bx);
let mut sad = 0.0_f64;
for dy in 0..bh {
let row = (by + dy) * width * channels + bx * channels;
for dx_c in 0..(bw * channels) {
let a = img_a[row + dx_c] as f64;
let b = img_b[row + dx_c] as f64;
sad += (a - b).abs();
}
}
if sad > worst_sad {
worst_sad = sad;
worst_x = bx;
worst_y = by;
}
bx += block_size;
}
by += block_size;
}
let final_w = block_size.min(width - worst_x);
let final_h = block_size.min(height - worst_y);
(worst_x, worst_y, final_w, final_h, worst_sad)
}
#[cfg(not(feature = "debug-rect"))]
pub fn find_worst_block(
_img_a: &[u8],
_img_b: &[u8],
_width: usize,
_height: usize,
_channels: usize,
_block_size: usize,
) -> (usize, usize, usize, usize, f64) {
(0, 0, 0, 0, 0.0)
}
#[cfg(feature = "debug-rect")]
pub fn diff_and_query(
img_a: &[u8],
img_b: &[u8],
width: usize,
height: usize,
channels: usize,
block_size: usize,
) -> (usize, usize, usize, usize, f64, Vec<String>) {
let (x, y, w, h, sad) = find_worst_block(img_a, img_b, width, height, channels, block_size);
let rows = query_overlapping(x as i64, y as i64, w as i64, h as i64);
(x, y, w, h, sad, rows)
}
#[cfg(not(feature = "debug-rect"))]
pub fn diff_and_query(
_img_a: &[u8],
_img_b: &[u8],
_width: usize,
_height: usize,
_channels: usize,
_block_size: usize,
) -> (usize, usize, usize, usize, f64, Vec<String>) {
(0, 0, 0, 0, 0.0, Vec::new())
}
#[cfg(feature = "debug-rect")]
pub fn find_worst_blocks(
img_a: &[u8],
img_b: &[u8],
width: usize,
height: usize,
channels: usize,
block_size: usize,
top_n: usize,
) -> Vec<(usize, usize, usize, usize, f64)> {
assert_eq!(img_a.len(), img_b.len());
assert!(img_a.len() >= width * height * channels);
assert!(block_size > 0);
let mut blocks: Vec<(usize, usize, usize, usize, f64)> = Vec::new();
let mut by = 0;
while by < height {
let bh = block_size.min(height - by);
let mut bx = 0;
while bx < width {
let bw = block_size.min(width - bx);
let mut sad = 0.0_f64;
for dy in 0..bh {
let row = (by + dy) * width * channels + bx * channels;
for dx_c in 0..(bw * channels) {
let a = img_a[row + dx_c] as f64;
let b = img_b[row + dx_c] as f64;
sad += (a - b).abs();
}
}
blocks.push((bx, by, bw, bh, sad));
bx += block_size;
}
by += block_size;
}
blocks.sort_by(|a, b| b.4.partial_cmp(&a.4).unwrap_or(core::cmp::Ordering::Equal));
blocks.truncate(top_n);
blocks
}
#[cfg(not(feature = "debug-rect"))]
pub fn find_worst_blocks(
_img_a: &[u8],
_img_b: &[u8],
_width: usize,
_height: usize,
_channels: usize,
_block_size: usize,
_top_n: usize,
) -> Vec<(usize, usize, usize, usize, f64)> {
Vec::new()
}