1use crate::{
6 distance_transform::{DistanceFrom, Norm, distance_transform_impl, distance_transform_mut},
7 point::Point,
8};
9use image::{GrayImage, Luma};
10use itertools::Itertools;
11
12pub fn dilate(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
72 let mut out = image.clone();
73 dilate_mut(&mut out, norm, k);
74 out
75}
76#[doc=generate_mut_doc_comment!("dilate")]
77pub fn dilate_mut(image: &mut GrayImage, norm: Norm, k: u8) {
78 distance_transform_mut(image, norm);
79 for p in image.iter_mut() {
80 *p = if *p <= k { 255 } else { 0 };
81 }
82}
83
84pub fn erode(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
163 let mut out = image.clone();
164 erode_mut(&mut out, norm, k);
165 out
166}
167#[doc=generate_mut_doc_comment!("erode")]
168pub fn erode_mut(image: &mut GrayImage, norm: Norm, k: u8) {
169 distance_transform_impl(image, norm, DistanceFrom::Background);
170 for p in image.iter_mut() {
171 *p = if *p <= k { 0 } else { 255 };
172 }
173}
174
175pub fn open(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
227 let mut out = image.clone();
228 open_mut(&mut out, norm, k);
229 out
230}
231#[doc=generate_mut_doc_comment!("open")]
232pub fn open_mut(image: &mut GrayImage, norm: Norm, k: u8) {
233 erode_mut(image, norm, k);
234 dilate_mut(image, norm, k);
235}
236
237pub fn close(image: &GrayImage, norm: Norm, k: u8) -> GrayImage {
327 let mut out = image.clone();
328 close_mut(&mut out, norm, k);
329 out
330}
331#[doc=generate_mut_doc_comment!("close")]
332pub fn close_mut(image: &mut GrayImage, norm: Norm, k: u8) {
333 dilate_mut(image, norm, k);
334 erode_mut(image, norm, k);
335}
336
337#[derive(Clone, Debug, PartialEq, Eq)]
339pub struct Mask {
340 elements: Vec<Point<i16>>,
345}
346
347macro_rules! lines {
348 ($mask:expr) => {
349 $mask
350 .elements
351 .iter()
352 .chunk_by(|p| p.y)
353 .into_iter()
354 .map(|(y, line)| (y, line.map(|p| p.x)))
355 };
356}
357
358impl Mask {
359 pub fn from_image(image: &GrayImage, center_x: u8, center_y: u8) -> Self {
367 assert!(
368 image.width() < 512,
369 "the input image must be at most 511 pixels wide"
370 );
371 assert!(
372 image.height() < 512,
373 "the input image must be at most 511 pixels high"
374 );
375 let center = Point::new(center_x, center_y).to_i16();
376 let elements = image
377 .enumerate_pixels()
378 .filter(|&(_, _, &p)| p[0] != 0)
379 .map(|(x, y, _)| Point::new(x, y).to_i16())
380 .map(|p| p - center)
381 .collect();
382 Self::new(elements)
383 }
384
385 fn new(elements: Vec<Point<i16>>) -> Self {
386 assert!(elements.len() <= (511 * 511) as usize);
387 debug_assert!(
388 elements
389 .iter()
390 .tuple_windows()
391 .all(|(a, b)| { if a.y == b.y { a.x < b.x } else { a.y < b.y } })
392 );
393 Self { elements }
394 }
395
396 pub fn square(radius: u8) -> Self {
416 let radius = i16::from(radius);
417 let range = -radius..=radius;
418 let elements = range
419 .clone()
420 .cartesian_product(range)
421 .map(|(y, x)| Point::new(x, y))
422 .collect();
423 Self::new(elements)
424 }
425
426 pub fn diamond(radius: u8) -> Self {
456 let cap = 1 + 2 * usize::from(radius) * (usize::from(radius) + 1);
457 let mut elements = Vec::with_capacity(cap);
458 let radius = i16::from(radius);
459 let points = (-radius..=radius)
460 .flat_map(|y| ((y.abs() - radius)..=(radius - y.abs())).map(move |x| Point::new(x, y)));
461 elements.extend(points);
462 Self::new(elements)
463 }
464
465 pub fn disk(radius: u8) -> Self {
502 let radius_squared = u32::from(radius).pow(2);
503 let half_widths_per_height = std::iter::successors(
504 Some((-i16::from(radius), 0u8)),
505 |&(last_height, last_half_width)| {
506 if last_height == i16::from(radius) {
507 return None;
508 };
509 let next_height = last_height + 1;
510 let height_squared = (u32::from(next_height.unsigned_abs())).pow(2);
511 let next_half_width = if next_height <= 0 {
512 (u32::from(last_half_width)..)
514 .find(|x| (x + 1).pow(2) + height_squared > radius_squared)?
515 } else {
516 (0u32..=last_half_width.into())
518 .rev()
519 .find(|&x| x.pow(2) + height_squared <= radius_squared)?
520 };
521 Some((next_height, next_half_width.try_into().unwrap()))
522 },
523 );
524 let cap = half_widths_per_height
525 .clone()
526 .map(|(_, half_width)| 2 * usize::from(half_width) + 1)
527 .sum();
528 let mut elements = Vec::with_capacity(cap);
529 let points = half_widths_per_height.flat_map(|(y, half_width)| {
530 (-i16::from(half_width)..=i16::from(half_width)).map(move |x| Point::new(x, y))
531 });
532 elements.extend(points);
533 Self::new(elements)
534 }
535}
536
537fn mask_reduce<F: Fn(u8, u8) -> u8>(
538 image: &GrayImage,
539 mask: &Mask,
540 neutral: u8,
541 operator: F,
542) -> GrayImage {
543 let mut result = GrayImage::from_pixel(image.width(), image.height(), Luma([neutral]));
544 for (y, line_group) in lines!(mask) {
545 let y = i64::from(y);
546 let line = line_group.collect::<Vec<_>>();
547 let input_rows = image
548 .chunks(image.width() as usize)
549 .skip(y.try_into().unwrap_or(0));
550 let output_rows = result
551 .chunks_mut(image.width() as usize)
552 .skip((-y).try_into().unwrap_or(0));
553 for (input_row, output_row) in input_rows.zip(output_rows) {
554 for x in line.iter().copied() {
555 let inputs = input_row.iter().skip(x.try_into().unwrap_or(0));
556 let outputs = output_row.iter_mut().skip((-x).try_into().unwrap_or(0));
557 for (&input, output) in inputs.zip(outputs) {
558 *output = operator(input, *output);
559 }
560 }
561 }
562 }
563 result
564}
565
566pub fn grayscale_dilate(image: &GrayImage, mask: &Mask) -> GrayImage {
640 mask_reduce(image, mask, u8::MIN, u8::max)
641}
642
643pub fn grayscale_erode(image: &GrayImage, mask: &Mask) -> GrayImage {
717 mask_reduce(image, mask, u8::MAX, u8::min)
718}
719
720pub fn grayscale_open(image: &GrayImage, mask: &Mask) -> GrayImage {
759 grayscale_dilate(&grayscale_erode(image, mask), mask)
760}
761
762pub fn grayscale_close(image: &GrayImage, mask: &Mask) -> GrayImage {
801 grayscale_erode(&grayscale_dilate(image, mask), mask)
802}
803
804#[cfg(test)]
805mod tests {
806 use super::*;
807
808 #[test]
809 fn test_dilate_point_l1_0() {
810 let image = gray_image!(
811 0, 0, 0, 0, 0;
812 0, 0, 0, 0, 0;
813 0, 0, 255, 0, 0;
814 0, 0, 0, 0, 0;
815 0, 0, 0, 0, 0
816 );
817 let dilated = dilate(&image, Norm::L1, 0);
818 assert_pixels_eq!(dilated, image);
819 }
820
821 #[test]
822 fn test_dilate_point_l1_1() {
823 let image = gray_image!(
824 0, 0, 0, 0, 0;
825 0, 0, 0, 0, 0;
826 0, 0, 255, 0, 0;
827 0, 0, 0, 0, 0;
828 0, 0, 0, 0, 0
829 );
830 let dilated = dilate(&image, Norm::L1, 1);
831
832 let expected = gray_image!(
833 0, 0, 0, 0, 0;
834 0, 0, 255, 0, 0;
835 0, 255, 255, 255, 0;
836 0, 0, 255, 0, 0;
837 0, 0, 0, 0, 0
838 );
839 assert_pixels_eq!(dilated, expected);
840 }
841
842 #[test]
843 fn test_dilate_point_l1_2() {
844 let image = gray_image!(
845 0, 0, 0, 0, 0;
846 0, 0, 0, 0, 0;
847 0, 0, 255, 0, 0;
848 0, 0, 0, 0, 0;
849 0, 0, 0, 0, 0
850 );
851 let dilated = dilate(&image, Norm::L1, 2);
852
853 let expected = gray_image!(
854 0, 0, 255, 0, 0;
855 0, 255, 255, 255, 0;
856 255, 255, 255, 255, 255;
857 0, 255, 255, 255, 0;
858 0, 0, 255, 0, 0
859 );
860 assert_pixels_eq!(dilated, expected);
861 }
862
863 #[test]
864 fn test_dilate_point_l1_4() {
865 let image = gray_image!(
866 0, 0, 0, 0, 0, 0, 0, 0, 0;
867 0, 0, 0, 0, 0, 0, 0, 0, 0;
868 0, 0, 0, 0, 0, 0, 0, 0, 0;
869 0, 0, 0, 0, 0, 0, 0, 0, 0;
870 0, 0, 0, 0, 255, 0, 0, 0, 0;
871 0, 0, 0, 0, 0, 0, 0, 0, 0;
872 0, 0, 0, 0, 0, 0, 0, 0, 0;
873 0, 0, 0, 0, 0, 0, 0, 0, 0;
874 0, 0, 0, 0, 0, 0, 0, 0, 0
875 );
876 let dilated = dilate(&image, Norm::L1, 4);
877
878 let expected = gray_image!(
879 0, 0, 0, 0, 255, 0, 0, 0, 0;
880 0, 0, 0, 255, 255, 255, 0, 0, 0;
881 0, 0, 255, 255, 255, 255, 255, 0, 0;
882 0, 255, 255, 255, 255, 255, 255, 255, 0;
883 255, 255, 255, 255, 255, 255, 255, 255, 255;
884 0, 255, 255, 255, 255, 255, 255, 255, 0;
885 0, 0, 255, 255, 255, 255, 255, 0, 0;
886 0, 0, 0, 255, 255, 255, 0, 0, 0;
887 0, 0, 0, 0, 255, 0, 0, 0, 0
888 );
889
890 assert_pixels_eq!(dilated, expected);
891 }
892
893 #[test]
894 fn test_dilate_point_l2_0() {
895 let image = gray_image!(
896 0, 0, 0, 0, 0;
897 0, 0, 0, 0, 0;
898 0, 0, 255, 0, 0;
899 0, 0, 0, 0, 0;
900 0, 0, 0, 0, 0
901 );
902 let dilated = dilate(&image, Norm::L2, 0);
903 assert_pixels_eq!(dilated, image);
904 }
905
906 #[test]
907 fn test_dilate_point_l2_1() {
908 let image = gray_image!(
909 0, 0, 0, 0, 0;
910 0, 0, 0, 0, 0;
911 0, 0, 255, 0, 0;
912 0, 0, 0, 0, 0;
913 0, 0, 0, 0, 0
914 );
915 let dilated = dilate(&image, Norm::L2, 1);
916
917 let expected = gray_image!(
918 0, 0, 0, 0, 0;
919 0, 0, 255, 0, 0;
920 0, 255, 255, 255, 0;
921 0, 0, 255, 0, 0;
922 0, 0, 0, 0, 0
923 );
924 assert_pixels_eq!(dilated, expected);
925 }
926
927 #[test]
928 fn test_dilate_point_l2_2() {
929 let image = gray_image!(
930 0, 0, 0, 0, 0;
931 0, 0, 0, 0, 0;
932 0, 0, 255, 0, 0;
933 0, 0, 0, 0, 0;
934 0, 0, 0, 0, 0
935 );
936 let dilated = dilate(&image, Norm::L2, 2);
937
938 let expected = gray_image!(
939 0, 0, 255, 0, 0;
940 0, 255, 255, 255, 0;
941 255, 255, 255, 255, 255;
942 0, 255, 255, 255, 0;
943 0, 0, 255, 0, 0
944 );
945 assert_pixels_eq!(dilated, expected);
946 }
947
948 #[test]
949 fn test_dilate_point_l2_4() {
950 let image = gray_image!(
951 0, 0, 0, 0, 0, 0, 0, 0, 0;
952 0, 0, 0, 0, 0, 0, 0, 0, 0;
953 0, 0, 0, 0, 0, 0, 0, 0, 0;
954 0, 0, 0, 0, 0, 0, 0, 0, 0;
955 0, 0, 0, 0, 255, 0, 0, 0, 0;
956 0, 0, 0, 0, 0, 0, 0, 0, 0;
957 0, 0, 0, 0, 0, 0, 0, 0, 0;
958 0, 0, 0, 0, 0, 0, 0, 0, 0;
959 0, 0, 0, 0, 0, 0, 0, 0, 0
960 );
961 let dilated = dilate(&image, Norm::L2, 4);
962
963 let expected = gray_image!(
964 0, 0, 0, 0, 255, 0, 0, 0, 0;
965 0, 0, 255, 255, 255, 255, 255, 0, 0;
966 0, 255, 255, 255, 255, 255, 255, 255, 0;
967 0, 255, 255, 255, 255, 255, 255, 255, 0;
968 255, 255, 255, 255, 255, 255, 255, 255, 255;
969 0, 255, 255, 255, 255, 255, 255, 255, 0;
970 0, 255, 255, 255, 255, 255, 255, 255, 0;
971 0, 0, 255, 255, 255, 255, 255, 0, 0;
972 0, 0, 0, 0, 255, 0, 0, 0, 0
973 );
974 assert_pixels_eq!(dilated, expected);
975 }
976
977 #[test]
978 fn test_dilate_point_linf_0() {
979 let image = gray_image!(
980 0, 0, 0, 0, 0;
981 0, 0, 0, 0, 0;
982 0, 0, 255, 0, 0;
983 0, 0, 0, 0, 0;
984 0, 0, 0, 0, 0
985 );
986 let dilated = dilate(&image, Norm::LInf, 0);
987 assert_pixels_eq!(dilated, image);
988 }
989
990 #[test]
991 fn test_dilate_point_linf_1() {
992 let image = gray_image!(
993 0, 0, 0, 0, 0;
994 0, 0, 0, 0, 0;
995 0, 0, 255, 0, 0;
996 0, 0, 0, 0, 0;
997 0, 0, 0, 0, 0
998 );
999 let dilated = dilate(&image, Norm::LInf, 1);
1000
1001 let expected = gray_image!(
1002 0, 0, 0, 0, 0;
1003 0, 255, 255, 255, 0;
1004 0, 255, 255, 255, 0;
1005 0, 255, 255, 255, 0;
1006 0, 0, 0, 0, 0
1007 );
1008 assert_pixels_eq!(dilated, expected);
1009 }
1010
1011 #[test]
1012 fn test_dilate_point_linf_2() {
1013 let image = gray_image!(
1014 0, 0, 0, 0, 0;
1015 0, 0, 0, 0, 0;
1016 0, 0, 255, 0, 0;
1017 0, 0, 0, 0, 0;
1018 0, 0, 0, 0, 0
1019 );
1020 let dilated = dilate(&image, Norm::LInf, 2);
1021
1022 let expected = gray_image!(
1023 255, 255, 255, 255, 255;
1024 255, 255, 255, 255, 255;
1025 255, 255, 255, 255, 255;
1026 255, 255, 255, 255, 255;
1027 255, 255, 255, 255, 255
1028 );
1029 assert_pixels_eq!(dilated, expected);
1030 }
1031
1032 #[test]
1033 fn test_dilate_point_linf_4() {
1034 let image = gray_image!(
1035 0, 0, 0, 0, 0, 0, 0, 0, 0;
1036 0, 0, 0, 0, 0, 0, 0, 0, 0;
1037 0, 0, 0, 0, 0, 0, 0, 0, 0;
1038 0, 0, 0, 0, 0, 0, 0, 0, 0;
1039 0, 0, 0, 0, 255, 0, 0, 0, 0;
1040 0, 0, 0, 0, 0, 0, 0, 0, 0;
1041 0, 0, 0, 0, 0, 0, 0, 0, 0;
1042 0, 0, 0, 0, 0, 0, 0, 0, 0;
1043 0, 0, 0, 0, 0, 0, 0, 0, 0
1044 );
1045 let dilated = dilate(&image, Norm::LInf, 4);
1046
1047 let expected = gray_image!(
1048 255, 255, 255, 255, 255, 255, 255, 255, 255;
1049 255, 255, 255, 255, 255, 255, 255, 255, 255;
1050 255, 255, 255, 255, 255, 255, 255, 255, 255;
1051 255, 255, 255, 255, 255, 255, 255, 255, 255;
1052 255, 255, 255, 255, 255, 255, 255, 255, 255;
1053 255, 255, 255, 255, 255, 255, 255, 255, 255;
1054 255, 255, 255, 255, 255, 255, 255, 255, 255;
1055 255, 255, 255, 255, 255, 255, 255, 255, 255;
1056 255, 255, 255, 255, 255, 255, 255, 255, 255
1057 );
1058 assert_pixels_eq!(dilated, expected);
1059 }
1060
1061 #[test]
1062 fn test_erode_point_l1_0() {
1063 let image = gray_image!(
1064 0, 0, 0, 0, 0;
1065 0, 0, 0, 0, 0;
1066 0, 0, 255, 0, 0;
1067 0, 0, 0, 0, 0;
1068 0, 0, 0, 0, 0
1069 );
1070 let eroded = erode(&image, Norm::L1, 0);
1071 assert_pixels_eq!(eroded, image);
1072 }
1073
1074 #[test]
1075 fn test_erode_point_l1_1() {
1076 let image = gray_image!(
1077 0, 0, 0, 0, 0;
1078 0, 0, 0, 0, 0;
1079 0, 0, 255, 0, 0;
1080 0, 0, 0, 0, 0;
1081 0, 0, 0, 0, 0
1082 );
1083 let eroded = erode(&image, Norm::L1, 1);
1084
1085 let expected = gray_image!(
1086 0, 0, 0, 0, 0;
1087 0, 0, 0, 0, 0;
1088 0, 0, 0, 0, 0;
1089 0, 0, 0, 0, 0;
1090 0, 0, 0, 0, 0
1091 );
1092 assert_pixels_eq!(eroded, expected);
1093 }
1094
1095 #[test]
1096 fn test_erode_dented_wall_l1_4() {
1097 let image = gray_image!(
1098 255, 255, 255, 255, 255, 255, 255, 255, 0;
1099 255, 255, 255, 255, 255, 255, 255, 255, 0;
1100 255, 255, 255, 255, 255, 255, 255, 255, 0;
1101 255, 255, 255, 255, 255, 255, 255, 255, 0;
1102 255, 255, 255, 255, 255, 255, 0, 0, 0;
1103 255, 255, 255, 255, 255, 255, 255, 255, 0;
1104 255, 255, 255, 255, 255, 255, 255, 255, 0;
1105 255, 255, 255, 255, 255, 255, 255, 255, 0;
1106 255, 255, 255, 255, 255, 255, 255, 255, 0
1107 );
1108 let dilated = erode(&image, Norm::L1, 4);
1109
1110 let expected = gray_image!(
1111 255, 255, 255, 255, 0, 0, 0, 0, 0;
1112 255, 255, 255, 255, 0, 0, 0, 0, 0;
1113 255, 255, 255, 255, 0, 0, 0, 0, 0;
1114 255, 255, 255, 0, 0, 0, 0, 0, 0;
1115 255, 255, 0, 0, 0, 0, 0, 0, 0;
1116 255, 255, 255, 0, 0, 0, 0, 0, 0;
1117 255, 255, 255, 255, 0, 0, 0, 0, 0;
1118 255, 255, 255, 255, 0, 0, 0, 0, 0;
1119 255, 255, 255, 255, 0, 0, 0, 0, 0
1120 );
1121 assert_pixels_eq!(dilated, expected);
1122 }
1123
1124 #[test]
1125 fn test_erode_point_l2_0() {
1126 let image = gray_image!(
1127 0, 0, 0, 0, 0;
1128 0, 0, 0, 0, 0;
1129 0, 0, 255, 0, 0;
1130 0, 0, 0, 0, 0;
1131 0, 0, 0, 0, 0
1132 );
1133 let eroded = erode(&image, Norm::L2, 0);
1134 assert_pixels_eq!(eroded, image);
1135 }
1136
1137 #[test]
1138 fn test_erode_point_l2_1() {
1139 let image = gray_image!(
1140 0, 0, 0, 0, 0;
1141 0, 0, 0, 0, 0;
1142 0, 0, 255, 0, 0;
1143 0, 0, 0, 0, 0;
1144 0, 0, 0, 0, 0
1145 );
1146 let eroded = erode(&image, Norm::L2, 1);
1147
1148 let expected = gray_image!(
1149 0, 0, 0, 0, 0;
1150 0, 0, 0, 0, 0;
1151 0, 0, 0, 0, 0;
1152 0, 0, 0, 0, 0;
1153 0, 0, 0, 0, 0
1154 );
1155 assert_pixels_eq!(eroded, expected);
1156 }
1157
1158 #[test]
1159 fn test_erode_dented_wall_l2_4() {
1160 let image = gray_image!(
1161 255, 255, 255, 255, 255, 255, 255, 255, 0;
1162 255, 255, 255, 255, 255, 255, 255, 255, 0;
1163 255, 255, 255, 255, 255, 255, 255, 255, 0;
1164 255, 255, 255, 255, 255, 255, 255, 255, 0;
1165 255, 255, 255, 255, 255, 255, 0, 0, 0;
1166 255, 255, 255, 255, 255, 255, 255, 255, 0;
1167 255, 255, 255, 255, 255, 255, 255, 255, 0;
1168 255, 255, 255, 255, 255, 255, 255, 255, 0;
1169 255, 255, 255, 255, 255, 255, 255, 255, 0
1170 );
1171 let dilated = erode(&image, Norm::L2, 4);
1172
1173 let expected = gray_image!(
1174 255, 255, 255, 255, 0, 0, 0, 0, 0;
1175 255, 255, 255, 255, 0, 0, 0, 0, 0;
1176 255, 255, 255, 0, 0, 0, 0, 0, 0;
1177 255, 255, 255, 0, 0, 0, 0, 0, 0;
1178 255, 255, 0, 0, 0, 0, 0, 0, 0;
1179 255, 255, 255, 0, 0, 0, 0, 0, 0;
1180 255, 255, 255, 0, 0, 0, 0, 0, 0;
1181 255, 255, 255, 255, 0, 0, 0, 0, 0;
1182 255, 255, 255, 255, 0, 0, 0, 0, 0
1183 );
1184 assert_pixels_eq!(dilated, expected);
1185 }
1186
1187 #[test]
1188 fn test_erode_point_linf_0() {
1189 let image = gray_image!(
1190 0, 0, 0, 0, 0;
1191 0, 0, 0, 0, 0;
1192 0, 0, 255, 0, 0;
1193 0, 0, 0, 0, 0;
1194 0, 0, 0, 0, 0
1195 );
1196 let eroded = erode(&image, Norm::LInf, 0);
1197 assert_pixels_eq!(eroded, image);
1198 }
1199
1200 #[test]
1201 fn test_erode_point_linf_1() {
1202 let image = gray_image!(
1203 0, 0, 0, 0, 0;
1204 0, 0, 0, 0, 0;
1205 0, 0, 255, 0, 0;
1206 0, 0, 0, 0, 0;
1207 0, 0, 0, 0, 0
1208 );
1209 let eroded = erode(&image, Norm::LInf, 1);
1210
1211 let expected = gray_image!(
1212 0, 0, 0, 0, 0;
1213 0, 0, 0, 0, 0;
1214 0, 0, 0, 0, 0;
1215 0, 0, 0, 0, 0;
1216 0, 0, 0, 0, 0
1217 );
1218 assert_pixels_eq!(eroded, expected);
1219 }
1220
1221 #[test]
1222 fn test_erode_dented_wall_linf_4() {
1223 let image = gray_image!(
1224 255, 255, 255, 255, 255, 255, 255, 255, 0;
1225 255, 255, 255, 255, 255, 255, 255, 255, 0;
1226 255, 255, 255, 255, 255, 255, 255, 255, 0;
1227 255, 255, 255, 255, 255, 255, 255, 255, 0;
1228 255, 255, 255, 255, 255, 255, 0, 0, 0;
1229 255, 255, 255, 255, 255, 255, 255, 255, 0;
1230 255, 255, 255, 255, 255, 255, 255, 255, 0;
1231 255, 255, 255, 255, 255, 255, 255, 255, 0;
1232 255, 255, 255, 255, 255, 255, 255, 255, 0
1233 );
1234 let dilated = erode(&image, Norm::LInf, 4);
1235
1236 let expected = gray_image!(
1237 255, 255, 0, 0, 0, 0, 0, 0, 0;
1238 255, 255, 0, 0, 0, 0, 0, 0, 0;
1239 255, 255, 0, 0, 0, 0, 0, 0, 0;
1240 255, 255, 0, 0, 0, 0, 0, 0, 0;
1241 255, 255, 0, 0, 0, 0, 0, 0, 0;
1242 255, 255, 0, 0, 0, 0, 0, 0, 0;
1243 255, 255, 0, 0, 0, 0, 0, 0, 0;
1244 255, 255, 0, 0, 0, 0, 0, 0, 0;
1245 255, 255, 0, 0, 0, 0, 0, 0, 0
1246 );
1247
1248 assert_pixels_eq!(dilated, expected);
1249 }
1250
1251 #[test]
1252 fn test_mask_from_image_equality() {
1253 let ring_mask_base = gray_image!(
1254 100, 75, 255, 222;
1255 84, 0, 0, 1;
1256 99, 0, 0, 22;
1257 255, 7, 255, 20
1258 );
1259
1260 let other_ring_mask_base = gray_image!(
1261 18, 172, 13, 5;
1262 45, 0, 0, 101;
1263 222, 0, 0, 93;
1264 1, 9, 212, 35
1265 );
1266
1267 assert_eq!(
1268 Mask::from_image(&ring_mask_base, 1, 1),
1269 Mask::from_image(&other_ring_mask_base, 1, 1)
1270 );
1271 }
1272
1273 #[test]
1274 fn test_mask_from_image_displacement_inequality() {
1275 let mask_base = gray_image!(
1276 100, 75, 255, 222;
1277 84, 0, 0, 1;
1278 99, 0, 0, 22;
1279 255, 7, 255, 20
1280 );
1281
1282 assert_ne!(
1283 Mask::from_image(&mask_base, 1, 1),
1284 Mask::from_image(&mask_base, 2, 2)
1285 );
1286 }
1287
1288 #[test]
1289 fn test_mask_from_image_empty() {
1290 let mask_base = gray_image!(0);
1291 assert!(Mask::from_image(&mask_base, 1, 1).elements.is_empty())
1292 }
1293
1294 #[test]
1296 fn test_mask_from_image_outside() {
1297 let mask_base = gray_image!(
1298 100, 75, 255, 222;
1299 84, 0, 0, 1;
1300 99, 0, 0, 22;
1301 255, 7, 255, 20
1302 );
1303 let _ = Mask::from_image(&mask_base, 20, 20);
1304 }
1305
1306 #[test]
1307 #[should_panic]
1308 fn test_mask_from_image_out_of_bounds() {
1309 let mask_base = GrayImage::new(600, 5);
1310 Mask::from_image(&mask_base, 5, 5);
1311 }
1312
1313 #[test]
1314 fn test_masks_0() {
1315 let mask_base = gray_image!(72);
1316 assert_eq!(Mask::from_image(&mask_base, 0, 0), Mask::square(0));
1317 assert_eq!(Mask::from_image(&mask_base, 0, 0), Mask::diamond(0));
1318 assert_eq!(Mask::from_image(&mask_base, 0, 0), Mask::disk(0))
1319 }
1320
1321 #[test]
1322 fn test_mask_square_1() {
1323 let mask_base = gray_image!(
1324 72, 31, 148;
1325 2, 219, 173;
1326 48, 7, 200
1327 );
1328 assert_eq!(Mask::from_image(&mask_base, 1, 1), Mask::square(1));
1329 }
1330
1331 #[test]
1332 fn test_mask_square_2() {
1333 let mask_base = gray_image!(
1334 217, 188, 101, 222, 137;
1335 231, 204, 255, 182, 193;
1336 193, 101, 188, 217, 149;
1337 217, 188, 231, 222, 137;
1338 101, 204, 222, 255, 193
1339 );
1340 assert_eq!(Mask::from_image(&mask_base, 2, 2), Mask::square(2));
1341 }
1342
1343 #[test]
1344 fn test_mask_square_3() {
1345 let mask_base = gray_image!(
1346 217, 188, 101, 222, 137, 101, 222;
1347 231, 204, 255, 222, 137, 222, 255;
1348 193, 101, 188, 217, 217, 222, 188;
1349 217, 188, 231, 222, 137, 217, 149;
1350 193, 255, 193, 188, 231, 222, 188;
1351 217, 188, 231, 149, 101, 188, 149;
1352 101, 204, 222, 255, 193, 255, 182
1353 );
1354 assert_eq!(Mask::from_image(&mask_base, 3, 3), Mask::square(3));
1355 }
1356
1357 #[test]
1358 fn test_mask_diamond_1() {
1359 let mask_base = gray_image!(
1360 0, 31, 0;
1361 2, 219, 173;
1362 0, 7, 0
1363 );
1364 assert_eq!(Mask::from_image(&mask_base, 1, 1), Mask::diamond(1));
1365 }
1366
1367 #[test]
1368 fn test_mask_diamond_2() {
1369 let mask_base = gray_image!(
1370 0, 0, 255, 0, 0;
1371 0, 231, 204, 101, 0;
1372 149, 193, 188, 137, 199;
1373 0, 222, 182, 114, 0;
1374 0, 0, 217, 0, 0
1375 );
1376 assert_eq!(Mask::from_image(&mask_base, 2, 2), Mask::diamond(2));
1377 }
1378
1379 #[test]
1380 fn test_mask_diamond_3() {
1381 let mask_base = gray_image!(
1382 0, 0, 0, 222, 0, 0, 0;
1383 0, 0, 255, 222, 137, 0, 0;
1384 0, 101, 188, 217, 217, 222, 0;
1385 217, 188, 231, 222, 137, 217, 149;
1386 0, 255, 193, 188, 231, 222, 0;
1387 0, 0, 231, 149, 101, 0, 0;
1388 0, 0, 0, 255, 0, 0, 0
1389 );
1390 assert_eq!(Mask::from_image(&mask_base, 3, 3), Mask::diamond(3));
1391 }
1392
1393 #[test]
1394 fn test_mask_disk_1() {
1395 let mask_base = gray_image!(
1396 0, 31, 0;
1397 2, 219, 173;
1398 0, 7, 0
1399 );
1400 assert_eq!(Mask::from_image(&mask_base, 1, 1), Mask::disk(1));
1401 }
1402
1403 #[test]
1404 fn test_mask_disk_2() {
1405 let mask_base = gray_image!(
1406 0, 0, 255, 0, 0;
1407 0, 231, 204, 101, 0;
1408 149, 193, 188, 137, 199;
1409 0, 222, 182, 114, 0;
1410 0, 0, 217, 0, 0
1411 );
1412 assert_eq!(Mask::from_image(&mask_base, 2, 2), Mask::disk(2));
1413 }
1414
1415 #[test]
1416 fn test_mask_disk_3() {
1417 let mask_base = gray_image!(
1418 0, 0, 0, 222, 0, 0, 0;
1419 0, 149, 255, 222, 137, 101, 0;
1420 0, 101, 188, 217, 217, 222, 0;
1421 217, 188, 231, 222, 137, 217, 149;
1422 0, 255, 193, 188, 231, 222, 0;
1423 0, 137, 231, 149, 101, 188, 0;
1424 0, 0, 0, 255, 0, 0, 0
1425 );
1426 assert_eq!(Mask::from_image(&mask_base, 3, 3), Mask::disk(3));
1427 }
1428
1429 #[test]
1430 fn test_grayscale_dilate_0() {
1431 let image = gray_image!(
1432 217, 188, 101, 222, 137, 101, 222;
1433 231, 204, 255, 222, 137, 222, 255;
1434 193, 101, 188, 217, 217, 222, 188;
1435 217, 188, 231, 222, 137, 217, 149;
1436 193, 255, 193, 188, 231, 222, 188;
1437 217, 188, 231, 149, 101, 188, 149;
1438 101, 204, 222, 255, 193, 255, 182
1439 );
1440 assert_eq!(grayscale_dilate(&image, &Mask::square(0)), image.clone());
1441 }
1442
1443 #[test]
1444 fn test_grayscale_dilate_diamond_1() {
1445 let image = gray_image!(
1446 80, 80, 80, 80, 80, 80, 80;
1447 80, 80, 80, 80, 80, 212, 80;
1448 80, 80, 80, 80, 212, 212, 80;
1449 0, 80, 80, 80, 80, 80, 80;
1450 0, 0, 80, 80, 80, 80, 80;
1451 0, 0, 0, 80, 80, 80, 80;
1452 0, 0, 0, 80, 80, 80, 80
1453 );
1454 let dilated = gray_image!(
1455 80, 80, 80, 80, 80, 212, 80;
1456 80, 80, 80, 80, 212, 212, 212;
1457 80, 80, 80, 212, 212, 212, 212;
1458 80, 80, 80, 80, 212, 212, 80;
1459 0, 80, 80, 80, 80, 80, 80;
1460 0, 0, 80, 80, 80, 80, 80;
1461 0, 0, 80, 80, 80, 80, 80
1462 );
1463 assert_eq!(grayscale_dilate(&image, &Mask::diamond(1)), dilated);
1464 }
1465
1466 #[test]
1467 fn test_grayscale_dilate_diamond_3() {
1468 let image = gray_image!(
1469 80, 80, 80, 80, 80, 80, 80;
1470 80, 80, 80, 80, 80, 212, 80;
1471 80, 80, 80, 80, 212, 212, 80;
1472 0, 80, 80, 80, 80, 80, 80;
1473 0, 0, 80, 80, 80, 80, 80;
1474 0, 0, 0, 80, 80, 80, 80;
1475 0, 0, 0, 80, 80, 80, 80
1476 );
1477 let dilated = gray_image!(
1478 80, 80, 80, 212, 212, 212, 212;
1479 80, 80, 212, 212, 212, 212, 212;
1480 80, 212, 212, 212, 212, 212, 212;
1481 80, 80, 212, 212, 212, 212, 212;
1482 80, 80, 80, 212, 212, 212, 212;
1483 80, 80, 80, 80, 212, 212, 80;
1484 80, 80, 80, 80, 80, 80, 80
1485 );
1486 assert_eq!(grayscale_dilate(&image, &Mask::diamond(3)), dilated);
1487 }
1488
1489 #[test]
1490 fn test_grayscale_dilate_square_1() {
1491 let image = gray_image!(
1492 80, 80, 80, 80, 80, 80, 80;
1493 80, 80, 80, 80, 80, 212, 80;
1494 80, 80, 80, 80, 212, 212, 80;
1495 0, 80, 80, 80, 80, 80, 80;
1496 0, 0, 80, 80, 80, 80, 80;
1497 0, 0, 0, 80, 80, 80, 80;
1498 0, 0, 0, 80, 80, 80, 80
1499 );
1500 let dilated = gray_image!(
1501 80, 80, 80, 80, 212, 212, 212;
1502 80, 80, 80, 212, 212, 212, 212;
1503 80, 80, 80, 212, 212, 212, 212;
1504 80, 80, 80, 212, 212, 212, 212;
1505 80, 80, 80, 80, 80, 80, 80;
1506 0, 80, 80, 80, 80, 80, 80;
1507 0, 0, 80, 80, 80, 80, 80
1508 );
1509 assert_eq!(grayscale_dilate(&image, &Mask::square(1)), dilated);
1510 }
1511
1512 #[test]
1513 fn test_grayscale_dilate_square_3() {
1514 let image = gray_image!(
1515 80, 80, 80, 80, 80, 80, 80;
1516 80, 80, 80, 80, 80, 212, 80;
1517 80, 80, 80, 80, 212, 212, 80;
1518 0, 80, 80, 80, 80, 80, 80;
1519 0, 0, 80, 80, 80, 80, 80;
1520 0, 0, 0, 80, 80, 80, 80;
1521 0, 0, 0, 80, 80, 80, 80
1522 );
1523 let dilated = gray_image!(
1524 80, 212, 212, 212, 212, 212, 212;
1525 80, 212, 212, 212, 212, 212, 212;
1526 80, 212, 212, 212, 212, 212, 212;
1527 80, 212, 212, 212, 212, 212, 212;
1528 80, 212, 212, 212, 212, 212, 212;
1529 80, 212, 212, 212, 212, 212, 212;
1530 80, 80, 80, 80, 80, 80, 80
1531 );
1532 assert_eq!(grayscale_dilate(&image, &Mask::square(3)), dilated);
1533 }
1534
1535 #[test]
1536 fn test_grayscale_dilate_disk_1() {
1537 let image = gray_image!(
1538 80, 80, 80, 80, 80, 80, 80;
1539 80, 80, 80, 80, 80, 212, 80;
1540 80, 80, 80, 80, 212, 212, 80;
1541 0, 80, 80, 80, 80, 80, 80;
1542 0, 0, 80, 80, 80, 80, 80;
1543 0, 0, 0, 80, 80, 80, 80;
1544 0, 0, 0, 80, 80, 80, 80
1545 );
1546 let dilated = gray_image!(
1547 80, 80, 80, 80, 80, 212, 80;
1548 80, 80, 80, 80, 212, 212, 212;
1549 80, 80, 80, 212, 212, 212, 212;
1550 80, 80, 80, 80, 212, 212, 80;
1551 0, 80, 80, 80, 80, 80, 80;
1552 0, 0, 80, 80, 80, 80, 80;
1553 0, 0, 80, 80, 80, 80, 80
1554 );
1555 assert_eq!(grayscale_dilate(&image, &Mask::disk(1)), dilated);
1556 }
1557
1558 #[test]
1559 fn test_grayscale_dilate_disk_3() {
1560 let image = gray_image!(
1561 80, 80, 80, 80, 80, 80, 80;
1562 80, 80, 80, 80, 80, 212, 80;
1563 80, 80, 80, 80, 212, 212, 80;
1564 0, 80, 80, 80, 80, 80, 80;
1565 0, 0, 80, 80, 80, 80, 80;
1566 0, 0, 0, 80, 80, 80, 80;
1567 0, 0, 0, 80, 80, 80, 80
1568 );
1569 let dilated = gray_image!(
1570 80, 80, 212, 212, 212, 212, 212;
1571 80, 80, 212, 212, 212, 212, 212;
1572 80, 212, 212, 212, 212, 212, 212;
1573 80, 80, 212, 212, 212, 212, 212;
1574 80, 80, 212, 212, 212, 212, 212;
1575 80, 80, 80, 80, 212, 212, 80;
1576 80, 80, 80, 80, 80, 80, 80
1577 );
1578 assert_eq!(grayscale_dilate(&image, &Mask::disk(3)), dilated);
1579 }
1580
1581 #[test]
1582 fn test_grayscale_dilate_arbitrary() {
1583 let mask = Mask::from_image(
1584 &gray_image!(
1585 15, 7;
1586 0, 17;
1587 0, 253;
1588 0, 22
1589 ),
1590 1,
1591 2,
1592 );
1593 let image = gray_image!(
1594 80, 80, 80, 80, 80, 80, 80;
1595 80, 80, 80, 80, 80, 212, 80;
1596 80, 80, 80, 80, 212, 212, 80;
1597 0, 80, 80, 80, 80, 80, 80;
1598 0, 0, 80, 80, 80, 80, 80;
1599 0, 0, 0, 80, 80, 80, 80;
1600 0, 0, 0, 80, 80, 80, 80
1601 );
1602 let dilated = gray_image!(
1603 80, 80, 80, 80, 80, 212, 80;
1604 80, 80, 80, 80, 212, 212, 80;
1605 80, 80, 80, 80, 212, 212, 80;
1606 80, 80, 80, 80, 212, 212, 212;
1607 80, 80, 80, 80, 212, 212, 212;
1608 0, 80, 80, 80, 80, 80, 80;
1609 0, 0, 80, 80, 80, 80, 80
1610 );
1611 assert_eq!(grayscale_dilate(&image, &mask), dilated);
1612 }
1613
1614 #[test]
1615 fn test_grayscale_dilate_default_value() {
1616 let mask = Mask::from_image(&gray_image!(), 0, 0);
1617 let image = gray_image!(
1618 80, 80, 80, 80, 80, 80, 80;
1619 80, 80, 80, 80, 80, 212, 80;
1620 80, 80, 80, 80, 212, 212, 80;
1621 0, 80, 80, 80, 80, 80, 80;
1622 0, 0, 80, 80, 80, 80, 80;
1623 0, 0, 0, 80, 80, 80, 80;
1624 0, 0, 0, 80, 80, 80, 80
1625 );
1626 let dilated = gray_image!(
1627 u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1628 u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1629 u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1630 u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1631 u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1632 u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN;
1633 u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN, u8::MIN
1634 );
1635 assert_eq!(grayscale_dilate(&image, &mask), dilated);
1636 }
1637
1638 #[test]
1639 fn test_grayscale_erode_0() {
1640 let image = gray_image!(
1641 217, 188, 101, 222, 137, 101, 222;
1642 231, 204, 255, 222, 137, 222, 255;
1643 193, 101, 188, 217, 217, 222, 188;
1644 217, 188, 231, 222, 137, 217, 149;
1645 193, 255, 193, 188, 231, 222, 188;
1646 217, 188, 231, 149, 101, 188, 149;
1647 101, 204, 222, 255, 193, 255, 182
1648 );
1649 assert_eq!(grayscale_erode(&image, &Mask::square(0)), image.clone());
1650 }
1651
1652 #[test]
1653 fn test_grayscale_erode_diamond_1() {
1654 let image = gray_image!(
1655 80, 80, 80, 212, 212, 212, 212;
1656 80, 80, 80, 80, 212, 212, 212;
1657 80, 80, 80, 80, 212, 212, 212;
1658 0, 80, 80, 80, 80, 212, 212;
1659 0, 0, 80, 80, 80, 80, 80;
1660 0, 0, 0, 80, 80, 80, 80;
1661 0, 0, 0, 80, 80, 80, 80
1662 );
1663 let eroded = gray_image!(
1664 80, 80, 80, 80, 212, 212, 212;
1665 80, 80, 80, 80, 80, 212, 212;
1666 0, 80, 80, 80, 80, 212, 212;
1667 0, 0, 80, 80, 80, 80, 80;
1668 0, 0, 0, 80, 80, 80, 80;
1669 0, 0, 0, 0, 80, 80, 80;
1670 0, 0, 0, 0, 80, 80, 80
1671 );
1672 assert_eq!(grayscale_erode(&image, &Mask::diamond(1)), eroded);
1673 }
1674
1675 #[test]
1676 fn test_grayscale_erode_diamond_3() {
1677 let image = gray_image!(
1678 80, 80, 80, 212, 212, 212, 212;
1679 80, 80, 80, 80, 212, 212, 212;
1680 80, 80, 80, 80, 212, 212, 212;
1681 0, 80, 80, 80, 80, 212, 212;
1682 0, 0, 80, 80, 80, 80, 80;
1683 0, 0, 0, 80, 80, 80, 80;
1684 0, 0, 0, 80, 80, 80, 80
1685 );
1686 let eroded = gray_image!(
1687 0, 80, 80, 80, 80, 80, 212;
1688 0, 0, 80, 80, 80, 80, 80;
1689 0, 0, 0, 80, 80, 80, 80;
1690 0, 0, 0, 0, 80, 80, 80;
1691 0, 0, 0, 0, 0, 80, 80;
1692 0, 0, 0, 0, 0, 0, 80;
1693 0, 0, 0, 0, 0, 0, 80
1694 );
1695 assert_eq!(grayscale_erode(&image, &Mask::diamond(3)), eroded);
1696 }
1697
1698 #[test]
1699 fn test_grayscale_erode_square_1() {
1700 let image = gray_image!(
1701 80, 80, 80, 212, 212, 212, 212;
1702 80, 80, 80, 80, 212, 212, 212;
1703 80, 80, 80, 80, 212, 212, 212;
1704 0, 80, 80, 80, 80, 212, 212;
1705 0, 0, 80, 80, 80, 80, 80;
1706 0, 0, 0, 80, 80, 80, 80;
1707 0, 0, 0, 80, 80, 80, 80
1708 );
1709 let eroded = gray_image!(
1710 80, 80, 80, 80, 80, 212, 212;
1711 80, 80, 80, 80, 80, 212, 212;
1712 0, 0, 80, 80, 80, 80, 212;
1713 0, 0, 0, 80, 80, 80, 80;
1714 0, 0, 0, 0, 80, 80, 80;
1715 0, 0, 0, 0, 80, 80, 80;
1716 0, 0, 0, 0, 80, 80, 80
1717 );
1718 assert_eq!(grayscale_erode(&image, &Mask::square(1)), eroded);
1719 }
1720
1721 #[test]
1722 fn test_grayscale_erode_square_3() {
1723 let image = gray_image!(
1724 80, 80, 80, 212, 212, 212, 212;
1725 80, 80, 80, 80, 212, 212, 212;
1726 80, 80, 80, 80, 212, 212, 212;
1727 0, 80, 80, 80, 80, 212, 212;
1728 0, 0, 80, 80, 80, 80, 80;
1729 0, 0, 0, 80, 80, 80, 80;
1730 0, 0, 0, 80, 80, 80, 80
1731 );
1732 let eroded = gray_image!(
1733 0, 0, 0, 0, 80, 80, 80;
1734 0, 0, 0, 0, 0, 80, 80;
1735 0, 0, 0, 0, 0, 0, 80;
1736 0, 0, 0, 0, 0, 0, 80;
1737 0, 0, 0, 0, 0, 0, 80;
1738 0, 0, 0, 0, 0, 0, 80;
1739 0, 0, 0, 0, 0, 0, 80
1740 );
1741 assert_eq!(grayscale_erode(&image, &Mask::square(3)), eroded);
1742 }
1743
1744 #[test]
1745 fn test_grayscale_erode_disk_1() {
1746 let image = gray_image!(
1747 80, 80, 80, 212, 212, 212, 212;
1748 80, 80, 80, 80, 212, 212, 212;
1749 80, 80, 80, 80, 212, 212, 212;
1750 0, 80, 80, 80, 80, 212, 212;
1751 0, 0, 80, 80, 80, 80, 80;
1752 0, 0, 0, 80, 80, 80, 80;
1753 0, 0, 0, 80, 80, 80, 80
1754 );
1755 let eroded = gray_image!(
1756 80, 80, 80, 80, 212, 212, 212;
1757 80, 80, 80, 80, 80, 212, 212;
1758 0, 80, 80, 80, 80, 212, 212;
1759 0, 0, 80, 80, 80, 80, 80;
1760 0, 0, 0, 80, 80, 80, 80;
1761 0, 0, 0, 0, 80, 80, 80;
1762 0, 0, 0, 0, 80, 80, 80
1763 );
1764 assert_eq!(grayscale_erode(&image, &Mask::disk(1)), eroded);
1765 }
1766
1767 #[test]
1768 fn test_grayscale_erode_disk_3() {
1769 let image = gray_image!(
1770 80, 80, 80, 212, 212, 212, 212;
1771 80, 80, 80, 80, 212, 212, 212;
1772 80, 80, 80, 80, 212, 212, 212;
1773 0, 80, 80, 80, 80, 212, 212;
1774 0, 0, 80, 80, 80, 80, 80;
1775 0, 0, 0, 80, 80, 80, 80;
1776 0, 0, 0, 80, 80, 80, 80
1777 );
1778 let eroded = gray_image!(
1779 0, 80, 80, 80, 80, 80, 212;
1780 0, 0, 0, 80, 80, 80, 80;
1781 0, 0, 0, 0, 80, 80, 80;
1782 0, 0, 0, 0, 0, 80, 80;
1783 0, 0, 0, 0, 0, 80, 80;
1784 0, 0, 0, 0, 0, 0, 80;
1785 0, 0, 0, 0, 0, 0, 80
1786 );
1787 assert_eq!(grayscale_erode(&image, &Mask::disk(3)), eroded);
1788 }
1789
1790 #[test]
1791 fn test_grayscale_erode_arbitrary() {
1792 let mask = Mask::from_image(
1793 &gray_image!(
1794 15, 7;
1795 1, 17;
1796 0, 253;
1797 0, 22
1798 ),
1799 1,
1800 2,
1801 );
1802 let image = gray_image!(
1803 80, 80, 80, 212, 212, 212, 212;
1804 80, 80, 80, 80, 212, 212, 212;
1805 80, 80, 80, 80, 212, 212, 212;
1806 0, 80, 80, 80, 80, 212, 212;
1807 0, 0, 80, 80, 80, 80, 80;
1808 0, 0, 0, 80, 80, 80, 80;
1809 0, 0, 0, 80, 80, 80, 80
1810 );
1811 let eroded = gray_image!(
1812 80, 80, 80, 80, 212, 212, 212;
1813 80, 80, 80, 80, 212, 212, 212;
1814 0, 80, 80, 80, 80, 212, 212;
1815 0, 0, 80, 80, 80, 80, 80;
1816 0, 0, 0, 80, 80, 80, 80;
1817 0, 0, 0, 80, 80, 80, 80;
1818 0, 0, 0, 0, 80, 80, 80
1819 );
1820 assert_eq!(grayscale_erode(&image, &mask), eroded);
1821 }
1822
1823 #[test]
1824 fn test_grayscale_erode_default_value() {
1825 let mask = Mask::from_image(&gray_image!(), 0, 0);
1826 let image = gray_image!(
1827 80, 80, 80, 80, 80, 80, 80;
1828 80, 80, 80, 80, 80, 212, 80;
1829 80, 80, 80, 80, 212, 212, 80;
1830 0, 80, 80, 80, 80, 80, 80;
1831 0, 0, 80, 80, 80, 80, 80;
1832 0, 0, 0, 80, 80, 80, 80;
1833 0, 0, 0, 80, 80, 80, 80
1834 );
1835 let dilated = gray_image!(
1836 u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1837 u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1838 u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1839 u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1840 u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1841 u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX;
1842 u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX, u8::MAX
1843 );
1844 assert_eq!(grayscale_erode(&image, &mask), dilated);
1845 }
1846
1847 #[test]
1848 fn test_dilate_mut_matches_dilate() {
1849 let mut image = gray_image!(
1850 0, 0, 0, 0, 0;
1851 0, 0, 0, 0, 0;
1852 0, 0, 255, 0, 0;
1853 0, 0, 0, 0, 0;
1854 0, 0, 0, 0, 0
1855 );
1856 let expected = dilate(&image, Norm::LInf, 1);
1857
1858 dilate_mut(&mut image, Norm::LInf, 1);
1859
1860 assert_pixels_eq!(image, expected);
1861 }
1862
1863 #[test]
1864 fn test_erode_mut_matches_erode() {
1865 let mut image = gray_image!(
1866 0, 0, 0, 0, 0;
1867 0, 255, 255, 255, 0;
1868 0, 255, 255, 255, 0;
1869 0, 255, 255, 255, 0;
1870 0, 0, 0, 0, 0
1871 );
1872 let expected = erode(&image, Norm::LInf, 1);
1873
1874 erode_mut(&mut image, Norm::LInf, 1);
1875
1876 assert_pixels_eq!(image, expected);
1877 }
1878
1879 #[test]
1880 fn test_open_removes_small_cross() {
1881 let image = gray_image!(
1882 0, 0, 0, 0, 0;
1883 0, 0, 255, 0, 0;
1884 0, 255, 255, 255, 0;
1885 0, 0, 255, 0, 0;
1886 0, 0, 0, 0, 0
1887 );
1888 let expected = gray_image!(
1889 0, 0, 0, 0, 0;
1890 0, 0, 0, 0, 0;
1891 0, 0, 0, 0, 0;
1892 0, 0, 0, 0, 0;
1893 0, 0, 0, 0, 0
1894 );
1895
1896 assert_pixels_eq!(open(&image, Norm::LInf, 1), expected);
1897 }
1898
1899 #[test]
1900 fn test_open_mut_matches_open() {
1901 let mut image = gray_image!(
1902 0, 0, 0, 0, 0;
1903 0, 255, 255, 255, 0;
1904 0, 255, 255, 255, 0;
1905 0, 255, 255, 255, 0;
1906 0, 0, 0, 0, 0
1907 );
1908 let expected = open(&image, Norm::LInf, 1);
1909
1910 open_mut(&mut image, Norm::LInf, 1);
1911
1912 assert_pixels_eq!(image, expected);
1913 }
1914
1915 #[test]
1916 fn test_close_fills_small_hole() {
1917 let image = gray_image!(
1918 255, 255, 255, 255;
1919 255, 0, 0, 255;
1920 255, 0, 0, 255;
1921 255, 255, 255, 255
1922 );
1923 let expected = gray_image!(
1924 255, 255, 255, 255;
1925 255, 255, 255, 255;
1926 255, 255, 255, 255;
1927 255, 255, 255, 255
1928 );
1929
1930 assert_pixels_eq!(close(&image, Norm::LInf, 1), expected);
1931 }
1932
1933 #[test]
1934 fn test_close_mut_matches_close() {
1935 let mut image = gray_image!(
1936 0, 0, 0, 0, 0;
1937 0, 0, 0, 0, 0;
1938 0, 0, 255, 0, 0;
1939 0, 0, 0, 0, 0;
1940 0, 0, 0, 0, 0
1941 );
1942 let expected = close(&image, Norm::LInf, 1);
1943
1944 close_mut(&mut image, Norm::LInf, 1);
1945
1946 assert_pixels_eq!(image, expected);
1947 }
1948
1949 #[test]
1950 fn test_grayscale_open_expected_output() {
1951 let image = gray_image!(
1952 100, 99, 99, 99, 222, 99;
1953 99, 99, 99, 222, 222, 222;
1954 99, 7, 99, 99, 222, 99;
1955 7, 7, 7, 99, 99, 99;
1956 99, 7, 99, 99, 99, 99
1957 );
1958 let expected = gray_image!(
1959 99, 99, 99, 99, 99, 99;
1960 99, 99, 99, 99, 99, 99;
1961 7, 7, 99, 99, 99, 99;
1962 7, 7, 7, 99, 99, 99;
1963 7, 7, 7, 99, 99, 99
1964 );
1965
1966 assert_pixels_eq!(grayscale_open(&image, &Mask::square(1)), expected);
1967 }
1968
1969 #[test]
1970 fn test_grayscale_close_expected_output() {
1971 let image = gray_image!(
1972 50, 99, 99, 99, 222, 99;
1973 99, 99, 99, 222, 222, 222;
1974 99, 7, 99, 99, 222, 99;
1975 7, 7, 7, 99, 99, 99;
1976 99, 7, 99, 99, 99, 99
1977 );
1978 let expected = gray_image!(
1979 99, 99, 99, 222, 222, 222;
1980 99, 99, 99, 222, 222, 222;
1981 99, 99, 99, 99, 222, 222;
1982 99, 99, 99, 99, 99, 99;
1983 99, 99, 99, 99, 99, 99
1984 );
1985
1986 assert_pixels_eq!(grayscale_close(&image, &Mask::square(1)), expected);
1987 }
1988}
1989
1990#[cfg(not(miri))]
1991#[cfg(test)]
1992mod proptests {
1993 use super::*;
1994 use crate::proptest_utils::{arbitrary_image, arbitrary_image_with};
1995 use proptest::prelude::*;
1996
1997 fn reference_mask_disk(radius: u8) -> Mask {
1998 let range = -(radius as i16)..=(radius as i16);
1999 let elements = range
2000 .clone()
2001 .cartesian_product(range)
2002 .filter(|(y, x)| {
2003 (x.unsigned_abs() as u32).pow(2) + (y.unsigned_abs() as u32).pow(2)
2004 <= (radius as u32).pow(2)
2005 })
2006 .map(|(y, x)| Point::new(x, y))
2007 .collect();
2008 Mask::new(elements)
2009 }
2010
2011 proptest! {
2012 #[test]
2013 fn proptest_mask_from_white_image(
2014 img in arbitrary_image_with(Just(255), 0..=511, 0..=511),
2015 x in any::<u8>(),
2016 y in any::<u8>(),
2017 ) {
2018 Mask::from_image(&img, x, y);
2019 }
2020
2021 #[test]
2022 fn proptest_mask_from_image(
2023 img in arbitrary_image(0..=511, 0..=511),
2024 x in any::<u8>(),
2025 y in any::<u8>(),
2026 ) {
2027 Mask::from_image(&img, x, y);
2028 }
2029
2030 #[test]
2031 fn proptest_mask_square(radius in any::<u8>()) {
2032 Mask::square(radius);
2033 }
2034
2035 #[test]
2036 fn proptest_mask_diamond(radius in any::<u8>()) {
2037 Mask::diamond(radius);
2038 }
2039
2040 #[test]
2041 fn proptest_mask_disk(radius in any::<u8>()) {
2042 let actual = Mask::disk(radius);
2043 let expected = reference_mask_disk(radius);
2044 prop_assert_eq!(actual, expected);
2045 }
2046 }
2047}
2048
2049#[cfg(not(miri))]
2050#[cfg(test)]
2051mod benches {
2052 use super::*;
2053 use ::test::*;
2054 use image::{GrayImage, Luma};
2055 use std::cmp::{max, min};
2056
2057 fn square() -> GrayImage {
2058 GrayImage::from_fn(500, 500, |x, y| {
2059 if min(x, y) > 100 && max(x, y) < 300 {
2060 Luma([255u8])
2061 } else {
2062 Luma([0u8])
2063 }
2064 })
2065 }
2066
2067 #[bench]
2068 fn bench_erode_l1_5(b: &mut Bencher) {
2069 let image = square();
2070 b.iter(|| {
2071 let dilated = dilate(&image, Norm::L1, 5);
2072 black_box(dilated);
2073 })
2074 }
2075
2076 #[bench]
2077 fn bench_dilate_l2_5(b: &mut Bencher) {
2078 let image = square();
2079 b.iter(|| {
2080 let dilated = dilate(&image, Norm::L2, 5);
2081 black_box(dilated);
2082 })
2083 }
2084
2085 #[bench]
2086 fn bench_dilate_linf_5(b: &mut Bencher) {
2087 let image = square();
2088 b.iter(|| {
2089 let dilated = dilate(&image, Norm::LInf, 5);
2090 black_box(dilated);
2091 })
2092 }
2093
2094 #[bench]
2095 fn bench_grayscale_mask_from_image(b: &mut Bencher) {
2096 let image = GrayImage::from_fn(200, 200, |x, y| Luma([(x + y % 3) as u8]));
2097 b.iter(|| {
2098 let mask = Mask::from_image(&image, 100, 100);
2099 black_box(mask);
2100 })
2101 }
2102
2103 macro_rules! bench_grayscale_mask {
2104 ($name:ident, $f:expr) => {
2105 #[bench]
2106 fn $name(b: &mut Bencher) {
2107 b.iter(|| {
2108 let mask = $f(100);
2109 black_box(mask);
2110 })
2111 }
2112 };
2113 }
2114
2115 bench_grayscale_mask!(bench_grayscale_square_mask, Mask::square);
2116 bench_grayscale_mask!(bench_grayscale_diamond_mask, Mask::diamond);
2117 bench_grayscale_mask!(bench_grayscale_disk_mask, Mask::disk);
2118
2119 macro_rules! bench_grayscale_operator {
2120 ($name:ident, $f:expr, $mask:expr, $img_size:expr) => {
2121 #[bench]
2122 fn $name(b: &mut Bencher) {
2123 let image =
2124 GrayImage::from_fn($img_size, $img_size, |x, y| Luma([(x + y % 3) as u8]));
2125 let mask = $mask;
2126 b.iter(|| {
2127 let processed = $f(&image, &mask);
2128 black_box(processed);
2129 })
2130 }
2131 };
2132 }
2133
2134 bench_grayscale_operator!(
2135 bench_grayscale_op_erode_small_image_point,
2136 grayscale_erode,
2137 Mask::diamond(0),
2138 50
2139 );
2140 bench_grayscale_operator!(
2141 bench_grayscale_op_erode_medium_image_point,
2142 grayscale_erode,
2143 Mask::diamond(0),
2144 200
2145 );
2146 bench_grayscale_operator!(
2147 bench_grayscale_op_erode_big_image_point,
2148 grayscale_erode,
2149 Mask::diamond(0),
2150 1000
2151 );
2152 bench_grayscale_operator!(
2153 bench_grayscale_op_erode_small_image_diamond,
2154 grayscale_erode,
2155 Mask::diamond(5),
2156 50
2157 );
2158 bench_grayscale_operator!(
2159 bench_grayscale_op_erode_medium_image_diamond,
2160 grayscale_erode,
2161 Mask::diamond(5),
2162 200
2163 );
2164 bench_grayscale_operator!(
2165 bench_grayscale_op_erode_big_image_diamond,
2166 grayscale_erode,
2167 Mask::diamond(5),
2168 1000
2169 );
2170 bench_grayscale_operator!(
2171 bench_grayscale_op_erode_small_image_large_square,
2172 grayscale_erode,
2173 Mask::square(25),
2174 50
2175 );
2176 bench_grayscale_operator!(
2177 bench_grayscale_op_erode_medium_image_large_square,
2178 grayscale_erode,
2179 Mask::square(25),
2180 200
2181 );
2182
2183 bench_grayscale_operator!(
2184 bench_grayscale_op_dilate_small_image_point,
2185 grayscale_dilate,
2186 Mask::diamond(0),
2187 50
2188 );
2189 bench_grayscale_operator!(
2190 bench_grayscale_op_dilate_medium_image_point,
2191 grayscale_dilate,
2192 Mask::diamond(0),
2193 200
2194 );
2195 bench_grayscale_operator!(
2196 bench_grayscale_op_dilate_big_image_point,
2197 grayscale_dilate,
2198 Mask::diamond(0),
2199 1000
2200 );
2201 bench_grayscale_operator!(
2202 bench_grayscale_op_dilate_small_image_diamond,
2203 grayscale_dilate,
2204 Mask::diamond(5),
2205 50
2206 );
2207 bench_grayscale_operator!(
2208 bench_grayscale_op_dilate_medium_image_diamond,
2209 grayscale_dilate,
2210 Mask::diamond(5),
2211 200
2212 );
2213 bench_grayscale_operator!(
2214 bench_grayscale_op_dilate_big_image_diamond,
2215 grayscale_dilate,
2216 Mask::diamond(5),
2217 1000
2218 );
2219 bench_grayscale_operator!(
2220 bench_grayscale_op_dilate_small_image_large_square,
2221 grayscale_dilate,
2222 Mask::square(25),
2223 50
2224 );
2225 bench_grayscale_operator!(
2226 bench_grayscale_op_dilate_medium_image_large_square,
2227 grayscale_dilate,
2228 Mask::square(25),
2229 200
2230 );
2231}