use serde::Serialize;
use std::collections::HashMap;
#[derive(Debug, Serialize)]
pub struct DiffResult {
pub dimensions: Dimensions,
pub stats: DiffStats,
#[serde(rename = "match")]
pub is_match: bool,
pub regions: Vec<Region>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dimension_mismatch: Option<DimensionMismatch>,
}
#[derive(Debug, Clone, Serialize)]
pub struct Dimensions {
pub width: u32,
pub height: u32,
}
#[derive(Debug, Serialize)]
pub struct DiffStats {
pub changed_pixels: u64,
pub total_pixels: u64,
pub diff_percentage: f64,
pub region_count: usize,
pub antialiased_pixels: u64,
}
#[derive(Debug, Serialize)]
pub struct Region {
pub id: u32,
pub bounding_box: BoundingBox,
pub pixel_count: u32,
pub avg_delta: f64,
pub max_delta: f64,
pub label: String,
#[serde(skip)]
pub component_ids: Vec<u32>,
}
#[derive(Debug, Clone, Serialize)]
pub struct BoundingBox {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, Serialize)]
pub struct DimensionMismatch {
pub baseline: Dimensions,
pub candidate: Dimensions,
}
struct RegionAccumulator {
min_x: u32,
min_y: u32,
max_x: u32,
max_y: u32,
pixel_count: u32,
delta_sum: f64,
max_delta: f64,
}
pub fn extract_regions(
labels: &[u32],
delta_map: &[f64],
width: u32,
height: u32,
min_region_size: u32,
) -> Vec<Region> {
let mut accumulators: HashMap<u32, RegionAccumulator> = HashMap::new();
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) as usize;
let label = labels[idx];
if label == 0 {
continue;
}
let delta = delta_map[idx];
accumulators
.entry(label)
.and_modify(|acc| {
acc.min_x = acc.min_x.min(x);
acc.min_y = acc.min_y.min(y);
acc.max_x = acc.max_x.max(x);
acc.max_y = acc.max_y.max(y);
acc.pixel_count += 1;
acc.delta_sum += delta;
acc.max_delta = acc.max_delta.max(delta);
})
.or_insert(RegionAccumulator {
min_x: x,
min_y: y,
max_x: x,
max_y: y,
pixel_count: 1,
delta_sum: delta,
max_delta: delta,
});
}
}
let mut regions: Vec<Region> = accumulators
.into_iter()
.filter(|(_, acc)| acc.pixel_count >= min_region_size)
.map(|(component_id, acc)| Region {
id: 0, bounding_box: BoundingBox {
x: acc.min_x,
y: acc.min_y,
width: acc.max_x - acc.min_x + 1,
height: acc.max_y - acc.min_y + 1,
},
pixel_count: acc.pixel_count,
avg_delta: acc.delta_sum / acc.pixel_count as f64,
max_delta: acc.max_delta,
label: String::new(),
component_ids: vec![component_id],
})
.collect();
regions.sort_by(|a, b| b.pixel_count.cmp(&a.pixel_count));
for (i, region) in regions.iter_mut().enumerate() {
region.id = (i + 1) as u32;
}
regions
}