pub(super) const REMOVED_TINT: [u8; 4] = [239, 68, 68, 150];
pub(super) const ADDED_TINT: [u8; 4] = [34, 197, 94, 150];
pub(super) const AFTER_OUTLINE: [u8; 4] = [74, 255, 128, 255];
pub(super) const BEFORE_OUTLINE: [u8; 4] = [255, 82, 82, 255];
pub(super) const FACET_EDGE: [u8; 4] = [235, 240, 250, 90];
struct Coverage<'a> {
pick: &'a [u32],
width: usize,
height: usize,
}
impl Coverage<'_> {
const fn at(&self, x: usize, y: usize) -> bool {
self.pick[y * self.width + x] != 0
}
fn any_neighbour(&self, x: usize, y: usize, covered: bool) -> bool {
let probe = |nx: Option<usize>, ny: Option<usize>| match (nx, ny) {
(Some(nx), Some(ny)) if nx < self.width && ny < self.height => {
self.at(nx, ny) == covered
}
_ => !covered,
};
probe(x.checked_sub(1), Some(y))
|| probe(Some(x + 1), Some(y))
|| probe(Some(x), y.checked_sub(1))
|| probe(Some(x), Some(y + 1))
}
fn is_inner_boundary(&self, x: usize, y: usize) -> bool {
self.at(x, y) && self.any_neighbour(x, y, false)
}
fn is_outer_boundary(&self, x: usize, y: usize) -> bool {
!self.at(x, y) && self.any_neighbour(x, y, true)
}
fn is_facet_edge(&self, x: usize, y: usize) -> bool {
let here = self.pick[y * self.width + x];
if here == 0 {
return false;
}
let differs = |nx: usize, ny: usize| {
nx < self.width && ny < self.height && {
let other = self.pick[ny * self.width + nx];
other != 0 && other != here
}
};
differs(x + 1, y) || differs(x, y + 1)
}
}
fn put(out: &mut [u8], index: usize, rgba: [u8; 4]) {
out[index * 4..index * 4 + 4].copy_from_slice(&rgba);
}
fn paint_tints(out: &mut [u8], before: &Coverage<'_>, after: &Coverage<'_>) {
for y in 0..before.height {
for x in 0..before.width {
match (before.at(x, y), after.at(x, y)) {
(true, false) => put(out, y * before.width + x, REMOVED_TINT),
(false, true) => put(out, y * before.width + x, ADDED_TINT),
_ => {}
}
}
}
}
fn paint_facet_edges(out: &mut [u8], after: &Coverage<'_>, edges: Option<&[u8]>) {
for y in 0..after.height {
for x in 0..after.width {
let index = y * after.width + x;
if out[index * 4 + 3] != 0 || !after.at(x, y) {
continue;
}
let is_edge = edges.map_or_else(|| after.is_facet_edge(x, y), |mask| mask[index] != 0);
if is_edge {
put(out, index, FACET_EDGE);
}
}
}
}
fn paint_outlines(out: &mut [u8], before: &Coverage<'_>, after: &Coverage<'_>) {
for y in 0..before.height {
for x in 0..before.width {
if before.is_inner_boundary(x, y) {
put(out, y * before.width + x, BEFORE_OUTLINE);
}
}
}
for y in 0..after.height {
for x in 0..after.width {
if after.is_inner_boundary(x, y) || after.is_outer_boundary(x, y) {
put(out, y * after.width + x, AFTER_OUTLINE);
}
}
}
}
#[must_use]
pub(super) fn difference_overlay(
before_pick: &[u32],
after_pick: &[u32],
after_edges: Option<&[u8]>,
size: (u32, u32),
) -> Vec<u8> {
let (width, height) = (size.0 as usize, size.1 as usize);
let count = width * height;
let mut out = vec![0u8; count * 4];
let mismatched = before_pick.len() != count
|| after_pick.len() != count
|| after_edges.is_some_and(|mask| mask.len() != count);
if mismatched {
return out;
}
let before = Coverage {
pick: before_pick,
width,
height,
};
let after = Coverage {
pick: after_pick,
width,
height,
};
paint_tints(&mut out, &before, &after);
paint_facet_edges(&mut out, &after, after_edges);
paint_outlines(&mut out, &before, &after);
out
}