1use crate::point::Point;
4use image::GrayImage;
5use num::{cast, Num, NumCast};
6use std::collections::VecDeque;
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq)]
10pub enum BorderType {
11 Outer,
14 Hole,
17}
18
19#[derive(Debug)]
21pub struct Contour<T> {
22 pub points: Vec<Point<T>>,
24 pub border_type: BorderType,
26 pub parent: Option<usize>,
29}
30
31impl<T> Contour<T> {
32 pub fn new(points: Vec<Point<T>>, border_type: BorderType, parent: Option<usize>) -> Self {
34 Contour {
35 points,
36 border_type,
37 parent,
38 }
39 }
40}
41
42pub fn find_contours<T>(image: &GrayImage) -> Vec<Contour<T>>
48where
49 T: Num + NumCast + Copy + PartialEq + Eq,
50{
51 find_contours_with_threshold(image, 0)
52}
53
54pub fn find_contours_with_threshold<T>(image: &GrayImage, threshold: u8) -> Vec<Contour<T>>
60where
61 T: Num + NumCast + Copy + PartialEq + Eq,
62{
63 let width = image.width() as usize;
64 let height = image.height() as usize;
65 let mut image_values = vec![vec![0i32; height]; width];
66
67 for y in 0..height {
68 for x in 0..width {
69 if image.get_pixel(x as u32, y as u32).0[0] > threshold {
70 image_values[x][y] = 1;
71 }
72 }
73 }
74
75 let mut diffs = VecDeque::from(vec![
76 Point::new(-1, 0), Point::new(-1, -1), Point::new(0, -1), Point::new(1, -1), Point::new(1, 0), Point::new(1, 1), Point::new(0, 1), Point::new(-1, 1), ]);
85
86 let mut contours: Vec<Contour<T>> = Vec::new();
87 let mut curr_border_num = 1;
88
89 for y in 0..height {
90 let mut parent_border_num = 1;
91
92 for x in 0..width {
93 if image_values[x][y] == 0 {
94 continue;
95 }
96
97 if let Some((adj, border_type)) =
98 if image_values[x][y] == 1 && x > 0 && image_values[x - 1][y] == 0 {
99 Some((Point::new(x - 1, y), BorderType::Outer))
100 } else if image_values[x][y] > 0 && x + 1 < width && image_values[x + 1][y] == 0 {
101 if image_values[x][y] > 1 {
102 parent_border_num = image_values[x][y] as usize;
103 }
104 Some((Point::new(x + 1, y), BorderType::Hole))
105 } else {
106 None
107 }
108 {
109 curr_border_num += 1;
110
111 let parent = if parent_border_num > 1 {
112 let parent_index = parent_border_num - 2;
113 let parent_contour = &contours[parent_index];
114 if (border_type == BorderType::Outer)
115 ^ (parent_contour.border_type == BorderType::Outer)
116 {
117 Some(parent_index)
118 } else {
119 parent_contour.parent
120 }
121 } else {
122 None
123 };
124
125 let mut contour_points = Vec::new();
126 let curr = Point::new(x, y);
127 rotate_to_value(&mut diffs, adj.to_i32() - curr.to_i32());
128
129 if let Some(pos1) = diffs.iter().find_map(|diff| {
130 get_position_if_non_zero_pixel(&image_values, curr.to_i32() + *diff)
131 }) {
132 let mut pos2 = pos1;
133 let mut pos3 = curr;
134
135 loop {
136 contour_points
137 .push(Point::new(cast(pos3.x).unwrap(), cast(pos3.y).unwrap()));
138 rotate_to_value(&mut diffs, pos2.to_i32() - pos3.to_i32());
139 let pos4 = diffs
140 .iter()
141 .rev() .find_map(|diff| {
143 get_position_if_non_zero_pixel(&image_values, pos3.to_i32() + *diff)
144 })
145 .unwrap();
146
147 let mut is_right_edge = false;
148 for diff in diffs.iter().rev() {
149 if *diff == (pos4.to_i32() - pos3.to_i32()) {
150 break;
151 }
152 if *diff == Point::new(1, 0) {
153 is_right_edge = true;
154 break;
155 }
156 }
157
158 if pos3.x + 1 == width || is_right_edge {
159 image_values[pos3.x][pos3.y] = -curr_border_num;
160 } else if image_values[pos3.x][pos3.y] == 1 {
161 image_values[pos3.x][pos3.y] = curr_border_num;
162 }
163
164 if pos4 == curr && pos3 == pos1 {
165 break;
166 }
167
168 pos2 = pos3;
169 pos3 = pos4;
170 }
171 } else {
172 contour_points.push(Point::new(cast(x).unwrap(), cast(y).unwrap()));
173 image_values[x][y] = -curr_border_num;
174 }
175 contours.push(Contour::new(contour_points, border_type, parent));
176 }
177
178 if image_values[x][y] != 1 {
179 parent_border_num = image_values[x][y].abs() as usize;
180 }
181 }
182 }
183
184 contours
185}
186
187fn rotate_to_value<T: Eq + Copy>(values: &mut VecDeque<T>, value: T) {
188 let rotate_pos = values.iter().position(|x| *x == value).unwrap();
189 values.rotate_left(rotate_pos);
190}
191
192fn get_position_if_non_zero_pixel(image: &[Vec<i32>], curr: Point<i32>) -> Option<Point<usize>> {
193 let (width, height) = (image.len() as i32, image[0].len() as i32);
194 let in_bounds = curr.x > -1 && curr.x < width && curr.y > -1 && curr.y < height;
195
196 if in_bounds && image[curr.x as usize][curr.y as usize] != 0 {
197 Some(Point::new(curr.x as usize, curr.y as usize))
198 } else {
199 None
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206 use crate::point::Point;
207
208 fn check_contour<T: Eq>(
211 contour: &Contour<T>,
212 expected_border_type: BorderType,
213 expected_parent: Option<usize>,
214 required_points: &[Point<T>],
215 ) {
216 for point in required_points {
217 assert!(contour.points.contains(point));
218 }
219 assert_eq!(contour.border_type, expected_border_type);
220 assert_eq!(contour.parent, expected_parent);
221 }
222
223 #[test]
224 fn test_contours_structured() {
225 use crate::drawing::draw_polygon_mut;
226 use image::Luma;
227
228 let white = Luma([255u8]);
229 let black = Luma([0u8]);
230
231 let mut image = GrayImage::from_pixel(300, 300, black);
232 draw_polygon_mut(
234 &mut image,
235 &[
236 Point::new(20, 20),
237 Point::new(280, 20),
238 Point::new(280, 280),
239 Point::new(20, 280),
240 ],
241 white,
242 );
243 draw_polygon_mut(
245 &mut image,
246 &[
247 Point::new(40, 40),
248 Point::new(260, 40),
249 Point::new(260, 260),
250 Point::new(40, 260),
251 ],
252 black,
253 );
254 draw_polygon_mut(
256 &mut image,
257 &[
258 Point::new(60, 60),
259 Point::new(240, 60),
260 Point::new(240, 240),
261 Point::new(60, 240),
262 ],
263 white,
264 );
265 draw_polygon_mut(
267 &mut image,
268 &[
269 Point::new(80, 80),
270 Point::new(220, 80),
271 Point::new(220, 220),
272 Point::new(80, 220),
273 ],
274 black,
275 );
276 draw_polygon_mut(
278 &mut image,
279 &[
280 Point::new(290, 290),
281 Point::new(300, 290),
282 Point::new(300, 300),
283 Point::new(290, 300),
284 ],
285 white,
286 );
287
288 let contours = find_contours::<i32>(&image);
289 assert_eq!(contours.len(), 5);
290
291 check_contour(
293 &contours[0],
294 BorderType::Outer,
295 None,
296 &[
297 Point::new(20, 20),
298 Point::new(280, 20),
299 Point::new(280, 280),
300 Point::new(20, 280),
301 ],
302 );
303
304 check_contour(
306 &contours[1],
307 BorderType::Hole,
308 Some(0),
309 &[
310 Point::new(39, 40),
311 Point::new(261, 40),
312 Point::new(261, 260),
313 Point::new(39, 260),
314 ],
315 );
316
317 check_contour(
319 &contours[2],
320 BorderType::Outer,
321 Some(1),
322 &[
323 Point::new(60, 60),
324 Point::new(240, 60),
325 Point::new(240, 240),
326 Point::new(60, 220),
327 ],
328 );
329
330 check_contour(
332 &contours[3],
333 BorderType::Hole,
334 Some(2),
335 &[
336 Point::new(79, 80),
337 Point::new(221, 80),
338 Point::new(221, 220),
339 Point::new(79, 220),
340 ],
341 );
342
343 check_contour(
345 &contours[4],
346 BorderType::Outer,
347 None,
348 &[
349 Point::new(290, 290),
350 Point::new(299, 290),
351 Point::new(299, 299),
352 Point::new(290, 299),
353 ],
354 );
355 }
356
357 #[test]
358 fn find_contours_basic_test() {
359 use crate::definitions::HasWhite;
360 use crate::drawing::draw_polygon_mut;
361 use image::Luma;
362
363 let mut image = GrayImage::new(15, 20);
364 draw_polygon_mut(
365 &mut image,
366 &[Point::new(5, 5), Point::new(11, 5)],
367 Luma::white(),
368 );
369
370 draw_polygon_mut(
371 &mut image,
372 &[Point::new(11, 5), Point::new(11, 9)],
373 Luma::white(),
374 );
375
376 draw_polygon_mut(
377 &mut image,
378 &[Point::new(11, 9), Point::new(5, 9)],
379 Luma::white(),
380 );
381
382 draw_polygon_mut(
383 &mut image,
384 &[Point::new(5, 5), Point::new(5, 9)],
385 Luma::white(),
386 );
387
388 draw_polygon_mut(
389 &mut image,
390 &[Point::new(8, 5), Point::new(8, 9)],
391 Luma::white(),
392 );
393
394 *image.get_pixel_mut(13, 6) = Luma::white();
395
396 let contours = find_contours::<u32>(&image);
397 assert_eq!(contours.len(), 4);
398
399 check_contour(
400 &contours[0],
401 BorderType::Outer,
402 None,
403 &[
404 Point::new(5, 5),
405 Point::new(11, 5),
406 Point::new(5, 9),
407 Point::new(11, 9),
408 ],
409 );
410 assert!(!contours[0].points.contains(&Point::new(13, 6)));
411
412 check_contour(
413 &contours[1],
414 BorderType::Hole,
415 Some(0),
416 &[
417 Point::new(5, 6),
418 Point::new(8, 6),
419 Point::new(6, 9),
420 Point::new(8, 8),
421 ],
422 );
423 assert!(!contours[1].points.contains(&Point::new(10, 5)));
424 assert!(!contours[1].points.contains(&Point::new(10, 9)));
425 assert!(!contours[1].points.contains(&Point::new(13, 6)));
426
427 check_contour(
428 &contours[2],
429 BorderType::Hole,
430 Some(0),
431 &[
432 Point::new(8, 6),
433 Point::new(10, 5),
434 Point::new(8, 8),
435 Point::new(10, 9),
436 ],
437 );
438 assert!(!contours[2].points.contains(&Point::new(6, 9)));
439 assert!(!contours[2].points.contains(&Point::new(5, 6)));
440 assert!(!contours[2].points.contains(&Point::new(13, 6)));
441
442 assert_eq!(contours[3].border_type, BorderType::Outer);
443 assert_eq!(contours[3].points, [Point::new(13, 6)]);
444 assert_eq!(contours[3].parent, None);
445 }
446
447 #[test]
448 fn get_contours_approx_points() {
449 use crate::drawing::draw_polygon_mut;
450 use image::{GrayImage, Luma};
451 let mut image = GrayImage::from_pixel(300, 300, Luma([0]));
452 let white = Luma([255]);
453
454 let star = vec![
455 Point::new(100, 20),
456 Point::new(120, 35),
457 Point::new(140, 30),
458 Point::new(115, 45),
459 Point::new(130, 60),
460 Point::new(100, 50),
461 Point::new(80, 55),
462 Point::new(90, 40),
463 Point::new(60, 25),
464 Point::new(90, 35),
465 ];
466 draw_polygon_mut(&mut image, &star, white);
467 let contours = find_contours::<u32>(&image);
468
469 let c1_approx = crate::geometry::approximate_polygon_dp(
470 &contours[0].points,
471 crate::geometry::arc_length(&contours[0].points, true) * 0.01,
472 true,
473 );
474 assert_eq!(
475 c1_approx,
476 vec![
477 Point::new(100, 20),
478 Point::new(90, 35),
479 Point::new(60, 25),
480 Point::new(90, 40),
481 Point::new(80, 55),
482 Point::new(101, 50),
483 Point::new(130, 60),
484 Point::new(115, 45),
485 Point::new(140, 30),
486 Point::new(120, 35)
487 ]
488 );
489 }
490}