Skip to content

Commit

Permalink
Merge pull request #318 from linebender/yet_more_robustness
Browse files Browse the repository at this point in the history
Improve cusp detection for strokes
  • Loading branch information
raphlinus authored Oct 19, 2023
2 parents 6d04edd + 36f274e commit 3a40570
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/cubicbez.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ impl CubicBez {
let d = q.eval(nearest.t);
let d2 = q.deriv().eval(nearest.t);
let cross = d.to_vec2().cross(d2.to_vec2());
if nearest.distance_sq.powi(3) < (cross * dimension).powi(2) {
if nearest.distance_sq.powi(3) <= (cross * dimension).powi(2) {
let a = 3. * det_012 + det_023 - 2. * det_013;
let b = -3. * det_012 + det_013;
let c = det_012;
Expand Down
30 changes: 15 additions & 15 deletions src/fit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,31 +189,31 @@ fn fit_to_bezpath_rec(
return;
}
}
if let Some(t) = source.break_cusp(start..end) {
fit_to_bezpath_rec(source, start..t, accuracy, path);
fit_to_bezpath_rec(source, t..end, accuracy, path);
let t = if let Some(t) = source.break_cusp(start..end) {
t
} else if let Some((c, _)) = fit_to_cubic(source, start..end, accuracy) {
if path.is_empty() {
path.move_to(c.p0);
}
path.curve_to(c.p1, c.p2, c.p3);
return;
} else {
// A smarter approach is possible than midpoint subdivision, but would be
// a significant increase in complexity.
let t = 0.5 * (start + end);
if t == start || t == end {
// infinite recursion, just draw a line
let p1 = start_p.lerp(end_p, 1.0 / 3.0);
let p2 = end_p.lerp(start_p, 1.0 / 3.0);
if path.is_empty() {
path.move_to(start_p);
}
path.curve_to(p1, p2, end_p);
return;
0.5 * (start + end)
};
if t == start || t == end {
// infinite recursion, just draw a line
let p1 = start_p.lerp(end_p, 1.0 / 3.0);
let p2 = end_p.lerp(start_p, 1.0 / 3.0);
if path.is_empty() {
path.move_to(start_p);
}
fit_to_bezpath_rec(source, start..t, accuracy, path);
fit_to_bezpath_rec(source, t..end, accuracy, path);
path.curve_to(p1, p2, end_p);
return;
}
fit_to_bezpath_rec(source, start..t, accuracy, path);
fit_to_bezpath_rec(source, t..end, accuracy, path);
}

const N_SAMPLE: usize = 20;
Expand Down
32 changes: 29 additions & 3 deletions src/stroke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ impl<'a, T: Iterator<Item = PathEl>> DashIterator<'a, T> {

#[cfg(test)]
mod tests {
use crate::{stroke, CubicBez, Shape, Stroke};
use crate::{stroke, Cap::Butt, CubicBez, Join::Miter, Shape, Stroke};

// A degenerate stroke with a cusp at the endpoint.
#[test]
Expand All @@ -781,8 +781,34 @@ mod tests {
let path = curve.into_path(0.1);
let stroke_style = Stroke::new(1.);
let stroked = stroke(path, &stroke_style, &Default::default(), 0.001);
for el in stroked.elements() {
assert!(el.is_finite());
assert!(stroked.is_finite());
}

// Test cases adapted from https://github.com/linebender/vello/pull/388
#[test]
fn broken_strokes() {
let broken_cubics = [
[(0., -0.01), (128., 128.001), (128., -0.01), (0., 128.001)], // Near-cusp
[(0., 0.), (0., -10.), (0., -10.), (0., 10.)], // Flat line with 180
[(10., 0.), (0., 0.), (20., 0.), (10., 0.)], // Flat line with 2 180s
[(39., -39.), (40., -40.), (40., -40.), (0., 0.)], // Flat diagonal with 180
[(40., 40.), (0., 0.), (200., 200.), (0., 0.)], // Diag w/ an internal 180
[(0., 0.), (1e-2, 0.), (-1e-2, 0.), (0., 0.)], // Circle
// Flat line with no turns:
[
(400.75, 100.05),
(400.75, 100.05),
(100.05, 300.95),
(100.05, 300.95),
],
[(0.5, 0.), (0., 0.), (20., 0.), (10., 0.)], // Flat line with 2 180s
[(10., 0.), (0., 0.), (10., 0.), (10., 0.)], // Flat line with a 180
];
let stroke_style = Stroke::new(30.).with_caps(Butt).with_join(Miter);
for cubic in &broken_cubics {
let path = CubicBez::new(cubic[0], cubic[1], cubic[2], cubic[3]).into_path(0.1);
let stroked = stroke(path, &stroke_style, &Default::default(), 0.001);
assert!(stroked.is_finite());
}
}
}

0 comments on commit 3a40570

Please sign in to comment.