Skip to content

Commit

Permalink
Merge pull request #343 from linebender/simplify_lines
Browse files Browse the repository at this point in the history
Allow lines in simplify input
  • Loading branch information
raphlinus authored Mar 13, 2024
2 parents 3ea8f6b + b2a16a5 commit 46820b0
Showing 1 changed file with 44 additions and 10 deletions.
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);
}
}

0 comments on commit 46820b0

Please sign in to comment.