Compare commits

...
Author SHA1 Message Date
bflatastic cf137c8c90 Docs: Image Trace's colour and photo presets, measured (ROADMAP log, parity evidence) 2026-10-13 04:42:29 +07:00
BFlat e131dfbb1c Merge pull request #1516 from storytold/u/trace-photo
Image Trace: photos trace into smooth tones (smoothing first, stacked layers by lightness)
2026-10-13 04:42:08 +07:00
bflatastic 77f81c8944 Image Trace: photos are smoothed within their tones first, and stacked layers go by lightness
A photo's noise is as large as the step from one palette colour to the next, so noise decided pixel by pixel
which of two neighbouring colours a pixel got: tens of thousands of specks, or (since the palette change)
neighbouring tones merged into one.

- A continuous-tone image (eight colours leave much of it unsaid; flat art, however noisy or unevenly lit, is
  not one) is smoothed before it is quantised: three rounds of a small bilateral filter, which averages noise
  away within a tone and keeps the edges between tones where and as sharp as they were.
- With Method: Overlapping, layers are stacked by lightness, from the largest colour's end of the scale,
  instead of by area. Each layer reaches under those above it, so where two colours meet every layer between
  them in the stack ends on that same edge and shows through its anti-aliasing: by area that was an unrelated
  colour's hairline around every region; by lightness it is a neighbouring tone.
- Ignore White with Overlapping: no layer reaches under the white that is left out.

On each preset's own inputs, all sizes and variants (docs/image-trace-quality.md), before -> after:
- High Fidelity Photo: colour error 2.37 -> 1.75 (2.30 at the baseline), edge error 2.59 -> 1.98, 639 -> 121
  paths (3,653 at the baseline), 25,604 -> 9,818 anchors, specks 152 -> 10; a 16 megapixel photo-like image is
  traced in 3.5 s (31,131 paths) where it was refused
- Low Fidelity Photo: colour error 2.67 -> 2.45, 55 -> 37 paths
- Shades of Gray: 73 -> 17 paths, colour error 9.33 -> 9.26
- The other presets' numbers are unchanged
2026-10-13 04:28:02 +07:00
BFlat 7fadeb4ed2 Merge pull request #1485 from webdevtodayjason/graph-csv-precision
Graph Data: the CSV keeps each value exactly, not rounded to two decimals
2026-10-13 04:06:03 +07:00
BFlat 396d7d2b76 Merge pull request #1487 from itrycodinganditsbad/fix-1458
Tabs panel: drag a tab stop from where the press landed
2026-10-13 04:05:48 +07:00
BFlat e30b1492a1 Merge pull request #1506 from storytold/fix/1483-slider-rails
Fix #1483: sliders in dialogs show their rail
2026-10-13 04:05:31 +07:00
BFlat e6ae1d0df3 Merge pull request #1505 from storytold/fix/1486-template-outline
Fix #1486: a template layer stays in Preview in Outline view
2026-10-13 04:05:18 +07:00
webdevtodayjason 43e1a7380c Graph Data: the CSV keeps each value exactly, not rounded to two decimals 2026-10-13 04:00:18 +07:00
Fabio LussiandClaude Opus 5.5 7ab9a46bf8 Tabs panel: drag a tab stop from where the press landed
egui reports drag_started only once the pointer is past its drag
threshold, farther from the press than the ruler's 5 px hit tolerance,
so hit-testing the current pointer usually missed the stop. Hit-test the
press origin instead, falling back to the pointer position.

Fixes #1458

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:00:17 +07:00
bflatastic dd1c26b627 Part of #1483: sliders in dialogs show their rail
The theme's widget fill matches the dialog background, so the sliders of
Save for Web, the raster export options, Preferences and the opacity
popup showed only their handle. They share one slider widget now, with a
visible rail filled up to the handle.
2026-10-13 04:00:17 +07:00
bflatastic a787fb487c Fix #1486: a template layer stays in Preview in Outline view
In Outline view a template layer drew as outlines like any other, so a
picture placed to be traced turned into a box. It now keeps drawing in
Preview (dimmed as before) while the other layers are in outline.
2026-10-13 04:00:17 +07:00
BFlat ce0c86f91a Merge pull request #1504 from storytold/u/trace-palette
Image Trace: colour presets trace flat art with its own colours (no shattering, no slivers)
2026-10-13 03:55:45 +07:00
15 changed files with 409 additions and 107 deletions

No files matched your search

