visioncortex/path/
spline.rs1use std::{cmp::Ordering};
2use crate::{BinaryImage, PathF64, PointF64, PathSimplifyMode};
3use super::{PathI32, smooth::SubdivideSmooth};
4
5#[derive(Debug, Default, Clone)]
6pub struct Spline {
8 pub points: Vec<PointF64>,
11}
12
13impl Spline {
14
15 pub fn new(point: PointF64) -> Self {
17 Self {
18 points: vec![point],
19 }
20 }
21
22 pub fn add(&mut self, point2: PointF64, point3: PointF64, point4: PointF64) {
24 self.points.push(point2);
25 self.points.push(point3);
26 self.points.push(point4);
27 }
28
29 pub fn iter(&self) -> std::slice::Iter<PointF64> {
31 self.points.iter()
32 }
33
34 pub fn get_control_points(&self) -> Vec<&[PointF64]> {
35 self.points.iter().as_slice().windows(4).step_by(3).collect()
36 }
37
38 pub fn len(&self) -> usize {
40 self.points.len()
41 }
42
43 pub fn num_curves(&self) -> usize {
45 if !self.points.is_empty() {(self.points.len()-1)/3} else {0}
46 }
47
48 pub fn is_empty(&self) -> bool {
51 self.points.len() <= 3
52 }
53
54 pub fn offset(&mut self, offset: &PointF64) {
56 for path in self.points.iter_mut() {
57 path.x += offset.x;
58 path.y += offset.y;
59 }
60 }
61
62 pub fn from_image(
71 image: &BinaryImage, clockwise: bool, corner_threshold: f64, outset_ratio: f64,
72 segment_length: f64, max_iterations: usize, splice_threshold: f64
73 ) -> Self {
74 let path = PathI32::image_to_path(image, clockwise, PathSimplifyMode::Polygon);
75 let path = path.smooth(corner_threshold, outset_ratio, segment_length, max_iterations);
76 Self::from_path_f64(&path, splice_threshold)
77 }
78
79 pub fn from_path_f64(path: &PathF64, splice_threshold: f64) -> Self {
83 let splice_points = SubdivideSmooth::find_splice_points(&path, splice_threshold);
85 let path = &path.path[0..path.len()-1];
86 let len = path.len();
87 if len<=1 {
88 return Self::new(PointF64 {x:0.0,y:0.0});
89 }
90 if len==2 {
91 let mut result = Self::new(path[0]);
92 result.add(path[1], path[1], path[1]);
93 return result;
94 }
95
96 let mut cut_points: Vec<usize> = splice_points.iter()
98 .enumerate()
99 .filter(|(_, &cut)| {cut})
100 .map(|(i, _)| {i})
101 .collect();
102
103 if cut_points.is_empty() {
104 cut_points.push(0);
105 }
106 if cut_points.len() == 1 {
107 cut_points.push((cut_points[0]+len/2)%len);
108 }
109 let num_cut_points = cut_points.len();
110
111 let mut result = Self::new(PointF64 {x:0.0,y:0.0}); for i in 0..num_cut_points {
113 let j = (i+1)%num_cut_points;
114
115 let current = cut_points[i];
116 let next = cut_points[j];
117 let subpath = Self::get_circular_subpath(path, current, next);
118 let bezier_points = SubdivideSmooth::fit_points_with_bezier(&subpath);
119
120 if i==0 {
122 result = Self::new(bezier_points[0]);
123 }
124 result.add(bezier_points[1], bezier_points[2], bezier_points[3]);
126 }
127
128 result
129 }
130
131 pub fn to_svg_string(&self, close: bool, offset: &PointF64, precision: Option<u32>) -> String {
133
134 let o = offset;
135
136 if self.is_empty() {
137 return String::from("");
138 }
139
140 if (self.len() - 1) % 3 != 0 {
141 panic!("Invalid spline! Length must be 1+3n.");
142 }
143
144 let points = &self.points;
145 let len = points.len();
146 let mut result: Vec<String> = vec![format!("M{} {} ", PointF64::number_format(points[0].x + o.x, precision), PointF64::number_format(points[0].y + o.y, precision))];
147
148 let mut i = 1;
149 while i < len {
150 result.push(
151 format!("C{} {} {} {} {} {} ",
152 PointF64::number_format(points[i].x + o.x, precision), PointF64::number_format(points[i].y + o.y, precision),
153 PointF64::number_format(points[i+1].x + o.x, precision), PointF64::number_format(points[i+1].y + o.y, precision),
154 PointF64::number_format(points[i+2].x + o.x, precision), PointF64::number_format(points[i+2].y + o.y, precision))
155 );
156 i += 3;
157 }
158
159 if close {
160 result.push(String::from("Z "));
161 }
162
163 result.concat()
164 }
165
166 fn get_circular_subpath(path: &[PointF64], from: usize, to: usize) -> Vec<PointF64> {
167
168 let len = path.len();
169 let mut subpath: Vec<PointF64> = vec![];
170
171 match from.cmp(&to) {
172 Ordering::Less => {
173 subpath.extend_from_slice(&path[from..=to]);
174 },
175 Ordering::Greater => {
176 subpath.extend_from_slice(&path[from..len]);
177 subpath.extend_from_slice(&path[0..=to]);
178 },
179 Ordering:: Equal => {}
180 }
181
182 subpath
183 }
184
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_spline_to_svg() {
193 let spline = Spline {
194 points: vec![
195 PointF64 { x: 2.22, y: 2.67 },
196 PointF64 { x: 3.50, y: 3.48 },
197 PointF64 { x: 4.19, y: 4.72 },
198 PointF64 { x: 5.68, y: 5.26 },
199 ]
200 };
201 assert_eq!(
202 spline.to_svg_string(false, &PointF64 { x: 0.0, y: 0.0 }, None),
203 "M2.22 2.67 C3.5 3.48 4.19 4.72 5.68 5.26 ".to_owned()
204 );
205 assert_eq!(
206 spline.to_svg_string(false, &PointF64 { x: 0.0, y: 0.0 }, Some(1)),
207 "M2.2 2.7 C3.5 3.5 4.2 4.7 5.7 5.3 ".to_owned()
208 );
209 assert_eq!(
210 spline.to_svg_string(false, &PointF64 { x: 0.0, y: 0.0 }, Some(0)),
211 "M2 3 C4 3 4 5 6 5 ".to_owned()
212 );
213 }
214}