1#![allow(unsafe_code)]
46
47use crate::error::{AlgorithmError, Result};
48
49#[cfg(target_arch = "aarch64")]
51mod neon_impl {
52 use std::arch::aarch64::*;
53
54 #[target_feature(enable = "neon")]
65 pub(crate) unsafe fn stencil_3x3<const MAX: bool>(
66 input: &[u8],
67 output: &mut [u8],
68 width: usize,
69 height: usize,
70 ) {
71 unsafe {
72 let in_ptr = input.as_ptr();
73 let out_ptr = output.as_mut_ptr();
74
75 for y in 1..(height - 1) {
76 let row_up = (y - 1) * width;
77 let row_mid = y * width;
78 let row_down = (y + 1) * width;
79
80 let mut x = 1usize;
83 while x + 16 < width {
84 let hmin_or_max = |row: usize| -> uint8x16_t {
85 let base = row + x;
86 let left = vld1q_u8(in_ptr.add(base - 1));
87 let mid = vld1q_u8(in_ptr.add(base));
88 let right = vld1q_u8(in_ptr.add(base + 1));
89 if MAX {
90 vmaxq_u8(vmaxq_u8(left, mid), right)
91 } else {
92 vminq_u8(vminq_u8(left, mid), right)
93 }
94 };
95
96 let up = hmin_or_max(row_up);
97 let midr = hmin_or_max(row_mid);
98 let down = hmin_or_max(row_down);
99 let result = if MAX {
100 vmaxq_u8(vmaxq_u8(up, midr), down)
101 } else {
102 vminq_u8(vminq_u8(up, midr), down)
103 };
104
105 vst1q_u8(out_ptr.add(row_mid + x), result);
106 x += 16;
107 }
108
109 while x < width - 1 {
111 let mut acc = if MAX { 0u8 } else { 255u8 };
112 for ky in 0..3 {
113 let row = (y + ky - 1) * width;
114 for kx in 0..3 {
115 let v = *input.get_unchecked(row + x + kx - 1);
116 acc = if MAX { acc.max(v) } else { acc.min(v) };
117 }
118 }
119 *output.get_unchecked_mut(row_mid + x) = acc;
120 x += 1;
121 }
122 }
123 }
124 }
125
126 #[target_feature(enable = "neon")]
131 pub(crate) unsafe fn saturating_sub(a: &[u8], b: &[u8], out: &mut [u8]) {
132 unsafe {
133 let len = a.len();
134 let chunks = len / 16;
135 let a_ptr = a.as_ptr();
136 let b_ptr = b.as_ptr();
137 let o_ptr = out.as_mut_ptr();
138 for i in 0..chunks {
139 let off = i * 16;
140 let va = vld1q_u8(a_ptr.add(off));
141 let vb = vld1q_u8(b_ptr.add(off));
142 vst1q_u8(o_ptr.add(off), vqsubq_u8(va, vb));
143 }
144 for i in (chunks * 16)..len {
145 *o_ptr.add(i) = (*a_ptr.add(i)).saturating_sub(*b_ptr.add(i));
146 }
147 }
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum StructuringElement {
154 Rectangle,
156 Cross,
158 Diamond,
160}
161
162pub fn erode_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
178 validate_buffer_size(input, output, width, height)?;
179
180 if width < 3 || height < 3 {
181 return Err(AlgorithmError::InvalidParameter {
182 parameter: "dimensions",
183 message: format!("Image too small for 3x3 operation: {}x{}", width, height),
184 });
185 }
186
187 #[cfg(target_arch = "aarch64")]
188 {
189 unsafe {
191 neon_impl::stencil_3x3::<false>(input, output, width, height);
192 }
193 }
194 #[cfg(not(target_arch = "aarch64"))]
195 {
196 stencil_3x3_scalar::<false>(input, output, width, height);
197 }
198
199 copy_borders(input, output, width, height);
201
202 Ok(())
203}
204
205#[cfg(not(target_arch = "aarch64"))]
207fn stencil_3x3_scalar<const MAX: bool>(
208 input: &[u8],
209 output: &mut [u8],
210 width: usize,
211 height: usize,
212) {
213 for y in 1..(height - 1) {
214 for x in 1..(width - 1) {
215 let mut acc = if MAX { 0u8 } else { 255u8 };
216 for ky in 0..3 {
217 for kx in 0..3 {
218 let px = x + kx - 1;
219 let py = y + ky - 1;
220 let v = input[py * width + px];
221 acc = if MAX { acc.max(v) } else { acc.min(v) };
222 }
223 }
224 output[y * width + x] = acc;
225 }
226 }
227}
228
229pub fn dilate_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
238 validate_buffer_size(input, output, width, height)?;
239
240 if width < 3 || height < 3 {
241 return Err(AlgorithmError::InvalidParameter {
242 parameter: "dimensions",
243 message: format!("Image too small for 3x3 operation: {}x{}", width, height),
244 });
245 }
246
247 #[cfg(target_arch = "aarch64")]
248 {
249 unsafe {
251 neon_impl::stencil_3x3::<true>(input, output, width, height);
252 }
253 }
254 #[cfg(not(target_arch = "aarch64"))]
255 {
256 stencil_3x3_scalar::<true>(input, output, width, height);
257 }
258
259 copy_borders(input, output, width, height);
261
262 Ok(())
263}
264
265pub fn opening_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
273 validate_buffer_size(input, output, width, height)?;
274
275 let mut temp = vec![0u8; width * height];
277
278 erode_3x3(input, &mut temp, width, height)?;
280
281 dilate_3x3(&temp, output, width, height)?;
283
284 Ok(())
285}
286
287pub fn closing_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
295 validate_buffer_size(input, output, width, height)?;
296
297 let mut temp = vec![0u8; width * height];
299
300 dilate_3x3(input, &mut temp, width, height)?;
302
303 erode_3x3(&temp, output, width, height)?;
305
306 Ok(())
307}
308
309pub fn morphological_gradient_3x3(
317 input: &[u8],
318 output: &mut [u8],
319 width: usize,
320 height: usize,
321) -> Result<()> {
322 validate_buffer_size(input, output, width, height)?;
323
324 let mut dilated = vec![0u8; width * height];
325 let mut eroded = vec![0u8; width * height];
326
327 dilate_3x3(input, &mut dilated, width, height)?;
328 erode_3x3(input, &mut eroded, width, height)?;
329
330 saturating_sub_dispatch(&dilated, &eroded, output);
332
333 Ok(())
334}
335
336fn saturating_sub_dispatch(a: &[u8], b: &[u8], out: &mut [u8]) {
339 #[cfg(target_arch = "aarch64")]
340 {
341 unsafe {
344 neon_impl::saturating_sub(a, b, out);
345 }
346 }
347 #[cfg(not(target_arch = "aarch64"))]
348 {
349 for i in 0..a.len() {
350 out[i] = a[i].saturating_sub(b[i]);
351 }
352 }
353}
354
355pub fn top_hat_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
364 validate_buffer_size(input, output, width, height)?;
365
366 let mut opened = vec![0u8; width * height];
367 opening_3x3(input, &mut opened, width, height)?;
368
369 saturating_sub_dispatch(input, &opened, output);
371
372 Ok(())
373}
374
375pub fn black_hat_3x3(input: &[u8], output: &mut [u8], width: usize, height: usize) -> Result<()> {
384 validate_buffer_size(input, output, width, height)?;
385
386 let mut closed = vec![0u8; width * height];
387 closing_3x3(input, &mut closed, width, height)?;
388
389 saturating_sub_dispatch(&closed, input, output);
391
392 Ok(())
393}
394
395pub fn binary_erode_3x3(
409 input: &[u8],
410 output: &mut [u8],
411 width: usize,
412 height: usize,
413 threshold: u8,
414) -> Result<()> {
415 validate_buffer_size(input, output, width, height)?;
416
417 if width < 3 || height < 3 {
418 return Err(AlgorithmError::InvalidParameter {
419 parameter: "dimensions",
420 message: format!("Image too small for 3x3 operation: {}x{}", width, height),
421 });
422 }
423
424 for y in 1..(height - 1) {
426 for x in 1..(width - 1) {
427 let mut all_set = true;
428
429 'outer: for ky in 0..3 {
431 for kx in 0..3 {
432 let px = x + kx - 1;
433 let py = y + ky - 1;
434 let idx = py * width + px;
435 if input[idx] < threshold {
436 all_set = false;
437 break 'outer;
438 }
439 }
440 }
441
442 let out_idx = y * width + x;
443 output[out_idx] = if all_set { 255 } else { 0 };
444 }
445 }
446
447 for y in 0..height {
449 for x in 0..width {
450 if y == 0 || y == height - 1 || x == 0 || x == width - 1 {
451 output[y * width + x] = if input[y * width + x] >= threshold {
452 255
453 } else {
454 0
455 };
456 }
457 }
458 }
459
460 Ok(())
461}
462
463pub fn binary_dilate_3x3(
469 input: &[u8],
470 output: &mut [u8],
471 width: usize,
472 height: usize,
473 threshold: u8,
474) -> Result<()> {
475 validate_buffer_size(input, output, width, height)?;
476
477 if width < 3 || height < 3 {
478 return Err(AlgorithmError::InvalidParameter {
479 parameter: "dimensions",
480 message: format!("Image too small for 3x3 operation: {}x{}", width, height),
481 });
482 }
483
484 for y in 1..(height - 1) {
486 for x in 1..(width - 1) {
487 let mut any_set = false;
488
489 'outer: for ky in 0..3 {
491 for kx in 0..3 {
492 let px = x + kx - 1;
493 let py = y + ky - 1;
494 let idx = py * width + px;
495 if input[idx] >= threshold {
496 any_set = true;
497 break 'outer;
498 }
499 }
500 }
501
502 let out_idx = y * width + x;
503 output[out_idx] = if any_set { 255 } else { 0 };
504 }
505 }
506
507 for y in 0..height {
509 for x in 0..width {
510 if y == 0 || y == height - 1 || x == 0 || x == width - 1 {
511 output[y * width + x] = if input[y * width + x] >= threshold {
512 255
513 } else {
514 0
515 };
516 }
517 }
518 }
519
520 Ok(())
521}
522
523pub fn skeleton(
532 input: &[u8],
533 output: &mut [u8],
534 width: usize,
535 height: usize,
536 threshold: u8,
537 max_iterations: usize,
538) -> Result<()> {
539 validate_buffer_size(input, output, width, height)?;
540
541 if width < 3 || height < 3 {
542 return Err(AlgorithmError::InvalidParameter {
543 parameter: "dimensions",
544 message: format!("Image too small for 3x3 operation: {}x{}", width, height),
545 });
546 }
547
548 for i in 0..input.len() {
550 output[i] = if input[i] >= threshold { 255 } else { 0 };
551 }
552
553 let mut changed = true;
554 let mut iteration = 0;
555
556 while changed && iteration < max_iterations {
557 changed = false;
558 iteration += 1;
559
560 let prev = output.to_vec();
561
562 for y in 1..(height - 1) {
564 for x in 1..(width - 1) {
565 let idx = y * width + x;
566
567 if prev[idx] == 255 {
568 let mut neighbor_count = 0;
570 for ky in 0..3 {
571 for kx in 0..3 {
572 if kx == 1 && ky == 1 {
573 continue;
574 }
575 let px = x + kx - 1;
576 let py = y + ky - 1;
577 if prev[py * width + px] == 255 {
578 neighbor_count += 1;
579 }
580 }
581 }
582
583 if neighbor_count < 2 {
585 output[idx] = 0;
586 changed = true;
587 }
588 }
589 }
590 }
591 }
592
593 Ok(())
594}
595
596fn validate_buffer_size(input: &[u8], output: &[u8], width: usize, height: usize) -> Result<()> {
599 let expected_size = width * height;
600 if input.len() != expected_size || output.len() != expected_size {
601 return Err(AlgorithmError::InvalidParameter {
602 parameter: "buffers",
603 message: format!(
604 "Buffer size mismatch: input={}, output={}, expected={}",
605 input.len(),
606 output.len(),
607 expected_size
608 ),
609 });
610 }
611 Ok(())
612}
613
614fn copy_borders(input: &[u8], output: &mut [u8], width: usize, height: usize) {
615 for x in 0..width {
617 output[x] = input[x];
618 output[(height - 1) * width + x] = input[(height - 1) * width + x];
619 }
620
621 for y in 0..height {
623 output[y * width] = input[y * width];
624 output[y * width + width - 1] = input[y * width + width - 1];
625 }
626}
627
628#[cfg(test)]
629mod tests {
630 use super::*;
631
632 #[test]
633 fn test_erode_uniform() {
634 let width = 10;
635 let height = 10;
636 let input = vec![255u8; width * height];
637 let mut output = vec![0u8; width * height];
638
639 erode_3x3(&input, &mut output, width, height)
640 .expect("Erosion should succeed on uniform image");
641
642 for y in 1..(height - 1) {
644 for x in 1..(width - 1) {
645 assert_eq!(output[y * width + x], 255);
646 }
647 }
648 }
649
650 #[test]
651 fn test_dilate_single_pixel() {
652 let width = 5;
653 let height = 5;
654 let mut input = vec![0u8; width * height];
655 input[2 * width + 2] = 255; let mut output = vec![0u8; width * height];
658 dilate_3x3(&input, &mut output, width, height)
659 .expect("Dilation should succeed on single pixel");
660
661 assert_eq!(output[2 * width + 2], 255);
663 assert_eq!(output[width + 2], 255);
664 assert_eq!(output[2 * width + 1], 255);
665 }
666
667 #[test]
668 fn test_opening_closing() {
669 let width = 10;
670 let height = 10;
671 let input = vec![128u8; width * height];
672 let mut opened = vec![0u8; width * height];
673 let mut closed = vec![0u8; width * height];
674
675 opening_3x3(&input, &mut opened, width, height).expect("Opening should succeed");
676 closing_3x3(&input, &mut closed, width, height).expect("Closing should succeed");
677
678 assert!(opened[5 * width + 5] > 0);
680 assert!(closed[5 * width + 5] > 0);
681 }
682
683 #[test]
684 fn test_morphological_gradient() {
685 let width = 10;
686 let height = 10;
687 let mut input = vec![128u8; width * height];
688
689 for y in 0..5 {
691 for x in 0..width {
692 input[y * width + x] = 0;
693 }
694 }
695
696 let mut output = vec![0u8; width * height];
697 morphological_gradient_3x3(&input, &mut output, width, height)
698 .expect("Morphological gradient should succeed");
699
700 assert!(output[5 * width + 5] > 0);
702 }
703
704 #[test]
705 fn test_top_hat() {
706 let width = 10;
707 let height = 10;
708 let input = vec![100u8; width * height];
709 let mut output = vec![0u8; width * height];
710
711 top_hat_3x3(&input, &mut output, width, height).expect("Top-hat transform should succeed");
712
713 assert!(output[5 * width + 5] < 10);
715 }
716
717 #[test]
718 fn test_binary_operations() {
719 let width = 10;
720 let height = 10;
721 let mut input = vec![0u8; width * height];
722
723 for y in 3..7 {
725 for x in 3..7 {
726 input[y * width + x] = 255;
727 }
728 }
729
730 let mut eroded = vec![0u8; width * height];
731 let mut dilated = vec![0u8; width * height];
732
733 binary_erode_3x3(&input, &mut eroded, width, height, 128)
734 .expect("Binary erosion should succeed");
735 binary_dilate_3x3(&input, &mut dilated, width, height, 128)
736 .expect("Binary dilation should succeed");
737
738 assert_eq!(eroded[4 * width + 4], 255);
740 assert_eq!(eroded[3 * width + 3], 0);
741
742 assert_eq!(dilated[5 * width + 5], 255);
744 }
745
746 #[test]
747 fn test_dimensions_too_small() {
748 let width = 2;
749 let height = 2;
750 let input = vec![0u8; width * height];
751 let mut output = vec![0u8; width * height];
752
753 let result = erode_3x3(&input, &mut output, width, height);
754 assert!(result.is_err());
755 }
756
757 #[test]
758 fn test_buffer_size_mismatch() {
759 let width = 10;
760 let height = 10;
761 let input = vec![0u8; width * height];
762 let mut output = vec![0u8; 50]; let result = erode_3x3(&input, &mut output, width, height);
765 assert!(result.is_err());
766 }
767
768 fn make_image(width: usize, height: usize, seed: u64) -> Vec<u8> {
770 let mut state = seed;
771 (0..width * height)
772 .map(|_| {
773 state = state
774 .wrapping_mul(6364136223846793005)
775 .wrapping_add(1442695040888963407);
776 (state >> 33) as u8
777 })
778 .collect()
779 }
780
781 #[test]
782 fn test_erode_dilate_match_scalar_reference() {
783 fn scalar_ref<const MAX: bool>(input: &[u8], width: usize, height: usize) -> Vec<u8> {
787 let mut out = input.to_vec();
788 for y in 1..(height - 1) {
789 for x in 1..(width - 1) {
790 let mut acc = if MAX { 0u8 } else { 255u8 };
791 for ky in 0..3 {
792 for kx in 0..3 {
793 let v = input[(y + ky - 1) * width + (x + kx - 1)];
794 acc = if MAX { acc.max(v) } else { acc.min(v) };
795 }
796 }
797 out[y * width + x] = acc;
798 }
799 }
800 out
801 }
802
803 for &(w, h) in &[(37usize, 19usize), (64, 8), (17, 17), (5, 5), (48, 12)] {
806 let input = make_image(w, h, 0x1234 ^ (w as u64) ^ ((h as u64) << 16));
807
808 let mut eroded = vec![0u8; w * h];
809 let mut dilated = vec![0u8; w * h];
810 erode_3x3(&input, &mut eroded, w, h).expect("erode ok");
811 dilate_3x3(&input, &mut dilated, w, h).expect("dilate ok");
812
813 let mut ref_erode = scalar_ref::<false>(&input, w, h);
814 let mut ref_dilate = scalar_ref::<true>(&input, w, h);
815 copy_borders(&input, &mut ref_erode, w, h);
817 copy_borders(&input, &mut ref_dilate, w, h);
818
819 assert_eq!(eroded, ref_erode, "erosion mismatch at {w}x{h}");
820 assert_eq!(dilated, ref_dilate, "dilation mismatch at {w}x{h}");
821 }
822 }
823
824 #[test]
825 fn test_saturating_sub_dispatch_matches_scalar() {
826 for len in [0usize, 1, 15, 16, 17, 31, 33, 256, 257] {
827 let a = make_image(len, 1, 0xABCD);
828 let b = make_image(len, 1, 0x1357);
829 let mut out = vec![0u8; len];
830 saturating_sub_dispatch(&a, &b, &mut out);
831 for i in 0..len {
832 assert_eq!(out[i], a[i].saturating_sub(b[i]), "mismatch at index {i}");
833 }
834 }
835 }
836
837 use proptest::prelude::*;
838
839 proptest! {
840 #[test]
843 fn prop_erode_le_dilate(
844 w in 3usize..40,
845 h in 3usize..40,
846 seed in any::<u64>(),
847 ) {
848 let input = make_image(w, h, seed);
849 let mut eroded = vec![0u8; w * h];
850 let mut dilated = vec![0u8; w * h];
851 erode_3x3(&input, &mut eroded, w, h).expect("erode");
852 dilate_3x3(&input, &mut dilated, w, h).expect("dilate");
853 for i in 0..w * h {
854 prop_assert!(eroded[i] <= dilated[i]);
855 }
856 }
857
858 #[test]
861 fn prop_erode_dilate_parity(
862 w in 3usize..40,
863 h in 3usize..40,
864 seed in any::<u64>(),
865 ) {
866 fn scalar_ref<const MAX: bool>(input: &[u8], width: usize, height: usize) -> Vec<u8> {
867 let mut out = input.to_vec();
868 for y in 1..(height - 1) {
869 for x in 1..(width - 1) {
870 let mut acc = if MAX { 0u8 } else { 255u8 };
871 for ky in 0..3 {
872 for kx in 0..3 {
873 let v = input[(y + ky - 1) * width + (x + kx - 1)];
874 acc = if MAX { acc.max(v) } else { acc.min(v) };
875 }
876 }
877 out[y * width + x] = acc;
878 }
879 }
880 out
881 }
882
883 let input = make_image(w, h, seed);
884 let mut eroded = vec![0u8; w * h];
885 let mut dilated = vec![0u8; w * h];
886 erode_3x3(&input, &mut eroded, w, h).expect("erode");
887 dilate_3x3(&input, &mut dilated, w, h).expect("dilate");
888
889 let mut ref_e = scalar_ref::<false>(&input, w, h);
890 let mut ref_d = scalar_ref::<true>(&input, w, h);
891 copy_borders(&input, &mut ref_e, w, h);
892 copy_borders(&input, &mut ref_d, w, h);
893 prop_assert_eq!(eroded, ref_e);
894 prop_assert_eq!(dilated, ref_d);
895 }
896 }
897}