mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Image Trace: large images trace with a warning; anchor limit 8,000,000
The public documentation says Image Trace warns about images larger than 1500 pixels on a side but traces them, only more slowly. VectorCraft traced them silently, and refused any trace of more than 2,000,000 anchors, which a 4000 x 3000 photo at High Fidelity Photo nearly reaches (1.9 million on a code-drawn photo) and a noisier one passes. - imageTrace.make and makeAndExpand return `warnings`: one for an image over 1500 px on a side (traced, but more slowly; a lower-fidelity preset or a smaller copy is quicker), one for an image over 64 MP that was traced from a scaled-down copy. The app shows the first in the status bar. - MAX_TRACE_ANCHORS is 8,000,000 natively (2,000,000 on wasm, where a page has at most 4 GB). Measured through imageTrace.make on code-drawn photos: about 90 bytes an anchor kept in the document; 7.9 million anchors kept 685 MB with an 876 MB peak (4000 x 3000, Full Tone at Colors 100, Paths 100, Noise 1, 37 s). - The refusal keeps its translated advice: raise Noise, lower Paths or Colors, or pick a lower-fidelity preset. Tests: a_large_image_traces_with_a_warning (1600 x 40 and 1500 x 8 code-drawn images, make and make-and-expand), trace_warnings_name_a_large_or_scaled_down_image, a compile-time check of the limit's range, and a UI test that the status bar shows the note. Source: https://helpx.adobe.com/illustrator/using/image-trace-results-optimization.html Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tBcdwNhptqtmX32JCGFzB
This commit is contained in:
committed by
bflatastic
co-authored by
Claude Opus 5.5
parent
a2e2eba2e2
commit
17a602d1dc
@@ -129,7 +129,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Make",
|
||||
["Object", "Image Trace"],
|
||||
None,
|
||||
"{id?: image or Image Trace group (default: selection), preset?: name (imageTrace.presets), enhanced?: false (the preset's enhanced version: with Auto Grouping, and with Shapes in all but High Fidelity Photo; Flat Logo has none and is traced as it is), params?: {mode: \"blackAndWhite\"|\"grayscale\"|\"color\"|\"logo\", threshold: 0-255, palette: \"limited\"|\"fullTone\"|\"automatic\"|\"documentLibrary\" (color mode; limited: the `colors` best colours, fullTone: as many as the tones need, more with colorDetail, automatic: flat art with its own colours, else fullTone, documentLibrary: at most `colors` of the library's colours, as they are), colors: 2-256, colorDetail: 0-100 (fullTone, automatic), library: \"document\" (the document's swatches) or a swatch library id or name (swatch.library.list; documentLibrary), paths: 0-100, corners: 0-100, noise: px, method: \"abutting\"|\"overlapping\", ignoreWhite (leave out white areas), ignoreColor?: \"#rrggbb\" (with ignoreWhite: the colour left out instead of white), snapCurvesToLines, shapes: false (filled outlines that are a circle, an ellipse, a rectangle or a regular polygon come out as that live shape), autoGrouping: false (each path that has others inside it is grouped with them), gradients: false (grayscale and color: an area whose colour changes evenly along one direction is one path with a linear gradient fill), fills: true, strokes: false (Create: lines up to strokeWidth wide become stroked centre lines; without fills, wider areas become stroked outlines), strokeWidth: px (10), logoColors?: [\"#rrggbb\"…] (mode logo: flat-colour logos only; colours found automatically or named here, white is always its own colour, at most ten; noise = areas under noise/10 px ignored; edges to a fraction of a pixel, sharp corners restored, lines and circles snapped; enclosed transparent areas are painted white unless ignoreWhite makes them holes; fills/strokes are ignored; gradients and photos are refused)}, view?: as imageTrace.setView (default: the traced object's own, else tracingResult), ids?: [id…] (instead of id)} → {id, paths, anchors, colors}",
|
||||
"{id?: image or Image Trace group (default: selection), preset?: name (imageTrace.presets), enhanced?: false (the preset's enhanced version: with Auto Grouping, and with Shapes in all but High Fidelity Photo; Flat Logo has none and is traced as it is), params?: {mode: \"blackAndWhite\"|\"grayscale\"|\"color\"|\"logo\", threshold: 0-255, palette: \"limited\"|\"fullTone\"|\"automatic\"|\"documentLibrary\" (color mode; limited: the `colors` best colours, fullTone: as many as the tones need, more with colorDetail, automatic: flat art with its own colours, else fullTone, documentLibrary: at most `colors` of the library's colours, as they are), colors: 2-256, colorDetail: 0-100 (fullTone, automatic), library: \"document\" (the document's swatches) or a swatch library id or name (swatch.library.list; documentLibrary), paths: 0-100, corners: 0-100, noise: px, method: \"abutting\"|\"overlapping\", ignoreWhite (leave out white areas), ignoreColor?: \"#rrggbb\" (with ignoreWhite: the colour left out instead of white), snapCurvesToLines, shapes: false (filled outlines that are a circle, an ellipse, a rectangle or a regular polygon come out as that live shape), autoGrouping: false (each path that has others inside it is grouped with them), gradients: false (grayscale and color: an area whose colour changes evenly along one direction is one path with a linear gradient fill), fills: true, strokes: false (Create: lines up to strokeWidth wide become stroked centre lines; without fills, wider areas become stroked outlines), strokeWidth: px (10), logoColors?: [\"#rrggbb\"…] (mode logo: flat-colour logos only; colours found automatically or named here, white is always its own colour, at most ten; noise = areas under noise/10 px ignored; edges to a fraction of a pixel, sharp corners restored, lines and circles snapped; enclosed transparent areas are painted white unless ignoreWhite makes them holes; fills/strokes are ignored; gradients and photos are refused)}, view?: as imageTrace.setView (default: the traced object's own, else tracingResult), ids?: [id…] (instead of id)} → {id, paths, anchors, colors, warnings: [text…] (the image is over 1500 px on a side, so it traces more slowly; or it was traced scaled down)}",
|
||||
has_selection_or_ids,
|
||||
|s, p| trace_make(s, p, false)
|
||||
),
|
||||
@@ -859,9 +859,16 @@ fn trace_library(s: &Session, params: &mut tr::TraceParams) -> Result<Vec<([u8;
|
||||
Ok(library)
|
||||
}
|
||||
|
||||
/// The most anchor points an Image Trace may make: a photo traced at high fidelity can make
|
||||
/// millions, more than the canvas draws smoothly or a laptop has memory for, so the trace is
|
||||
/// refused with advice instead (#525).
|
||||
/// The most anchor points an Image Trace may make, a guard for memory: a trace past it is refused
|
||||
/// with advice instead (#525). A trace keeps about 90 bytes an anchor in the document (measured on
|
||||
/// code-drawn photos of 2000 × 2000 to 6000 × 4000 pixels), so 8,000,000 anchors are about
|
||||
/// 0.7 GB, with a peak of about 1 GB while tracing: a 4000 × 3000 photo at High Fidelity Photo
|
||||
/// makes about 1.9 million, and even at Paths 100, Noise 1 and every tone about 7.9 million. On the
|
||||
/// web, where a page has at most 4 GB of memory, the limit stays at 2,000,000.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub const MAX_TRACE_ANCHORS: usize = 8_000_000;
|
||||
/// The most anchor points an Image Trace may make on the web (see the native limit).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub const MAX_TRACE_ANCHORS: usize = 2_000_000;
|
||||
|
||||
/// The live shape for a shape the tracer found (in the image's pixels), placed in the document by `xf`.
|
||||
@@ -975,6 +982,7 @@ fn trace_make(s: &mut Session, p: &Value, expand: bool) -> Result<Value> {
|
||||
let raster = tr::Raster::decode_to_fit(&blob.bytes).map_err(|e| EngineError::Other(e.to_string()))?;
|
||||
let library = trace_library(s, &mut params)?;
|
||||
let res = tr::trace_within(&raster, ¶ms, MAX_TRACE_ANCHORS).map_err(|e| EngineError::Other(e.to_string()))?;
|
||||
let warnings = trace_warnings((img.width, img.height), (raster.width, raster.height));
|
||||
// Pixel space of the decoded raster → the image object's pixel space → document.
|
||||
let sx = img.width.max(1) as f64 / raster.width.max(1) as f64;
|
||||
let sy = img.height.max(1) as f64 / raster.height.max(1) as f64;
|
||||
@@ -1044,7 +1052,34 @@ fn trace_make(s: &mut Session, p: &Value, expand: bool) -> Result<Value> {
|
||||
sel.set([gid]);
|
||||
Ok(gid)
|
||||
})?;
|
||||
Ok(json!({ "id": id.0, "paths": npaths, "anchors": anchors, "colors": colors }))
|
||||
Ok(json!({ "id": id.0, "paths": npaths, "anchors": anchors, "colors": colors, "warnings": warnings }))
|
||||
}
|
||||
|
||||
/// Images larger than this on a side are traced, but more slowly: the trace says so.
|
||||
pub const LARGE_TRACE_SIDE: u32 = 1500;
|
||||
|
||||
/// What a trace of an image of `size` pixels, traced at `traced` pixels, tells the user: that a
|
||||
/// large image takes longer (the public documentation's warning for images over 1500 px), and that
|
||||
/// one over the tracer's [`tr::MAX_PIXELS`] was traced scaled down.
|
||||
pub(crate) fn trace_warnings(size: (u32, u32), traced: (u32, u32)) -> Vec<String> {
|
||||
let mut out = vec![];
|
||||
if size.0.max(size.1) > LARGE_TRACE_SIDE {
|
||||
out.push(format!(
|
||||
"The image is {} × {} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster.",
|
||||
size.0, size.1
|
||||
));
|
||||
}
|
||||
if u64::from(traced.0) * u64::from(traced.1) < u64::from(size.0) * u64::from(size.1) {
|
||||
out.push(format!(
|
||||
"The image was traced at {} × {} pixels, scaled down from {} × {} ({} megapixels at most).",
|
||||
traced.0,
|
||||
traced.1,
|
||||
size.0,
|
||||
size.1,
|
||||
tr::MAX_PIXELS >> 20
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn trace_targets(s: &Session, p: &Value) -> Result<Vec<NodeId>> {
|
||||
@@ -1113,3 +1148,21 @@ fn trace_expand(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
})?;
|
||||
Ok(json!({ "ids": ids_json(&targets) }))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod trace_limit_tests {
|
||||
use super::{MAX_TRACE_ANCHORS, trace_warnings};
|
||||
|
||||
#[test]
|
||||
fn trace_warnings_name_a_large_or_scaled_down_image() {
|
||||
assert!(trace_warnings((1500, 1500), (1500, 1500)).is_empty());
|
||||
assert_eq!(trace_warnings((1501, 10), (1501, 10)).len(), 1);
|
||||
let w = trace_warnings((16000, 12000), (9459, 7094));
|
||||
assert_eq!(w.len(), 2, "{w:?}");
|
||||
assert!(w[0].contains("16000 × 12000") && w[1].contains("9459 × 7094") && w[1].contains("64 megapixels"), "{w:?}");
|
||||
}
|
||||
|
||||
// A 4000 × 3000 photo at High Fidelity Photo makes about 1.9 million anchors, which must fit;
|
||||
// the limit still guards memory (about 90 bytes an anchor). Checked when the tests build.
|
||||
const _: () = assert!(MAX_TRACE_ANCHORS >= 2_000_000 && MAX_TRACE_ANCHORS <= 16_000_000);
|
||||
}
|
||||
|
||||
@@ -908,6 +908,34 @@ fn traced_paths(n: &Node) -> Vec<Arc<Node>> {
|
||||
n.children().unwrap().iter().filter(|c| !matches!(c.kind, NodeKind::Image(_))).cloned().collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_large_image_traces_with_a_warning() {
|
||||
// The public documentation: images over 1500 px on a side are traced, only more slowly.
|
||||
let mut s = session();
|
||||
let art = vectorcraft_trace::Raster::from_fn(
|
||||
1600,
|
||||
40,
|
||||
|x, y| if (100..1500).contains(&x) && (10..30).contains(&y) { [0, 0, 0, 255] } else { [255; 4] },
|
||||
);
|
||||
add_image(&mut s, &art, Affine::IDENTITY);
|
||||
let r = s.execute("imageTrace.make", &json!({"params": {"ignoreWhite": true}})).unwrap();
|
||||
assert_eq!(r["paths"], 1);
|
||||
let w = r["warnings"].as_array().unwrap();
|
||||
assert_eq!(w.len(), 1, "{w:?}");
|
||||
assert!(w[0].as_str().unwrap().contains("1600 × 40") && w[0].as_str().unwrap().contains("1500"), "{w:?}");
|
||||
// Make and Expand says so too.
|
||||
let r = s.execute("imageTrace.makeAndExpand", &json!({"id": r["id"], "params": {"ignoreWhite": true}})).unwrap();
|
||||
assert_eq!(r["warnings"].as_array().map(Vec::len), Some(1));
|
||||
// At 1500 px and under, no warning.
|
||||
let mut s = session();
|
||||
image_doc(&mut s);
|
||||
let r = s.execute("imageTrace.make", &json!({})).unwrap();
|
||||
assert_eq!(r["warnings"], json!([]));
|
||||
let edge = vectorcraft_trace::Raster::from_fn(1500, 8, |x, _| if x < 700 { [0, 0, 0, 255] } else { [255; 4] });
|
||||
add_image(&mut s, &edge, Affine::IDENTITY);
|
||||
assert_eq!(s.execute("imageTrace.make", &json!({})).unwrap()["warnings"], json!([]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn image_trace_make_places_paths_over_the_image() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -33,7 +33,7 @@ use measure::{Metrics, measure};
|
||||
use vectorcraft_trace::{Raster, TraceParams, TraceResult, TracedPath, presets, trace_within};
|
||||
|
||||
/// The engine's limit (`MAX_TRACE_ANCHORS`): a trace past it is refused.
|
||||
const MAX_ANCHORS: usize = 2_000_000;
|
||||
const MAX_ANCHORS: usize = 8_000_000;
|
||||
|
||||
struct Counting;
|
||||
static LIVE: AtomicUsize = AtomicUsize::new(0);
|
||||
@@ -243,7 +243,7 @@ fn cost(img: &Raster, params: &TraceParams) -> String {
|
||||
Ok(res) => format!("{secs:.2} s, {} paths, {} anchors, {mb:.0} MB", res.paths.len(), res.anchor_count()),
|
||||
Err(e) => {
|
||||
let why = e.to_string();
|
||||
let why = if why.contains("anchor points") { "over 2,000,000 anchors" } else { "not flat art" };
|
||||
let why = if why.contains("anchor points") { "over 8,000,000 anchors" } else { "not flat art" };
|
||||
format!("refused after {secs:.2} s ({why}), {mb:.0} MB")
|
||||
}
|
||||
}
|
||||
@@ -341,7 +341,7 @@ fn report() -> String {
|
||||
.map(|mp| (mp.sqrt() * 1024.0).round() as u32)
|
||||
.collect();
|
||||
let (photo, badge) = (inputs.iter().find(|i| i.name == "photo-like").unwrap(), inputs.iter().find(|i| i.name == "four-colour badge").unwrap());
|
||||
md += "\n## 5. Cost\n\nOne trace at a time on this machine (all cores); memory is the peak allocated above the image itself. A trace of more than 2,000,000 anchors is refused, as in the app.\n\n| preset | image | megapixels | result |\n|:--|:--|--:|:--|\n";
|
||||
md += "\n## 5. Cost\n\nOne trace at a time on this machine (all cores); memory is the peak allocated above the image itself. A trace of more than 8,000,000 anchors is refused, as in the app.\n\n| preset | image | megapixels | result |\n|:--|:--|--:|:--|\n";
|
||||
for size in sizes {
|
||||
let n = size as usize;
|
||||
let images = [("photo-like", photo.draw(size, Variant::Clean)), ("four-colour badge", raster::render(&badge.scaled(size), badge.bg, n, n))];
|
||||
|
||||
@@ -751,6 +751,12 @@ impl VectorcraftApp {
|
||||
match &r {
|
||||
Err(e) => self.ui.status = e.clone(),
|
||||
Ok(v) => {
|
||||
// Image Trace's notes (a large image traces more slowly, or was scaled down).
|
||||
if id.starts_with("imageTrace.make")
|
||||
&& let Some(w) = v["warnings"].as_array().and_then(|w| w.first()).and_then(Value::as_str)
|
||||
{
|
||||
self.ui.status = w.to_string();
|
||||
}
|
||||
if id == "file.new" {
|
||||
self.ui.status.clear();
|
||||
// New Document's Pixel preview mode (Overprint Preview is the engine's).
|
||||
|
||||
@@ -519,6 +519,20 @@ mod tests {
|
||||
app
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_large_image_traces_and_the_status_bar_says_it_is_slower() {
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
let img = image::RgbaImage::from_fn(1601, 12, |x, _| image::Rgba(if x < 800 { [0, 0, 0, 255] } else { [255, 255, 255, 255] }));
|
||||
let mut png = vec![];
|
||||
img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).unwrap();
|
||||
let data = vectorcraft_format::base64_encode(&png);
|
||||
app.run("file.place", json!({"name": "wide.png", "dataBase64": data, "link": false})).unwrap();
|
||||
trace_with(&mut app, "Default");
|
||||
assert!(target(&app).2.is_some(), "the image is traced");
|
||||
assert!(app.ui.status.contains("1601 × 12") && app.ui.status.contains("more slowly"), "{}", app.ui.status);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sketch_quick_trace_reveals_all_adjustments_and_keeps_the_source() {
|
||||
let mut app = traced_app();
|
||||
|
||||
@@ -26,6 +26,15 @@ so Undo also restores the panel values before its next frame. View can show the
|
||||
Use **Expand** when finished to turn the result into ordinary editable vector objects;
|
||||
after expanding, the tracing settings are no longer available. **Release** restores the source.
|
||||
|
||||
**Large images.** An image of more than 1500 pixels on a side is traced as any other, only more
|
||||
slowly, and the trace says so: `imageTrace.make` returns the note in `warnings` and the status bar
|
||||
shows it (the [results optimization](https://helpx.adobe.com/illustrator/using/image-trace-results-optimization.html)
|
||||
page describes the same warning for images over 1500 px). An image of more than 64 megapixels (up to
|
||||
192) is traced from a copy scaled down to 64 megapixels, and the trace says that too. A trace that would
|
||||
make more than 8,000,000 anchor points (2,000,000 on the web) is refused to protect memory, with advice:
|
||||
raise Noise, lower Paths or Colors, or pick a lower-fidelity preset. A trace keeps
|
||||
about 90 bytes an anchor; a 4000 × 3000 photo at High Fidelity Photo makes about 1.9 million.
|
||||
|
||||
These controls use VectorCraft's existing tracing algorithm. Parameter effects follow its
|
||||
implementation; they do not claim pixel-identical results with another application.
|
||||
The terminology and general control behavior are described in the
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Image Trace quality
|
||||
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-17 · **Change:** minor (Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; earlier: Gradients: grown over noise and soft rims)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-17 · **Change:** minor (Image Trace: Raster::new returns an error for a buffer of the wrong length; earlier: Gradients: grown over noise and soft rims)
|
||||
> **Last reviewed:** 2026-10-17 · **Last updated:** 2026-10-17 · **Change:** minor (Create Strokes: maximal-disc pruning and junction merging; centre-lines rerun)
|
||||
> **Last reviewed:** 2026-10-17 · **Last updated:** 2026-10-17 · **Change:** minor (Create Strokes: exact crop distances and Guo–Hall thinning; centre-lines rerun)
|
||||
@@ -14,8 +15,37 @@ compared with the numbers before each change. Nothing here is a judgement by eye
|
||||
with any other product except the open tracer named under [Yardstick](#4-yardstick).
|
||||
|
||||
|
||||
## Collapsed loops keep few segments
|
||||
## Large images
|
||||
|
||||
Images over 1500 px on a side are traced with a warning that they trace more slowly (see
|
||||
[image-trace-adjustments.md](image-trace-adjustments.md)), and the anchor limit that guards memory is
|
||||
8,000,000 (2,000,000 on the web) instead of 2,000,000. Measured through `imageTrace.make` (release
|
||||
build, 4 cores, one trace at a time; memory is the peak allocated above the placed image, and what the
|
||||
document keeps after the trace) on code-drawn photo-like pictures (40 soft-edged shaded discs on a
|
||||
gradient, texture, sensor noise):
|
||||
|
||||
| size | preset | time | anchors | paths | peak | kept |
|
||||
|:--|:--|--:|--:|--:|--:|--:|
|
||||
| 2000 × 2000 | High Fidelity Photo | 4.1 s | 737,478 | 16,064 | 152 MB | 52 MB |
|
||||
| 2000 × 2000 | Low Fidelity Photo | 1.6 s | 114,008 | 1,486 | 142 MB | 7 MB |
|
||||
| 2000 × 2000 | Default | 0.9 s | 103,084 | 1,001 | 117 MB | 6 MB |
|
||||
| 3000 × 2000 | High Fidelity Photo | 6.4 s | 1,013,114 | 25,256 | 236 MB | 75 MB |
|
||||
| 3000 × 2000 | Default | 1.2 s | 145,756 | 1,574 | 162 MB | 9 MB |
|
||||
| 4000 × 3000 | High Fidelity Photo | 12.8 s | 1,914,018 | 58,638 | 471 MB | 150 MB |
|
||||
| 4000 × 3000 | Low Fidelity Photo | 5.1 s | 258,826 | 4,833 | 428 MB | 18 MB |
|
||||
| 4000 × 3000 | Default | 2.4 s | 273,392 | 3,568 | 317 MB | 17 MB |
|
||||
| 4000 × 3000 | 16 Colors | 3.0 s | 103,222 | 2,434 | 364 MB | 7 MB |
|
||||
| 4000 × 3000 | Full Tone, Colors 85, Paths 90, Noise 4 | 29.6 s | 5,230,847 | 164,687 | 684 MB | 412 MB |
|
||||
| 4000 × 3000 | Full Tone, Colors 100, Paths 100, Noise 1 | 37.2 s | 7,885,709 | 338,561 | 876 MB | 685 MB |
|
||||
| 6000 × 4000 | Full Tone, Colors 85, Paths 90, Noise 4 | 106.1 s | 10,445,409 | 398,772 | 1169 MB | 872 MB |
|
||||
| 6000 × 4000 | Full Tone, Colors 100, Paths 100, Noise 1 | 116.3 s | 13,508,871 | 658,133 | 1456 MB | 1230 MB |
|
||||
|
||||
(The last two rows were measured without a limit; they are refused now.) The 4000 × 3000 High Fidelity
|
||||
Photo trace was 86,000 anchors under the old limit; a noisier scan of the same size would have been
|
||||
refused. At 8,000,000 anchors a trace keeps about 0.7 GB, with a peak of about 0.9 GB while tracing.
|
||||
|
||||
|
||||
## Collapsed loops keep few segments
|
||||
A small closed loop (or a piece that returns to its own junction) whose fit collapsed to a single
|
||||
cubic cannot stay as one, so it kept every positioned point as a straight segment. A looser Paths
|
||||
collapses more loops, so it could give more segments than a tighter one. Such a loop now tries the
|
||||
@@ -24,7 +54,6 @@ stays within 1.5 × max(tolerance, 0.5 px) of the points both ways: the fit at u
|
||||
tighter tolerances (what a slightly higher Paths gives); for a closed loop then two cubics split at
|
||||
its start and its farthest point, then the outline simplified at the tolerance and finer; the
|
||||
positioned points last, as before.
|
||||
|
||||
Test on code-drawn images: `a_higher_paths_never_gives_fewer_segments` (32 seeded 48 px images of
|
||||
soft blobs in four colours plus pixel noise, Color and Black and White, Noise 1 and 3, segments at
|
||||
Paths 0, 25, 50, 75 and 100 must not fall). Before, six runs fell, e.g. 125 → 79 segments from
|
||||
@@ -32,7 +61,6 @@ Paths 0 to 25 (seed 21, Color, Noise 1) and 71 → 63 → 52 from Paths 0 to 50
|
||||
White, Noise 3); now they are 75 → 79 and 41 → 48 → 52. One image (seed 16 in Black and White) still
|
||||
falls from 140 to 136 segments between Paths 0 and 25, before and after this change; that fall has
|
||||
another cause, still open, and the image is left out of the test. The crate's other tests pass unchanged.
|
||||
|
||||
## Notches read only their own piece
|
||||
Where the pixel at a sharp corner falls to the other colour, the outline cuts the corner in two
|
||||
one-pixel steps, and between two straight edges of three cracks or more those two steps become one
|
||||
@@ -1212,6 +1240,7 @@ The remaining spur/extra-junction target is not met by this graph-only change.
|
||||
|
||||
| date | change |
|
||||
|:--|:--|
|
||||
| 2026-10-17 | Images over 1500 px on a side trace with a warning; the anchor limit is 8,000,000 (2,000,000 on the web); time and memory of 2000 × 2000 to 6000 × 4000 code-drawn photos |
|
||||
| 2026-10-17 | `Raster::new` returns an error for RGBA bytes of the wrong length (it failed an assertion) and `Raster::pixel` reads nothing past the raster; traces of valid images unchanged |
|
||||
| 2026-10-17 | M12: maximal-disc graph pruning, split-crossing merges and degree-two joins; correct junction degrees 30→44/45 aa and hard, none missed; remaining spurs unchanged. Centre-lines rerun, scores and earlier tables unchanged. |
|
||||
| 2026-10-17 | M12: exact per-blob Euclidean distances and Guo–Hall thinning; centre-lines rerun on 46 inputs. Ends found: aa 638→682/688, hard 631→681/688. Whole-blob stroke/fill choice retained; preset/cost tables unchanged. |
|
||||
|
||||
+3
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user