doc_quad/topology/
contour.rs1use glam::Vec2;
3use crate::topology::chain::{find_next_pixel, reverse_dir};
4
5pub struct ContourExtractor;
6
7impl ContourExtractor {
8 pub fn extract(edges: &[u8], width: u32, height: u32) -> Vec<Vec<Vec2>> {
10 let mut visited = vec![false; (width * height) as usize];
11 let mut contours = Vec::new();
12
13 for y in 0..height {
14 for x in 0..width {
15 let idx = (y * width + x) as usize;
16
17 if edges[idx] == 0 || visited[idx] {
18 continue;
19 }
20
21 let contour = Self::trace_contour(edges, &mut visited, width, height, x, y);
22
23 if contour.len() >= 4 {
24 contours.push(contour);
25 }
26 }
27 }
28
29 contours
30 }
31
32 fn trace_contour(
33 edges: &[u8],
34 visited: &mut [bool],
35 width: u32,
36 height: u32,
37 start_x: u32,
38 start_y: u32,
39 ) -> Vec<Vec2> {
40 let mut contour = Vec::with_capacity(16);
41 let mut cx = start_x;
42 let mut cy = start_y;
43
44 let mut search_dir = 7;
45
46 contour.push(Vec2::new(cx as f32, cy as f32));
47 visited[(cy * width + cx) as usize] = true;
48
49 let mut prev_dir: Option<u8> = None;
50 let max_pts = width * height;
51
52 for _ in 0..max_pts {
53 if let Some((nx, ny, found_dir)) = find_next_pixel(edges, width, height, cx, cy, search_dir) {
54 visited[(ny * width + nx) as usize] = true;
55
56 if Some(found_dir) == prev_dir && contour.len() > 1 {
57 let last_idx = contour.len() - 1;
58 contour[last_idx] = Vec2::new(nx as f32, ny as f32);
59 } else {
60 contour.push(Vec2::new(nx as f32, ny as f32));
61 }
62
63 prev_dir = Some(found_dir);
64
65 if nx == start_x && ny == start_y {
66 break;
67 }
68
69 cx = nx;
70 cy = ny;
71
72 search_dir = (reverse_dir(found_dir) + 1) % 8;
73 } else {
74 break;
75 }
76 }
77
78 contour
79 }
80}