+2 -1
View File
@@ -6,7 +6,7 @@
**Stage: alpha** · next: beta, ~13 points (ready for real work ~62% → ~75%, and reliable `.ai` exchange) and ~170–270 h away
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (community PRs and fixes of 2026-10-12: Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; many-object edits, #1429 in part; headline numbers unchanged; Image Trace places edges to a fraction of a pixel, measured in `docs/image-trace-quality.md`, headline numbers unchanged; Transform dialogs preview and the transform tools act on direct-selected anchors, #1412 and #1408; `.ai` compound path paint, #1410, overprint, #1388, and text-only PDF parts, #1387; community PRs: strokes wider than their shape, corner drags without the bounding box, pasted pictures, compound paths in `document.inspect`, MCP guide; headline numbers unchanged; community PRs: `.ai` editing-data follow-ups (gradient stops, type of objects with an appearance, short area-type frames), PDF bleed with editing data, dialog keyboard focus, art flush with an artboard, font rescan, symbol instance redraw, `--control` port check; headline numbers unchanged; Rectangular Grid and Polar Grid tool options, #1377 in part; `.ai` files with placed (linked) pictures open from their editing data, #1264; community PRs: `.ai` and PDF bleed and editing-data import, gradient shapes imported as paths, ruler origin, Align To, Live Corners corner selection; headline numbers unchanged; G2: Film Grain added; Photoshop effects 29/57; 28 filters remain; community PRs: graph designs, Graph Data worksheet and Graph Type options; Align both centres; Pen endpoint fixes; earlier: Effect › Brush Strokes landed: Photoshop-style filters 28 of 57, raster effects 40% → 48%; `.ai` gradients, meshes and artboard names from the editing data; headline numbers unchanged; `.ai` art styles and clipping fills from the editing data; headline numbers unchanged; `editingData: "always"`; headline numbers unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Image Trace colour and photo presets, measured in `docs/image-trace-quality.md`, headline numbers unchanged; community PRs and fixes of 2026-10-12: Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; many-object edits, #1429 in part; headline numbers unchanged; Image Trace places edges to a fraction of a pixel, measured in `docs/image-trace-quality.md`, headline numbers unchanged; Transform dialogs preview and the transform tools act on direct-selected anchors, #1412 and #1408; `.ai` compound path paint, #1410, overprint, #1388, and text-only PDF parts, #1387; community PRs: strokes wider than their shape, corner drags without the bounding box, pasted pictures, compound paths in `document.inspect`, MCP guide; headline numbers unchanged; community PRs: `.ai` editing-data follow-ups (gradient stops, type of objects with an appearance, short area-type frames), PDF bleed with editing data, dialog keyboard focus, art flush with an artboard, font rescan, symbol instance redraw, `--control` port check; headline numbers unchanged; Rectangular Grid and Polar Grid tool options, #1377 in part; `.ai` files with placed (linked) pictures open from their editing data, #1264; community PRs: `.ai` and PDF bleed and editing-data import, gradient shapes imported as paths, ruler origin, Align To, Live Corners corner selection; headline numbers unchanged; G2: Film Grain added; Photoshop effects 29/57; 28 filters remain; community PRs: graph designs, Graph Data worksheet and Graph Type options; Align both centres; Pen endpoint fixes; earlier: Effect › Brush Strokes landed: Photoshop-style filters 28 of 57, raster effects 40% → 48%; `.ai` gradients, meshes and artboard names from the editing data; headline numbers unchanged; `.ai` art styles and clipping fills from the editing data; headline numbers unchanged; `editingData: "always"`; headline numbers unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
VectorCraft is a clean-room, open-source, pure-Rust reimplementation of the Adobe Illustrator workflow. It runs
on macOS, Windows, Linux, FreeBSD and the web (WASM), and agents can drive all of it over MCP, the CLI and a JSON
@@ -127,6 +127,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap
| Date | What landed |
|---|---|
| 2026-10-13 | Image Trace's colour presets trace flat art with its own colours however many are asked for (noise no longer shatters it: 16 Colors 70 → 4 paths on the harness's flat art; no slivers of in-between colour along anti-aliased edges), and the photo presets trace a photo into smooth tones without specks or hairlines (High Fidelity Photo 3,653 → 121 paths, colour error 2.30 → 1.75; 16 megapixels traced where 4 were refused) ([`docs/image-trace-quality.md`](docs/image-trace-quality.md)). |
| 2026-10-13 | Image Trace traces anti-aliased and blurred images to a fraction of a pixel in Black and White, Grayscale and Color, with corners put back, straight edges as lines and round outlines as circles; a harness measures every preset before and after ([`docs/image-trace-quality.md`](docs/image-trace-quality.md)): outlines 0.31 → 0.09 px off on average (Default), 0.27 → 0.06 (Black and White Logo). |
| 2026-10-12 | Effect › 3D and Materials: Extrude & Bevel (depth, twist, taper, solid or hollow caps, five bevel shapes with repeat, spacing, inside and both sides), Inflate (volume, both sides) and Rotate, with Position presets and the rotation and light gizmos; live, exported as vector faces, expandable (#1407). |
| 2026-10-12 | `convert --editing-data` (`editingData: "always"`) opens a `.ai` from its editing data even where its page, which can be older, draws differently, with a note. |
+10 -1
View File
@@ -146,7 +146,7 @@ fn to_csv(g: &GraphSpec) -> String {
}
out.push(',');
}
out.push_str(&r.iter().map(|v| v.map(fmt_value).unwrap_or_default()).collect::<Vec<_>>().join(","));
out.push_str(&r.iter().map(|v| v.map(|v| v.to_string()).unwrap_or_default()).collect::<Vec<_>>().join(","));
out.push('\n');
}
out
@@ -2338,6 +2338,15 @@ mod tests {
assert_eq!(super::nice_axis(0.0, 7.0, 0), (0.0, 8.0, 2.0));
}
#[test]
fn numeric_csv_cells_round_trip_without_rounding_or_saturation() {
let mut g = GraphSpec::default();
g.set_cells(vec![vec![Some(1.2345), Some(-1.2345)], vec![Some(1e-6), Some(-1e-6)], vec![Some(1e20), Some(-1e20)], vec![Some(0.0), None]]);
let csv = super::to_csv(&g);
let (_, _, cells) = super::parse_csv(&csv);
assert_eq!(cells, g.cells(), "{csv}");
}
#[test]
fn empty_cells_are_blank_and_quoted_numbers_are_labels() {
// Quoted years label the rows; empty and non-numeric cells are blank values, not zeros.
+7 -1
View File
@@ -1023,7 +1023,13 @@ impl Renderer {
&& f.opts.dim_templates
&& !f.opts.skip_templates
{
if !preview && !f.opts.outline {
// A template layer stays in Preview while the view is in Outline (and whatever its own
// Preview option says): its picture is there to be traced (#1486).
if *template && f.opts.outline {
let opts = RenderOptions { outline: false, ..f.opts.clone() };
return self.draw_node(ctx, &Frame { opts: &opts, ..*f }, n, force);
}
if !preview && !template && !f.opts.outline {
let opts = RenderOptions { outline: true, ..f.opts.clone() };
return self.draw_node(ctx, &Frame { opts: &opts, ..*f }, n, force);
}
+28
View File
@@ -67,6 +67,34 @@ fn dim_images_fades_only_the_images() {
assert_eq!(render(&d, true).pixel(65, 25), [255, 0, 0, 255], "exports are not dimmed");
}
/// In Outline view a template layer still draws in Preview, dimmed, so its picture can be traced;
/// the layer above it draws in outline (#1486).
#[test]
fn a_template_layer_stays_in_preview_in_outline_view() {
let mut d = doc(|k| {
if let NodeKind::Layer { template, preview, .. } = k {
*template = true;
// Preview off doesn't put a template in outline either.
*preview = false;
}
});
let top = Node::layer(d.alloc_id(), "Drawing", vectorcraft_doc::LayerColor::Preset(0));
let top_id = d.insert(None, 9, top).unwrap();
let red = Appearance::basic(Paint::solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0);
let sq = Node::path(d.alloc_id(), shapes::rectangle(Rect::new(10.0, 60.0, 40.0, 90.0)), red);
d.insert(Some(top_id), 0, sq).unwrap();
let draw = |outline: bool| {
let opts = RenderOptions { background: Some([255, 255, 255, 255]), outline, ..Default::default() };
Renderer::new().render(&d, 100, 100, Affine::IDENTITY, &opts)
};
let (preview, outline) = (draw(false), draw(true));
let [r, g, b, _] = outline.pixel(65, 25);
assert!(r == 255 && (120..=135).contains(&g) && g == b, "the template's image, dimmed: {:?}", outline.pixel(65, 25));
assert_eq!(outline.pixel(65, 25), preview.pixel(65, 25), "as in Preview");
assert_eq!(outline.pixel(25, 75), [255, 255, 255, 255], "the drawing layer has no fill in outline");
assert_eq!(preview.pixel(25, 75), [255, 0, 0, 255]);
}
/// A guide path draws on screen only: exports leave it out, as they leave templates out (and as
/// the SVG, PSD and PDF exports and printing already do).
#[test]
+26 -1
View File
@@ -12,6 +12,9 @@
//! colours: the blends that anti-aliasing and blur leave along edges take none of it, colours that
//! noise made of one colour are merged, and a blend pixel goes to one of the two colours it lies
//! between (see `quantize`).
//! A continuous-tone image (a photo) is first smoothed within its tones, edges kept (see
//! `smooth`): otherwise its noise decides, pixel by pixel, which of two neighbouring tones a
//! pixel gets, and every such pixel is a speck.
//! 2. **Remove noise**: 4-connected same-colour components smaller than `noise` pixels are merged
//! into their most common neighbouring colour.
//! 3. **Follow boundaries**: for each colour layer, the pixel-crack edges between inside and
@@ -41,6 +44,7 @@ mod fit;
mod logo;
mod mosaic;
mod quantize;
mod smooth;
mod subpixel;
use std::sync::atomic::{AtomicUsize, Ordering};
@@ -457,6 +461,9 @@ pub fn trace_within(img: &Raster, params: &TraceParams, max_anchors: usize) -> R
if params.mode == Mode::Logo {
return logo::trace_logo(img, params, max_anchors);
}
// A photo is traced from a copy with its noise smoothed away (see `smooth`).
let smoothed = (params.mode != Mode::BlackAndWhite && quantize::continuous_tone(img)).then(|| smooth::smoothed(img));
let img = smoothed.as_ref().unwrap_or(img);
let mut q = quantize(img, params);
denoise(&mut q.labels, w, h, params.noise as usize);
// Pixel count per palette entry.
@@ -469,6 +476,19 @@ pub fn trace_within(img: &Raster, params: &TraceParams, max_anchors: usize) -> R
// Layers bottom → top: largest area first.
let mut order: Vec<usize> = (0..q.palette.len()).filter(|&i| counts[i] > 0).collect();
order.sort_by(|a, b| counts[*b].cmp(&counts[*a]).then(a.cmp(b)));
if params.method == Method::Overlapping {
// Stacked layers go by lightness instead, from the largest colour's end of the scale: each
// layer reaches under all those above it, so where two colours meet, every layer between
// them in the stack ends on that same edge and shows through its anti-aliasing. Neighbours
// in a picture are mostly neighbours in tone, and in this order they are neighbours in the
// stack too (by area, an unrelated colour's edge would shine through as a hairline).
let light = |i: usize| q.palette.get(i).map_or(0, |c| u32::from(c[0]) * 299 + u32::from(c[1]) * 587 + u32::from(c[2]) * 114);
let first = order.first().copied();
order.sort_by(|a, b| light(*a).cmp(&light(*b)).then(a.cmp(b)));
if first.is_some_and(|f| order.iter().position(|&i| i == f).is_some_and(|at| at * 2 >= order.len())) {
order.reverse();
}
}
let mut rank = vec![usize::MAX; q.palette.len()];
for (r, &i) in order.iter().enumerate() {
rank[i] = r;
@@ -538,6 +558,11 @@ struct Layers<'a> {
}
impl Layers<'_> {
/// Ignore White: white is no part of any layer (stacked layers reach under the layers above
/// them, and must not reach under white that is left out).
fn ignored(&self, label: u16) -> bool {
self.params.ignore_white && self.q.palette.get(label as usize).is_some_and(|c| is_white(*c))
}
/// How far the edge lies from a crack between two pixels, towards the outside one (−0.5 to 0.5).
///
/// Each pixel of the column across the crack is read as a blend of the tones on its two sides
@@ -649,7 +674,7 @@ impl Layers<'_> {
.map(|(i, &l)| {
l != TRANSPARENT
&& !thin.get(i).copied().unwrap_or(false)
&& if overlapping { self.rank.get(l as usize).is_some_and(|&lr| lr >= r) } else { l as usize == ci }
&& if overlapping { self.rank.get(l as usize).is_some_and(|&lr| lr >= r) && !self.ignored(l) } else { l as usize == ci }
})
.collect();
// Without Fills, areas are outlined with a 1 px stroke.
+29
View File
@@ -305,6 +305,35 @@ fn settled(mut q: Quantized, img: &Raster, own: &[bool]) -> Quantized {
if changed { compact(q.labels, q.palette) } else { q }
}
/// Whether `img` is continuous tone (a photo, a gradient) rather than flat art, however noisy
/// or unevenly lit (a scan): whether eight colours leave much of it unsaid. Flat art is a few
/// colours and what noise and lighting make of them, and eight colours come within a few levels
/// of every pixel; a photo's tones spread all through its range, and eight colours stay far from
/// most of them (more than 6 levels, and more than a tenth of how far a single colour stays).
pub(crate) fn continuous_tone(img: &Raster) -> bool {
let hk = HistKey::new(img);
let mut bins = vec![Bin::default(); 1 << 15];
for p in img.rgba.as_chunks::<4>().0 {
if p[3] >= 128 {
bins[hk.key(p[0], p[1], p[2])].add(p);
}
}
let used: Vec<Used> = bins.iter().enumerate().filter(|(_, b)| b.n > 0).map(|(i, b)| (i, b.mean(), b.n as f64)).collect();
// The root-mean-square distance (levels) from the pixels to the nearest of `k` colours.
let unsaid = |k: usize| {
let colors = centers(&median_cut(&used, k, 0.0));
let (mut sum, mut n) = (0.0, 0.0);
for (_, c, weight) in &used {
let d2 = colors.iter().map(|m| (0..3).map(|ch| (m[ch] - c[ch]).powi(2)).sum::<f64>()).fold(f64::INFINITY, f64::min);
sum += d2 * weight;
n += weight;
}
if n > 0.0 { (sum / n).sqrt() } else { 0.0 }
};
let eight = unsaid(8);
eight > 6.0 && eight > 0.1 * unsaid(1)
}
/// Drop unused palette entries and merge duplicates.
fn compact(mut labels: Vec<u16>, palette: Vec<[u8; 3]>) -> Quantized {
let mut used = vec![false; palette.len()];
+97
View File
@@ -0,0 +1,97 @@
//! Edge-preserving smoothing of continuous-tone images before they are quantised.
//!
//! A photo's tones run into each other, and its noise is as large as the step from one palette
//! colour to the next: quantised as it is, noise decides pixel by pixel which of two neighbouring
//! colours a pixel gets, and the trace is tens of thousands of specks. Each pixel is averaged with
//! its neighbours instead, each weighted by how alike it is (a bilateral filter: C. Tomasi and
//! R. Manduchi, "Bilateral Filtering for Gray and Color Images", 1998), so that noise within a tone
//! is averaged away while an edge between two tones stays where and as sharp as it was. Three
//! rounds, each along rows and then along columns, three pixels at a time.
use crate::{Raster, parallel_map};
/// Colours this far apart (levels, all channels together) count half as much as equal ones.
const ALIKE: f64 = 20.0;
/// One pass along rows (`dx = 1`) or columns (`dx = 0`).
fn pass(src: &[u8], w: usize, h: usize, along_rows: bool) -> Vec<u8> {
let rows: Vec<usize> = (0..h).collect();
let px = |x: usize, y: usize| src.get((y * w + x) * 4..(y * w + x) * 4 + 4);
let done = parallel_map(&rows, |&y| {
let mut out = Vec::with_capacity(w * 4);
for x in 0..w {
let Some(p) = px(x, y) else { continue };
if p[3] < 128 {
out.extend_from_slice(p);
continue;
}
let near = if along_rows {
[(x > 0).then(|| (x - 1, y)), (x + 1 < w).then(|| (x + 1, y))]
} else {
[(y > 0).then(|| (x, y - 1)), (y + 1 < h).then(|| (x, y + 1))]
};
let (mut sum, mut weight) = ([f64::from(p[0]), f64::from(p[1]), f64::from(p[2])], 1.0);
for q in near.into_iter().flatten().filter_map(|(x, y)| px(x, y)).filter(|q| q[3] >= 128) {
let d2: f64 = (0..3).map(|k| (f64::from(q[k]) - f64::from(p[k])).powi(2)).sum();
let wt = 1.0 / (1.0 + d2 / (ALIKE * ALIKE)).powi(2);
for k in 0..3 {
sum[k] += wt * f64::from(q[k]);
}
weight += wt;
}
out.extend(sum.map(|v| (v / weight).round() as u8));
out.push(p[3]);
}
out
});
done.concat()
}
/// `img` with the noise within its tones smoothed away and its edges kept.
pub(crate) fn smoothed(img: &Raster) -> Raster {
let (w, h) = (img.width as usize, img.height as usize);
let mut rgba = img.rgba.clone();
if rgba.len() == w * h * 4 {
for _ in 0..3 {
rgba = pass(&pass(&rgba, w, h, true), w, h, false);
}
}
Raster { width: img.width, height: img.height, rgba }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn noise_goes_and_an_edge_stays() {
// Left half about 60, right half about 160, with noise of ±6 on both.
let img = Raster::from_fn(40, 20, |x, y| {
let mut n = x.wrapping_mul(0x9E37_79B1) ^ y.wrapping_mul(0x85EB_CA77);
n ^= n >> 15;
n = n.wrapping_mul(0x2C1B_3C6D);
let n = ((n >> 12) % 13) as u8;
let v = if x < 20 { 54 + n } else { 154 + n };
[v, v, v, 255]
});
let out = smoothed(&img);
let spread = |r: &Raster, x0: u32, x1: u32| {
let v: Vec<u8> = (0..20).flat_map(|y| (x0..x1).map(move |x| (x, y))).map(|(x, y)| r.pixel(x, y)[0]).collect();
v.iter().max().unwrap() - v.iter().min().unwrap()
};
assert_eq!(spread(&img, 0, 20), 12);
assert!(spread(&out, 0, 20) <= 6 && spread(&out, 20, 40) <= 6, "{} {}", spread(&out, 0, 20), spread(&out, 20, 40));
// The edge is still one pixel sharp, where it was.
assert!(out.pixel(19, 10)[0] < 70 && out.pixel(20, 10)[0] > 150);
}
#[test]
fn transparent_pixels_and_odd_sizes_pass_through() {
let img = Raster::from_fn(3, 1, |x, _| [10 * x as u8, 0, 0, if x == 1 { 0 } else { 255 }]);
let out = smoothed(&img);
assert_eq!(out.pixel(1, 0), [10, 0, 0, 0]);
assert_eq!((out.width, out.height, out.rgba.len()), (3, 1, 12));
let broken = Raster { width: 4, height: 4, rgba: vec![0; 7] };
assert_eq!(smoothed(&broken).rgba.len(), 7);
}
}
+42
View File
@@ -944,3 +944,45 @@ fn the_bands_of_a_gradient_stay_apart() {
});
assert!(quantize(&noisy, &sixteen_colors()).palette.len() >= 12);
}
#[test]
fn a_noisy_gradient_keeps_its_tones_without_specks() {
// Tones 0–255 across with noise larger than High Fidelity Photo's 64 steps are apart.
let img = Raster::from_fn(256, 96, |x, y| {
let c = |k: u32| (f64::from(x) + 5.0 * jitter(x, y, k)).round().clamp(0.0, 255.0) as u8;
[c(0), c(1), c(2), 255]
});
let params = preset("High Fidelity Photo").unwrap();
let res = trace(&img, &params);
// Smoothed first, the noise mixes fewer neighbouring tones into one (`quantize` alone works
// on the picture as it is), and leaves no specks.
let unsmoothed = quantize(&img, &params).palette.len();
assert!(res.palette.len() > unsmoothed && res.palette.len() >= 24, "{} colours, {unsmoothed} unsmoothed", res.palette.len());
assert!(res.paths.len() <= 2 * res.palette.len(), "{} paths of {} colours", res.paths.len(), res.palette.len());
}
#[test]
fn stacked_layers_go_by_lightness_and_leave_ignored_white_alone() {
// Stripes: black, white, mid gray, light gray (the widest).
let img = Raster::from_fn(100, 20, |x, _| {
let v = match x {
0..20 => 0,
20..40 => 255,
40..60 => 120,
_ => 200,
};
[v, v, v, 255]
});
let params = TraceParams { mode: Mode::Grayscale, colors: 4, noise: 0, method: Method::Overlapping, ..TraceParams::default() };
let tones = |res: &TraceResult| res.paths.iter().map(|p| p.color[0]).collect::<Vec<u8>>();
// The largest colour is light, so the stack starts at the light end.
// (Each layer reaches under those above it: under the black stripe too, a part of its own.)
assert_eq!(tones(&trace(&img, &params)), [255, 200, 200, 120, 120, 0]);
// Without white, no layer reaches under where it was.
let res = trace(&img, &TraceParams { ignore_white: true, ..params });
assert_eq!(tones(&res), [200, 200, 120, 120, 0]);
for p in &res.paths {
let b = p.path.bounds().unwrap();
assert!(b.x1 <= 20.5 || b.x0 >= 39.5, "a {} layer covers the white stripe: {b:?}", p.color[0]);
}
}
+1 -1
View File
@@ -239,7 +239,7 @@ fn quality_row(ui: &mut egui::Ui, d: &mut Dialog, label: &dyn Fn(&mut egui::Ui,
label(ui, tl!("Quality:"));
let mut q = (d.f64("quality", 90.0) / 10.0).round().clamp(0.0, 10.0) as u8;
ui.horizontal(|ui| {
if ui.add(egui::Slider::new(&mut q, 0..=10)).changed() {
if widgets::slider(ui, egui::Slider::new(&mut q, 0..=10)).changed() {
d.fields.insert("quality".into(), json!(u32::from(q) * 10));
}
label(ui, QUALITY_BANDS[q as usize]);
+1 -1
View File
@@ -632,7 +632,7 @@ fn slider_row(ui: &mut egui::Ui, d: &mut Dialog, key: &str, label: &str, suffix:
widgets::label_row(ui, label, LABEL_W, |ui| {
let mut v = d.f64(key, 0.0).round().clamp(0.0, 100.0) as u8;
ui.spacing_mut().slider_width = FIELD_W - 60.0;
if ui.add(egui::Slider::new(&mut v, 0..=100).suffix(suffix)).changed() {
if widgets::slider(ui, egui::Slider::new(&mut v, 0..=100).suffix(suffix)).changed() {
d.fields.insert(key.into(), json!(v));
}
});
+53 -3
View File
@@ -203,10 +203,14 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
}
}
let drag: Option<usize> = pstate(ui.ctx(), "tabs-drag");
// The stop within 5 px of `x`.
let hit_at = |x: f32| stops.iter().position(|s| (to_x(s.position) - x).abs() <= 5.0);
// egui reports the drag only once the pointer is a few pixels from the press, past the hit
// tolerance, so hit-test where the button went down (#1458).
if resp.drag_started()
&& let Some(pos) = resp.interact_pointer_pos()
&& let Some(pos) = ui.input(|i| i.pointer.press_origin()).or(resp.interact_pointer_pos())
{
let hit = stops.iter().position(|s| (to_x(s.position) - pos.x).abs() <= 5.0);
let hit = hit_at(pos.x);
set_pstate(ui.ctx(), "tabs-drag", hit);
if let Some(i) = hit {
set_pstate(ui.ctx(), "tabs-sel", i);
@@ -229,7 +233,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
} else if resp.clicked()
&& let Some(pos) = resp.interact_pointer_pos()
{
match stops.iter().position(|s| (to_x(s.position) - pos.x).abs() <= 5.0) {
match hit_at(pos.x) {
Some(i) => set_pstate(ui.ctx(), "tabs-sel", i),
None => {
let position = to_pt(pos.x);
@@ -359,6 +363,52 @@ mod tests {
}
}
/// #1458: a stop moves when the drag starts on it, even though egui only reports the drag once
/// the pointer is past its threshold (farther from the press than the 5 px hit tolerance).
#[test]
fn dragging_a_stop_moves_it() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.session.execute("file.new", &json!({})).unwrap();
app.session.execute("text.create", &json!({"x": 10, "y": 20, "text": "a b", "area": {"width": 300, "height": 100}})).unwrap();
app.session.execute("text.tabs.set", &json!({"stops": [{"position": 50}]})).unwrap();
let ctx = egui::Context::default();
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(320.0, 400.0));
// One frame with `events` → where the ruler's inch labels (9 pt) are painted.
let frame = |app: &mut VectorcraftApp, events: Vec<egui::Event>| {
let mut out = ctx.run_ui(egui::RawInput { screen_rect: Some(screen), events, ..Default::default() }, |ui| show(app, ui));
out.textures_delta.clear();
out.shapes
.iter()
.filter_map(|c| match &c.shape {
egui::Shape::Text(t) if t.galley.job.sections.first().is_some_and(|s| s.format.font_id.size == 9.0) => {
Some((t.galley.text().to_string(), t.pos))
}
_ => None,
})
.collect::<Vec<_>>()
};
let labels = frame(&mut app, vec![]);
let at = |label: &str| labels.iter().find(|(t, _)| t == label).unwrap_or_else(|| panic!("{label} in {labels:?}")).1;
// A label sits 2 px right of its tick: 0 and 1 inch give the ruler's scale.
let (zero, inch) = (at("0"), at("1"));
let to_x = |pt: f32| zero.x - 2.0 + pt * (inch.x - zero.x) / 72.0;
let y = zero.y + 15.0;
let button = |pos, pressed| egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
// Press 3 px right of the stop, then drag right; egui starts the drag 6+ px from the press.
let press = egui::pos2(to_x(50.0) + 3.0, y);
frame(&mut app, vec![egui::Event::PointerMoved(press), button(press, true)]);
for dx in [9.0, 20.0, 40.0] {
frame(&mut app, vec![egui::Event::PointerMoved(press + vec2(dx, 0.0))]);
}
let end = press + vec2(40.0, 0.0);
frame(&mut app, vec![button(end, false)]);
frame(&mut app, vec![]);
let stops = current(&app).unwrap().1;
assert_eq!(stops.len(), 1);
let moved_to = to_x(stops[0].position as f32);
assert!((moved_to - end.x).abs() < 1.0, "stop at {moved_to}, released at {}", end.x);
}
/// Position Panel Above Text floats the panel over the text, its ruler's 0 on the frame's left
/// edge and a stop's mark over the column it sets, at any zoom (#729).
#[test]
+2 -2
View File
@@ -274,7 +274,7 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) {
labeled(ui, sp.label, |ui| {
let mut x = v.as_f64().unwrap_or(min);
let new = if sp.key == "uiScaling" {
ui.add(egui::Slider::new(&mut x, min..=max).step_by(0.05).text(tl!("Smaller ↔ Larger"))).changed().then_some(x)
widgets::slider(ui, egui::Slider::new(&mut x, min..=max).step_by(0.05).text(tl!("Smaller ↔ Larger"))).changed().then_some(x)
} else {
widgets::range_field(ui, sp.key, x, min..=max, &format!(" {unit}"), 3, 110.0)
};
@@ -297,7 +297,7 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) {
labeled(ui, sp.label, |ui| {
let mut x = v.as_i64().unwrap_or(min);
let new = if sp.key == "anchorSize" {
ui.add(egui::Slider::new(&mut x, min..=max).show_value(false).text(tl!("Size"))).changed().then_some(x)
widgets::slider(ui, egui::Slider::new(&mut x, min..=max).show_value(false).text(tl!("Size"))).changed().then_some(x)
} else {
widgets::range_field(ui, sp.key, x as f64, min as f64..=max as f64, "", 0, 110.0).map(|x| x as i64)
};
+14 -1
View File
@@ -895,7 +895,7 @@ pub fn opacity_blend(
icons::paint(ui, "chevron-right", r.shrink2(vec2(3.0, 7.0)), if enabled { t.icon } else { t.text_disabled });
egui::Popup::menu(&resp).close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside).show(|ui| {
let mut o = opacity.unwrap_or(1.0) * 100.0;
let r = ui.add(egui::Slider::new(&mut o, 0.0..=100.0).show_value(false));
let r = slider(ui, egui::Slider::new(&mut o, 0.0..=100.0).show_value(false));
let phase = if r.drag_stopped() || (r.changed() && !r.dragged()) {
Live::Released
} else if r.changed() {
@@ -990,6 +990,19 @@ pub fn icon_button_enabled(ui: &mut Ui, icon: &str, tip: &str, selected: bool, e
if tip.is_empty() { resp } else { resp.on_hover_text(tl!(tip)) }
}
/// A slider whose rail shows, filled up to the handle: the theme's widget fill matches the panel
/// and dialog background, which leaves only the handle visible (#1483).
pub fn slider(ui: &mut Ui, slider: egui::Slider<'_>) -> Response {
let t = Tokens::get(ui.ctx());
ui.scope(|ui| {
let v = ui.visuals_mut();
v.widgets.inactive.bg_fill = t.input_border;
v.selection.bg_fill = t.accent;
ui.add(slider.trailing_fill(true))
})
.inner
}
/// Regular-weight panel sub-header ("Shape Modes:", "Align Objects:").
pub fn subheader(ui: &mut Ui, text: &str) {
let t = Tokens::get(ui.ctx());
+94 -93
View File
@@ -1,6 +1,6 @@
# Image Trace quality
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (the palette: 16 Colors on flat art 70 to 4.2 paths, High Fidelity Photo 3,636 to 639)
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (photos: High Fidelity Photo 3,653 paths to 121 with its colour error down from 2.30 to 1.75)
What Image Trace (`crates/trace`) makes of images whose right answer is known, measured by a harness and
compared with the numbers before each change. Nothing here is a judgement by eye, and nothing is compared
@@ -72,17 +72,17 @@ Each preset on the inputs it is meant for, all sizes and variants.
| Technical Drawing | polygons, star, letter A, lines | 2.66 | 13.45 | 0.201 | 1.37 | 105.76 | 4.8 | 32 | 2.23 | 100 | 85 | 5.9 | – | – | – | – | 0.1 | 0.0 |
| Flat Logo | two touching halves, four-colour badge | 0.43 | 3.53 | 3.106 | 88.50 | 0.78 | 69.9 | 352 | 38.92 | 38 | 80 | 237.0 | 0.0 | 0 | 13.8 | – | 59.1 | 0.2 |
### Now (sub-pixel edges and the palette)
### Now (sub-pixel edges, the palette, photo smoothing)
| preset | inputs | ΔE | edge ΔE | off mean | off max | missed max | paths | anchors | × art | lines % | corners % | kinks | odd colours | odd px | gap px | overlap px | specks | outside px |
|:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| Default | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.60 | 10.12 | 0.091 | 1.02 | 2.17 | 1.9 | 18 | 1.81 | 29 | 68 | 0.4 | – | – | 22.2 | 22.4 | 0.0 | 0.0 |
| High Fidelity Photo | gradient, photo-like | 2.37 | 2.59 | – | – | – | 638.7 | 25604 | – | 29 | – | – | – | – | 0.0 | – | 151.5 | 0.5 |
| Low Fidelity Photo | gradient, photo-like | 2.67 | 2.93 | – | – | – | 55.3 | 5019 | – | 36 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| High Fidelity Photo | gradient, photo-like | 1.75 | 1.98 | – | – | – | 120.7 | 9818 | – | 43 | – | – | – | – | 0.0 | – | 10.3 | 0.1 |
| Low Fidelity Photo | gradient, photo-like | 2.45 | 2.69 | – | – | – | 37.1 | 2132 | – | 44 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| 3 Colors | two touching halves | 0.43 | 4.32 | 0.087 | 0.74 | 0.56 | 2.0 | 9 | 1.57 | 39 | 100 | 0.0 | 0.0 | 0 | 48.6 | 29.4 | 0.0 | 0.0 |
| 6 Colors | two touching halves, four-colour badge | 0.56 | 5.01 | 0.137 | 1.09 | 1.14 | 4.0 | 50 | 3.15 | 40 | 88 | 3.6 | 0.0 | 0 | 39.0 | 35.7 | 0.0 | 0.0 |
| 16 Colors | two touching halves, four-colour badge | 0.55 | 4.89 | 0.133 | 1.09 | 1.15 | 4.2 | 66 | 3.97 | 41 | 88 | 4.4 | 0.0 | 0 | 34.5 | 26.4 | 0.0 | 0.0 |
| Shades of Gray | gradient, photo-like | 9.33 | 9.15 | – | – | – | 72.8 | 2681 | – | 50 | – | – | – | – | 0.0 | – | 1.9 | 0.8 |
| Shades of Gray | gradient, photo-like | 9.26 | 9.06 | – | – | – | 16.5 | 879 | – | 53 | – | – | – | – | 0.0 | – | 0.4 | 0.8 |
| Black and White Logo | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.58 | 9.39 | 0.064 | 1.00 | 2.18 | 1.4 | 27 | 2.73 | 40 | 67 | 0.3 | – | – | – | – | 0.0 | 0.0 |
| Sketched Art | circle, ellipses, badge with a hole, letter D, lines | 2.71 | 12.11 | 0.195 | 1.00 | 105.54 | 1.8 | 17 | 1.49 | 16 | 86 | 0.0 | – | – | – | – | 0.0 | 0.0 |
| Silhouettes | circle, ellipses, badge with a hole, letter D | 2.98 | 19.28 | 0.532 | 2.24 | 2.24 | 1.2 | 14 | 1.65 | 12 | 79 | 0.1 | – | – | – | – | 0.0 | 0.0 |
@@ -90,7 +90,7 @@ Each preset on the inputs it is meant for, all sizes and variants.
| Technical Drawing | polygons, star, letter A, lines | 2.53 | 9.47 | 0.086 | 0.99 | 105.85 | 4.8 | 57 | 3.73 | 69 | 74 | 0.7 | – | – | – | – | 0.3 | 0.0 |
| Flat Logo | two touching halves, four-colour badge | 0.43 | 3.53 | 3.106 | 88.50 | 0.78 | 69.9 | 352 | 38.92 | 38 | 80 | 237.0 | 0.0 | 0 | 13.8 | – | 59.1 | 0.2 |
The sections below are the numbers now (sub-pixel edges and the palette).
The sections below are the numbers now (sub-pixel edges, the palette, photo smoothing).
## 1. Each preset on the inputs it is meant for
@@ -99,12 +99,12 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
| preset | inputs | ΔE | edge ΔE | off mean | off max | missed max | paths | anchors | × art | lines % | corners % | kinks | odd colours | odd px | gap px | overlap px | specks | outside px |
|:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| Default | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.60 | 10.12 | 0.091 | 1.02 | 2.17 | 1.9 | 18 | 1.81 | 29 | 68 | 0.4 | – | – | 22.2 | 22.4 | 0.0 | 0.0 |
| High Fidelity Photo | gradient, photo-like | 2.37 | 2.59 | – | – | – | 638.7 | 25604 | – | 29 | – | – | – | – | 0.0 | – | 151.5 | 0.5 |
| Low Fidelity Photo | gradient, photo-like | 2.67 | 2.93 | – | – | – | 55.3 | 5019 | – | 36 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| High Fidelity Photo | gradient, photo-like | 1.75 | 1.98 | – | – | – | 120.7 | 9818 | – | 43 | – | – | – | – | 0.0 | – | 10.3 | 0.1 |
| Low Fidelity Photo | gradient, photo-like | 2.45 | 2.69 | – | – | – | 37.1 | 2132 | – | 44 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| 3 Colors | two touching halves | 0.43 | 4.32 | 0.087 | 0.74 | 0.56 | 2.0 | 9 | 1.57 | 39 | 100 | 0.0 | 0.0 | 0 | 48.6 | 29.4 | 0.0 | 0.0 |
| 6 Colors | two touching halves, four-colour badge | 0.56 | 5.01 | 0.137 | 1.09 | 1.14 | 4.0 | 50 | 3.15 | 40 | 88 | 3.6 | 0.0 | 0 | 39.0 | 35.7 | 0.0 | 0.0 |
| 16 Colors | two touching halves, four-colour badge | 0.55 | 4.89 | 0.133 | 1.09 | 1.15 | 4.2 | 66 | 3.97 | 41 | 88 | 4.4 | 0.0 | 0 | 34.5 | 26.4 | 0.0 | 0.0 |
| Shades of Gray | gradient, photo-like | 9.33 | 9.15 | – | – | – | 72.8 | 2681 | – | 50 | – | – | – | – | 0.0 | – | 1.9 | 0.8 |
| Shades of Gray | gradient, photo-like | 9.26 | 9.06 | – | – | – | 16.5 | 879 | – | 53 | – | – | – | – | 0.0 | – | 0.4 | 0.8 |
| Black and White Logo | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.58 | 9.39 | 0.064 | 1.00 | 2.18 | 1.4 | 27 | 2.73 | 40 | 67 | 0.3 | – | – | – | – | 0.0 | 0.0 |
| Sketched Art | circle, ellipses, badge with a hole, letter D, lines | 2.71 | 12.11 | 0.195 | 1.00 | 105.54 | 1.8 | 17 | 1.49 | 16 | 86 | 0.0 | – | – | – | – | 0.0 | 0.0 |
| Silhouettes | circle, ellipses, badge with a hole, letter D | 2.98 | 19.28 | 0.532 | 2.24 | 2.24 | 1.2 | 14 | 1.65 | 12 | 79 | 0.1 | – | – | – | – | 0.0 | 0.0 |
@@ -123,34 +123,34 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
| Default | gradient, photo | 39.13 | 38.78 | – | – | – | 3.2 | 698 | – | 57 | – | – | – | – | 31.2 | 40.7 | 0.0 | 0.3 |
| High Fidelity Photo | curved shapes | 0.04 | 1.74 | 0.026 | 0.16 | 0.16 | 1.2 | 20 | 2.17 | 21 | 100 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
| High Fidelity Photo | polygons | 0.05 | 1.92 | 0.031 | 0.51 | 1.34 | 1.7 | 17 | 1.50 | 87 | 88 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
| High Fidelity Photo | thin lines | 0.17 | 2.58 | 0.135 | 0.85 | 4.31 | 33.3 | 264 | 13.20 | 46 | – | – | 20.7 | 425 | 0.0 | – | 3.3 | 0.0 |
| High Fidelity Photo | flat colour art | 0.08 | 2.31 | 0.033 | 0.20 | 0.20 | 4.0 | 31 | 2.50 | 38 | 100 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
| High Fidelity Photo | gradient, photo | 2.46 | 2.67 | – | – | – | 428.3 | 22245 | – | 30 | – | – | – | – | 0.0 | – | 54.5 | 0.3 |
| High Fidelity Photo | thin lines | 0.34 | 6.00 | 0.076 | 0.85 | 4.31 | 40.3 | 333 | 16.67 | 37 | – | – | 27.3 | 4489 | 0.0 | – | 19.0 | 0.0 |
| High Fidelity Photo | flat colour art | 0.14 | 4.17 | 0.028 | 0.20 | 0.20 | 3.5 | 71 | 4.44 | 35 | 100 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
| High Fidelity Photo | gradient, photo | 1.80 | 2.05 | – | – | – | 134.7 | 11186 | – | 43 | – | – | – | – | 0.0 | – | 7.7 | 0.1 |
| Low Fidelity Photo | curved shapes | 0.06 | 2.65 | 0.045 | 0.27 | 0.27 | 1.2 | 14 | 1.53 | 19 | 100 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
| Low Fidelity Photo | polygons | 0.06 | 2.25 | 0.037 | 0.51 | 1.34 | 1.7 | 14 | 1.25 | 86 | 88 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
| Low Fidelity Photo | thin lines | 0.47 | 4.44 | 0.140 | 0.86 | 45.38 | 5.0 | 37 | 1.85 | 61 | – | – | 1.0 | 265 | 0.0 | – | 0.0 | 0.0 |
| Low Fidelity Photo | flat colour art | 0.09 | 2.61 | 0.048 | 0.27 | 0.27 | 4.0 | 25 | 1.88 | 36 | 100 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
| Low Fidelity Photo | gradient, photo | 2.77 | 3.04 | – | – | – | 52.7 | 5273 | – | 35 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | thin lines | 0.51 | 5.73 | 0.081 | 0.86 | 45.38 | 7.0 | 56 | 2.80 | 47 | – | – | 4.0 | 3974 | 0.0 | – | 0.0 | 0.0 |
| Low Fidelity Photo | flat colour art | 0.15 | 4.33 | 0.046 | 0.27 | 0.27 | 3.5 | 55 | 3.38 | 32 | 100 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
| Low Fidelity Photo | gradient, photo | 2.53 | 2.80 | – | – | – | 42.7 | 2289 | – | 43 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| 3 Colors | curved shapes | 0.06 | 2.65 | 0.046 | 0.33 | 0.24 | 1.8 | 16 | 1.77 | 16 | 100 | 0.0 | 0.0 | 0 | 18.7 | 15.2 | 0.0 | 0.0 |
| 3 Colors | polygons | 0.06 | 2.25 | 0.037 | 0.51 | 1.34 | 2.0 | 15 | 1.35 | 86 | 88 | 0.0 | 0.0 | 0 | 5.1 | 4.7 | 0.0 | 0.0 |
| 3 Colors | thin lines | 0.44 | 4.40 | 0.107 | 0.90 | 45.38 | 5.0 | 44 | 2.22 | 49 | – | – | 2.0 | 290 | 23.8 | 27.4 | 0.0 | 0.0 |
| 3 Colors | flat colour art | 1.32 | 3.85 | 0.113 | 0.90 | 60.22 | 2.5 | 24 | 1.77 | 46 | 100 | 0.0 | 1.0 | 9919 | 30.3 | 31.0 | 0.0 | 0.0 |
| 3 Colors | gradient, photo | 7.49 | 7.60 | – | – | – | 5.0 | 801 | – | 50 | – | – | – | – | 30.2 | 45.2 | 0.0 | 0.2 |
| 3 Colors | gradient, photo | 7.49 | 7.60 | – | – | – | 5.7 | 532 | – | 48 | – | – | – | – | 25.5 | 26.9 | 0.0 | 0.2 |
| 6 Colors | curved shapes | 0.06 | 2.38 | 0.042 | 0.28 | 0.24 | 1.8 | 17 | 1.86 | 17 | 100 | 0.0 | 0.0 | 0 | 17.9 | 14.3 | 0.0 | 0.0 |
| 6 Colors | polygons | 0.05 | 2.07 | 0.034 | 0.51 | 1.34 | 2.0 | 16 | 1.42 | 86 | 88 | 0.0 | 0.0 | 0 | 3.9 | 3.9 | 0.0 | 0.0 |
| 6 Colors | thin lines | 0.50 | 4.54 | 0.096 | 0.86 | 45.38 | 4.0 | 32 | 1.60 | 53 | – | – | 1.7 | 398 | 18.5 | 22.8 | 0.0 | 0.0 |
| 6 Colors | flat colour art | 0.12 | 2.66 | 0.044 | 0.25 | 0.20 | 3.0 | 28 | 2.00 | 43 | 100 | 0.0 | 0.0 | 0 | 21.9 | 21.2 | 0.0 | 0.0 |
| 6 Colors | gradient, photo | 5.03 | 5.30 | – | – | – | 20.7 | 1735 | – | 40 | – | – | – | – | 61.6 | 102.9 | 0.0 | 0.2 |
| 6 Colors | gradient, photo | 5.01 | 5.30 | – | – | – | 13.0 | 833 | – | 36 | – | – | – | – | 42.6 | 37.0 | 0.0 | 0.2 |
| 16 Colors | curved shapes | 0.05 | 2.26 | 0.038 | 0.26 | 0.23 | 1.8 | 18 | 1.93 | 17 | 100 | 0.0 | 0.0 | 0 | 14.9 | 12.5 | 0.0 | 0.0 |
| 16 Colors | polygons | 0.05 | 2.02 | 0.034 | 0.51 | 1.34 | 2.0 | 16 | 1.44 | 86 | 88 | 0.0 | 0.0 | 0 | 3.3 | 4.2 | 0.0 | 0.0 |
| 16 Colors | thin lines | 0.41 | 3.87 | 0.103 | 0.86 | 23.65 | 8.7 | 50 | 2.52 | 51 | – | – | 5.7 | 305 | 18.4 | 25.4 | 0.0 | 0.0 |
| 16 Colors | flat colour art | 0.12 | 2.53 | 0.041 | 0.24 | 0.20 | 3.0 | 28 | 2.07 | 44 | 100 | 0.0 | 0.0 | 0 | 19.8 | 19.5 | 0.0 | 0.0 |
| 16 Colors | gradient, photo | 3.12 | 3.44 | – | – | – | 50.7 | 3313 | – | 34 | – | – | – | – | 121.9 | 183.8 | 0.0 | 0.2 |
| 16 Colors | gradient, photo | 3.02 | 3.33 | – | – | – | 47.2 | 2154 | – | 29 | – | – | – | – | 89.7 | 80.2 | 0.2 | 0.2 |
| Shades of Gray | curved shapes | 0.06 | 2.72 | 0.046 | 0.48 | 0.65 | 1.2 | 14 | 1.56 | 18 | 92 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
| Shades of Gray | polygons | 0.05 | 1.97 | 0.033 | 0.42 | 1.26 | 1.7 | 14 | 1.21 | 89 | 91 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
| Shades of Gray | thin lines | 0.49 | 4.80 | 0.142 | 0.86 | 45.38 | 9.0 | 54 | 2.70 | 63 | – | – | 6.0 | 472 | 0.0 | – | 0.0 | 0.0 |
| Shades of Gray | flat colour art | 7.34 | 10.80 | 0.384 | 1.40 | 179.38 | 12.5 | 108 | 9.54 | 43 | 50 | 30.0 | 11.2 | 39147 | 0.0 | – | 0.0 | 0.0 |
| Shades of Gray | gradient, photo | 9.73 | 9.61 | – | – | – | 81.8 | 3187 | – | 51 | – | – | – | – | 0.0 | – | 2.2 | 0.8 |
| Shades of Gray | thin lines | 0.56 | 6.69 | 0.078 | 0.86 | 45.38 | 10.3 | 71 | 3.57 | 49 | – | – | 8.0 | 4283 | 0.0 | – | 0.0 | 0.0 |
| Shades of Gray | flat colour art | 7.33 | 11.25 | 0.264 | 1.47 | 179.38 | 4.7 | 214 | 19.86 | 24 | 50 | 0.3 | 3.7 | 173356 | 0.0 | – | 0.0 | 0.0 |
| Shades of Gray | gradient, photo | 9.71 | 9.55 | – | – | – | 18.2 | 955 | – | 53 | – | – | – | – | 0.0 | – | 0.5 | 0.8 |
| Black and White Logo | curved shapes | 0.04 | 1.52 | 0.024 | 0.50 | 0.60 | 1.2 | 21 | 2.25 | 21 | 75 | 0.0 | – | – | – | – | 0.0 | 0.0 |
| Black and White Logo | polygons | 0.04 | 1.83 | 0.030 | 0.45 | 1.23 | 1.7 | 15 | 1.34 | 89 | 90 | 0.0 | – | – | – | – | 0.0 | 0.0 |
| Black and White Logo | thin lines | 0.50 | 4.22 | 0.065 | 0.50 | 77.21 | 10.3 | 66 | 3.30 | 65 | – | – | – | – | – | – | 0.0 | 0.0 |
@@ -191,16 +191,16 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
| Default | blurred | 0.82 | 16.65 | 0.078 | 0.86 | 2.17 | 1.9 | 26 | 2.50 | 7 | 19 | 1.6 | – | – | 23.0 | 23.6 | 0.0 | 0.0 |
| Default | noisy | 1.74 | 4.55 | 0.052 | 0.57 | 1.23 | 1.9 | 16 | 1.64 | 38 | 67 | 0.0 | – | – | 16.2 | 15.1 | 0.0 | 0.0 |
| Default | scanned | 15.06 | 20.24 | 0.134 | 0.87 | 2.17 | 1.9 | 26 | 2.53 | 0 | 25 | 1.0 | – | – | 20.8 | 24.7 | 0.0 | 0.0 |
| High Fidelity Photo | clean | 2.46 | 2.67 | – | – | – | 428.3 | 22245 | – | 30 | – | – | – | – | 0.0 | – | 54.5 | 0.3 |
| High Fidelity Photo | aliased | 2.32 | 2.54 | – | – | – | 239.0 | 15797 | – | 28 | – | – | – | – | 0.0 | – | 25.5 | 0.3 |
| High Fidelity Photo | blurred | 1.84 | 2.29 | – | – | – | 406.0 | 23561 | – | 24 | – | – | – | – | 0.0 | – | 39.0 | 0.1 |
| High Fidelity Photo | noisy | 2.84 | 3.01 | – | – | – | 1455.0 | 39709 | – | 20 | – | – | – | – | 0.0 | – | 479.0 | 0.5 |
| High Fidelity Photo | scanned | 2.19 | 2.29 | – | – | – | 1086.0 | 33427 | – | 38 | – | – | – | – | 0.0 | – | 353.5 | 0.3 |
| Low Fidelity Photo | clean | 2.77 | 3.04 | – | – | – | 52.7 | 5273 | – | 35 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | aliased | 2.83 | 3.08 | – | – | – | 64.0 | 3798 | – | 36 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | blurred | 2.47 | 2.92 | – | – | – | 40.5 | 3229 | – | 31 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | noisy | 2.99 | 3.17 | – | – | – | 72.5 | 6536 | – | 38 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | scanned | 2.09 | 2.19 | – | – | – | 52.0 | 5754 | – | 41 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| High Fidelity Photo | clean | 1.80 | 2.05 | – | – | – | 134.7 | 11186 | – | 43 | – | – | – | – | 0.0 | – | 7.7 | 0.1 |
| High Fidelity Photo | aliased | 1.78 | 2.01 | – | – | – | 73.0 | 7695 | – | 45 | – | – | – | – | 0.0 | – | 5.5 | 0.1 |
| High Fidelity Photo | blurred | 1.59 | 1.95 | – | – | – | 72.0 | 7184 | – | 45 | – | – | – | – | 0.0 | – | 0.5 | 0.1 |
| High Fidelity Photo | noisy | 2.02 | 2.19 | – | – | – | 155.5 | 10562 | – | 38 | – | – | – | – | 0.0 | – | 18.5 | 0.1 |
| High Fidelity Photo | scanned | 1.45 | 1.55 | – | – | – | 140.5 | 9726 | – | 41 | – | – | – | – | 0.0 | – | 24.5 | 0.1 |
| Low Fidelity Photo | clean | 2.53 | 2.80 | – | – | – | 42.7 | 2289 | – | 43 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | aliased | 2.53 | 2.78 | – | – | – | 36.5 | 1504 | – | 46 | – | – | – | – | 0.0 | – | 0.0 | 0.1 |
| Low Fidelity Photo | blurred | 2.35 | 2.75 | – | – | – | 29.0 | 2004 | – | 45 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | noisy | 2.70 | 2.85 | – | – | – | 32.0 | 1960 | – | 46 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | scanned | 1.95 | 2.05 | – | – | – | 34.5 | 2588 | – | 42 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| 3 Colors | clean | 0.07 | 2.53 | 0.051 | 0.24 | 0.14 | 2.0 | 10 | 1.61 | 39 | 100 | 0.0 | 0.0 | 0 | 40.0 | 25.5 | 0.0 | 0.0 |
| 3 Colors | aliased | 0.18 | 9.81 | 0.236 | 0.74 | 0.56 | 2.0 | 7 | 1.17 | 39 | 100 | 0.0 | 0.0 | 0 | 123.2 | 82.4 | 0.0 | 0.0 |
| 3 Colors | blurred | 0.33 | 7.80 | 0.088 | 0.21 | 0.16 | 2.0 | 10 | 1.67 | 39 | 100 | 0.0 | 0.0 | 0 | 29.7 | 18.6 | 0.0 | 0.0 |
@@ -216,11 +216,11 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
| 16 Colors | blurred | 0.62 | 9.37 | 0.250 | 1.08 | 1.15 | 7.0 | 157 | 8.43 | 44 | 50 | 11.5 | 0.0 | 0 | 27.8 | 38.2 | 0.0 | 0.0 |
| 16 Colors | noisy | 0.85 | 2.34 | 0.042 | 0.27 | 0.22 | 3.0 | 32 | 2.27 | 34 | 100 | 0.0 | 0.0 | 0 | 23.4 | 15.6 | 0.0 | 0.0 |
| 16 Colors | scanned | 1.74 | 4.56 | 0.243 | 1.09 | 0.96 | 7.5 | 156 | 8.53 | 32 | 62 | 19.0 | 0.0 | 0 | 53.3 | 20.8 | 0.0 | 0.0 |
| Shades of Gray | clean | 9.73 | 9.61 | – | – | – | 81.8 | 3187 | – | 51 | – | – | – | – | 0.0 | – | 2.2 | 0.8 |
| Shades of Gray | aliased | 9.73 | 9.58 | – | – | – | 80.0 | 2429 | – | 54 | – | – | – | – | 0.0 | – | 3.0 | 0.8 |
| Shades of Gray | blurred | 9.69 | 9.39 | – | – | – | 62.0 | 1644 | – | 50 | – | – | – | – | 0.0 | – | 1.0 | 0.7 |
| Shades of Gray | noisy | 9.79 | 9.50 | – | – | – | 72.5 | 2703 | – | 50 | – | – | – | – | 0.0 | – | 2.0 | 0.2 |
| Shades of Gray | scanned | 6.87 | 6.77 | – | – | – | 49.5 | 2430 | – | 44 | – | – | – | – | 0.0 | – | 1.0 | 0.2 |
| Shades of Gray | clean | 9.71 | 9.55 | – | – | – | 18.2 | 955 | – | 53 | – | – | – | – | 0.0 | – | 0.5 | 0.8 |
| Shades of Gray | aliased | 9.70 | 9.52 | – | – | – | 11.0 | 788 | – | 58 | – | – | – | – | 0.0 | – | 0.5 | 0.7 |
| Shades of Gray | blurred | 9.66 | 9.32 | – | – | – | 11.5 | 729 | – | 56 | – | – | – | – | 0.0 | – | 0.5 | 0.7 |
| Shades of Gray | noisy | 9.74 | 9.44 | – | – | – | 19.5 | 823 | – | 47 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Shades of Gray | scanned | 6.58 | 6.46 | – | – | – | 19.0 | 947 | – | 49 | – | – | – | – | 0.0 | – | 0.5 | 0.2 |
| Black and White Logo | clean | 0.04 | 1.65 | 0.027 | 0.50 | 1.23 | 1.4 | 18 | 1.86 | 50 | 86 | 0.0 | – | – | – | – | 0.0 | 0.0 |
| Black and White Logo | aliased | 0.39 | 20.41 | 0.176 | 1.00 | 1.34 | 1.4 | 24 | 2.47 | 53 | 98 | 0.0 | – | – | – | – | 0.0 | 0.0 |
| Black and White Logo | blurred | 0.81 | 16.37 | 0.047 | 0.80 | 2.18 | 1.4 | 39 | 3.78 | 23 | 19 | 1.0 | – | – | – | – | 0.0 | 0.0 |
@@ -284,18 +284,18 @@ One trace at a time on this machine (all cores); memory is the peak allocated ab
|:--|:--|--:|:--|
| Default | photo-like | 1 | 0.05 s, 29 paths, 7947 anchors, 15 MB |
| Default | four-colour badge | 1 | 0.03 s, 3 paths, 106 anchors, 15 MB |
| High Fidelity Photo | photo-like | 1 | 0.42 s, 4298 paths, 260070 anchors, 143 MB |
| High Fidelity Photo | four-colour badge | 1 | 0.05 s, 7 paths, 66 anchors, 34 MB |
| Low Fidelity Photo | photo-like | 1 | 0.14 s, 245 paths, 52047 anchors, 118 MB |
| Low Fidelity Photo | four-colour badge | 1 | 0.05 s, 7 paths, 54 anchors, 34 MB |
| 3 Colors | photo-like | 1 | 0.07 s, 11 paths, 7582 anchors, 22 MB |
| 3 Colors | four-colour badge | 1 | 0.04 s, 4 paths, 79 anchors, 21 MB |
| 6 Colors | photo-like | 1 | 0.09 s, 143 paths, 20207 anchors, 38 MB |
| High Fidelity Photo | photo-like | 1 | 0.25 s, 968 paths, 113205 anchors, 238 MB |
| High Fidelity Photo | four-colour badge | 1 | 0.06 s, 6 paths, 192 anchors, 34 MB |
| Low Fidelity Photo | photo-like | 1 | 0.13 s, 129 paths, 22788 anchors, 141 MB |
| Low Fidelity Photo | four-colour badge | 1 | 0.05 s, 6 paths, 144 anchors, 36 MB |
| 3 Colors | photo-like | 1 | 0.07 s, 9 paths, 3391 anchors, 26 MB |
| 3 Colors | four-colour badge | 1 | 0.05 s, 4 paths, 79 anchors, 21 MB |
| 6 Colors | photo-like | 1 | 0.08 s, 78 paths, 9239 anchors, 42 MB |
| 6 Colors | four-colour badge | 1 | 0.05 s, 5 paths, 89 anchors, 33 MB |
| 16 Colors | photo-like | 1 | 0.12 s, 403 paths, 39143 anchors, 95 MB |
| 16 Colors | photo-like | 1 | 0.11 s, 178 paths, 21069 anchors, 102 MB |
| 16 Colors | four-colour badge | 1 | 0.05 s, 5 paths, 92 anchors, 33 MB |
| Shades of Gray | photo-like | 1 | 0.12 s, 447 paths, 31045 anchors, 53 MB |
| Shades of Gray | four-colour badge | 1 | 0.05 s, 36 paths, 292 anchors, 51 MB |
| Shades of Gray | photo-like | 1 | 0.09 s, 82 paths, 8511 anchors, 65 MB |
| Shades of Gray | four-colour badge | 1 | 0.06 s, 10 paths, 780 anchors, 56 MB |
| Black and White Logo | photo-like | 1 | 0.05 s, 71 paths, 9067 anchors, 9 MB |
| Black and White Logo | four-colour badge | 1 | 0.02 s, 1 paths, 58 anchors, 8 MB |
| Sketched Art | photo-like | 1 | 0.05 s, 5 paths, 3207 anchors, 9 MB |
@@ -307,64 +307,65 @@ One trace at a time on this machine (all cores); memory is the peak allocated ab
| Technical Drawing | photo-like | 1 | 0.06 s, 221 paths, 10646 anchors, 9 MB |
| Technical Drawing | four-colour badge | 1 | 0.02 s, 1 paths, 58 anchors, 8 MB |
| Flat Logo | photo-like | 1 | refused after 0.09 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 1 | 0.65 s, 5 paths, 78 anchors, 146 MB |
| Default | photo-like | 4 | 0.21 s, 48 paths, 20717 anchors, 60 MB |
| Flat Logo | four-colour badge | 1 | 0.64 s, 5 paths, 78 anchors, 146 MB |
| Default | photo-like | 4 | 0.19 s, 48 paths, 20717 anchors, 60 MB |
| Default | four-colour badge | 4 | 0.10 s, 3 paths, 148 anchors, 64 MB |
| High Fidelity Photo | photo-like | 4 | 1.75 s, 27583 paths, 1089750 anchors, 784 MB |
| High Fidelity Photo | four-colour badge | 4 | 0.22 s, 7 paths, 93 anchors, 126 MB |
| Low Fidelity Photo | photo-like | 4 | 0.46 s, 764 paths, 138552 anchors, 469 MB |
| Low Fidelity Photo | four-colour badge | 4 | 0.22 s, 7 paths, 74 anchors, 126 MB |
| 3 Colors | photo-like | 4 | 0.27 s, 22 paths, 16576 anchors, 88 MB |
| 3 Colors | four-colour badge | 4 | 0.17 s, 4 paths, 122 anchors, 84 MB |
| 6 Colors | photo-like | 4 | 0.30 s, 358 paths, 54088 anchors, 153 MB |
| 6 Colors | four-colour badge | 4 | 0.18 s, 5 paths, 116 anchors, 128 MB |
| 16 Colors | photo-like | 4 | 0.41 s, 1475 paths, 113487 anchors, 393 MB |
| 16 Colors | four-colour badge | 4 | 0.17 s, 5 paths, 124 anchors, 128 MB |
| Shades of Gray | photo-like | 4 | 0.38 s, 1289 paths, 83191 anchors, 212 MB |
| Shades of Gray | four-colour badge | 4 | 0.25 s, 37 paths, 428 anchors, 197 MB |
| Black and White Logo | photo-like | 4 | 0.22 s, 208 paths, 25916 anchors, 36 MB |
| Black and White Logo | four-colour badge | 4 | 0.09 s, 1 paths, 64 anchors, 36 MB |
| Sketched Art | photo-like | 4 | 0.22 s, 9 paths, 6875 anchors, 36 MB |
| Sketched Art | four-colour badge | 4 | 0.09 s, 2 paths, 279 anchors, 36 MB |
| High Fidelity Photo | photo-like | 4 | 0.88 s, 4475 paths, 344985 anchors, 964 MB |
| High Fidelity Photo | four-colour badge | 4 | 0.22 s, 6 paths, 216 anchors, 152 MB |
| Low Fidelity Photo | photo-like | 4 | 0.38 s, 304 paths, 61608 anchors, 592 MB |
| Low Fidelity Photo | four-colour badge | 4 | 0.23 s, 6 paths, 169 anchors, 152 MB |
| 3 Colors | photo-like | 4 | 0.28 s, 20 paths, 8647 anchors, 104 MB |
| 3 Colors | four-colour badge | 4 | 0.18 s, 4 paths, 122 anchors, 84 MB |
| 6 Colors | photo-like | 4 | 0.27 s, 115 paths, 24075 anchors, 165 MB |
| 6 Colors | four-colour badge | 4 | 0.19 s, 5 paths, 116 anchors, 128 MB |
| 16 Colors | photo-like | 4 | 0.33 s, 353 paths, 54215 anchors, 380 MB |
| 16 Colors | four-colour badge | 4 | 0.18 s, 5 paths, 124 anchors, 128 MB |
| Shades of Gray | photo-like | 4 | 0.29 s, 137 paths, 22704 anchors, 232 MB |
| Shades of Gray | four-colour badge | 4 | 0.27 s, 10 paths, 1234 anchors, 220 MB |
| Black and White Logo | photo-like | 4 | 0.21 s, 208 paths, 25916 anchors, 36 MB |
| Black and White Logo | four-colour badge | 4 | 0.08 s, 1 paths, 64 anchors, 36 MB |
| Sketched Art | photo-like | 4 | 0.21 s, 9 paths, 6875 anchors, 36 MB |
| Sketched Art | four-colour badge | 4 | 0.08 s, 2 paths, 279 anchors, 36 MB |
| Silhouettes | photo-like | 4 | 0.18 s, 2 paths, 2133 anchors, 44 MB |
| Silhouettes | four-colour badge | 4 | 0.09 s, 1 paths, 32 anchors, 36 MB |
| Silhouettes | four-colour badge | 4 | 0.10 s, 1 paths, 32 anchors, 36 MB |
| Line Art | photo-like | 4 | 0.21 s, 127 paths, 12595 anchors, 36 MB |
| Line Art | four-colour badge | 4 | 0.09 s, 1 paths, 465 anchors, 36 MB |
| Technical Drawing | photo-like | 4 | 0.23 s, 931 paths, 33946 anchors, 36 MB |
| Technical Drawing | four-colour badge | 4 | 0.09 s, 1 paths, 64 anchors, 36 MB |
| Line Art | four-colour badge | 4 | 0.08 s, 1 paths, 465 anchors, 36 MB |
| Technical Drawing | photo-like | 4 | 0.22 s, 931 paths, 33946 anchors, 36 MB |
| Technical Drawing | four-colour badge | 4 | 0.08 s, 1 paths, 64 anchors, 36 MB |
| Flat Logo | photo-like | 4 | refused after 0.10 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 4 | 0.65 s, 5 paths, 69 anchors, 146 MB |
| Default | photo-like | 16 | 1.13 s, 127 paths, 46199 anchors, 256 MB |
| Default | four-colour badge | 16 | 0.51 s, 3 paths, 284 anchors, 240 MB |
| High Fidelity Photo | photo-like | 16 | refused after 5.01 s (over 2,000,000 anchors), 3055 MB |
| High Fidelity Photo | four-colour badge | 16 | 1.00 s, 7 paths, 100 anchors, 504 MB |
| Low Fidelity Photo | photo-like | 16 | 1.60 s, 3759 paths, 363242 anchors, 1757 MB |
| Low Fidelity Photo | four-colour badge | 16 | 1.01 s, 7 paths, 79 anchors, 504 MB |
| 3 Colors | photo-like | 16 | 1.30 s, 118 paths, 41993 anchors, 352 MB |
| 3 Colors | four-colour badge | 16 | 0.77 s, 4 paths, 179 anchors, 336 MB |
| 6 Colors | photo-like | 16 | 1.34 s, 1677 paths, 155283 anchors, 601 MB |
| 6 Colors | four-colour badge | 16 | 0.79 s, 5 paths, 118 anchors, 500 MB |
| 16 Colors | photo-like | 16 | 1.73 s, 8717 paths, 394936 anchors, 1571 MB |
| 16 Colors | four-colour badge | 16 | 0.82 s, 5 paths, 120 anchors, 512 MB |
| Shades of Gray | photo-like | 16 | 1.50 s, 5996 paths, 256732 anchors, 789 MB |
| Shades of Gray | four-colour badge | 16 | 1.26 s, 38 paths, 593 anchors, 692 MB |
| Black and White Logo | photo-like | 16 | 1.16 s, 1128 paths, 77629 anchors, 144 MB |
| Black and White Logo | four-colour badge | 16 | 0.42 s, 1 paths, 103 anchors, 128 MB |
| Sketched Art | photo-like | 16 | 1.13 s, 15 paths, 17817 anchors, 144 MB |
| Sketched Art | four-colour badge | 16 | 0.45 s, 2 paths, 478 anchors, 128 MB |
| Silhouettes | photo-like | 16 | 1.37 s, 3 paths, 4891 anchors, 176 MB |
| Silhouettes | four-colour badge | 16 | 0.54 s, 1 paths, 44 anchors, 144 MB |
| Line Art | photo-like | 16 | 1.22 s, 713 paths, 64481 anchors, 144 MB |
| Flat Logo | four-colour badge | 4 | 0.66 s, 5 paths, 69 anchors, 146 MB |
| Default | photo-like | 16 | 1.08 s, 127 paths, 46199 anchors, 256 MB |
| Default | four-colour badge | 16 | 0.46 s, 3 paths, 284 anchors, 240 MB |
| High Fidelity Photo | photo-like | 16 | 3.48 s, 31131 paths, 1254672 anchors, 3954 MB |
| High Fidelity Photo | four-colour badge | 16 | 1.07 s, 6 paths, 248 anchors, 576 MB |
| Low Fidelity Photo | photo-like | 16 | 1.57 s, 2036 paths, 185091 anchors, 2050 MB |
| Low Fidelity Photo | four-colour badge | 16 | 1.08 s, 6 paths, 188 anchors, 576 MB |
| 3 Colors | photo-like | 16 | 1.27 s, 76 paths, 23365 anchors, 416 MB |
| 3 Colors | four-colour badge | 16 | 0.80 s, 4 paths, 179 anchors, 336 MB |
| 6 Colors | photo-like | 16 | 1.17 s, 470 paths, 63030 anchors, 673 MB |
| 6 Colors | four-colour badge | 16 | 0.84 s, 5 paths, 118 anchors, 512 MB |
| 16 Colors | photo-like | 16 | 1.30 s, 1973 paths, 149717 anchors, 1633 MB |
| 16 Colors | four-colour badge | 16 | 0.84 s, 5 paths, 120 anchors, 504 MB |
| Shades of Gray | photo-like | 16 | 1.32 s, 688 paths, 65052 anchors, 850 MB |
| Shades of Gray | four-colour badge | 16 | 1.28 s, 9 paths, 2296 anchors, 800 MB |
| Black and White Logo | photo-like | 16 | 1.04 s, 1128 paths, 77629 anchors, 144 MB |
| Black and White Logo | four-colour badge | 16 | 0.45 s, 1 paths, 103 anchors, 128 MB |
| Sketched Art | photo-like | 16 | 1.15 s, 15 paths, 17817 anchors, 144 MB |
| Sketched Art | four-colour badge | 16 | 0.48 s, 2 paths, 478 anchors, 128 MB |
| Silhouettes | photo-like | 16 | 1.40 s, 3 paths, 4891 anchors, 176 MB |
| Silhouettes | four-colour badge | 16 | 0.55 s, 1 paths, 44 anchors, 144 MB |
| Line Art | photo-like | 16 | 1.21 s, 713 paths, 64481 anchors, 144 MB |
| Line Art | four-colour badge | 16 | 0.45 s, 1 paths, 786 anchors, 128 MB |
| Technical Drawing | photo-like | 16 | 1.37 s, 4578 paths, 114292 anchors, 144 MB |
| Technical Drawing | four-colour badge | 16 | 0.43 s, 1 paths, 103 anchors, 128 MB |
| Technical Drawing | photo-like | 16 | 1.20 s, 4578 paths, 114292 anchors, 144 MB |
| Technical Drawing | four-colour badge | 16 | 0.44 s, 1 paths, 103 anchors, 128 MB |
| Flat Logo | photo-like | 16 | refused after 0.11 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 16 | 0.67 s, 5 paths, 67 anchors, 146 MB |
| Flat Logo | four-colour badge | 16 | 0.66 s, 5 paths, 67 anchors, 146 MB |
## Revision history
| date | change |
|:--|:--|
| 2026-10-13 | Photos: continuous-tone images are smoothed within their tones before quantising, and stacked layers go by lightness |
| 2026-10-13 | The palette: made of the picture's own colours, noise-made colours merged, blend pixels settled, cuts where colours differ most, colours compared in Oklab |
| 2026-10-13 | Sub-pixel edges and the outline fitter in Black and White, Grayscale and Color; the harness gains an aliased variant and an "outside px" column, so the baseline was measured again with it (same tracer, main at `baf9693f`) |
| 2026-10-12 | The harness and the baseline: every preset measured before any change to the tracer |
+3 -2
View File
@@ -1,6 +1,6 @@
# Parity with Adobe Illustrator
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 29/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 29/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
The authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it; [`gaps.md`](gaps.md) is
the work list made from it. Deep areas have their own checklists: [UI and interaction](ui-parity.md),
@@ -468,7 +468,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- **Interface languages, vertical type:** Preferences ▸ User Interface (or VectorCraft ▸ Language on macOS) picks Automatic (the system locale) or a registered language, persisted as `interfaceLanguage`. Translations come from per-language catalogs (`i18n/*.tsv`): Traditional and Simplified Chinese, Japanese, German (every `de-*` locale), Spanish (neutral international Spanish, for every `es-*` locale), French (every `fr-*` locale), Italian (every `it-*` locale) Russian (every `ru-*` locale) and Ukrainian (every `uk-*` locale) cover the whole interface, Czech and Brazilian Portuguese every menu label, Brazilian Portuguese the shell's `tl!` literals too (tested); German, Spanish, French, Italian, Japanese, Russian and Ukrainian also translate status-bar and error messages as they are shown (`@msg` templates, every message literal tested; the messages themselves stay English for agents); untranslated text stays English. Japanese and Chinese glyphs come from the craft-fonts build input (release builds) or the installed fonts, Czech, German, Spanish, French, Italian, Russian and Ukrainian ones from the bundled UI fonts. The web app follows the browser's languages (or `?lang=`). The Basic toolbar's Type button holds Vertical Type in its flyout. Vertical Type, Vertical Area Type and Vertical Type on a Path create vertical text (columns right to left, upright CJK glyphs and marks with `vert` alternates, Latin and longer numbers on their side, two- and three-digit numbers set as tate-chu-yoko in one em of the column (kept whole when justified), upright glyphs centred on the column (tracked or justified too), point type's anchor on the first column's centre line); kinsoku applies to horizontal and vertical Japanese (Paragraph panel › Kinsoku Set, `text.setFormat {kinsoku}`: Hard, the default, keeps closing marks, iteration marks, the prolonged sound mark and small kana off the line start and opening brackets off its end; Soft lets 々, ー and small kana start a line as JLREQ's level 3 rules do; None turns it off), and justified Japanese lines spread their room between characters (not inside Latin words); Paragraph panel › Mojikumi Set (shown with Preferences › Type › Show East Asian Options, `text.setFormat {mojikumi}`): None or Line-end Punctuation Half Width (new type's default, as in Illustrator; older documents and imported text keep None) shares one half-em space between consecutive punctuation, sets a closing mark ending a line half width and an opening bracket starting a line flush with its start (a paragraph's first line at its first-line indent, JIS X 4051's principle in JLREQ 3.1.5), and puts a quarter em between Japanese and Latin letters or digits; Paragraph panel menu › Top-to-Top / Bottom-to-Bottom Leading measures leading from em box top to top (area type's first line touches the frame) or from baseline to baseline (new type in a Japanese interface starts top to top, with em box centre character alignment, #432); Paragraph panel menu › Burasagari None / Regular / Force (`text.setFormat {burasagari: "none"|"standard"|"forced"}`) hangs an East Asian comma or full stop (、。,.、。) ending an area type line outside it, Regular (new type's default) when it doesn't fit and Force always (the rest of the line then fills the measure; closing brackets and Latin punctuation don't hang); Character panel menu › Character Alignment (with the East Asian options; `text.setFormat` or `text.setRangeStyle {charAlign}`) lines characters smaller than the largest on their line up on the Roman baseline, the em box top/right, centre or bottom/left, or the ICF (ideographic character face) top/right or bottom/left, from the font's BASE table or, without one, the average ink box of some ideographs and kana; Proportional Metrics (the same menu; `{proportionalMetrics}`) sets full-width glyphs on the font's proportional widths (OpenType `palt`) in horizontal type and upright glyphs on its proportional heights (`vpal`, shaped top to bottom; fonts without vertical metrics keep their cells) in vertical type, and Line-end Punctuation Half Width takes nothing more off the punctuation they re-spaced; Type ▸ Type Orientation switches existing text; caret, selection, hit testing and arrow keys follow the writing direction. Vertical text exports to PDF as real text and to SVG as outlines.
- **Transparency:** opacity masks (clip/invert/disable/link), exported as SVG `<mask>` and PDF soft masks. The constant alpha soft mask that PDF and `.ai` export draw a CMYK page with transparency through is white, so applications that read every soft mask by its luminosity show the page too (#1359, part of #1286).
Disable, Enable, Unlink and Link Opacity Mask and Opacity Mask Options (`transparency.setOpacityMask`) record no undo step when every mask they act on already has the flags they set.
- **Image Trace, measured:** `crates/trace/tests/quality` traces drawings made from known vector art with every preset and [`image-trace-quality.md`](image-trace-quality.md) records the numbers against the baseline. Black and White, Grayscale and Color place each edge by the blend of the pixels across it (to a fraction of a pixel on anti-aliased and blurred images; hard-edged images are read as staircases) and fit outlines as Flat Logo does. *Measured* on each preset's own inputs, all sizes and variants: Default's outlines went from 0.31 px off on average to 0.09 (largest 1.51 to 1.02), Black and White Logo's from 0.27 to 0.06 with 0.3 kinks per trace where it had 15, and the gaps between abutting paths from 64 px to 22. On clean curved shapes Black and White Logo is 0.02 px off (the open tracer measured beside it: 0.20). Still weak, by the same numbers: 6 and 16 Colors shatter noisy flat art, High Fidelity Photo makes 51,000 paths of a 1 megapixel photo-like image, thin lines in blurred images take 7 times the art's anchors.
- **Image Trace, measured:** `crates/trace/tests/quality` traces drawings made from known vector art with every preset and [`image-trace-quality.md`](image-trace-quality.md) records the numbers against the baseline. Black and White, Grayscale and Color place each edge by the blend of the pixels across it (to a fraction of a pixel on anti-aliased and blurred images; hard-edged images are read as staircases) and fit outlines as Flat Logo does. *Measured* on each preset's own inputs, all sizes and variants: Default's outlines went from 0.31 px off on average to 0.09 (largest 1.51 to 1.02), Black and White Logo's from 0.27 to 0.06 with 0.3 kinks per trace where it had 15, and the gaps between abutting paths from 64 px to 22. On clean curved shapes Black and White Logo is 0.02 px off (the open tracer measured beside it: 0.20). The palette is made of the picture's own colours (blends along edges take none of it, colours that noise made of one colour merge, a blend pixel goes to one of the two colours it lies between; #1504): 16 Colors on flat art, all variants, went from 70 paths to 4.2 and from 5.1 px off to 0.13, with no path left in a colour the art doesn't have (5.9 before). Photos are smoothed within their tones before they are quantised and stacked layers go by lightness (#1516): High Fidelity Photo went from 3,653 paths to 121 with its colour error down from 2.30 to 1.75, and a 16 megapixel photo-like image is traced in 3.5 s where 4 megapixels were refused. Still weak, by the same numbers: abutting paths leave 22 to 50 px of gaps (each region's edge is still fitted on its own), thin lines in blurred and scanned images take several times the art's anchors, hairlines under half a pixel are lost to the threshold, a scan's uneven paper splits into tones when more colours are asked for than the art has, and a 16 megapixel photo takes about 4 GB while it is traced.
- **Image Trace Flat Logo mode:** a fourth mode beside Black and White, Grayscale and Color, for flat-colour logos. Each pixel is read as a blend of the logo's colours (found automatically or named), the blend is interpolated smoothly and every outline is placed where two colours are equally present, so edges are accurate to a fraction of a pixel. Outlines are then cleaned: corners the blur rounded are put back, near-straight runs and near-circular arcs become exact lines and arcs, the rest is fitted with as few Béziers as the tolerance allows, touching colours overlap by a hair so no gap opens, and an outline that strays from the raw contour is fitted plainly instead. Gradients and photos are refused with advice to use Color. *Measured* (a local experiment, not run in CI): a flat-colour logo's vector original was rendered down to a soft 199 × 100 px transparent PNG, traced, and the result compared with the original at 1600 px wide. The built-in colour presets (6 Colors, 16 Colors, High Fidelity Photo, Low Fidelity Photo) had a mean edge error of 2.5 to 3.4 px and region overlap of 0.88 to 0.91; Flat Logo had 0.78 px and 0.966 (0.48 px and 0.975 from a crisp source), in about 0.1 s. Limits: flat-colour art only; needle-thin sharp tips are shortened by about 1.7 source pixels because the blur erases them; it works at no more than 1024 px on the long side (larger images are averaged down first); only one logo family has been scored against a vector original.
- **Advanced art:** live Blends (steps/distance/smooth colour, editable spine, anchor-targeted Blend tool, faithful interpolation, knockout), Envelope Distort (warp/mesh/top object, Reset, full Envelope Options, mesh handles, Edit Contents; type, images, symbols, gradients, patterns and appearance distorted everywhere), Liquify tools with options and pen pressure, Puppet Warp with rotating pins, Perspective Grid with presets, movable planes and projected type (choosing a perspective tool shows the grid; Hide Grid hides it with the tool still chosen), Gradient Mesh, Shape Builder (stroke-only open paths and lines cut the regions they cross and enclose regions of their own, and regions they bound merge without seams where the lines cross at rounded coordinates (#893); filled open paths count as closed; erase mode while Alt/Option is held: a minus pointer, red highlights of what will go, a click or drag deletes the regions touched, the pieces of open lines and the edges (outline pieces between the points where paths meet, opening their paths there) it passes over, one undo step, `shapeBuilder.merge {erase}` and `shapeBuilder.regions` edges over MCP), Live Paint (every path an edge, open lines included), Image Trace (12 presets, colour layers traced in parallel; a trace past two million anchor points is refused with advice rather than running out of memory, an image over 64 megapixels is traced scaled down to that size (aspect kept, paths at the image's own size) and one over 192 megapixels is refused, and a selection too large to outline shows its bounds (#525); Object › Image Trace › Make and Make and Expand trace at once with the Default preset, #543; with an image selected the Control bar and the Properties panel show Image Trace, whose arrow traces with any preset, Mask (`object.maskImage`: a clipping path on the image's outline, selected so its handles crop), Crop Image (a crop box on the image, starting on its part over the artboard, dragged by its handles or inside it, the outside dimmed, with X/Y/W/H, Apply and Cancel in the Control bar, Enter and Escape, #734; `object.cropImage {rect}` for agents, `{trim: true}` cuts an image to its pixels that aren't fully transparent, pixel for pixel) and Embed or Unembed in place of Fill and Stroke; with an Image Trace object selected, its Preset (choosing another traces again), View, the Image Trace panel and Expand, plus Release in Properties; the five View modes (Tracing Result, with Outlines, Outlines, Outlines with Source Image, Source Image) redraw a trace on screen without tracing again, are saved with it and `imageTrace.setView` sets them, while exports and Expand keep the result; the panel's Advanced section is a header that stays open or closed as it was left (part of #641); Color mode's Palette: Limited, Full Tone (as many colours as the tones need, Less … More), Automatic (flat art with its own colours, else Full Tone; a heuristic) and Document Library (the most used colours of the document's swatches or a swatch library, filled with the swatch colours as they are), part of #641; Advanced › Create: Fills and/or Strokes, where lines up to the Stroke width trace as stroked centre lines (distance transform, thinning, a skeleton graph with spurs pruned, curve fitting; one width per line; without Fills, wider areas become stroked outlines), part of #641), pattern swatches with pattern editing mode, live Repeat (radial/grid/mirror).
- **Paint and appearance (M3):**
@@ -525,6 +525,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
| Date | Change | Summary |
|---|---|---|
| 2026-10-13 | minor | Image Trace, measured in `image-trace-quality.md`: the palette is made of the picture's own colours (#1504): 16 Colors on flat art 70 → 4.2 paths and 5.1 → 0.13 px off, paths in a colour the art lacks 5.9 → 0; photos are smoothed within their tones first and stacked layers go by lightness (#1516): High Fidelity Photo 3,653 → 121 paths with its colour error 2.30 → 1.75, 16 megapixels traced where 4 were refused. Score and hours unchanged (estimated) |
| 2026-10-13 | minor | Automation: a command run through MCP, the control channel or the CLI refuses a parameter key it doesn't take, naming it and the nearest accepted key (#1496, fixes #1442; 598 of 737 engine commands checked, 168 params descriptions corrected); plug-ins: a numeric parameter's default stays inside its declared range (#1463); stability: a window surface larger than 8192 px is configured up to the graphics adapter's limit instead of closing the app (#1495, part of #1459; not reproduced on a mixed-scale setup); text frames, wrap shapes and type paths beyond a billion points hold no text instead of hanging the layout (#1451); scores and hours unchanged (estimated) |
| 2026-10-13 | minor | File formats: PDF and `.ai` import keep a stroke opaque and unmasked when the fill under it was faded or masked by its group, and a `.ai` read as its PDF part keeps its RGB numbers (#1434); SVG export writes knockout groups of opaque art without masks, and `convert` takes `--decimals` (#1435); grayscale TIFFs with an alpha channel or other extra channels place, relink and open (#1489, part of #1466; colourising 1-bit and grayscale images is still open); score and hours unchanged (estimated) |
| 2026-10-13 | minor | Path operations and transforms: Split Into Grid has its row and column sizes, gutters, totals, Add Guides and Preview (#1488, fixes #1462); the Pathfinder panel works on a group selected alone (#1493, for #1450; one plain path alone is not settled by the documentation); Transform Objects in the Transform dialogs and Transform Each, and Reflect across an angle (#1494, fixes #1448 and #1460); Fit Headline in a frame one line tall (#1445, fixes #1444); value fields: selected text is readable (#1490, part of #1465) and a focused field follows its value (#1497, fixes #1457); the many-object edits of #1430 and #1464 are under Performance already; scores and hours unchanged (estimated) |