use std::{cmp::Ordering, convert::TryInto};
use crate::{BinaryImage, PathF64, PointF64, PathSimplifyMode};
use super::{PathI32, smooth::SubdivideSmooth};
#[derive(Debug, Default, Clone)]
pub struct Spline {
pub points: Vec<PointF64>,
}
impl Spline {
pub fn new(point: PointF64) -> Self {
Self {
points: vec![point],
}
}
pub fn add(&mut self, point2: PointF64, point3: PointF64, point4: PointF64) {
self.points.push(point2);
self.points.push(point3);
self.points.push(point4);
}
pub fn iter(&self) -> std::slice::Iter<PointF64> {
self.points.iter()
}
pub fn get_control_points(&self) -> Vec<&[PointF64]> {
self.points.iter().as_slice().windows(4).step_by(3).collect()
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn num_curves(&self) -> usize {
if !self.points.is_empty() {(self.points.len()-1)/3} else {0}
}
pub fn is_empty(&self) -> bool {
self.points.len() <= 3
}
pub fn offset(&mut self, offset: &PointF64) {
for path in self.points.iter_mut() {
path.x += offset.x;
path.y += offset.y;
}
}
pub fn from_image(
image: &BinaryImage, clockwise: bool, corner_threshold: f64, outset_ratio: f64,
segment_length: f64, max_iterations: usize, splice_threshold: f64
) -> Self {
let path = PathI32::image_to_path(image, clockwise, PathSimplifyMode::Polygon);
let path = path.smooth(corner_threshold, outset_ratio, segment_length, max_iterations);
Self::from_path_f64(&path, splice_threshold)
}
pub fn from_path_f64(path: &PathF64, splice_threshold: f64) -> Self {
let splice_points = SubdivideSmooth::find_splice_points(&path, splice_threshold);
let path = &path.path[0..path.len()-1];
let len = path.len();
if len<=1 {
return Self::new(PointF64 {x:0.0,y:0.0});
}
if len==2 {
let mut result = Self::new(path[0]);
result.add(path[1], path[1], path[1]);
return result;
}
let mut cut_points: Vec<usize> = splice_points.iter()
.enumerate()
.filter(|(_, &cut)| {cut})
.map(|(i, _)| {i})
.collect();
if cut_points.is_empty() {
cut_points.push(0);
}
if cut_points.len() == 1 {
cut_points.push((cut_points[0]+len/2)%len);
}
let num_cut_points = cut_points.len();
let mut result = Self::new(PointF64 {x:0.0,y:0.0}); for i in 0..num_cut_points {
let j = (i+1)%num_cut_points;
let current = cut_points[i];
let next = cut_points[j];
let subpath = Self::get_circular_subpath(path, current, next);
let bezier_points = SubdivideSmooth::fit_points_with_bezier(&subpath);
if i==0 {
result = Self::new(bezier_points[0]);
}
result.add(bezier_points[1], bezier_points[2], bezier_points[3]);
}
result
}
pub fn to_svg_string(&self, close: bool, offset: &PointF64, precision: Option<u32>) -> String {
let o = offset;
if self.is_empty() {
return String::from("");
}
if (self.len() - 1) % 3 != 0 {
panic!("Invalid spline! Length must be 1+3n.");
}
let points = &self.points;
let len = points.len();
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))];
let mut i = 1;
while i < len {
result.push(
format!("C{} {} {} {} {} {} ",
PointF64::number_format(points[i].x + o.x, precision), PointF64::number_format(points[i].y + o.y, precision),
PointF64::number_format(points[i+1].x + o.x, precision), PointF64::number_format(points[i+1].y + o.y, precision),
PointF64::number_format(points[i+2].x + o.x, precision), PointF64::number_format(points[i+2].y + o.y, precision))
);
i += 3;
}
if close {
result.push(String::from("Z "));
}
result.concat()
}
fn get_circular_subpath(path: &[PointF64], from: usize, to: usize) -> Vec<PointF64> {
let len = path.len();
let mut subpath: Vec<PointF64> = vec![];
match from.cmp(&to) {
Ordering::Less => {
subpath.extend_from_slice(&path[from..=to]);
},
Ordering::Greater => {
subpath.extend_from_slice(&path[from..len]);
subpath.extend_from_slice(&path[0..=to]);
},
Ordering:: Equal => {}
}
subpath
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spline_to_svg() {
let spline = Spline {
points: vec![
PointF64 { x: 2.22, y: 2.67 },
PointF64 { x: 3.50, y: 3.48 },
PointF64 { x: 4.19, y: 4.72 },
PointF64 { x: 5.68, y: 5.26 },
]
};
assert_eq!(
spline.to_svg_string(false, &PointF64 { x: 0.0, y: 0.0 }, None),
"M2.22 2.67 C3.5 3.48 4.19 4.72 5.68 5.26 ".to_owned()
);
assert_eq!(
spline.to_svg_string(false, &PointF64 { x: 0.0, y: 0.0 }, Some(1)),
"M2.2 2.7 C3.5 3.5 4.2 4.7 5.7 5.3 ".to_owned()
);
assert_eq!(
spline.to_svg_string(false, &PointF64 { x: 0.0, y: 0.0 }, Some(0)),
"M2 3 C4 3 4 5 6 5 ".to_owned()
);
}
}