1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
/* * Contour tracing library (Rust) * https://github.com/STPR/contour_tracing * * Copyright (c) 2020, STPR - https://github.com/STPR * * SPDX-License-Identifier: EUPL-1.2 */ //! A 2D library to trace contours. //! //! # Features //! Core features: //! - Trace contours using the Theo Pavlidis' algorithm (connectivity: 4-connected) //! - Trace **outlines** in **clockwise direction** //! - Trace **holes** in **counterclockwise direction** //! - Input format: a 2D array of bits //! - Output format: a string of SVG Path commands //! //! Manual parameters: //! - User can specify to close or not the paths (with the SVG Path **Z** command) //! //! # Examples //! For examples, have a look at the **bits_to_paths** function below. #[allow(unused_imports)] use std; static T: [(i8, i8); 8] = [(0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1)]; static O_VERTEX: [(i8, i8); 7] = [(-1, 0), (0, 0), (-1, -1), (0, 0), (0, -1), (0, 0), (0, 0)]; // Vertex coordinates for the outlines (bottom left) according to the orientation static H_VERTEX: [(i8, i8); 7] = [(0, 0), (0, 0), (-1, 0), (0, 0), (-1, -1), (0, 0), (0, -1)]; // Vertex coordinates for the holes (bottom right) according to the orientation static O_VALUE: [i8; 7] = [1, 0, 2, 0, 4, 0, 8]; // Value to add into the array of contours for the outlines static H_VALUE: [i8; 7] = [-4, 0, -8, 0, -1, 0, -2]; // Value to add into the array of contours for the holes /* contours: an array of contours ol: outlines level hl: holes level rn: reachable neighbor - For outlines -> 0: none, 1: front left neighbor, 2: front neighbor, 3: front right neighbor - For holes -> 0: none, 1: front right neighbor, 2: front neighbor, 3: front left neighbor o: orientation: North ┌───────────┐ │ 7 0 1 │ West │ 6 o 2 │ East │ 5 4 3 │ └───────────┘ South - To the north, o = {0, 1, 2, 3, 4, 5, 6, 7} - To the east, o = {2, 3, 4, 5, 6, 7, 0, 1} - To the south, o = {4, 5, 6, 7, 0, 1, 2, 3} - To the west, o = {6, 7, 0, 1, 2, 3, 4, 5} */ /// A function that takes a 2D array of bits and an option as input and return a string of SVG Path commands as output. /// # Examples /// ``` /// extern crate contour_tracing; /// use contour_tracing::bits_to_paths; /// ``` /// - A simple example with the **closepaths option** set to **false**: /// ``` /// # extern crate contour_tracing; /// # use contour_tracing::bits_to_paths; /// let bits = vec![vec![ 0,0,0,0,0,0,0,0,0,0,0,0,0 ], /// vec![ 0,0,1,1,1,0,0,1,1,1,1,1,0 ], /// vec![ 0,1,0,0,0,1,0,1,0,0,0,1,0 ], /// vec![ 0,1,0,0,0,1,0,1,0,1,0,1,0 ], /// vec![ 0,1,0,0,0,1,0,1,0,0,0,1,0 ], /// vec![ 0,0,1,1,1,0,0,1,1,1,1,1,0 ], /// vec![ 0,0,0,0,0,0,0,0,0,0,0,0,0 ]]; /// /// # assert_eq!(bits_to_paths(bits.to_vec(), false), "M2 1H5V2H2M7 1H12V6H7M1 2H2V5H1M5 2H6V5H5M8 2V5H11V2M9 3H10V4H9M2 5H5V6H2"); /// println!("{}", bits_to_paths(bits, false)); /// ``` /// - When the **closepaths option** is set to **true**, each path is closed with the SVG Path **Z** command: /// ``` /// # extern crate contour_tracing; /// # use contour_tracing::bits_to_paths; /// # let bits = vec![vec![ 0,0,0,0,0,0,0,0,0,0,0,0,0 ], /// # vec![ 0,0,1,1,1,0,0,1,1,1,1,1,0 ], /// # vec![ 0,1,0,0,0,1,0,1,0,0,0,1,0 ], /// # vec![ 0,1,0,0,0,1,0,1,0,1,0,1,0 ], /// # vec![ 0,1,0,0,0,1,0,1,0,0,0,1,0 ], /// # vec![ 0,0,1,1,1,0,0,1,1,1,1,1,0 ], /// # vec![ 0,0,0,0,0,0,0,0,0,0,0,0,0 ]]; /// # assert_eq!(bits_to_paths(bits.to_vec(), true), "M2 1H5V2H2ZM7 1H12V6H7ZM1 2H2V5H1ZM5 2H6V5H5ZM8 2V5H11V2ZM9 3H10V4H9ZM2 5H5V6H2Z"); /// println!("{}", bits_to_paths(bits, true)); /// ``` /// - If you plan to reuse the array of bits after using this function, use the `to_vec()` method like this: /// /// ``` /// # extern crate contour_tracing; /// # use contour_tracing::bits_to_paths; /// let bits = vec![vec![ 1,0,0 ], /// vec![ 0,1,0 ], /// vec![ 0,0,1 ]]; /// /// # assert_eq!(bits_to_paths(bits.to_vec(), true), "M0 0H1V1H0ZM1 1H2V2H1ZM2 2H3V3H2Z"); /// println!("{}", bits_to_paths(bits.to_vec(), true)); /// println!("{:?}", bits); /// ``` pub fn bits_to_paths(bits: Vec<Vec<i8>>, closepaths: bool) -> String { let rows: usize = bits.len(); let cols: usize = bits[0].len(); let mut contours = vec![vec![0i8; cols + 2]; rows + 2]; // The array of contours needs a border of 1 bit for y in 0..=rows - 1 as usize { for x in 0..=cols - 1 as usize { contours[y + 1][x + 1] = bits[y][x]; } } let mut paths = String::new(); let mut ol: usize; let mut hl: usize; for y in 1..=rows as usize { ol = 1; hl = 1; for x in 1..=cols as usize { if ol == hl && contours[y][x] == 1 && contours[y][x - 1] <= 0 && contours[y - 1][x] <= 0 { trace(true, x, y, [2, 3, 4, 5, 6, 7, 0, 1], 2, (7, 1, 0), O_VERTEX, O_VALUE, &mut contours, &mut paths, closepaths); } if contours[y][x] == 2 || contours[y][x] == 4 || contours[y][x] == 10 || contours[y][x] == 12 { ol += 1; } if contours[y][x] == 5 || contours[y][x] == 7 || contours[y][x] == 13 || contours[y][x] == 15 { ol -= 1; } if ol > hl && contours[y][x] == 0 && contours[y][x - 1] > 0 && contours[y - 1][x] > 0 { trace(false, x, y, [4, 5, 6, 7, 0, 1, 2, 3], -2, (1, 7, 6), H_VERTEX, H_VALUE, &mut contours, &mut paths, closepaths); } if contours[y][x] == -1 || contours[y][x] == -3 || contours[y][x] == -9 || contours[y][x] == -11 { hl += 1; } if contours[y][x] == -4 || contours[y][x] == -6 || contours[y][x] == -12 || contours[y][x] == -14 { hl -= 1; } } } paths } fn trace(hole: bool, x: usize, y: usize, mut o: [usize; 8], rot: i8, viv: (usize, usize, usize), c_vertex: [(i8, i8); 7], c_value: [i8; 7], contours: &mut Vec<Vec<i8>>, paths: &mut String, closepaths: bool) { let mut cx = x; // Current x let mut cy = y; // Current y let mut v: usize = 1; // Number of vertices paths.push_str(&format!("M{} {}", cx.wrapping_add(c_vertex[o[0]].0 as usize), cy.wrapping_add(c_vertex[o[0]].1 as usize))); let mut rn: u8; loop { let neighbors: [i8; 8] = [contours[cy - 1][cx], contours[cy - 1][cx + 1], contours[cy][cx + 1], contours[cy + 1][cx + 1], contours[cy + 1][cx], contours[cy + 1][cx - 1], contours[cy][cx - 1], contours[cy - 1][cx - 1]]; if hole { if neighbors[o[7]] > 0 && neighbors[o[0]] > 0 { rn = 1; } else if neighbors[o[0]] > 0 { rn = 2; } else if neighbors[o[1]] > 0 && neighbors[o[2]] > 0 { rn = 3; } else { rn = 0; } } else { if neighbors[o[1]] <= 0 && neighbors[o[0]] <= 0 { rn = 1; } else if neighbors[o[0]] <= 0 { rn = 2; } else if neighbors[o[7]] <= 0 && neighbors[o[6]] <= 0 { rn = 3; } else { rn = 0; } } if rn == 1 { contours[cy][cx] += c_value[o[0]]; cx = cx.wrapping_add(T[o[viv.0]].0 as usize); cy = cy.wrapping_add(T[o[viv.0]].1 as usize); o.rotate_right(rot.rem_euclid(8) as usize); // Rotate 90 degrees, counterclockwise for the outlines (rot = 2) or clockwise for the holes (rot = -2) v += 1; if o[0] == 0 || o[0] == 4 { paths.push_str(&format!("H{}", cx.wrapping_add(c_vertex[o[0]].0 as usize))); } else { paths.push_str(&format!("V{}", cy.wrapping_add(c_vertex[o[0]].1 as usize))); } } else if rn == 2 { contours[cy][cx] += c_value[o[0]]; cx = cx.wrapping_add(T[o[0]].0 as usize); cy = cy.wrapping_add(T[o[0]].1 as usize); } else if rn == 3 { contours[cy][cx] += c_value[o[0]]; o.rotate_left(rot.rem_euclid(8) as usize); // Rotate 90 degrees, clockwise for the outlines (rot = 2) or counterclockwise for the holes (rot = -2) contours[cy][cx] += c_value[o[0]]; v += 1; if o[0] == 0 || o[0] == 4 { paths.push_str(&format!("H{}", cx.wrapping_add(c_vertex[o[0]].0 as usize))); } else { paths.push_str(&format!("V{}", cy.wrapping_add(c_vertex[o[0]].1 as usize))); } o.rotate_right(rot.rem_euclid(8) as usize); cx = cx.wrapping_add(T[o[viv.1]].0 as usize); cy = cy.wrapping_add(T[o[viv.1]].1 as usize); v += 1; if o[0] == 0 || o[0] == 4 { paths.push_str(&format!("H{}", cx.wrapping_add(c_vertex[o[0]].0 as usize))); } else { paths.push_str(&format!("V{}", cy.wrapping_add(c_vertex[o[0]].1 as usize))); } } else { contours[cy][cx] += c_value[o[0]]; o.rotate_left(rot.rem_euclid(8) as usize); v += 1; if o[0] == 0 || o[0] == 4 { paths.push_str(&format!("H{}", cx.wrapping_add(c_vertex[o[0]].0 as usize))); } else { paths.push_str(&format!("V{}", cy.wrapping_add(c_vertex[o[0]].1 as usize))); } } if cx == x && cy == y && v > 2 { break; } } loop { contours[cy][cx] += c_value[o[0]]; if o[0] == viv.2 { break; } o.rotate_left(rot.rem_euclid(8) as usize); v += 1; if o[0] == 0 || o[0] == 4 { paths.push_str(&format!("H{}", cx.wrapping_add(c_vertex[o[0]].0 as usize))); } else { paths.push_str(&format!("V{}", cy.wrapping_add(c_vertex[o[0]].1 as usize))); } } if closepaths { paths.push_str("Z"); } }