mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-12 07:45:56 +00:00
address review: Offset Paths copies are budgeted by the geometry offset_path returns
This commit is contained in:
Generated
+1
@@ -1758,6 +1758,7 @@ dependencies = [
|
||||
"effectcraft-raster",
|
||||
"kurbo",
|
||||
"linesweeper",
|
||||
"log",
|
||||
"tiny-skia 0.12.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -14,5 +14,6 @@ effectcraft-geom = { workspace = true }
|
||||
effectcraft-keyframe = { workspace = true }
|
||||
effectcraft-raster = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
log = { workspace = true }
|
||||
tiny-skia = { workspace = true }
|
||||
linesweeper = "0.5"
|
||||
|
||||
+15
-1
@@ -351,6 +351,11 @@ fn round_sub(sub: &Sub, r: f64, out: &mut BezPath) {
|
||||
/// join. `copies` copies are made, copy *i* (0-based) offset by `amount · (i + 1 + copy_offset)`,
|
||||
/// all returned in the same compound path.
|
||||
pub fn offset(paths: &[BezPath], amount: f64, join: Join, miter_limit: f64, copies: f64, copy_offset: f64) -> Vec<BezPath> {
|
||||
offset_within(paths, amount, join, miter_limit, copies, copy_offset, MAX_OPERATOR_SEGMENTS)
|
||||
}
|
||||
|
||||
/// [`offset`] with each path's result held to `max` elements (its first copy aside).
|
||||
fn offset_within(paths: &[BezPath], amount: f64, join: Join, miter_limit: f64, copies: f64, copy_offset: f64, max: usize) -> Vec<BezPath> {
|
||||
let n = pieces_within_budget(paths, copies.round().clamp(1.0, 1000.0) as usize);
|
||||
paths
|
||||
.iter()
|
||||
@@ -358,7 +363,16 @@ pub fn offset(paths: &[BezPath], amount: f64, join: Join, miter_limit: f64, copi
|
||||
let mut out = BezPath::new();
|
||||
for i in 0..n {
|
||||
let d = amount * (i as f64 + 1.0 + copy_offset);
|
||||
out.extend(crate::offset::offset_path(p, d, join, miter_limit).elements().iter().copied());
|
||||
let copy = crate::offset::offset_path(p, d, join, miter_limit);
|
||||
// The budget is spent on what `offset_path` returns (an open subpath comes back as
|
||||
// a closed outline of many segments; a zero offset as the path itself): the first
|
||||
// copy is always kept, and a copy that would pass the budget is not made, so the
|
||||
// work stays within a small multiple of it.
|
||||
if i > 0 && out.elements().len().saturating_add(copy.elements().len()) > max {
|
||||
log::warn!("path: Offset Paths made {i} of {n} copies; more would pass {max} segments");
|
||||
break;
|
||||
}
|
||||
out.extend(copy.elements().iter().copied());
|
||||
}
|
||||
out
|
||||
})
|
||||
|
||||
@@ -182,6 +182,25 @@ fn offset_copies() {
|
||||
assert!(close(o[0].bounding_box().width(), 140.0, 1e-3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset_copies_are_budgeted_by_the_output_they_return() {
|
||||
// An open one-segment subpath comes back as a closed outline of several elements, so the copies
|
||||
// stop when the returned elements pass the budget, not when the input segments would.
|
||||
let line = || {
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((0.0, 0.0));
|
||||
p.line_to((50.0, 0.0));
|
||||
p
|
||||
};
|
||||
let one = offset_within(&[line()], 5.0, Join::Round, 4.0, 1.0, 0.0, usize::MAX)[0].elements().len();
|
||||
assert!(one > 4, "{one}");
|
||||
let o = offset_within(&[line()], 5.0, Join::Round, 4.0, 50.0, 0.0, one * 3);
|
||||
let moves = o[0].elements().iter().filter(|e| matches!(e, PathEl::MoveTo(_))).count();
|
||||
assert!((1..50).contains(&moves) && o[0].elements().len() <= one * 4, "{moves} contours, {} elements", o[0].elements().len());
|
||||
// The first copy always comes back, however small the budget.
|
||||
assert!(!offset_within(&[line()], 5.0, Join::Round, 4.0, 5.0, 0.0, 0)[0].elements().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offset_open_path_outlines() {
|
||||
let mut l = BezPath::new();
|
||||
|
||||
Reference in New Issue
Block a user