skeletonize/
edge_detection.rs1use crate::error::{LumaConversionErrorKind, SkeletonizeError};
4use crate::ForegroundColor;
5
6#[rustfmt::skip]
8pub const SOBEL_NORTH: [f32; 9] = [
9 1.0, 2.0, 1.0,
10 0.0, 0.0, 0.0,
11 -1.0, -2.0, -1.0,
12];
13#[rustfmt::skip]
15pub const SOBEL_SOUTH: [f32; 9] = [
16 -1.0, -2.0, -1.0,
17 0.0, 0.0, 0.0,
18 1.0, 2.0, 1.0,
19];
20#[rustfmt::skip]
22pub const SOBEL_EAST: [f32; 9] = [
23 -1.0, 0.0, 1.0,
24 -2.0, 0.0, 2.0,
25 -1.0, 0.0, 1.0,
26];
27#[rustfmt::skip]
29pub const SOBEL_WEST: [f32; 9] = [
30 1.0, 0.0, -1.0,
31 2.0, 0.0, -2.0,
32 1.0, 0.0, -1.0,
33];
34
35pub fn sobel<F: ForegroundColor>(
43 img: &image::DynamicImage,
44 threshold: Option<f32>,
45) -> Result<image::DynamicImage, SkeletonizeError> {
46 let mut filter_up = img.filter3x3(&SOBEL_NORTH);
47 let filtered_right = img.filter3x3(&SOBEL_EAST);
48 let mutable_error = SkeletonizeError::LumaConversion(LumaConversionErrorKind::SobelMutableLuma);
49 let immutable_error = SkeletonizeError::LumaConversion(LumaConversionErrorKind::SobelLuma);
50
51 let iter_down = filter_up.as_mut_luma8().ok_or(mutable_error)?.iter_mut();
52 let iter_right = filtered_right.as_luma8().ok_or(immutable_error)?.iter();
53
54 for (g_down, g_right) in iter_down.zip(iter_right) {
55 let res = (f32::from(*g_down) / 255.0).hypot(f32::from(*g_right) / 255.0);
56
57 if let Some(threshold) = threshold {
58 *g_down = if res < threshold {
59 F::BACKGROUND_COLOR
60 } else {
61 !F::BACKGROUND_COLOR
62 }
63 } else {
64 *g_down = (res * 255.0).round() as u8;
65 }
66 }
67
68 if threshold.is_none() && F::BACKGROUND_COLOR == 255 {
71 filter_up.invert()
72 }
73
74 Ok(filter_up)
75}
76
77pub fn sobel4<F: ForegroundColor>(
86 img: &image::DynamicImage,
87 threshold: Option<f32>,
88) -> Result<image::DynamicImage, SkeletonizeError> {
89 let mut filter_up = img.filter3x3(&SOBEL_NORTH);
90 let filter_down = img.filter3x3(&SOBEL_SOUTH);
91 let filter_right = img.filter3x3(&SOBEL_EAST);
92 let filter_left = img.filter3x3(&SOBEL_WEST);
93
94 let mutable_error = SkeletonizeError::LumaConversion(LumaConversionErrorKind::SobelMutableLuma);
95 let immutable_error = SkeletonizeError::LumaConversion(LumaConversionErrorKind::SobelLuma);
96
97 let iter_up = filter_up.as_mut_luma8().ok_or(mutable_error)?.iter_mut();
98 let iter_down = filter_down.as_luma8().ok_or(immutable_error)?.iter();
99 let iter_right = filter_right.as_luma8().ok_or(immutable_error)?.iter();
100 let iter_left = filter_left.as_luma8().ok_or(immutable_error)?.iter();
101
102 for (((g_up, g_down), g_left), g_right) in iter_up.zip(iter_down).zip(iter_right).zip(iter_left)
103 {
104 let vertical = (f32::from(*g_up) - f32::from(*g_down)) / 255.0;
105 let horizontal = (f32::from(*g_right) - f32::from(*g_left)) / 255.0;
106 let res = vertical.hypot(horizontal);
107
108 if let Some(threshold) = threshold {
109 *g_up = if res < threshold {
110 F::BACKGROUND_COLOR
111 } else {
112 !F::BACKGROUND_COLOR
113 }
114 } else {
115 *g_up = (res * 255.0).round() as u8;
116 }
117 }
118
119 if threshold.is_none() && F::BACKGROUND_COLOR == 255 {
122 filter_up.invert()
123 }
124
125 Ok(filter_up)
126}