use crate::error::{AlgorithmError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructuringElement {
Rectangle,
Cross,
Diamond,
}
pub fn erode_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
validate_buffer_size(input, output, width, height)?;
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: format!("Image too small for 3x3 operation: {}x{}", width, height),
});
}
for y in 1..(height - 1) {
for x in 1..(width - 1) {
let mut min_val = 255u8;
for ky in 0..3 {
for kx in 0..3 {
let px = x + kx - 1;
let py = y + ky - 1;
let idx = py * width + px;
min_val = min_val.min(input[idx]);
}
}
let out_idx = y * width + x;
output[out_idx] = min_val;
}
}
copy_borders(input, output, width, height);
Ok(())
}
pub fn dilate_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
validate_buffer_size(input, output, width, height)?;
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: format!("Image too small for 3x3 operation: {}x{}", width, height),
});
}
for y in 1..(height - 1) {
for x in 1..(width - 1) {
let mut max_val = 0u8;
for ky in 0..3 {
for kx in 0..3 {
let px = x + kx - 1;
let py = y + ky - 1;
let idx = py * width + px;
max_val = max_val.max(input[idx]);
}
}
let out_idx = y * width + x;
output[out_idx] = max_val;
}
}
copy_borders(input, output, width, height);
Ok(())
}
pub fn opening_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
validate_buffer_size(input, output, width, height)?;
let mut temp = vec![0u8; width * height];
erode_3x3(input, &mut temp, width, height)?;
dilate_3x3(&temp, output, width, height)?;
Ok(())
}
pub fn closing_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
validate_buffer_size(input, output, width, height)?;
let mut temp = vec![0u8; width * height];
dilate_3x3(input, &mut temp, width, height)?;
erode_3x3(&temp, output, width, height)?;
Ok(())
}
pub fn morphological_gradient_3x3(
input: &[u8],
output: &mut [u8],
width: usize,
height: usize,
) -> Result<()> {
validate_buffer_size(input, output, width, height)?;
let mut dilated = vec![0u8; width * height];
let mut eroded = vec![0u8; width * height];
dilate_3x3(input, &mut dilated, width, height)?;
erode_3x3(input, &mut eroded, width, height)?;
const LANES: usize = 16;
let chunks = dilated.len() / LANES;
for i in 0..chunks {
let start = i * LANES;
let end = start + LANES;
for j in start..end {
output[j] = dilated[j].saturating_sub(eroded[j]);
}
}
let remainder_start = chunks * LANES;
for i in remainder_start..dilated.len() {
output[i] = dilated[i].saturating_sub(eroded[i]);
}
Ok(())
}
pub fn top_hat_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
validate_buffer_size(input, output, width, height)?;
let mut opened = vec![0u8; width * height];
opening_3x3(input, &mut opened, width, height)?;
const LANES: usize = 16;
let chunks = input.len() / LANES;
for i in 0..chunks {
let start = i * LANES;
let end = start + LANES;
for j in start..end {
output[j] = input[j].saturating_sub(opened[j]);
}
}
let remainder_start = chunks * LANES;
for i in remainder_start..input.len() {
output[i] = input[i].saturating_sub(opened[i]);
}
Ok(())
}
pub fn black_hat_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
validate_buffer_size(input, output, width, height)?;
let mut closed = vec![0u8; width * height];
closing_3x3(input, &mut closed, width, height)?;
const LANES: usize = 16;
let chunks = input.len() / LANES;
for i in 0..chunks {
let start = i * LANES;
let end = start + LANES;
for j in start..end {
output[j] = closed[j].saturating_sub(input[j]);
}
}
let remainder_start = chunks * LANES;
for i in remainder_start..input.len() {
output[i] = closed[i].saturating_sub(input[i]);
}
Ok(())
}
pub fn binary_erode_3x3(
input: &[u8],
output: &mut [u8],
width: usize,
height: usize,
threshold: u8,
) -> Result<()> {
validate_buffer_size(input, output, width, height)?;
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: format!("Image too small for 3x3 operation: {}x{}", width, height),
});
}
for y in 1..(height - 1) {
for x in 1..(width - 1) {
let mut all_set = true;
'outer: for ky in 0..3 {
for kx in 0..3 {
let px = x + kx - 1;
let py = y + ky - 1;
let idx = py * width + px;
if input[idx] < threshold {
all_set = false;
break 'outer;
}
}
}
let out_idx = y * width + x;
output[out_idx] = if all_set { 255 } else { 0 };
}
}
for y in 0..height {
for x in 0..width {
if y == 0 || y == height - 1 || x == 0 || x == width - 1 {
output[y * width + x] = if input[y * width + x] >= threshold {
255
} else {
0
};
}
}
}
Ok(())
}
pub fn binary_dilate_3x3(
input: &[u8],
output: &mut [u8],
width: usize,
height: usize,
threshold: u8,
) -> Result<()> {
validate_buffer_size(input, output, width, height)?;
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: format!("Image too small for 3x3 operation: {}x{}", width, height),
});
}
for y in 1..(height - 1) {
for x in 1..(width - 1) {
let mut any_set = false;
'outer: for ky in 0..3 {
for kx in 0..3 {
let px = x + kx - 1;
let py = y + ky - 1;
let idx = py * width + px;
if input[idx] >= threshold {
any_set = true;
break 'outer;
}
}
}
let out_idx = y * width + x;
output[out_idx] = if any_set { 255 } else { 0 };
}
}
for y in 0..height {
for x in 0..width {
if y == 0 || y == height - 1 || x == 0 || x == width - 1 {
output[y * width + x] = if input[y * width + x] >= threshold {
255
} else {
0
};
}
}
}
Ok(())
}
pub fn skeleton(
input: &[u8],
output: &mut [u8],
width: usize,
height: usize,
threshold: u8,
max_iterations: usize,
) -> Result<()> {
validate_buffer_size(input, output, width, height)?;
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: format!("Image too small for 3x3 operation: {}x{}", width, height),
});
}
for i in 0..input.len() {
output[i] = if input[i] >= threshold { 255 } else { 0 };
}
let mut changed = true;
let mut iteration = 0;
while changed && iteration < max_iterations {
changed = false;
iteration += 1;
let prev = output.to_vec();
for y in 1..(height - 1) {
for x in 1..(width - 1) {
let idx = y * width + x;
if prev[idx] == 255 {
let mut neighbor_count = 0;
for ky in 0..3 {
for kx in 0..3 {
if kx == 1 && ky == 1 {
continue;
}
let px = x + kx - 1;
let py = y + ky - 1;
if prev[py * width + px] == 255 {
neighbor_count += 1;
}
}
}
if neighbor_count < 2 {
output[idx] = 0;
changed = true;
}
}
}
}
}
Ok(())
}
fn validate_buffer_size(input: &[u8], output: &[u8], width: usize, height: usize) -> Result<()> {
let expected_size = width * height;
if input.len() != expected_size || output.len() != expected_size {
return Err(AlgorithmError::InvalidParameter {
parameter: "buffers",
message: format!(
"Buffer size mismatch: input={}, output={}, expected={}",
input.len(),
output.len(),
expected_size
),
});
}
Ok(())
}
fn copy_borders(input: &[u8], output: &mut [u8], width: usize, height: usize) {
for x in 0..width {
output[x] = input[x];
output[(height - 1) * width + x] = input[(height - 1) * width + x];
}
for y in 0..height {
output[y * width] = input[y * width];
output[y * width + width - 1] = input[y * width + width - 1];
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_erode_uniform() {
let width = 10;
let height = 10;
let input = vec![255u8; width * height];
let mut output = vec![0u8; width * height];
erode_3x3(&input, &mut output, width, height)
.expect("Erosion should succeed on uniform image");
for y in 1..(height - 1) {
for x in 1..(width - 1) {
assert_eq!(output[y * width + x], 255);
}
}
}
#[test]
fn test_dilate_single_pixel() {
let width = 5;
let height = 5;
let mut input = vec![0u8; width * height];
input[2 * width + 2] = 255;
let mut output = vec![0u8; width * height];
dilate_3x3(&input, &mut output, width, height)
.expect("Dilation should succeed on single pixel");
assert_eq!(output[2 * width + 2], 255);
assert_eq!(output[width + 2], 255);
assert_eq!(output[2 * width + 1], 255);
}
#[test]
fn test_opening_closing() {
let width = 10;
let height = 10;
let input = vec![128u8; width * height];
let mut opened = vec![0u8; width * height];
let mut closed = vec![0u8; width * height];
opening_3x3(&input, &mut opened, width, height).expect("Opening should succeed");
closing_3x3(&input, &mut closed, width, height).expect("Closing should succeed");
assert!(opened[5 * width + 5] > 0);
assert!(closed[5 * width + 5] > 0);
}
#[test]
fn test_morphological_gradient() {
let width = 10;
let height = 10;
let mut input = vec![128u8; width * height];
for y in 0..5 {
for x in 0..width {
input[y * width + x] = 0;
}
}
let mut output = vec![0u8; width * height];
morphological_gradient_3x3(&input, &mut output, width, height)
.expect("Morphological gradient should succeed");
assert!(output[5 * width + 5] > 0);
}
#[test]
fn test_top_hat() {
let width = 10;
let height = 10;
let input = vec![100u8; width * height];
let mut output = vec![0u8; width * height];
top_hat_3x3(&input, &mut output, width, height).expect("Top-hat transform should succeed");
assert!(output[5 * width + 5] < 10);
}
#[test]
fn test_binary_operations() {
let width = 10;
let height = 10;
let mut input = vec![0u8; width * height];
for y in 3..7 {
for x in 3..7 {
input[y * width + x] = 255;
}
}
let mut eroded = vec![0u8; width * height];
let mut dilated = vec![0u8; width * height];
binary_erode_3x3(&input, &mut eroded, width, height, 128)
.expect("Binary erosion should succeed");
binary_dilate_3x3(&input, &mut dilated, width, height, 128)
.expect("Binary dilation should succeed");
assert_eq!(eroded[4 * width + 4], 255);
assert_eq!(eroded[3 * width + 3], 0);
assert_eq!(dilated[5 * width + 5], 255);
}
#[test]
fn test_dimensions_too_small() {
let width = 2;
let height = 2;
let input = vec![0u8; width * height];
let mut output = vec![0u8; width * height];
let result = erode_3x3(&input, &mut output, width, height);
assert!(result.is_err());
}
#[test]
fn test_buffer_size_mismatch() {
let width = 10;
let height = 10;
let input = vec![0u8; width * height];
let mut output = vec![0u8; 50];
let result = erode_3x3(&input, &mut output, width, height);
assert!(result.is_err());
}
}