Image Trace: Shapes, and corners whose pixel is mostly outside the shape

Shapes (params `shapes`, a checkbox in the panel; off unless asked for): a filled
outline that is a circle, an ellipse, a rectangle or a regular polygon comes out
as that live shape. A shape is only made when every point of the traced outline
lies within 0.5 px (0.2% of the shape's size, if more) of it; an ellipse whose
radii differ by less than twice that is a circle of its area.

The outline before fitting: a corner's own pixel no longer pulls the two edges
that meet there, and a one-pixel step between two long edges is the corner where
they meet. Sharp corners kept: Default 69% -> 76%, the colour presets 86% -> 91%.

The harness gains plain-shape inputs and "shapes %".
This commit is contained in:
bflatastic
2026-10-13 10:25:05 +07:00
parent f0f3cb4eb1
commit 9ca74ac011
12 changed files with 900 additions and 322 deletions
+26
View File
@@ -671,6 +671,32 @@ fn image_trace_make_places_paths_over_the_image() {
assert_eq!(paths[0].appearance.fill_paint(), Paint::solid(Color::rgb8(0, 0, 0)));
}
/// Shapes: the disc of the test image comes out as a live ellipse where the image lies, the same
/// circle as its traced path; without the option it is a plain path.
#[test]
fn image_trace_shapes_makes_live_shapes() {
let mut s = session();
image_doc(&mut s);
let r = s.execute("imageTrace.make", &json!({"params": {"ignoreWhite": true, "shapes": true}})).unwrap();
let n = node(&s, NodeId(r["id"].as_u64().unwrap()));
let paths = traced_paths(&n);
assert_eq!(paths.len(), 1);
let NodeKind::Path { path, live: Some(live), .. } = &paths[0].kind else { panic!("not a live shape: {:?}", paths[0].kind) };
let vectorcraft_doc::LiveShape::Ellipse { w, h, .. } = **live else { panic!("{live:?}") };
// A circle of r = 30 px, drawn twice the size.
assert!(w == h && (w - 60.0).abs() < 0.75, "{w} × {h}");
let bb = path.bounds().unwrap();
assert!((bb.center().x - 150.0).abs() < 1.5 && (bb.center().y - 150.0).abs() < 1.5 && (bb.width() - 120.0).abs() < 1.5, "{bb:?}");
assert_eq!(*path, live.to_path());
assert_eq!(paths[0].appearance.fill_paint(), Paint::solid(Color::rgb8(0, 0, 0)));
// Off, as documents saved before have it: a path.
let mut s = session();
image_doc(&mut s);
let r = s.execute("imageTrace.make", &json!({"params": {"ignoreWhite": true}})).unwrap();
let n = node(&s, NodeId(r["id"].as_u64().unwrap()));
assert!(matches!(traced_paths(&n)[0].kind, NodeKind::Path { live: None, .. }));
}
#[test]
fn image_trace_release_and_expand() {
let mut s = session();