Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow lines in simplify input #343

Merged
merged 1 commit into from
Mar 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 44 additions & 10 deletions src/simplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,24 +252,30 @@ impl SimplifyState {
self.queue.move_to(seg.start());
}
match seg {
PathSeg::Line(l) => self.queue.line_to(l.p1),
PathSeg::Quad(q) => self.queue.quad_to(q.p1, q.p2),
PathSeg::Cubic(c) => self.queue.curve_to(c.p1, c.p2, c.p3),
_ => unreachable!(),
}
}

fn flush(&mut self, accuracy: f64, options: &SimplifyOptions) {
if self.queue.is_empty() {
return;
}
// TODO: if queue is one segment, just output that
let s = SimplifyBezPath::new(&self.queue);
let b = match options.opt_level {
SimplifyOptLevel::Subdivide => fit_to_bezpath(&s, accuracy),
SimplifyOptLevel::Optimize => fit_to_bezpath_opt(&s, accuracy),
};
self.result
.extend(b.iter().skip(!self.needs_moveto as usize));
if self.queue.elements().len() == 2 {
// Queue is just one segment (count is moveto + primitive)
// Just output the segment, no simplification is possible.
self.result
.extend(self.queue.iter().skip(!self.needs_moveto as usize));
} else {
let s = SimplifyBezPath::new(&self.queue);
let b = match options.opt_level {
SimplifyOptLevel::Subdivide => fit_to_bezpath(&s, accuracy),
SimplifyOptLevel::Optimize => fit_to_bezpath_opt(&s, accuracy),
};
self.result
.extend(b.iter().skip(!self.needs_moveto as usize));
}
self.needs_moveto = false;
self.queue.truncate(0);
}
Expand All @@ -279,6 +285,16 @@ impl SimplifyState {
///
/// This function simplifies an arbitrary Bézier path; it is designed to handle
/// multiple subpaths and also corners.
///
/// The underlying curve-fitting approach works best if the source path is very
/// smooth. If it contains higher frequency noise, then results may be poor, as
/// the resulting curve matches the original with G1 continuity at each subdivision
/// point, and also preserves the area. For such inputs, consider some form of
/// smoothing or low-pass filtering before simplification. In particular, if the
/// input is derived from a sequence of points, consider fitting a smooth spline.
///
/// We may add such capabilities in the future, possibly as opt-in smoothing
/// specified through the options.
pub fn simplify_bezpath(
path: impl IntoIterator<Item = PathEl>,
accuracy: f64,
Expand All @@ -300,7 +316,6 @@ pub fn simplify_bezpath(
if last == p {
continue;
}
state.flush(accuracy, options);
this_seg = Some(PathSeg::Line(Line::new(last, p)));
}
PathEl::QuadTo(p1, p2) => {
Expand Down Expand Up @@ -360,3 +375,22 @@ impl SimplifyOptions {
self
}
}

#[cfg(test)]
mod tests {
use crate::BezPath;

use super::{simplify_bezpath, SimplifyOptions};

#[test]
fn simplify_lines_corner() {
// Make sure lines are passed through unchanged if there is a corner.
let mut path = BezPath::new();
path.move_to((1., 2.));
path.line_to((3., 4.));
path.line_to((10., 5.));
let options = SimplifyOptions::default();
let simplified = simplify_bezpath(path.clone(), 1.0, &options);
assert_eq!(path, simplified);
}
}
Loading