1use crate::error::{AlgorithmError, Result};
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum StructuringElement {
43 Rectangle,
45 Cross,
47 Diamond,
49}
50
51pub fn erode_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
67 validate_buffer_size(input, output, width, height)?;
68
69 if width < 3 || height < 3 {
70 return Err(AlgorithmError::InvalidParameter {
71 parameter: "dimensions",
72 message: format!("Image too small for 3x3 operation: {}x{}", width, height),
73 });
74 }
75
76 for y in 1..(height - 1) {
78 for x in 1..(width - 1) {
79 let mut min_val = 255u8;
80
81 for ky in 0..3 {
83 for kx in 0..3 {
84 let px = x + kx - 1;
85 let py = y + ky - 1;
86 let idx = py * width + px;
87 min_val = min_val.min(input[idx]);
88 }
89 }
90
91 let out_idx = y * width + x;
92 output[out_idx] = min_val;
93 }
94 }
95
96 copy_borders(input, output, width, height);
98
99 Ok(())
100}
101
102pub fn dilate_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
111 validate_buffer_size(input, output, width, height)?;
112
113 if width < 3 || height < 3 {
114 return Err(AlgorithmError::InvalidParameter {
115 parameter: "dimensions",
116 message: format!("Image too small for 3x3 operation: {}x{}", width, height),
117 });
118 }
119
120 for y in 1..(height - 1) {
122 for x in 1..(width - 1) {
123 let mut max_val = 0u8;
124
125 for ky in 0..3 {
127 for kx in 0..3 {
128 let px = x + kx - 1;
129 let py = y + ky - 1;
130 let idx = py * width + px;
131 max_val = max_val.max(input[idx]);
132 }
133 }
134
135 let out_idx = y * width + x;
136 output[out_idx] = max_val;
137 }
138 }
139
140 copy_borders(input, output, width, height);
142
143 Ok(())
144}
145
146pub fn opening_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
154 validate_buffer_size(input, output, width, height)?;
155
156 let mut temp = vec![0u8; width * height];
158
159 erode_3x3(input, &mut temp, width, height)?;
161
162 dilate_3x3(&temp, output, width, height)?;
164
165 Ok(())
166}
167
168pub fn closing_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
176 validate_buffer_size(input, output, width, height)?;
177
178 let mut temp = vec![0u8; width * height];
180
181 dilate_3x3(input, &mut temp, width, height)?;
183
184 erode_3x3(&temp, output, width, height)?;
186
187 Ok(())
188}
189
190pub fn morphological_gradient_3x3(
198 input: &[u8],
199 output: &mut [u8],
200 width: usize,
201 height: usize,
202) -> Result<()> {
203 validate_buffer_size(input, output, width, height)?;
204
205 let mut dilated = vec![0u8; width * height];
206 let mut eroded = vec![0u8; width * height];
207
208 dilate_3x3(input, &mut dilated, width, height)?;
209 erode_3x3(input, &mut eroded, width, height)?;
210
211 const LANES: usize = 16;
213 let chunks = dilated.len() / LANES;
214
215 for i in 0..chunks {
216 let start = i * LANES;
217 let end = start + LANES;
218
219 for j in start..end {
220 output[j] = dilated[j].saturating_sub(eroded[j]);
221 }
222 }
223
224 let remainder_start = chunks * LANES;
226 for i in remainder_start..dilated.len() {
227 output[i] = dilated[i].saturating_sub(eroded[i]);
228 }
229
230 Ok(())
231}
232
233pub fn top_hat_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
242 validate_buffer_size(input, output, width, height)?;
243
244 let mut opened = vec![0u8; width * height];
245 opening_3x3(input, &mut opened, width, height)?;
246
247 const LANES: usize = 16;
249 let chunks = input.len() / LANES;
250
251 for i in 0..chunks {
252 let start = i * LANES;
253 let end = start + LANES;
254
255 for j in start..end {
256 output[j] = input[j].saturating_sub(opened[j]);
257 }
258 }
259
260 let remainder_start = chunks * LANES;
262 for i in remainder_start..input.len() {
263 output[i] = input[i].saturating_sub(opened[i]);
264 }
265
266 Ok(())
267}
268
269pub fn black_hat_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
278 validate_buffer_size(input, output, width, height)?;
279
280 let mut closed = vec![0u8; width * height];
281 closing_3x3(input, &mut closed, width, height)?;
282
283 const LANES: usize = 16;
285 let chunks = input.len() / LANES;
286
287 for i in 0..chunks {
288 let start = i * LANES;
289 let end = start + LANES;
290
291 for j in start..end {
292 output[j] = closed[j].saturating_sub(input[j]);
293 }
294 }
295
296 let remainder_start = chunks * LANES;
298 for i in remainder_start..input.len() {
299 output[i] = closed[i].saturating_sub(input[i]);
300 }
301
302 Ok(())
303}
304
305pub fn binary_erode_3x3(
319 input: &[u8],
320 output: &mut [u8],
321 width: usize,
322 height: usize,
323 threshold: u8,
324) -> Result<()> {
325 validate_buffer_size(input, output, width, height)?;
326
327 if width < 3 || height < 3 {
328 return Err(AlgorithmError::InvalidParameter {
329 parameter: "dimensions",
330 message: format!("Image too small for 3x3 operation: {}x{}", width, height),
331 });
332 }
333
334 for y in 1..(height - 1) {
336 for x in 1..(width - 1) {
337 let mut all_set = true;
338
339 'outer: for ky in 0..3 {
341 for kx in 0..3 {
342 let px = x + kx - 1;
343 let py = y + ky - 1;
344 let idx = py * width + px;
345 if input[idx] < threshold {
346 all_set = false;
347 break 'outer;
348 }
349 }
350 }
351
352 let out_idx = y * width + x;
353 output[out_idx] = if all_set { 255 } else { 0 };
354 }
355 }
356
357 for y in 0..height {
359 for x in 0..width {
360 if y == 0 || y == height - 1 || x == 0 || x == width - 1 {
361 output[y * width + x] = if input[y * width + x] >= threshold {
362 255
363 } else {
364 0
365 };
366 }
367 }
368 }
369
370 Ok(())
371}
372
373pub fn binary_dilate_3x3(
379 input: &[u8],
380 output: &mut [u8],
381 width: usize,
382 height: usize,
383 threshold: u8,
384) -> Result<()> {
385 validate_buffer_size(input, output, width, height)?;
386
387 if width < 3 || height < 3 {
388 return Err(AlgorithmError::InvalidParameter {
389 parameter: "dimensions",
390 message: format!("Image too small for 3x3 operation: {}x{}", width, height),
391 });
392 }
393
394 for y in 1..(height - 1) {
396 for x in 1..(width - 1) {
397 let mut any_set = false;
398
399 'outer: for ky in 0..3 {
401 for kx in 0..3 {
402 let px = x + kx - 1;
403 let py = y + ky - 1;
404 let idx = py * width + px;
405 if input[idx] >= threshold {
406 any_set = true;
407 break 'outer;
408 }
409 }
410 }
411
412 let out_idx = y * width + x;
413 output[out_idx] = if any_set { 255 } else { 0 };
414 }
415 }
416
417 for y in 0..height {
419 for x in 0..width {
420 if y == 0 || y == height - 1 || x == 0 || x == width - 1 {
421 output[y * width + x] = if input[y * width + x] >= threshold {
422 255
423 } else {
424 0
425 };
426 }
427 }
428 }
429
430 Ok(())
431}
432
433pub fn skeleton(
442 input: &[u8],
443 output: &mut [u8],
444 width: usize,
445 height: usize,
446 threshold: u8,
447 max_iterations: usize,
448) -> Result<()> {
449 validate_buffer_size(input, output, width, height)?;
450
451 if width < 3 || height < 3 {
452 return Err(AlgorithmError::InvalidParameter {
453 parameter: "dimensions",
454 message: format!("Image too small for 3x3 operation: {}x{}", width, height),
455 });
456 }
457
458 for i in 0..input.len() {
460 output[i] = if input[i] >= threshold { 255 } else { 0 };
461 }
462
463 let mut changed = true;
464 let mut iteration = 0;
465
466 while changed && iteration < max_iterations {
467 changed = false;
468 iteration += 1;
469
470 let prev = output.to_vec();
471
472 for y in 1..(height - 1) {
474 for x in 1..(width - 1) {
475 let idx = y * width + x;
476
477 if prev[idx] == 255 {
478 let mut neighbor_count = 0;
480 for ky in 0..3 {
481 for kx in 0..3 {
482 if kx == 1 && ky == 1 {
483 continue;
484 }
485 let px = x + kx - 1;
486 let py = y + ky - 1;
487 if prev[py * width + px] == 255 {
488 neighbor_count += 1;
489 }
490 }
491 }
492
493 if neighbor_count < 2 {
495 output[idx] = 0;
496 changed = true;
497 }
498 }
499 }
500 }
501 }
502
503 Ok(())
504}
505
506fn validate_buffer_size(input: &[u8], output: &[u8], width: usize, height: usize) -> Result<()> {
509 let expected_size = width * height;
510 if input.len() != expected_size || output.len() != expected_size {
511 return Err(AlgorithmError::InvalidParameter {
512 parameter: "buffers",
513 message: format!(
514 "Buffer size mismatch: input={}, output={}, expected={}",
515 input.len(),
516 output.len(),
517 expected_size
518 ),
519 });
520 }
521 Ok(())
522}
523
524fn copy_borders(input: &[u8], output: &mut [u8], width: usize, height: usize) {
525 for x in 0..width {
527 output[x] = input[x];
528 output[(height - 1) * width + x] = input[(height - 1) * width + x];
529 }
530
531 for y in 0..height {
533 output[y * width] = input[y * width];
534 output[y * width + width - 1] = input[y * width + width - 1];
535 }
536}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541
542 #[test]
543 fn test_erode_uniform() {
544 let width = 10;
545 let height = 10;
546 let input = vec![255u8; width * height];
547 let mut output = vec![0u8; width * height];
548
549 erode_3x3(&input, &mut output, width, height)
550 .expect("Erosion should succeed on uniform image");
551
552 for y in 1..(height - 1) {
554 for x in 1..(width - 1) {
555 assert_eq!(output[y * width + x], 255);
556 }
557 }
558 }
559
560 #[test]
561 fn test_dilate_single_pixel() {
562 let width = 5;
563 let height = 5;
564 let mut input = vec![0u8; width * height];
565 input[2 * width + 2] = 255; let mut output = vec![0u8; width * height];
568 dilate_3x3(&input, &mut output, width, height)
569 .expect("Dilation should succeed on single pixel");
570
571 assert_eq!(output[2 * width + 2], 255);
573 assert_eq!(output[width + 2], 255);
574 assert_eq!(output[2 * width + 1], 255);
575 }
576
577 #[test]
578 fn test_opening_closing() {
579 let width = 10;
580 let height = 10;
581 let input = vec![128u8; width * height];
582 let mut opened = vec![0u8; width * height];
583 let mut closed = vec![0u8; width * height];
584
585 opening_3x3(&input, &mut opened, width, height).expect("Opening should succeed");
586 closing_3x3(&input, &mut closed, width, height).expect("Closing should succeed");
587
588 assert!(opened[5 * width + 5] > 0);
590 assert!(closed[5 * width + 5] > 0);
591 }
592
593 #[test]
594 fn test_morphological_gradient() {
595 let width = 10;
596 let height = 10;
597 let mut input = vec![128u8; width * height];
598
599 for y in 0..5 {
601 for x in 0..width {
602 input[y * width + x] = 0;
603 }
604 }
605
606 let mut output = vec![0u8; width * height];
607 morphological_gradient_3x3(&input, &mut output, width, height)
608 .expect("Morphological gradient should succeed");
609
610 assert!(output[5 * width + 5] > 0);
612 }
613
614 #[test]
615 fn test_top_hat() {
616 let width = 10;
617 let height = 10;
618 let input = vec![100u8; width * height];
619 let mut output = vec![0u8; width * height];
620
621 top_hat_3x3(&input, &mut output, width, height).expect("Top-hat transform should succeed");
622
623 assert!(output[5 * width + 5] < 10);
625 }
626
627 #[test]
628 fn test_binary_operations() {
629 let width = 10;
630 let height = 10;
631 let mut input = vec![0u8; width * height];
632
633 for y in 3..7 {
635 for x in 3..7 {
636 input[y * width + x] = 255;
637 }
638 }
639
640 let mut eroded = vec![0u8; width * height];
641 let mut dilated = vec![0u8; width * height];
642
643 binary_erode_3x3(&input, &mut eroded, width, height, 128)
644 .expect("Binary erosion should succeed");
645 binary_dilate_3x3(&input, &mut dilated, width, height, 128)
646 .expect("Binary dilation should succeed");
647
648 assert_eq!(eroded[4 * width + 4], 255);
650 assert_eq!(eroded[3 * width + 3], 0);
651
652 assert_eq!(dilated[5 * width + 5], 255);
654 }
655
656 #[test]
657 fn test_dimensions_too_small() {
658 let width = 2;
659 let height = 2;
660 let input = vec![0u8; width * height];
661 let mut output = vec![0u8; width * height];
662
663 let result = erode_3x3(&input, &mut output, width, height);
664 assert!(result.is_err());
665 }
666
667 #[test]
668 fn test_buffer_size_mismatch() {
669 let width = 10;
670 let height = 10;
671 let input = vec![0u8; width * height];
672 let mut output = vec![0u8; 50]; let result = erode_3x3(&input, &mut output, width, height);
675 assert!(result.is_err());
676 }
677}