From c9fac957609fbbca0f82ad61a6d1a917ca9a04ca Mon Sep 17 00:00:00 2001 From: bflatastic Date: Tue, 13 Oct 2026 06:28:44 +0700 Subject: [PATCH] Image Trace: a 16 megapixel photo is traced in 0.9 GB, not 3.9 Every colour layer took a mask, component labels, a fill stack and an edge map the size of the whole image, and as many layers were traced at once as the machine has cores. - A layer is traced within the box its pixels lie in. - The stack that fills components holds `u32` indices (a mask has at most 64 million pixels). - Layers are traced as many at a time as fit in half a gigabyte (about ten bytes a pixel of the largest layer's box each), on however many cores. Peak memory above the image itself, photo-like image (docs/image-trace-quality.md), before -> after: - 16 megapixels: High Fidelity Photo 3,856 -> 880 MB (2.85 -> 3.65 s), Low Fidelity Photo 1,972 -> 754 MB (1.49 -> 1.80 s), 16 Colors 1,625 -> 298 MB, Shades of Gray 852 -> 407 MB, Default 256 -> 199 MB - 4 megapixels: High Fidelity Photo 981 -> 643 MB, 16 Colors 408 -> 122 MB, times unchanged The traces are the same, path for path. --- crates/trace/src/contour.rs | 8 +- crates/trace/src/lib.rs | 69 +++++++++++----- crates/trace/src/tests.rs | 12 +++ docs/image-trace-quality.md | 155 ++++++++++++++++++------------------ 4 files changed, 145 insertions(+), 99 deletions(-) diff --git a/crates/trace/src/contour.rs b/crates/trace/src/contour.rs index ebd3d6a0..ce2d79f6 100644 --- a/crates/trace/src/contour.rs +++ b/crates/trace/src/contour.rs @@ -51,7 +51,8 @@ fn step(d: u8) -> (i32, i32) { fn label(mask: &[bool], w: usize, h: usize) -> (Vec, Vec) { let mut comp = vec![u32::MAX; w * h]; let mut sizes = Vec::new(); - let mut stack = Vec::new(); + // (Indices as `u32`: a mask has at most 64 million pixels, and this stack can hold them all.) + let mut stack: Vec = Vec::new(); for s in 0..w * h { if !mask[s] || comp[s] != u32::MAX { continue; @@ -59,14 +60,15 @@ fn label(mask: &[bool], w: usize, h: usize) -> (Vec, Vec) { let id = sizes.len() as u32; let mut n = 0usize; comp[s] = id; - stack.push(s); + stack.push(s as u32); while let Some(i) = stack.pop() { + let i = i as usize; n += 1; let (x, y) = (i % w, i / w); let mut go = |j: usize| { if mask[j] && comp[j] == u32::MAX { comp[j] = id; - stack.push(j); + stack.push(j as u32); } }; if x > 0 { diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs index b7e82dab..e1502881 100644 --- a/crates/trace/src/lib.rs +++ b/crates/trace/src/lib.rs @@ -469,11 +469,14 @@ pub fn trace_within(img: &Raster, params: &TraceParams, max_anchors: usize) -> R 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. + // Pixel count per palette entry, and the box its pixels lie in. let mut counts = vec![0usize; q.palette.len()]; - for &l in &q.labels { - if let Some(c) = counts.get_mut(l as usize) { + let mut boxes: Vec> = vec![None; q.palette.len()]; + for (i, &l) in q.labels.iter().enumerate() { + if let (Some(c), Some(b)) = (counts.get_mut(l as usize), boxes.get_mut(l as usize)) { *c += 1; + let (x, y) = (i % w, i / w); + *b = Some(b.map_or((x, y, x + 1, y + 1), |b| (b.0.min(x), b.1.min(y), b.2.max(x + 1), b.3.max(y + 1)))); } } // Layers bottom → top: largest area first. @@ -503,6 +506,7 @@ pub fn trace_within(img: &Raster, params: &TraceParams, max_anchors: usize) -> R rank: &rank, params, img, + boxes, bw_tones: bw_tones(img, &q), style: params.outline_style(), opts: fit::FitOptions { @@ -517,7 +521,12 @@ pub fn trace_within(img: &Raster, params: &TraceParams, max_anchors: usize) -> R max_anchors, }; let mut out = TraceResult::default(); - for (paths, &(_, ci)) in parallel_map(&layers, |&(r, ci)| job.layer(r, ci)).into_iter().zip(&layers) { + // A layer takes about ten bytes a pixel of its box while it is traced (its mask, the labels + // of its components, the stack that fills them, its edges): as many layers at a time as fit + // in half a gigabyte, so that a large image is not traced at the cost of gigabytes. + let largest = job.boxes.iter().flatten().map(|b| (b.2 - b.0) * (b.3 - b.1)).max().unwrap_or(1).max(1); + let at_a_time = ((512 << 20) / (largest * 10)).max(1); + for (paths, &(_, ci)) in parallel_map_at_most(at_a_time, &layers, |&(r, ci)| job.layer(r, ci)).into_iter().zip(&layers) { out.paths.extend(paths.ok_or(TraceError::TooComplex { max: max_anchors })?); let color = q.palette[ci]; if !out.palette.contains(&color) { @@ -547,6 +556,8 @@ struct Layers<'a> { rank: &'a [usize], params: &'a TraceParams, img: &'a Raster, + /// The box each palette entry's pixels lie in: `(x0, y0, x1, y1)`, the far sides excluded. + boxes: Vec>, /// Black and White: the mean luminance of the white pixels and of the black ones. bw_tones: [f64; 2], /// How outlines are fitted. @@ -709,17 +720,23 @@ impl Layers<'_> { } else { (vec![], vec![]) }; - let mask: Vec = self - .q - .labels - .iter() - .enumerate() - .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) && !self.ignored(l) } else { l as usize == ci } - }) - .collect(); + // The layer is traced within the box its pixels lie in, not the whole image. + let part = |l: u16| if overlapping { self.rank.get(l as usize).is_some_and(|&lr| lr >= r) && !self.ignored(l) } else { l as usize == ci }; + let (mut x0, mut y0, mut x1, mut y1) = (w, h, 0, 0); + for (l, b) in self.boxes.iter().enumerate() { + if let Some(b) = b.filter(|_| part(l as u16)) { + (x0, y0, x1, y1) = (x0.min(b.0), y0.min(b.1), x1.max(b.2), y1.max(b.3)); + } + } + if x0 >= x1 || y0 >= y1 { + return Some(vec![]); + } + let (bw, bh) = (x1 - x0, y1 - y0); + let mut mask = Vec::with_capacity(bw * bh); + for y in y0..y1 { + let row = self.q.labels.get(y * w + x0..y * w + x1)?; + mask.extend(row.iter().enumerate().map(|(k, &l)| l != TRANSPARENT && !thin.get(y * w + x0 + k).copied().unwrap_or(false) && part(l))); + } // Without Fills, areas are outlined with a 1 px stroke. let area_stroke = (params.strokes && !params.fills).then_some(1.0); let min_hole = params.noise.max(1) as i64; @@ -732,7 +749,16 @@ impl Layers<'_> { paths.push(TracedPath { path, color, pixels, stroke }); true }; - for comp in trace_mask(&mask, w, h) { + let mut comps = trace_mask(&mask, bw, bh); + drop(mask); + for comp in &mut comps { + for l in std::iter::once(&mut comp.outer).chain(&mut comp.holes) { + for p in &mut l.pts { + *p = (p.0 + x0 as i32, p.1 + y0 as i32); + } + } + } + for comp in comps { let fit = |l: &Loop| fit::fit_loop(l, w, h, &|c| (self.shift(c), self.neighbour(c)), &|x, y| self.junction(x, y), &self.style); let Some(outer) = fit(&comp.outer) else { continue }; let mut subs = vec![outer]; @@ -756,9 +782,14 @@ impl Layers<'_> { } /// `f` of each of `items`, in order, spread over the available cores. -#[cfg(not(target_arch = "wasm32"))] fn parallel_map(items: &[T], f: impl Fn(&T) -> R + Sync) -> Vec { - let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(items.len()); + parallel_map_at_most(usize::MAX, items, f) +} + +/// `f` of each of `items`, in order, spread over the available cores, at most `at_a_time` at once. +#[cfg(not(target_arch = "wasm32"))] +fn parallel_map_at_most(at_a_time: usize, items: &[T], f: impl Fn(&T) -> R + Sync) -> Vec { + let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(items.len()).min(at_a_time); if threads <= 1 { return items.iter().map(f).collect(); } @@ -787,7 +818,7 @@ fn parallel_map(items: &[T], f: impl Fn(&T) -> R + Sync) -> Ve /// `f` of each of `items`, in order (no threads on the web). #[cfg(target_arch = "wasm32")] -fn parallel_map(items: &[T], f: impl Fn(&T) -> R + Sync) -> Vec { +fn parallel_map_at_most(_: usize, items: &[T], f: impl Fn(&T) -> R + Sync) -> Vec { items.iter().map(f).collect() } diff --git a/crates/trace/src/tests.rs b/crates/trace/src/tests.rs index e61a9762..a67e56ee 100644 --- a/crates/trace/src/tests.rs +++ b/crates/trace/src/tests.rs @@ -1037,3 +1037,15 @@ fn abutting_regions_share_one_curve_along_their_edge() { } assert!(shared >= 12, "{shared} shared curves"); } + +#[test] +fn layers_traced_a_few_at_a_time_come_out_the_same() { + let items: Vec = (0..40).collect(); + for at_a_time in [1, 3, usize::MAX] { + assert_eq!(parallel_map_at_most(at_a_time, &items, |i| i * i), items.iter().map(|i| i * i).collect::>()); + } + // A layer is traced within the box of its pixels: a shape far from the image's origin stays there. + let img = Raster::from_fn(90, 70, |x, y| if (60..80).contains(&x) && (40..60).contains(&y) { BLACK } else { WHITE }); + let b = trace(&img, &bw()).paths[0].path.bounds().unwrap(); + assert!([(b.x0, 60.0), (b.y0, 40.0), (b.x1, 80.0), (b.y1, 60.0)].iter().all(|(got, want)| (got - want).abs() < 0.01), "{b:?}"); +} diff --git a/docs/image-trace-quality.md b/docs/image-trace-quality.md index 4a6faf4d..23f31b76 100644 --- a/docs/image-trace-quality.md +++ b/docs/image-trace-quality.md @@ -1,6 +1,6 @@ # Image Trace quality -> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (shared edges: gaps and overlaps between abutting paths from 22 to 50 px down to under 1) +> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (memory: a 16 megapixel photo traced in 0.9 GB where it took 3.9) 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,7 +72,7 @@ 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, the palette, photo smoothing, shared edges) +### Now (sub-pixel edges, the palette, photo smoothing, shared edges, memory) | 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 | |:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:| @@ -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.55 | 9.72 | 0.092 | 1.06 | 105.75 | 4.8 | 56 | 3.67 | 71 | 74 | 0.7 | – | – | – | – | 1.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, the palette, photo smoothing, shared edges). +The sections below are the numbers now (sub-pixel edges, the palette, photo smoothing, shared edges, memory). ## 1. Each preset on the inputs it is meant for @@ -282,89 +282,90 @@ One trace at a time on this machine (all cores); memory is the peak allocated ab | preset | image | megapixels | result | |:--|:--|--:|:--| -| Default | photo-like | 1 | 0.04 s, 29 paths, 6888 anchors, 15 MB | -| Default | four-colour badge | 1 | 0.02 s, 3 paths, 96 anchors, 15 MB | -| High Fidelity Photo | photo-like | 1 | 0.21 s, 968 paths, 114383 anchors, 238 MB | -| High Fidelity Photo | four-colour badge | 1 | 0.06 s, 6 paths, 172 anchors, 36 MB | -| Low Fidelity Photo | photo-like | 1 | 0.11 s, 129 paths, 22929 anchors, 144 MB | -| Low Fidelity Photo | four-colour badge | 1 | 0.05 s, 6 paths, 136 anchors, 36 MB | -| 3 Colors | photo-like | 1 | 0.08 s, 9 paths, 3367 anchors, 26 MB | -| 3 Colors | four-colour badge | 1 | 0.05 s, 4 paths, 88 anchors, 21 MB | -| 6 Colors | photo-like | 1 | 0.08 s, 78 paths, 9008 anchors, 42 MB | -| 6 Colors | four-colour badge | 1 | 0.05 s, 5 paths, 84 anchors, 33 MB | -| 16 Colors | photo-like | 1 | 0.10 s, 178 paths, 20466 anchors, 102 MB | -| 16 Colors | four-colour badge | 1 | 0.05 s, 5 paths, 88 anchors, 33 MB | -| Shades of Gray | photo-like | 1 | 0.08 s, 81 paths, 6698 anchors, 65 MB | -| Shades of Gray | four-colour badge | 1 | 0.06 s, 10 paths, 733 anchors, 56 MB | -| Black and White Logo | photo-like | 1 | 0.04 s, 71 paths, 5465 anchors, 9 MB | -| Black and White Logo | four-colour badge | 1 | 0.02 s, 1 paths, 52 anchors, 8 MB | -| Sketched Art | photo-like | 1 | 0.04 s, 5 paths, 2971 anchors, 9 MB | -| Sketched Art | four-colour badge | 1 | 0.02 s, 2 paths, 131 anchors, 8 MB | -| Silhouettes | photo-like | 1 | 0.04 s, 2 paths, 791 anchors, 11 MB | -| Silhouettes | four-colour badge | 1 | 0.02 s, 1 paths, 25 anchors, 9 MB | -| Line Art | photo-like | 1 | 0.04 s, 54 paths, 4421 anchors, 9 MB | -| Line Art | four-colour badge | 1 | 0.02 s, 1 paths, 232 anchors, 8 MB | -| Technical Drawing | photo-like | 1 | 0.04 s, 221 paths, 7680 anchors, 9 MB | -| Technical Drawing | four-colour badge | 1 | 0.02 s, 1 paths, 52 anchors, 8 MB | +| Default | photo-like | 1 | 0.04 s, 29 paths, 6888 anchors, 12 MB | +| Default | four-colour badge | 1 | 0.02 s, 3 paths, 96 anchors, 12 MB | +| High Fidelity Photo | photo-like | 1 | 0.22 s, 968 paths, 114383 anchors, 170 MB | +| High Fidelity Photo | four-colour badge | 1 | 0.05 s, 6 paths, 172 anchors, 23 MB | +| Low Fidelity Photo | photo-like | 1 | 0.11 s, 129 paths, 22929 anchors, 103 MB | +| Low Fidelity Photo | four-colour badge | 1 | 0.05 s, 6 paths, 136 anchors, 23 MB | +| 3 Colors | photo-like | 1 | 0.07 s, 9 paths, 3367 anchors, 18 MB | +| 3 Colors | four-colour badge | 1 | 0.05 s, 4 paths, 88 anchors, 13 MB | +| 6 Colors | photo-like | 1 | 0.08 s, 78 paths, 9008 anchors, 24 MB | +| 6 Colors | four-colour badge | 1 | 0.05 s, 5 paths, 84 anchors, 12 MB | +| 16 Colors | photo-like | 1 | 0.10 s, 178 paths, 20466 anchors, 32 MB | +| 16 Colors | four-colour badge | 1 | 0.05 s, 5 paths, 88 anchors, 12 MB | +| Shades of Gray | photo-like | 1 | 0.08 s, 81 paths, 6698 anchors, 45 MB | +| Shades of Gray | four-colour badge | 1 | 0.06 s, 10 paths, 733 anchors, 34 MB | +| Black and White Logo | photo-like | 1 | 0.04 s, 71 paths, 5465 anchors, 8 MB | +| Black and White Logo | four-colour badge | 1 | 0.02 s, 1 paths, 52 anchors, 6 MB | +| Sketched Art | photo-like | 1 | 0.04 s, 5 paths, 2971 anchors, 8 MB | +| Sketched Art | four-colour badge | 1 | 0.02 s, 2 paths, 131 anchors, 6 MB | +| Silhouettes | photo-like | 1 | 0.04 s, 2 paths, 791 anchors, 9 MB | +| Silhouettes | four-colour badge | 1 | 0.02 s, 1 paths, 25 anchors, 6 MB | +| Line Art | photo-like | 1 | 0.04 s, 54 paths, 4421 anchors, 8 MB | +| Line Art | four-colour badge | 1 | 0.02 s, 1 paths, 232 anchors, 6 MB | +| Technical Drawing | photo-like | 1 | 0.04 s, 221 paths, 7680 anchors, 8 MB | +| Technical Drawing | four-colour badge | 1 | 0.02 s, 1 paths, 52 anchors, 6 MB | | Flat Logo | photo-like | 1 | refused after 0.09 s (not flat art), 40 MB | -| Flat Logo | four-colour badge | 1 | 0.64 s, 5 paths, 78 anchors, 146 MB | -| Default | photo-like | 4 | 0.18 s, 48 paths, 17801 anchors, 60 MB | -| Default | four-colour badge | 4 | 0.09 s, 3 paths, 178 anchors, 64 MB | -| High Fidelity Photo | photo-like | 4 | 0.72 s, 4475 paths, 341272 anchors, 981 MB | -| High Fidelity Photo | four-colour badge | 4 | 0.24 s, 6 paths, 200 anchors, 152 MB | -| Low Fidelity Photo | photo-like | 4 | 0.38 s, 304 paths, 61093 anchors, 568 MB | -| Low Fidelity Photo | four-colour badge | 4 | 0.23 s, 6 paths, 152 anchors, 152 MB | -| 3 Colors | photo-like | 4 | 0.28 s, 20 paths, 8453 anchors, 104 MB | -| 3 Colors | four-colour badge | 4 | 0.18 s, 4 paths, 122 anchors, 84 MB | -| 6 Colors | photo-like | 4 | 0.37 s, 115 paths, 23128 anchors, 168 MB | -| 6 Colors | four-colour badge | 4 | 0.19 s, 5 paths, 92 anchors, 128 MB | -| 16 Colors | photo-like | 4 | 0.32 s, 353 paths, 52749 anchors, 408 MB | -| 16 Colors | four-colour badge | 4 | 0.19 s, 5 paths, 96 anchors, 128 MB | -| Shades of Gray | photo-like | 4 | 0.29 s, 138 paths, 18794 anchors, 224 MB | -| Shades of Gray | four-colour badge | 4 | 0.27 s, 10 paths, 1265 anchors, 220 MB | -| Black and White Logo | photo-like | 4 | 0.20 s, 208 paths, 17188 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.20 s, 9 paths, 6884 anchors, 36 MB | -| Sketched Art | four-colour badge | 4 | 0.09 s, 2 paths, 236 anchors, 36 MB | -| Silhouettes | photo-like | 4 | 0.19 s, 2 paths, 1951 anchors, 44 MB | -| Silhouettes | four-colour badge | 4 | 0.10 s, 1 paths, 32 anchors, 36 MB | -| Line Art | photo-like | 4 | 0.19 s, 127 paths, 12091 anchors, 36 MB | -| Line Art | four-colour badge | 4 | 0.09 s, 1 paths, 419 anchors, 36 MB | -| Technical Drawing | photo-like | 4 | 0.20 s, 931 paths, 23132 anchors, 36 MB | -| Technical Drawing | four-colour badge | 4 | 0.08 s, 1 paths, 64 anchors, 36 MB | +| Flat Logo | four-colour badge | 1 | 0.65 s, 5 paths, 78 anchors, 146 MB | +| Default | photo-like | 4 | 0.19 s, 48 paths, 17801 anchors, 49 MB | +| Default | four-colour badge | 4 | 0.09 s, 3 paths, 178 anchors, 48 MB | +| High Fidelity Photo | photo-like | 4 | 0.72 s, 4475 paths, 341272 anchors, 643 MB | +| High Fidelity Photo | four-colour badge | 4 | 0.23 s, 6 paths, 200 anchors, 93 MB | +| Low Fidelity Photo | photo-like | 4 | 0.36 s, 304 paths, 61093 anchors, 412 MB | +| Low Fidelity Photo | four-colour badge | 4 | 0.23 s, 6 paths, 152 anchors, 96 MB | +| 3 Colors | photo-like | 4 | 0.26 s, 20 paths, 8453 anchors, 73 MB | +| 3 Colors | four-colour badge | 4 | 0.18 s, 4 paths, 122 anchors, 53 MB | +| 6 Colors | photo-like | 4 | 0.26 s, 115 paths, 23128 anchors, 100 MB | +| 6 Colors | four-colour badge | 4 | 0.19 s, 5 paths, 92 anchors, 46 MB | +| 16 Colors | photo-like | 4 | 0.28 s, 353 paths, 52749 anchors, 122 MB | +| 16 Colors | four-colour badge | 4 | 0.19 s, 5 paths, 96 anchors, 46 MB | +| Shades of Gray | photo-like | 4 | 0.31 s, 138 paths, 18794 anchors, 154 MB | +| Shades of Gray | four-colour badge | 4 | 0.26 s, 10 paths, 1265 anchors, 138 MB | +| Black and White Logo | photo-like | 4 | 0.19 s, 208 paths, 17188 anchors, 32 MB | +| Black and White Logo | four-colour badge | 4 | 0.08 s, 1 paths, 64 anchors, 24 MB | +| Sketched Art | photo-like | 4 | 0.20 s, 9 paths, 6884 anchors, 32 MB | +| Sketched Art | four-colour badge | 4 | 0.08 s, 2 paths, 236 anchors, 24 MB | +| Silhouettes | photo-like | 4 | 0.17 s, 2 paths, 1951 anchors, 36 MB | +| Silhouettes | four-colour badge | 4 | 0.08 s, 1 paths, 32 anchors, 24 MB | +| Line Art | photo-like | 4 | 0.18 s, 127 paths, 12091 anchors, 32 MB | +| Line Art | four-colour badge | 4 | 0.08 s, 1 paths, 419 anchors, 24 MB | +| Technical Drawing | photo-like | 4 | 0.19 s, 931 paths, 23132 anchors, 32 MB | +| Technical Drawing | four-colour badge | 4 | 0.08 s, 1 paths, 64 anchors, 24 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.04 s, 127 paths, 43656 anchors, 256 MB | -| Default | four-colour badge | 16 | 0.47 s, 3 paths, 250 anchors, 240 MB | -| High Fidelity Photo | photo-like | 16 | 2.85 s, 31125 paths, 1175671 anchors, 3856 MB | -| High Fidelity Photo | four-colour badge | 16 | 1.11 s, 6 paths, 274 anchors, 576 MB | -| Low Fidelity Photo | photo-like | 16 | 1.49 s, 2036 paths, 181832 anchors, 1972 MB | -| Low Fidelity Photo | four-colour badge | 16 | 1.07 s, 6 paths, 187 anchors, 576 MB | -| 3 Colors | photo-like | 16 | 1.22 s, 76 paths, 22878 anchors, 416 MB | -| 3 Colors | four-colour badge | 16 | 0.82 s, 4 paths, 190 anchors, 336 MB | -| 6 Colors | photo-like | 16 | 1.13 s, 470 paths, 60539 anchors, 672 MB | -| 6 Colors | four-colour badge | 16 | 0.82 s, 5 paths, 126 anchors, 500 MB | -| 16 Colors | photo-like | 16 | 1.18 s, 1973 paths, 145173 anchors, 1625 MB | -| 16 Colors | four-colour badge | 16 | 0.84 s, 5 paths, 128 anchors, 504 MB | -| Shades of Gray | photo-like | 16 | 1.48 s, 686 paths, 53872 anchors, 852 MB | -| Shades of Gray | four-colour badge | 16 | 1.40 s, 9 paths, 2216 anchors, 800 MB | -| Black and White Logo | photo-like | 16 | 1.15 s, 1128 paths, 52547 anchors, 144 MB | -| Black and White Logo | four-colour badge | 16 | 0.47 s, 1 paths, 95 anchors, 128 MB | -| Sketched Art | photo-like | 16 | 1.16 s, 15 paths, 16298 anchors, 144 MB | -| Sketched Art | four-colour badge | 16 | 0.47 s, 2 paths, 414 anchors, 128 MB | -| Silhouettes | photo-like | 16 | 1.34 s, 3 paths, 4516 anchors, 176 MB | -| Silhouettes | four-colour badge | 16 | 0.53 s, 1 paths, 40 anchors, 144 MB | -| Line Art | photo-like | 16 | 1.04 s, 713 paths, 43982 anchors, 144 MB | -| Line Art | four-colour badge | 16 | 0.45 s, 1 paths, 707 anchors, 128 MB | -| Technical Drawing | photo-like | 16 | 1.21 s, 4578 paths, 84475 anchors, 144 MB | -| Technical Drawing | four-colour badge | 16 | 0.46 s, 1 paths, 95 anchors, 128 MB | +| Default | photo-like | 16 | 1.02 s, 127 paths, 43656 anchors, 199 MB | +| Default | four-colour badge | 16 | 0.47 s, 3 paths, 250 anchors, 184 MB | +| High Fidelity Photo | photo-like | 16 | 3.65 s, 31125 paths, 1175671 anchors, 880 MB | +| High Fidelity Photo | four-colour badge | 16 | 1.08 s, 6 paths, 274 anchors, 257 MB | +| Low Fidelity Photo | photo-like | 16 | 1.80 s, 2036 paths, 181832 anchors, 754 MB | +| Low Fidelity Photo | four-colour badge | 16 | 1.04 s, 6 paths, 187 anchors, 257 MB | +| 3 Colors | photo-like | 16 | 1.20 s, 76 paths, 22878 anchors, 290 MB | +| 3 Colors | four-colour badge | 16 | 0.80 s, 4 paths, 190 anchors, 205 MB | +| 6 Colors | photo-like | 16 | 1.13 s, 470 paths, 60539 anchors, 298 MB | +| 6 Colors | four-colour badge | 16 | 0.83 s, 5 paths, 126 anchors, 184 MB | +| 16 Colors | photo-like | 16 | 1.15 s, 1973 paths, 145173 anchors, 298 MB | +| 16 Colors | four-colour badge | 16 | 0.89 s, 5 paths, 128 anchors, 185 MB | +| Shades of Gray | photo-like | 16 | 1.76 s, 686 paths, 53872 anchors, 407 MB | +| Shades of Gray | four-colour badge | 16 | 1.33 s, 9 paths, 2216 anchors, 270 MB | +| Black and White Logo | photo-like | 16 | 1.13 s, 1128 paths, 52547 anchors, 129 MB | +| Black and White Logo | four-colour badge | 16 | 0.36 s, 1 paths, 95 anchors, 88 MB | +| Sketched Art | photo-like | 16 | 0.99 s, 15 paths, 16298 anchors, 128 MB | +| Sketched Art | four-colour badge | 16 | 0.40 s, 2 paths, 414 anchors, 88 MB | +| Silhouettes | photo-like | 16 | 1.37 s, 3 paths, 4516 anchors, 144 MB | +| Silhouettes | four-colour badge | 16 | 0.40 s, 1 paths, 40 anchors, 95 MB | +| Line Art | photo-like | 16 | 1.07 s, 713 paths, 43982 anchors, 129 MB | +| Line Art | four-colour badge | 16 | 0.37 s, 1 paths, 707 anchors, 88 MB | +| Technical Drawing | photo-like | 16 | 1.17 s, 4578 paths, 84475 anchors, 130 MB | +| Technical Drawing | four-colour badge | 16 | 0.37 s, 1 paths, 95 anchors, 88 MB | | Flat Logo | photo-like | 16 | refused after 0.11 s (not flat art), 40 MB | -| Flat Logo | four-colour badge | 16 | 0.69 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 | Memory: layers traced within the box of their pixels and a few at a time (16 megapixel photo: 3.9 GB to 0.9 GB) | | 2026-10-13 | Shared edges: outlines are cut at their junctions and each piece is fitted once, the same from either side | | 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 |