use image::GenericImage;
use crate::error::{LumaConversionErrorKind, SkeletonizeError};
use crate::neighbors::get_neighbor_info;
use crate::{Edge, ForegroundColor, MarkingMethod};
#[allow(clippy::collapsible_else_if)]
#[allow(clippy::nonminimal_bool)]
pub fn thin_image_edges<F: ForegroundColor>(
img: &mut image::DynamicImage,
method: MarkingMethod,
iterations: Option<u32>,
) -> Result<u32, SkeletonizeError> {
let mut pixels_to_remove = Vec::new();
let mut phase_one = true;
let iterations = iterations.unwrap_or(u32::MAX);
for iters in 0..iterations {
let luma_img = img.as_luma8().ok_or(SkeletonizeError::LumaConversion(
LumaConversionErrorKind::ImageThinningLuma,
))?;
let (width, height) = luma_img.dimensions();
for (x, y, p) in luma_img.enumerate_pixels() {
if *p == image::Luma([F::BACKGROUND_COLOR]) {
continue;
}
let info = get_neighbor_info::<F>(luma_img, width, height, x, y);
let [p2, p3, p4, p5, p6, p7, p8, p9] = info.edge_status;
match method {
MarkingMethod::Standard => {
if !(2..=6).contains(&info.filled) || info.neighbors != 8 {
continue;
}
let mut transitions = 0;
for pair in [p2, p3, p4, p5, p6, p7, p8, p9, p2].windows(2) {
if pair[0] == Edge::Empty && pair[1] == Edge::Filled {
transitions += 1;
}
}
if transitions != 1 {
continue;
}
if phase_one {
if (p2 == Edge::Empty || p4 == Edge::Empty || p6 == Edge::Empty)
&& (p4 == Edge::Empty || p6 == Edge::Empty || p8 == Edge::Empty)
{
pixels_to_remove.push((x, y));
}
} else {
if (p2 == Edge::Empty || p4 == Edge::Empty || p8 == Edge::Empty)
&& (p2 == Edge::Empty || p6 == Edge::Empty || p8 == Edge::Empty)
{
pixels_to_remove.push((x, y));
}
}
}
MarkingMethod::Modified => {
if !(2..=7).contains(&info.filled) || info.neighbors != 8 {
continue;
}
let mut transitions = 0;
for pair in [p2, p3, p4, p5, p6, p7, p8, p9, p2].windows(2) {
if pair[0] == Edge::Empty && pair[1] == Edge::Filled {
transitions += 1;
}
}
if !(transitions == 1 || transitions == 2) {
continue;
}
if phase_one {
if transitions == 1 {
if (p2 == Edge::Empty || p4 == Edge::Empty || p6 == Edge::Empty)
&& (p4 == Edge::Empty || p6 == Edge::Empty || p8 == Edge::Empty)
{
pixels_to_remove.push((x, y));
}
} else {
if ((p2 == Edge::Filled && p4 == Edge::Filled)
&& (p6 == Edge::Empty && p7 == Edge::Empty && p8 == Edge::Empty))
|| ((p4 == Edge::Filled && p6 == Edge::Filled)
&& (p2 == Edge::Empty
&& p8 == Edge::Empty
&& p9 == Edge::Empty))
{
pixels_to_remove.push((x, y));
}
}
} else {
if transitions == 1 {
if (p2 == Edge::Empty || p4 == Edge::Empty || p8 == Edge::Empty)
&& (p2 == Edge::Empty || p6 == Edge::Empty || p8 == Edge::Empty)
{
pixels_to_remove.push((x, y));
}
} else {
if ((p2 == Edge::Filled && p8 == Edge::Filled)
&& (p4 == Edge::Empty && p5 == Edge::Empty && p6 == Edge::Empty))
|| ((p6 == Edge::Filled && p8 == Edge::Filled)
&& (p2 == Edge::Empty
&& p3 == Edge::Empty
&& p4 == Edge::Empty))
{
pixels_to_remove.push((x, y));
}
}
}
}
}
}
phase_one = !phase_one;
for &(x, y) in &pixels_to_remove {
img.put_pixel(x, y, image::Rgba([F::BACKGROUND_COLOR; 4]));
}
if pixels_to_remove.is_empty() {
return Ok(iters);
}
pixels_to_remove.clear();
}
Err(SkeletonizeError::MaxThinningIterations)
}