use crate::{ImageBuffer, Point2i};
pub fn binary_border<'a>(src: &ImageBuffer, dst: &'a mut [i32]) -> &'a [i32] {
let src_data = src.data;
let height = src.height as usize;
let width = src.width as usize;
let mut pos_src = 0;
let mut pos_dst = 0;
for _ in -2..(width as isize) {
dst[pos_dst] = 0;
pos_dst += 1;
}
for _ in 0..height {
dst[pos_dst] = 0;
pos_dst += 1;
for _ in 0..width {
dst[pos_dst] = if src_data[pos_src] == 0 { 0 } else { 1 };
pos_dst += 1;
pos_src += 1;
}
dst[pos_dst] = 0;
pos_dst += 1;
}
for _ in -2..(width as isize) {
dst[pos_dst] = 0;
pos_dst += 1;
}
dst
}
pub const NEIGHBORHOOD: [[i32; 2]; 8] = [
[1, 0],
[1, -1],
[0, -1],
[-1, -1],
[-1, 0],
[-1, 1],
[0, 1],
[1, 1],
];
pub fn neighborhood_deltas(width: i32) -> [i32; 16] {
let mut deltas = [0i32; 16];
for i in 0..8 {
let delta = NEIGHBORHOOD[i][0] + NEIGHBORHOOD[i][1] * width;
deltas[i] = delta;
deltas[i + 8] = delta;
}
deltas
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Contour {
pub points: Vec<Point2i>,
pub hole: bool,
}
pub fn border_following(
src: &mut [i32],
pos: usize,
nbd: i32,
mut point: Point2i,
hole: bool,
deltas: &[i32; 16],
) -> Contour {
let mut contour = Contour {
points: Vec::new(),
hole,
};
let mut s: usize = if hole { 0 } else { 4 };
let mut s_end = s;
let mut pos1;
let mut pos3;
let mut pos4;
loop {
s = s.wrapping_sub(1) & 7;
pos1 = (pos as isize + deltas[s] as isize) as usize;
if src[pos1] != 0 {
break;
}
if s == s_end {
break;
}
}
if s == s_end {
src[pos] = -nbd;
contour.points.push(Point2i::new(point.x, point.y));
} else {
pos3 = pos;
loop {
s_end = s;
loop {
s = (s + 1) & 15;
pos4 = (pos3 as isize + deltas[s] as isize) as usize;
if src[pos4] != 0 {
break;
}
}
s &= 7;
let s_minus_1 = s.wrapping_sub(1) as u32;
let s_end_u32 = s_end as u32;
if s_minus_1 < s_end_u32 {
src[pos3] = -nbd;
} else if src[pos3] == 1 {
src[pos3] = nbd;
}
contour.points.push(Point2i::new(point.x, point.y));
point.x += NEIGHBORHOOD[s][0];
point.y += NEIGHBORHOOD[s][1];
if pos4 == pos && pos3 == pos1 {
break;
}
pos3 = pos4;
s = (s + 4) & 7;
}
}
contour
}
pub fn find_contours(src_img: &ImageBuffer, binary: &mut [i32]) -> Vec<Contour> {
let width = src_img.width as usize;
let height = src_img.height as usize;
let mut contours = Vec::new();
binary_border(src_img, binary);
let deltas = neighborhood_deltas((width + 2) as i32);
let mut pos = width + 3; let mut nbd = 1;
for i in 0..height {
for j in 0..width {
let pix = binary[pos];
if pix != 0 {
let mut outer = false;
let mut hole = false;
if pix == 1 && binary[pos - 1] == 0 {
outer = true;
} else if pix >= 1 && binary[pos + 1] == 0 {
hole = true;
}
if outer || hole {
nbd += 1;
let point = Point2i::new(j as i32, i as i32);
let contour = border_following(binary, pos, nbd, point, hole, &deltas);
contours.push(contour);
}
}
pos += 1; }
pos += 2; }
contours
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_binary_border() {
let src_data = [1, 0, 1, 0, 1, 0, 0, 0, 1];
let src = ImageBuffer {
data: &src_data,
width: 3,
height: 3,
};
let bw = 5;
let mut dst = vec![0i32; bw * bw];
let bordered = binary_border(&src, &mut dst);
assert_eq!(bordered.len(), 25);
for i in 0..5 {
assert_eq!(bordered[i], 0);
assert_eq!(bordered[20 + i], 0);
}
let interior_expected = [1, 0, 1, 0, 1, 0, 0, 0, 1];
let mut idx = 0;
for y in 0..3 {
let row_start = (y + 1) * bw + 1;
for x in 0..3 {
assert_eq!(bordered[row_start + x], interior_expected[idx]);
idx += 1;
}
}
}
#[test]
fn test_find_contours() {
let src_data = [
0, 0, 0, 0, 0, 0, 255, 255, 255, 0, 0, 255, 0, 255, 0, 0, 255, 255, 255, 0, 0, 0, 0, 0,
0,
];
let img = ImageBuffer {
data: &src_data,
width: 5,
height: 5,
};
let mut binary = vec![0i32; 7 * 7]; let contours = find_contours(&img, &mut binary);
assert!(!contours.is_empty());
assert_eq!(contours.len(), 2);
assert!(!contours[0].hole); assert!(contours[1].hole); }
}