mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #2012 from andeecollard/fix/trace-small-closed-contours
Image Trace: retain tiny components and holes after fitting
This commit is contained in:
@@ -139,6 +139,13 @@ pub(crate) fn fit_closed(pts: &[Point], style: &Style) -> Option<Vec<Cubic>> {
|
||||
if stats.reverted > 0 && !all_within(&sample_cubics(&segs, 0.25), &resample_closed(&pts, 0.5).map_or_else(|| pts.clone(), |(p, _)| p), 1.5) {
|
||||
segs = polygon(&pts, style.tol);
|
||||
}
|
||||
// A coarse fit of a tiny loop can be a single cubic. A closed editable
|
||||
// subpath needs at least two anchors, so that fit would silently discard
|
||||
// a component (or hole) which survived Noise. Keep the positioned outline
|
||||
// instead; do not simplify it again with the tolerance that collapsed it.
|
||||
if segs.len() < 2 {
|
||||
segs = polygon(&pts, 0.0);
|
||||
}
|
||||
if style.upright {
|
||||
snap_upright(&mut segs, true, false);
|
||||
}
|
||||
@@ -244,3 +251,62 @@ fn polygon(pts: &[Point], tol: f64) -> Vec<Cubic> {
|
||||
let kept: Vec<Point> = (0..n).filter(|&i| keep[i]).map(|i| pts[i]).collect();
|
||||
(0..kept.len()).map(|i| [kept[i], kept[i], kept[(i + 1) % kept.len()], kept[(i + 1) % kept.len()]]).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod component_survival_tests {
|
||||
use crate::{Raster, TraceParams, preset, trace};
|
||||
|
||||
fn params(corners: f64, noise: u32) -> TraceParams {
|
||||
TraceParams { paths: 50.0, corners, noise, ignore_white: true, ..preset("Black and White Logo").unwrap() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_one_pixel_component_survives_fitting_at_every_corner_setting() {
|
||||
let image = Raster::from_fn(32, 32, |x, y| if x == 16 && y == 16 { [0, 0, 0, 255] } else { [255; 4] });
|
||||
for corners in [0.0, 25.0, 50.0, 75.0, 100.0] {
|
||||
for noise in [0, 1] {
|
||||
let result = trace(&image, ¶ms(corners, noise));
|
||||
assert_eq!(result.paths.len(), 1, "corners={corners}, noise={noise}");
|
||||
assert!(result.paths[0].path.subpaths[0].area().abs() > 0.1);
|
||||
}
|
||||
assert!(trace(&image, ¶ms(corners, 2)).paths.is_empty(), "Noise must still remove the one-pixel component");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_one_pixel_hole_survives_fitting_without_overriding_noise() {
|
||||
let image =
|
||||
Raster::from_fn(
|
||||
32,
|
||||
32,
|
||||
|x, y| if (4..28).contains(&x) && (4..28).contains(&y) && (x, y) != (16, 16) { [0, 0, 0, 255] } else { [255; 4] },
|
||||
);
|
||||
for corners in [0.0, 25.0, 50.0, 75.0, 100.0] {
|
||||
for noise in [0, 1] {
|
||||
let result = trace(&image, ¶ms(corners, noise));
|
||||
assert_eq!(result.paths.len(), 1);
|
||||
assert_eq!(result.paths[0].path.subpaths.len(), 2, "hole lost at corners={corners}, noise={noise}");
|
||||
let subs = &result.paths[0].path.subpaths;
|
||||
assert!(subs[0].area() * subs[1].area() < 0.0, "hole must keep opposite winding");
|
||||
}
|
||||
let cleaned = trace(&image, ¶ms(corners, 2));
|
||||
assert_eq!(cleaned.paths.len(), 1);
|
||||
assert_eq!(cleaned.paths[0].path.subpaths.len(), 1, "Noise must still fill the one-pixel hole");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_small_stepped_component_is_not_dropped_by_low_corners() {
|
||||
let image =
|
||||
Raster::from_fn(
|
||||
32,
|
||||
32,
|
||||
|x, y| if (10..14).contains(&x) && y == 10 || (10..13).contains(&x) && y == 11 { [0, 0, 0, 255] } else { [255; 4] },
|
||||
);
|
||||
for corners in [0.0, 25.0, 50.0, 75.0, 100.0] {
|
||||
let result = trace(&image, ¶ms(corners, 1));
|
||||
assert_eq!(result.paths.len(), 1, "stepped region lost at corners={corners}");
|
||||
assert!(result.paths[0].path.subpaths[0].area().abs() > 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,18 @@ What Image Trace (`crates/trace`) makes of images whose right answer is known, m
|
||||
compared with the numbers before each change. Nothing here is a judgement by eye, and nothing is compared
|
||||
with any other product except the open tracer named under [Yardstick](#4-yardstick).
|
||||
|
||||
## Tiny closed contours
|
||||
|
||||
A coarse fit can reduce a small closed outline to one cubic, which the editable-path builder cannot
|
||||
represent as a closed subpath with two anchors. Previously that dropped the component or hole after
|
||||
it survived Noise. The fitter now retains the positioned contour without further simplification when
|
||||
that minimum is not met. Corners and Noise defaults are unchanged.
|
||||
|
||||
Three source-generated regressions cover a one-pixel island, a one-pixel hole with opposite winding,
|
||||
and a stepped seven-pixel region across Corners values. They also check that Noise still removes the
|
||||
island and fills the hole when its threshold requests that. These are survival and winding checks,
|
||||
not a new quality-harness baseline or a general claim about small-feature fidelity.
|
||||
|
||||
## How it is measured
|
||||
|
||||
`crates/trace/tests/quality` draws its own inputs from vector art (no image files), traces each with every
|
||||
@@ -955,6 +967,7 @@ One trace at a time on this machine (all cores); memory is the peak allocated ab
|
||||
|
||||
| date | change |
|
||||
|:--|:--|
|
||||
| 2026-10-14 | Tiny closed fits retain their positioned outlines instead of losing islands/holes; three generated regression tests; quality-harness baseline unchanged |
|
||||
| 2026-10-14 | Gradients: a gradient that noise or a soft rim broke into pieces is found in its largest piece and grown over the rest (blurred and noisy linear gradients: 3 to 11 paths beside the gradient down to 0.5 with 6 Colors, 24 to 56 down to 0.5 to 1.5 with High Fidelity Photo) |
|
||||
| 2026-10-14 | Enhanced versions of the presets (Auto Grouping, and Shapes in all but High Fidelity Photo; Flat Logo has none), each measured beside its preset; a hole that one colour fills is the same shape as what fills it, rectangles are sized by where their sides lie, and a group keeps its paths in the order they were traced in (grouped, a letter with a counter was painted with a light seam: Default with Auto Grouping, edge ΔE 10.24 to 9.88) |
|
||||
| 2026-10-14 | Create: Strokes: a line whose ink is cut square at its ends gets square caps, and a wide line whose skeleton hooks towards a corner of its end ends on the line it was following (Outline: largest distance 2.94 px to 0.88; aliased lines 0.88 to 0.81) |
|
||||
|
||||
+2
-1
@@ -17,6 +17,7 @@ Checked 2026-10-10 from the code, tests and open issues; no workflow fails, so V
|
||||
|
||||
| Core workflow | Works end to end? | Evidence | Hours to pass |
|
||||
|---|---|---|---|
|
||||
| 2026-10-14 | minor | M12: tiny fitted contours retain islands and holes after Noise; source-generated regressions; defaults and estimates unchanged |
|
||||
| 2026-10-12 | minor | Desktop PDF spooler shared through craft-print; exact physical sizes, composed copies and Windows 7 fallback retained. Scores and remaining gaps unchanged. |
|
||||
| Draw and edit paths precisely: Pen, Curvature, Direct Selection on anchors and handles, smart guides and snapping, Pathfinder | yes | `crates/tools/src/pen.rs`, `direct.rs`, `guides.rs` and their tests; 10 exact Pathfinder booleans; [ui-parity.md](ui-parity.md). Shape Builder misses regions in some files (#937), Pathfinder covers the same results | 0 |
|
||||
| Paint and style: fills and strokes, global and spot swatches, gradients, Appearance, transparency, graphic styles | yes | M3 done (92 tasks); area scores 92–97% in [target-app-parity.md](target-app-parity.md#feature-areas) | 0 |
|
||||
@@ -61,7 +62,7 @@ Checked 2026-10-10 from the code, tests and open issues; no workflow fails, so V
|
||||
| M9 | Live effects (+ 3D & Materials) | 🟡 2D effects done incl. Effect → Pathfinder, Color Adjustments and Document Raster Effects Settings; PDF export warns when the pixel cap lowers raster-effect resolution; Artistic › Film Grain done (29/57 Photoshop filters); Revolve with automatic smooth vector shading, default-on circular caps and continuous rims, Extrude & Bevel, Inflate and Rotate; SVG Filters, remaining raster effects and 3D materials, lights and shadows pending | 81–130 |
|
||||
| M10 | Brushes, symbols, patterns, Repeat | 🟡 pattern swatches (5 tile types, Pattern Options, editing mode, SVG `<pattern>`/PDF export) and live Repeat (radial/grid/mirror) done; brushes/symbols in progress | 23–37 |
|
||||
| M11 | Artboards & views (selected-set deletion shared by Artboards menu, trash and Properties, retaining artboard 1 when all are selected; Artboard deletion keys keep the first of an all-selected set and protect artwork; history prunes invalid indices while preserving valid multiselection (history tests and full workspace CI pass); click-to-fit objects with the Artboard tool (#1227), artboard escape routing and document setup modal handoff done; artboard panel/tool with Move Artwork for X/Y fields and Artboard Options, Trim View, middle-button pan, print tiling and the status bar's artboard navigator with synchronized Artboard tool selection done; multiple windows, presentation polish) | 🟡 | 15–25 |
|
||||
| M12 | Advanced color & art (CMYK/ICC, separations, Gradient Mesh, Live Paint, Image Trace, Graphs) | 🟡 Gradient Mesh, Live Paint, Image Trace (quick presets reveal adjustment controls; Properties reopens them; 15 presets, the 14 documented ones each with an enhanced version; Ignore Color, Shapes, Auto Grouping and Gradients options; measured in `image-trace-quality.md`; the Flat Logo mode places edges to a fraction of a pixel, restores sharp corners, snaps lines and circles, and refuses gradients), Recolor Artwork, colour management (ICC, soft proofing, separations preview), Graphs (all 9 tools, Graph Data/Type, group-selected series colours persist through regenerate/save/open) done; Graph Design and Marker designs, Transpose and Switch x/y, `|` label line breaks, the pie options, drop shadows, the legend across the top, overlapping widths, First Row/Column in Front, filled lines, kept label formatting and the Graph Data worksheet done; graph Column designs pending | 6–10 |
|
||||
| M12 | Advanced color & art (CMYK/ICC, separations, Gradient Mesh, Live Paint, Image Trace, Graphs) | 🟡 Gradient Mesh, Live Paint, Image Trace (quick presets reveal adjustment controls; Properties reopens them; tiny fitted contours retain islands and holes; 15 presets, the 14 documented ones each with an enhanced version; Ignore Color, Shapes, Auto Grouping and Gradients options; measured in `image-trace-quality.md`; the Flat Logo mode places edges to a fraction of a pixel, restores sharp corners, snaps lines and circles, and refuses gradients), Recolor Artwork, colour management (ICC, soft proofing, separations preview), Graphs (all 9 tools, Graph Data/Type, group-selected series colours persist through regenerate/save/open) done; Graph Design and Marker designs, Transpose and Switch x/y, `|` label line breaks, the pie options, drop shadows, the legend across the top, overlapping widths, First Row/Column in Front, filled lines, kept label formatting and the Graph Data worksheet done; graph Column designs pending | 6–10 |
|
||||
| M13 | Automation (Actions ✅ record/playback, persisted; variables, scripting, batch) | 🟡 | 18–27 |
|
||||
| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) | 🟡 Numeric-field pointer selection and focus regression-tested (#1326, in part; short movements still select all); RPM runtime capabilities and glibc floor checked (#1275; real-distro install/start-up pending), Eyedropper cursor with precise-setting checks (#1269), Preferences, shortcut editor (chords with Shift and a punctuation key fire as typed; other apps' text shortcut sets imported), workspaces, a custom title bar on Windows/Linux with a Home button, content-sized dialogs, menus that scroll when longer than the window and macOS native menu dismissal done (M14.8), and native macOS save sheets with a Format menu (#866); an experimental, unsupported Windows 7 x64 portable OpenGL build (not verified by maintainers); accessibility and broader Windows/Linux packaging/QA pending | 18–28 |
|
||||
| — | Interaction fidelity pass (every tool's modifiers, Properties panel per context, isolation, nuance; width-point creation and adjoining adjustment regression-tested; dialog layout reviewed) | ⬜ (new ruler-guide Alt orientation implemented; broader pass remains) | 60–90 |
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user