Image Trace: a palette of the picture's own colours

Color and Grayscale chose their colours from every pixel, so the blends along anti-aliased edges took palette
entries and traced as slivers, and with more colours asked for than the art has, k-means split flat colours
along their noise and the halves traced as specks all over each other.

- The palette is made from the picture's own colours: a pixel that lies between the pixels on opposite sides
  of it is a blend and counts for nothing (lines one pixel wide and noise are no blends).
- Colours that noise made of one colour merge: close colours whose pixels are mixed through each other. The
  bands of a gradient keep to themselves and stay apart.
- A blend pixel whose nearest palette colour is a third colour (halfway from navy to yellow lies an olive that
  is nearer to a green elsewhere) goes to the nearer of the two colours it lies between.
- Boxes are cut where their two sides differ most (Otsu's criterion) instead of at the median, which split the
  largest colour of flat art along its noise and left two smaller colours in one box.
- Colours are compared in Oklab when pixels are assigned and in k-means.

On each preset's own inputs, all sizes and variants (docs/image-trace-quality.md), before -> after:
- 16 Colors: 69.9 -> 4.2 paths, 1743 -> 66 anchors, outlines 5.08 -> 0.13 px off on average (largest 85.9 ->
  1.09), kinks 1376 -> 4.4, paths in a colour the art doesn't have 5.9 -> 0
- 6 Colors: 21.2 -> 4.0 paths, 4.44 -> 0.14 px off; 3 Colors: colour error 4.86 -> 0.43, 0.33 -> 0.09 px off
- High Fidelity Photo: 3636 -> 639 paths, 81,597 -> 25,604 anchors, colour error 2.50 -> 2.37; a 1 megapixel
  photo-like image 1.98 -> 0.42 s (51,389 -> 4,298 paths), a 4 megapixel one traced (27,583 paths) where it was
  refused
- Low Fidelity Photo: 128 -> 55 paths, colour error 2.59 -> 2.67; Shades of Gray: 91 -> 73 paths, 9.29 -> 9.33
This commit is contained in:
bflatastic 2026-10-13 03:36:24 +07:00
parent a891530ef2
commit 4204ce36db
4 changed files with 556 additions and 223 deletions

View File

@ -8,7 +8,10 @@
//! histogram as the [`Palette`] asks: median cut to a colour count followed by weighted k-means
//! (Limited), median cut until every colour box is tight (Full Tone), one of those two by
//! whether the image is flat art (Automatic), or the most used colours of a swatch library
//! (Document Library). Transparent pixels are left out.
//! (Document Library). Transparent pixels are left out. The palette is made of the picture's own
//! 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`).
//! 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

View File

@ -1,4 +1,5 @@
//! Colour reduction and noise removal.
//! Colour reduction and noise removal. Colours are compared in Oklab, where equal distances look
//! equally different.
use crate::{Mode, Palette, Raster, TraceParams};
@ -37,12 +38,18 @@ pub fn quantize(img: &Raster, params: &TraceParams) -> Quantized {
Quantized { labels, palette: vec![[255, 255, 255], [0, 0, 0]] }
}
Mode::Grayscale => {
let mut hist = [0u64; 256];
for p in img.rgba.as_chunks::<4>().0 {
if p[3] >= 128 {
hist[luma([p[0], p[1], p[2], p[3]]) as usize] += 1;
let own = own_colours(img);
let histogram = |only_own: bool| {
let mut hist = [0u64; 256];
for (p, &f) in img.rgba.as_chunks::<4>().0.iter().zip(&own) {
if p[3] >= 128 && (f || !only_own) {
hist[luma([p[0], p[1], p[2], p[3]]) as usize] += 1;
}
}
}
hist
};
let (every, calm) = (histogram(false), histogram(true));
let hist = if palette_from_own(calm.iter().sum::<u64>() as f64, every.iter().sum::<u64>() as f64) { calm } else { every };
let levels = kmeans_1d(&hist, params.colors.clamp(2, 256) as usize);
let mut lut = [0u16; 256];
for (v, slot) in lut.iter_mut().enumerate() {
@ -53,36 +60,251 @@ pub fn quantize(img: &Raster, params: &TraceParams) -> Quantized {
let g = l.round().clamp(0.0, 255.0) as u8;
[g, g, g]
});
compact(labels, palette.collect())
settled(merge_mixed(compact(labels, palette.collect()), img.width as usize, img.height as usize), img, &own)
}
// Logo mode traces without quantising; asked directly, it quantises like Color.
Mode::Color | Mode::Logo => {
// 15-bit colour histogram with per-bin sums (exact bin means), fitted to the image's
// range per channel so that close tones (a dark, low-contrast photo) stay apart.
let hk = HistKey::new(img);
let mut bins: Vec<Bin> = vec![Bin::default(); 1 << 15];
for p in img.rgba.as_chunks::<4>().0 {
let own = own_colours(img);
let (mut every, mut calm) = (vec![Bin::default(); 1 << 15], vec![Bin::default(); 1 << 15]);
for (p, &f) in img.rgba.as_chunks::<4>().0.iter().zip(&own) {
if p[3] >= 128 {
let b = &mut bins[hk.key(p[0], p[1], p[2])];
b.n += 1;
b.sum[0] += p[0] as u64;
b.sum[1] += p[1] as u64;
b.sum[2] += p[2] as u64;
let k = hk.key(p[0], p[1], p[2]);
every[k].add(p);
if f {
calm[k].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();
let centers = color_palette(&used, params);
let mut lut = vec![0u16; 1 << 15];
for (i, c, _) in &used {
lut[*i] = nearest_rgb(&centers, *c) as u16;
}
let labels = px.map(|p| if p[3] < 128 { TRANSPARENT } else { lut[hk.key(p[0], p[1], p[2])] }).collect();
let palette = centers.iter().map(|c| [c[0].round() as u8, c[1].round() as u8, c[2].round() as u8]).collect();
compact(labels, palette)
let used =
|bins: &[Bin]| -> Vec<Used> { bins.iter().enumerate().filter(|(_, b)| b.n > 0).map(|(i, b)| (i, b.mean(), b.n as f64)).collect() };
let (every, calm) = (used(&every), used(&calm));
let count = |u: &[Used]| u.iter().map(|b| b.2).sum::<f64>();
let from = if palette_from_own(count(&calm), count(&every)) { &calm } else { &every };
// The picture with `colors` colours asked for; fewer come back when it has fewer, or
// when noise made several of one (see `merge_mixed`).
let reduce = |colors: u32| {
let centers = color_palette(from, &TraceParams { colors, ..params.clone() });
let mut lut = vec![0u16; 1 << 15];
let center_labs = labs(&centers);
for (i, c, _) in &every {
lut[*i] = nearest_lab(&center_labs, oklab(*c)) as u16;
}
let labels =
img.rgba.as_chunks::<4>().0.iter().map(|p| if p[3] < 128 { TRANSPARENT } else { lut[hk.key(p[0], p[1], p[2])] }).collect();
let palette = centers.iter().map(|c| [c[0].round() as u8, c[1].round() as u8, c[2].round() as u8]).collect();
let q = compact(labels, palette);
// A swatch library's colours are used as they are.
if params.palette == Palette::DocumentLibrary && !params.swatches.is_empty() {
q
} else {
merge_mixed(q, img.width as usize, img.height as usize)
}
};
let q = reduce(params.colors);
settled(q, img, &own)
}
}
}
/// The opaque pixels whose colour is the picture's own, not a blend: a pixel is a blend when it
/// lies between the two pixels on opposite sides of it (left and right, above and below, or
/// across a diagonal) that differ by more than 20 levels, which is what anti-aliasing and blur
/// leave along an edge. A line one pixel wide is no blend (the pixels on its two sides are alike),
/// nor is noise.
fn own_colours(img: &Raster) -> Vec<bool> {
let (w, h) = (img.width as isize, img.height as isize);
let px = img.rgba.as_chunks::<4>().0;
let at = |x: isize, y: isize| {
let p = (x >= 0 && y >= 0 && x < w && y < h).then(|| px.get((y * w + x) as usize)).flatten()?;
(p[3] >= 128).then(|| [f64::from(p[0]), f64::from(p[1]), f64::from(p[2])])
};
let mut out = Vec::with_capacity(px.len());
for y in 0..h {
for x in 0..w {
let Some(p) = at(x, y) else {
out.push(false);
continue;
};
let blend = [(1, 0), (0, 1), (1, 1), (1, -1)].into_iter().any(|(dx, dy)| {
let (Some(a), Some(b)) = (at(x - dx, y - dy), at(x + dx, y + dy)) else { return false };
let d = [0, 1, 2].map(|k| b[k] - a[k]);
let len2: f64 = d.iter().map(|v| v * v).sum();
if len2 <= 400.0 {
return false;
}
let t = (0..3).map(|k| (p[k] - a[k]) * d[k]).sum::<f64>() / len2;
let off2: f64 = (0..3).map(|k| (p[k] - a[k] - t * d[k]).powi(2)).sum();
t > 0.06 && t < 0.94 && off2 < len2 / 16.0
});
out.push(!blend);
}
}
out
}
/// Whether the palette is made from the picture's own colours alone (`own` of `all` opaque
/// pixels), so that the blends along edges take none of its colours. A picture with next to none
/// is all soft edges: there every pixel counts.
fn palette_from_own(own: f64, all: f64) -> bool {
own >= 0.05 * all && own >= 16.0
}
/// sRGB (0–255) to Oklab × 100 (Björn Ottosson, "A perceptual color space for image processing",
/// 2020): equal distances look equally different, and 2 or 3 is about the least one sees.
fn oklab(c: [f64; 3]) -> [f64; 3] {
let lin = c.map(|v| {
let v = (v / 255.0).clamp(0.0, 1.0);
if v <= 0.04045 { v / 12.92 } else { ((v + 0.055) / 1.055).powf(2.4) }
});
let l = (0.412_221_470_8 * lin[0] + 0.536_332_536_3 * lin[1] + 0.051_445_992_9 * lin[2]).cbrt();
let m = (0.211_903_498_2 * lin[0] + 0.680_699_545_1 * lin[1] + 0.107_396_956_6 * lin[2]).cbrt();
let s = (0.088_302_461_9 * lin[0] + 0.281_718_837_6 * lin[1] + 0.629_978_700_5 * lin[2]).cbrt();
[
100.0 * (0.210_454_255_3 * l + 0.793_617_785 * m - 0.004_072_046_8 * s),
100.0 * (1.977_998_495_1 * l - 2.428_592_205 * m + 0.450_593_709_9 * s),
100.0 * (0.025_904_037_1 * l + 0.782_771_766_2 * m - 0.808_675_766 * s),
]
}
/// `q` with the colours that noise made of one colour merged, largest mix first.
///
/// Asked for more colours than a picture has, k-means splits a flat colour along its noise, and
/// the two halves would trace as specks of one all over the other. Such halves are close (under 30
/// levels apart: noise is about as strong in a dark colour as in a light one, so the distance is
/// taken in RGB) and thoroughly mixed: the pixels of the lesser one have, on average, more than
/// half a neighbour of the other each; or they are closer still (under 12 levels) and mixed in
/// patches (compression blocks). The bands of a gradient are close too, but each keeps to itself
/// and they stay apart.
fn merge_mixed(q: Quantized, w: usize, h: usize) -> Quantized {
let k = q.palette.len();
if k < 2 || q.labels.len() != w * h {
return q;
}
// How many pixels each colour has, and how many neighbouring pairs each pair of colours.
let mut count = vec![0.0f64; k];
let mut touch = vec![0.0f64; k * k];
let label = |i: usize| q.labels.get(i).map(|&l| l as usize).filter(|&l| l < k);
for i in 0..w * h {
let Some(a) = label(i) else { continue };
count[a] += 1.0;
for j in [(i % w + 1 < w).then_some(i + 1), (i + w < w * h).then_some(i + w)].into_iter().flatten() {
if let Some(b) = label(j).filter(|&b| b != a) {
touch[a * k + b] += 1.0;
touch[b * k + a] += 1.0;
}
}
}
let mut color: Vec<[f64; 3]> = q.palette.iter().map(|c| c.map(f64::from)).collect();
// Which colour each has merged into (itself when none).
let mut into: Vec<usize> = (0..k).collect();
loop {
let mut best: Option<(f64, usize, usize)> = None;
for a in 0..k {
for b in a + 1..k {
if into[a] != a || into[b] != b || count[a] <= 0.0 || count[b] <= 0.0 {
continue;
}
let mixed = touch[a * k + b] / count[a].min(count[b]);
let apart = (0..3).map(|c| (color[a][c] - color[b][c]).powi(2)).sum::<f64>().sqrt();
if (mixed > 0.5 && apart < 30.0 || mixed > 0.15 && apart < 12.0) && best.is_none_or(|x| mixed > x.0) {
best = Some((mixed, a, b));
}
}
}
let Some((_, a, b)) = best else { break };
color[a] = [0, 1, 2].map(|c| (color[a][c] * count[a] + color[b][c] * count[b]) / (count[a] + count[b]));
count[a] += count[b];
count[b] = 0.0;
into[b] = a;
for c in 0..k {
if c != a && c != b {
touch[a * k + c] += touch[b * k + c];
touch[c * k + a] = touch[a * k + c];
}
}
}
if into.iter().enumerate().all(|(i, &t)| i == t) {
return q;
}
// A colour may have merged into one that merged again: follow each to where it ended up.
for i in 0..k {
let mut to = into[i];
while into[to] != to {
to = into[to];
}
into[i] = to;
}
let labels = q.labels.iter().map(|&l| into.get(l as usize).map_or(l, |&t| t as u16)).collect();
compact(labels, color.iter().map(|c| [c[0].round() as u8, c[1].round() as u8, c[2].round() as u8]).collect())
}
/// `q` with its blend pixels settled, and the colours that leaves unused dropped.
///
/// A pixel on an edge is a blend of the colours on its two sides, and the palette colour nearest
/// to a blend can be a third colour altogether (halfway from navy to yellow lies an olive that is
/// nearer to a green elsewhere in the picture), which would trace as a sliver of that colour along
/// the edge. A blend pixel (see [`own_colours`]) whose colour is none of those of the picture's
/// own pixels within two pixels of it takes the side it is nearer to of the pair of those colours
/// it is best a blend of, when that explains it twice as well as its own colour does. With one
/// colour around it, it keeps its own.
fn settled(mut q: Quantized, img: &Raster, own: &[bool]) -> Quantized {
let (w, h) = (img.width as usize, img.height as usize);
if q.labels.len() != w * h || own.len() != w * h {
return q;
}
let before = q.labels.clone();
let color = |l: u16| q.palette.get(l as usize).map(|c| c.map(f64::from));
let dist = |a: [f64; 3], b: [f64; 3]| (0..3).map(|k| (a[k] - b[k]).powi(2)).sum::<f64>().sqrt();
let mut changed = false;
for i in 0..w * h {
let mine_l = before[i];
if own[i] || mine_l == TRANSPARENT {
continue;
}
let (x, y) = (i % w, i / w);
// The colours of the picture's own pixels within two pixels (seldom more than three).
let mut near: Vec<u16> = Vec::new();
for yy in y.saturating_sub(2)..=(y + 2).min(h - 1) {
for xx in x.saturating_sub(2)..=(x + 2).min(w - 1) {
let j = yy * w + xx;
if own[j] && before[j] != TRANSPARENT && !near.contains(&before[j]) {
near.push(before[j]);
}
}
}
let (Some(p), Some(mine)) = (img.rgba.get(i * 4..i * 4 + 3).map(|p| [f64::from(p[0]), f64::from(p[1]), f64::from(p[2])]), color(mine_l))
else {
continue;
};
if near.len() < 2 || near.contains(&mine_l) {
continue;
}
let mut best: Option<(f64, u16)> = None;
for (n, &a) in near.iter().enumerate() {
for &b in &near[n + 1..] {
let (Some(ca), Some(cb)) = (color(a), color(b)) else { continue };
let d = [0, 1, 2].map(|k| cb[k] - ca[k]);
let len2: f64 = d.iter().map(|v| v * v).sum();
let t = if len2 > 0.0 { ((0..3).map(|k| (p[k] - ca[k]) * d[k]).sum::<f64>() / len2).clamp(0.0, 1.0) } else { 0.0 };
let off = dist(p, [0, 1, 2].map(|k| ca[k] + t * d[k]));
if best.is_none_or(|x| off < x.0) {
best = Some((off, if t < 0.5 { a } else { b }));
}
}
}
if let Some((off, to)) = best
&& off < 0.5 * dist(p, mine)
{
q.labels[i] = to;
changed = true;
}
}
if changed { compact(q.labels, q.palette) } else { q }
}
/// 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()];
@ -120,6 +342,12 @@ struct Bin {
}
impl Bin {
fn add(&mut self, p: &[u8; 4]) {
self.n += 1;
for (sum, v) in self.sum.iter_mut().zip(p) {
*sum += u64::from(*v);
}
}
fn mean(&self) -> [f64; 3] {
let n = self.n.max(1) as f64;
[self.sum[0] as f64 / n, self.sum[1] as f64 / n, self.sum[2] as f64 / n]
@ -208,16 +436,11 @@ fn kmeans_1d(hist: &[u64; 256], k: usize) -> Vec<f64> {
levels
}
fn dist2(a: [f64; 3], b: [f64; 3]) -> f64 {
// Slightly perceptual weights.
let (dr, dg, db) = (a[0] - b[0], a[1] - b[1], a[2] - b[2]);
2.0 * dr * dr + 4.0 * dg * dg + 3.0 * db * db
}
fn nearest_rgb(centers: &[[f64; 3]], c: [f64; 3]) -> usize {
/// The index of the colour of `labs` (Oklab) nearest to `c` (Oklab): the one that looks most like it.
fn nearest_lab(labs: &[[f64; 3]], c: [f64; 3]) -> usize {
let mut best = (f64::INFINITY, 0);
for (i, &m) in centers.iter().enumerate() {
let d = dist2(m, c);
for (i, m) in labs.iter().enumerate() {
let d = (m[0] - c[0]).powi(2) + (m[1] - c[1]).powi(2) + (m[2] - c[2]).powi(2);
if d < best.0 {
best = (d, i);
}
@ -225,6 +448,10 @@ fn nearest_rgb(centers: &[[f64; 3]], c: [f64; 3]) -> usize {
best.1
}
fn labs(colors: &[[f64; 3]]) -> Vec<[f64; 3]> {
colors.iter().map(|c| oklab(*c)).collect()
}
/// The most colours Full Tone (and Automatic on continuous-tone images) makes.
pub const FULL_TONE_MAX: usize = 256;
@ -295,8 +522,9 @@ fn library_colors(used: &[Used], swatches: &[[u8; 3]], k: usize) -> Vec<[f64; 3]
}
}
let mut weight = vec![0.0; all.len()];
let all_labs = labs(&all);
for (_, c, n) in used {
if let Some(w) = weight.get_mut(nearest_rgb(&all, *c)) {
if let Some(w) = weight.get_mut(nearest_lab(&all_labs, oklab(*c))) {
*w += n;
}
}
@ -321,60 +549,73 @@ fn kmeans_rgb(used: &[Used], k: usize) -> Vec<[f64; 3]> {
refine(used, centers(&median_cut(used, k, 0.0)))
}
/// Median cut: repeatedly split the box with the largest weighted extent, while there are fewer
/// than `k` boxes and some box spans more than `spread` levels of a channel.
/// Divisive clustering in the manner of median cut, but each box is cut where that leaves its two
/// sides most alike within themselves (the cut, along one channel, with the largest between-side
/// variance: Otsu's criterion), and the box cut next is the one whose cut gains most. A cut at the
/// median, as in classic median cut, splits the largest colour of flat art in two along its noise
/// and leaves two smaller colours in one box. Boxes are cut while there are fewer than `k` and
/// some box spans more than `spread` levels of a channel.
fn median_cut(used: &[Used], k: usize, spread: f64) -> Vec<ColorBox> {
if used.is_empty() {
return vec![ColorBox { mean: [255.0; 3], weight: 0.0, peak: 0.0 }];
}
let mut boxes: Vec<Vec<usize>> = vec![(0..used.len()).collect()];
while boxes.len() < k {
let mut best: Option<(f64, usize, usize)> = None;
for (bi, b) in boxes.iter().enumerate() {
if b.len() < 2 {
continue;
}
let (mut lo, mut hi) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
let mut wsum = 0.0;
for &i in b {
for ch in 0..3 {
lo[ch] = lo[ch].min(used[i].1[ch]);
hi[ch] = hi[ch].max(used[i].1[ch]);
/// A box's bins and its best cut: (gain, bins sorted along the cut's channel, bins on the first side).
struct Cut {
bins: Vec<usize>,
best: Option<(f64, Vec<usize>, usize)>,
}
let plan = |bins: Vec<usize>| {
let mut best: Option<(f64, Vec<usize>, usize)> = None;
let widest = (0..3)
.map(|ch| {
let v = bins.iter().map(|&i| used[i].1[ch]);
v.clone().fold(f64::NEG_INFINITY, f64::max) - v.fold(f64::INFINITY, f64::min)
})
.fold(0.0, f64::max);
if bins.len() >= 2 && widest > spread.max(0.0) {
for ch in 0..3 {
let mut sorted = bins.clone();
sorted.sort_by(|x, y| used[*x].1[ch].total_cmp(&used[*y].1[ch]));
let (total, sum): (f64, f64) = sorted.iter().fold((0.0, 0.0), |a, &i| (a.0 + used[i].2, a.1 + used[i].1[ch] * used[i].2));
let (mut w, mut s) = (0.0, 0.0);
let mut cut: Option<(f64, usize)> = None;
for (j, &i) in sorted.iter().enumerate().take(sorted.len() - 1) {
w += used[i].2;
s += used[i].1[ch] * used[i].2;
let rest = total - w;
if w > 0.0 && rest > 0.0 {
let gain = w * rest / total * (s / w - (sum - s) / rest).powi(2);
if cut.is_none_or(|c| gain > c.0) {
cut = Some((gain, j + 1));
}
}
}
if let Some((gain, at)) = cut
&& best.as_ref().is_none_or(|b| gain > b.0)
{
best = Some((gain, sorted, at));
}
wsum += used[i].2;
}
let (ch, range) = (0..3).map(|c| (c, hi[c] - lo[c])).fold((0, -1.0), |a, b| if b.1 > a.1 { b } else { a });
if range <= spread.max(0.0) {
continue;
}
let score = range * wsum.sqrt();
if best.is_none_or(|x| score > x.0) {
best = Some((score, bi, ch));
}
}
let Some((_, bi, ch)) = best else { break };
let mut b = boxes.swap_remove(bi);
b.sort_by(|x, y| used[*x].1[ch].total_cmp(&used[*y].1[ch]));
let total: f64 = b.iter().map(|&i| used[i].2).sum();
let mut acc = 0.0;
let mut cut = 1;
for (j, &i) in b.iter().enumerate() {
acc += used[i].2;
if acc >= total / 2.0 {
cut = (j + 1).clamp(1, b.len() - 1);
break;
}
}
let tail = b.split_off(cut);
boxes.push(b);
boxes.push(tail);
Cut { bins, best }
};
let mut boxes = vec![plan((0..used.len()).collect())];
while boxes.len() < k {
let gain = |c: &Cut| c.best.as_ref().map_or(f64::NEG_INFINITY, |b| b.0);
let Some(bi) = (0..boxes.len()).filter(|&i| boxes[i].best.is_some()).max_by(|&a, &b| gain(&boxes[a]).total_cmp(&gain(&boxes[b]))) else {
break;
};
let Some((_, mut sorted, at)) = boxes.swap_remove(bi).best else { break };
let tail = sorted.split_off(at);
boxes.push(plan(sorted));
boxes.push(plan(tail));
}
boxes
.iter()
.map(|b| {
let mut s = [0.0; 3];
let (mut w, mut peak) = (0.0, 0.0f64);
for &i in b {
for &i in &b.bins {
for (acc, v) in s.iter_mut().zip(used[i].1) {
*acc += v * used[i].2;
}
@ -386,13 +627,16 @@ fn median_cut(used: &[Used], k: usize, spread: f64) -> Vec<ColorBox> {
.collect()
}
/// Lloyd refinement of `centers` over the bins `used`.
/// Lloyd refinement of `centers` over the bins `used`: each bin goes to the centre that looks most
/// like it (nearest in Oklab), and each centre becomes the mean colour of its bins.
fn refine(used: &[Used], mut centers: Vec<[f64; 3]>) -> Vec<[f64; 3]> {
let bins = labs(&used.iter().map(|u| u.1).collect::<Vec<_>>());
for _ in 0..12 {
let center_labs = labs(&centers);
let mut sum = vec![[0.0; 3]; centers.len()];
let mut cnt = vec![0.0; centers.len()];
for (_, c, n) in used {
let j = nearest_rgb(&centers, *c);
for ((_, c, n), lab) in used.iter().zip(&bins) {
let j = nearest_lab(&center_labs, *lab);
for ch in 0..3 {
sum[j][ch] += c[ch] * n;
}
@ -402,7 +646,7 @@ fn refine(used: &[Used], mut centers: Vec<[f64; 3]>) -> Vec<[f64; 3]> {
for j in 0..centers.len() {
if cnt[j] > 0.0 {
let m = [sum[j][0] / cnt[j], sum[j][1] / cnt[j], sum[j][2] / cnt[j]];
moved = moved.max(dist2(m, centers[j]));
moved = moved.max((0..3).map(|ch| (m[ch] - centers[j][ch]).powi(2)).sum());
centers[j] = m;
}
}

View File

@ -859,3 +859,88 @@ fn no_path_of_a_noisy_picture_leaves_the_picture() {
}
}
}
/// A deterministic value in `-1.0..1.0` for a pixel.
fn jitter(x: u32, y: u32, salt: u32) -> f64 {
let mut v = x.wrapping_mul(0x9E37_79B1) ^ y.wrapping_mul(0x85EB_CA77) ^ salt.wrapping_mul(0xC2B2_AE3D);
v ^= v >> 15;
v = v.wrapping_mul(0x2C1B_3C6D);
v ^= v >> 12;
f64::from(v & 0xFFFF) / 32768.0 - 1.0
}
/// Three flat colours (a navy field, a yellow disc, a green bar) drawn anti-aliased, with `noise`
/// levels of noise per channel.
fn three_colour_art(noise: f64) -> Raster {
const NAVY: [f64; 3] = [20.0, 40.0, 90.0];
const YELLOW: [f64; 3] = [250.0, 200.0, 40.0];
const GREEN: [f64; 3] = [40.0, 160.0, 90.0];
Raster::from_fn(120, 120, |x, y| {
let mut sum = [0.0; 3];
for i in 0..16 {
let (sx, sy) = (f64::from(x) + (f64::from(i % 4) + 0.5) / 4.0, f64::from(y) + (f64::from(i / 4) + 0.5) / 4.0);
let c = if Point::new(sx, sy).distance(Point::new(60.3, 45.6)) <= 30.0 {
YELLOW
} else if (20.4..100.7).contains(&sx) && (90.3..108.6).contains(&sy) {
GREEN
} else {
NAVY
};
for k in 0..3 {
sum[k] += c[k] / 16.0;
}
}
let c = |k: usize| (sum[k] + noise * jitter(x, y, k as u32)).round().clamp(0.0, 255.0) as u8;
[c(0), c(1), c(2), 255]
})
}
fn sixteen_colors() -> TraceParams {
preset("16 Colors").unwrap()
}
#[test]
fn more_colours_than_flat_art_has_trace_its_own_colours() {
for noise in [0.0, 8.0] {
let res = trace(&three_colour_art(noise), &sixteen_colors());
assert_eq!(res.palette.len(), 3, "noise {noise}: {:?}", res.palette);
assert_eq!(res.paths.len(), 3, "noise {noise}");
for want in [[20, 40, 90], [250, 200, 40], [40, 160, 90]] {
assert!(res.palette.iter().any(|c| (0..3).all(|k| c[k].abs_diff(want[k]) <= 3)), "noise {noise}: no {want:?} in {:?}", res.palette);
}
}
}
#[test]
fn a_blend_of_two_colours_is_no_sliver_of_a_third() {
// Halfway from navy to yellow lies an olive nearer to the green of the bar than to either.
let res = trace(&three_colour_art(0.0), &TraceParams { colors: 3, noise: 0, ..sixteen_colors() });
let green: Vec<&TracedPath> = res.paths.iter().filter(|p| p.color[1] > p.color[0] && p.color[1] > p.color[2]).collect();
assert_eq!(green.len(), 1, "the bar, and nothing around the disc");
let b = green[0].path.bounds().unwrap();
assert!(b.y0 > 89.0 && b.y1 < 110.0, "{b:?}");
}
#[test]
fn hairlines_keep_their_colour_in_color_mode() {
// Black lines one pixel wide on white: every black pixel is on an edge, none is a blend.
let img = Raster::from_fn(80, 80, |x, y| if x % 10 == 4 || y % 10 == 4 { BLACK } else { WHITE });
let q = quantize(&img, &TraceParams { mode: Mode::Color, colors: 6, ..TraceParams::default() });
let black = q.palette.iter().position(|c| *c == [0, 0, 0]).unwrap() as u16;
assert_eq!(q.palette.len(), 2);
assert!(q.palette.contains(&[255, 255, 255]));
assert_eq!(q.labels.iter().filter(|&&l| l == black).count(), 8 * 80 * 2 - 64);
}
#[test]
fn the_bands_of_a_gradient_stay_apart() {
let img = Raster::from_fn(256, 64, |x, y| [x as u8, (x / 2 + y) as u8, 200 - (x / 2) as u8, 255]);
let q = quantize(&img, &sixteen_colors());
assert_eq!(q.palette.len(), 16);
// With noise as strong as the bands are apart, the bands still follow the gradient.
let noisy = Raster::from_fn(256, 64, |x, y| {
let c = |v: u32, k: u32| (f64::from(v) + 6.0 * jitter(x, y, k)).round().clamp(0.0, 255.0) as u8;
[c(x, 0), c(x / 2 + y, 1), c(200 - x / 2, 2), 255]
});
assert!(quantize(&noisy, &sixteen_colors()).palette.len() >= 12);
}

View File

@ -1,6 +1,6 @@
# Image Trace quality
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (sub-pixel edges in every mode: outlines 0.31 to 0.09 px off for Default, 0.27 to 0.06 for Black and White Logo)
> **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)
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)
### Now (sub-pixel edges and the palette)
| 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.50 | 2.81 | – | – | – | 3636.4 | 81597 | – | 25 | – | – | – | – | 0.0 | – | 995.3 | 1.3 |
| Low Fidelity Photo | gradient, photo-like | 2.59 | 2.87 | – | – | – | 128.3 | 7277 | – | 37 | – | – | – | – | 0.0 | – | 0.1 | 0.4 |
| 3 Colors | two touching halves | 4.86 | 8.48 | 0.332 | 2.02 | 89.49 | 2.1 | 38 | 6.33 | 11 | 29 | 2.6 | 1.6 | 15527 | 50.3 | 40.4 | 0.0 | 0.0 |
| 6 Colors | two touching halves, four-colour badge | 0.61 | 4.88 | 4.442 | 83.47 | 30.03 | 21.2 | 473 | 53.89 | 45 | 95 | 344.2 | 2.4 | 279 | 68.6 | 79.0 | 0.0 | 0.2 |
| 16 Colors | two touching halves, four-colour badge | 0.60 | 5.32 | 5.084 | 85.85 | 4.16 | 69.9 | 1743 | 196.62 | 49 | 98 | 1376.4 | 5.9 | 612 | 152.8 | 141.8 | 0.2 | 0.6 |
| Shades of Gray | gradient, photo-like | 9.29 | 9.12 | – | – | – | 90.7 | 3183 | – | 51 | – | – | – | – | 0.0 | – | 5.3 | 0.8 |
| 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 |
| 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 |
| 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).
The sections below are the numbers now (sub-pixel edges and the palette).
## 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.50 | 2.81 | – | – | – | 3636.4 | 81597 | – | 25 | – | – | – | – | 0.0 | – | 995.3 | 1.3 |
| Low Fidelity Photo | gradient, photo-like | 2.59 | 2.87 | – | – | – | 128.3 | 7277 | – | 37 | – | – | – | – | 0.0 | – | 0.1 | 0.4 |
| 3 Colors | two touching halves | 4.86 | 8.48 | 0.332 | 2.02 | 89.49 | 2.1 | 38 | 6.33 | 11 | 29 | 2.6 | 1.6 | 15527 | 50.3 | 40.4 | 0.0 | 0.0 |
| 6 Colors | two touching halves, four-colour badge | 0.61 | 4.88 | 4.442 | 83.47 | 30.03 | 21.2 | 473 | 53.89 | 45 | 95 | 344.2 | 2.4 | 279 | 68.6 | 79.0 | 0.0 | 0.2 |
| 16 Colors | two touching halves, four-colour badge | 0.60 | 5.32 | 5.084 | 85.85 | 4.16 | 69.9 | 1743 | 196.62 | 49 | 98 | 1376.4 | 5.9 | 612 | 152.8 | 141.8 | 0.2 | 0.6 |
| Shades of Gray | gradient, photo-like | 9.29 | 9.12 | – | – | – | 90.7 | 3183 | – | 51 | – | – | – | – | 0.0 | – | 5.3 | 0.8 |
| 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 |
| 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 |
| 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 |
@ -121,36 +121,36 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
| Default | thin lines | 0.56 | 4.77 | 0.059 | 0.48 | 105.57 | 3.3 | 22 | 1.12 | 42 | – | – | – | – | 49.1 | 34.0 | 0.0 | 0.0 |
| Default | flat colour art | 19.95 | 18.92 | 0.197 | 0.64 | 179.52 | 1.5 | 22 | 1.32 | 15 | 0 | 0.0 | – | – | 24.3 | 23.0 | 0.0 | 0.0 |
| 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.08 | 3.65 | 0.243 | 1.07 | 0.58 | 9.9 | 187 | 24.84 | 28 | 50 | 0.3 | 8.7 | 299 | 0.0 | – | 3.8 | 0.0 |
| High Fidelity Photo | polygons | 0.13 | 4.80 | 0.160 | 1.14 | 2.43 | 3.3 | 173 | 16.11 | 29 | 78 | 0.1 | 1.7 | 87 | 0.0 | – | 0.0 | 0.0 |
| High Fidelity Photo | thin lines | 0.73 | 8.66 | 0.218 | 0.93 | 45.32 | 13.0 | 498 | 24.90 | 37 | – | – | 6.7 | 532 | 0.0 | – | 7.0 | 0.0 |
| High Fidelity Photo | flat colour art | 0.09 | 2.56 | 0.351 | 1.08 | 0.62 | 48.2 | 497 | 41.54 | 53 | 83 | 9.0 | 23.2 | 1119 | 0.0 | – | 19.2 | 0.0 |
| High Fidelity Photo | gradient, photo | 2.58 | 2.90 | – | – | – | 2926.5 | 87619 | – | 27 | – | – | – | – | 0.0 | – | 792.5 | 0.9 |
| Low Fidelity Photo | curved shapes | 0.09 | 4.45 | 0.219 | 1.02 | 0.50 | 5.0 | 55 | 6.69 | 34 | 100 | 6.7 | 3.8 | 261 | 0.0 | – | 0.0 | 0.0 |
| Low Fidelity Photo | polygons | 0.15 | 5.82 | 0.172 | 0.97 | 2.43 | 3.3 | 55 | 5.12 | 33 | 80 | 0.2 | 1.7 | 81 | 0.0 | – | 0.0 | 0.0 |
| Low Fidelity Photo | thin lines | 0.80 | 9.43 | 0.238 | 0.82 | 45.33 | 5.0 | 182 | 9.08 | 48 | – | – | 1.3 | 497 | 0.0 | – | 0.0 | 0.0 |
| Low Fidelity Photo | flat colour art | 0.11 | 3.30 | 0.323 | 1.04 | 0.56 | 20.0 | 142 | 10.37 | 61 | 100 | 35.7 | 7.0 | 940 | 0.0 | – | 0.0 | 0.0 |
| Low Fidelity Photo | gradient, photo | 2.67 | 2.97 | – | – | – | 96.2 | 8623 | – | 36 | – | – | – | – | 0.0 | – | 0.0 | 0.4 |
| 3 Colors | curved shapes | 0.47 | 4.85 | 0.128 | 1.06 | 0.52 | 2.8 | 30 | 3.42 | 20 | 100 | 1.7 | 1.0 | 86 | 35.0 | 41.9 | 0.0 | 0.0 |
| 3 Colors | polygons | 0.62 | 4.45 | 0.080 | 1.03 | 1.50 | 2.4 | 28 | 2.58 | 43 | 81 | 0.0 | 0.4 | 16 | 10.9 | 13.4 | 0.0 | 0.0 |
| 3 Colors | thin lines | 0.74 | 7.45 | 0.129 | 1.06 | 45.26 | 5.0 | 60 | 3.00 | 37 | – | – | 3.7 | 1123 | 84.3 | 53.1 | 0.0 | 0.0 |
| 3 Colors | flat colour art | 3.77 | 6.61 | 0.191 | 1.35 | 89.46 | 2.3 | 26 | 1.90 | 34 | 38 | 0.3 | 1.5 | 15138 | 35.8 | 28.5 | 0.2 | 0.0 |
| 3 Colors | gradient, photo | 7.85 | 7.91 | – | – | – | 4.3 | 611 | – | 47 | – | – | – | – | 23.6 | 30.1 | 0.0 | 0.2 |
| 6 Colors | curved shapes | 0.11 | 4.81 | 0.186 | 0.96 | 0.50 | 3.6 | 56 | 6.76 | 25 | 100 | 2.8 | 1.8 | 155 | 30.9 | 29.4 | 0.0 | 0.0 |
| 6 Colors | polygons | 0.12 | 4.81 | 0.128 | 0.80 | 1.51 | 3.1 | 56 | 5.23 | 35 | 81 | 0.2 | 1.1 | 47 | 22.2 | 20.6 | 0.0 | 0.0 |
| 6 Colors | thin lines | 0.91 | 9.26 | 0.160 | 0.86 | 45.36 | 4.0 | 132 | 6.60 | 40 | – | – | 1.7 | 409 | 53.6 | 44.0 | 0.0 | 0.0 |
| 6 Colors | flat colour art | 0.19 | 2.98 | 0.216 | 1.29 | 0.51 | 5.0 | 60 | 5.27 | 56 | 100 | 3.0 | 1.5 | 170 | 27.9 | 39.3 | 0.0 | 0.0 |
| 6 Colors | gradient, photo | 5.50 | 5.70 | – | – | – | 15.5 | 1421 | – | 38 | – | – | – | – | 54.3 | 75.6 | 0.0 | 0.2 |
| 16 Colors | curved shapes | 0.09 | 4.14 | 0.200 | 0.97 | 0.50 | 4.2 | 91 | 11.37 | 29 | 100 | 5.3 | 2.5 | 168 | 21.8 | 21.7 | 0.0 | 0.0 |
| 16 Colors | polygons | 0.13 | 5.20 | 0.138 | 0.97 | 2.43 | 3.1 | 81 | 7.53 | 33 | 81 | 0.3 | 1.1 | 52 | 15.3 | 18.5 | 0.0 | 0.0 |
| 16 Colors | thin lines | 0.79 | 9.20 | 0.187 | 0.82 | 45.35 | 4.0 | 287 | 14.35 | 35 | – | – | 1.3 | 362 | 24.9 | 22.6 | 0.0 | 0.0 |
| 16 Colors | flat colour art | 0.12 | 3.16 | 0.279 | 0.99 | 0.45 | 7.5 | 127 | 10.37 | 61 | 100 | 7.5 | 3.0 | 321 | 59.9 | 31.8 | 0.0 | 0.0 |
| 16 Colors | gradient, photo | 3.23 | 3.58 | – | – | – | 72.0 | 4652 | – | 30 | – | – | – | – | 165.5 | 258.6 | 0.0 | 0.8 |
| Shades of Gray | curved shapes | 0.11 | 4.71 | 0.216 | 1.00 | 0.59 | 5.1 | 49 | 6.02 | 30 | 100 | 5.9 | 3.8 | 252 | 0.0 | – | 0.0 | 0.0 |
| Shades of Gray | polygons | 0.11 | 4.32 | 0.147 | 0.80 | 1.39 | 3.3 | 45 | 4.09 | 36 | 82 | 0.0 | 1.7 | 76 | 0.0 | – | 0.0 | 0.0 |
| Shades of Gray | thin lines | 0.81 | 8.80 | 0.239 | 0.70 | 45.31 | 5.0 | 141 | 7.03 | 44 | – | – | 1.3 | 499 | 0.0 | – | 0.0 | 0.0 |
| Shades of Gray | flat colour art | 7.35 | 10.90 | 0.357 | 1.42 | 179.46 | 13.3 | 121 | 10.08 | 42 | 50 | 30.8 | 12.7 | 39181 | 0.0 | – | 0.0 | 0.0 |
| Shades of Gray | gradient, photo | 9.73 | 9.60 | – | – | – | 82.0 | 3181 | – | 51 | – | – | – | – | 0.0 | – | 2.2 | 0.8 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 |
| 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,36 +191,36 @@ 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.58 | 2.90 | – | – | – | 2926.5 | 87619 | – | 27 | – | – | – | – | 0.0 | – | 792.5 | 0.9 |
| High Fidelity Photo | aliased | 2.55 | 2.88 | – | – | – | 2686.0 | 56883 | – | 22 | – | – | – | – | 0.0 | – | 695.5 | 0.9 |
| High Fidelity Photo | blurred | 2.05 | 2.50 | – | – | – | 1486.5 | 37982 | – | 22 | – | – | – | – | 0.0 | – | 205.5 | 1.3 |
| High Fidelity Photo | noisy | 3.10 | 3.37 | – | – | – | 7294.5 | 118138 | – | 26 | – | – | – | – | 0.0 | – | 2051.5 | 0.6 |
| High Fidelity Photo | scanned | 2.04 | 2.22 | – | – | – | 5208.5 | 95324 | – | 24 | – | – | – | – | 0.0 | – | 1637.0 | 0.4 |
| Low Fidelity Photo | clean | 2.67 | 2.97 | – | – | – | 96.2 | 8623 | – | 36 | – | – | – | – | 0.0 | – | 0.0 | 0.4 |
| Low Fidelity Photo | aliased | 2.62 | 2.91 | – | – | – | 125.5 | 5195 | – | 34 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | blurred | 2.67 | 3.13 | – | – | – | 151.5 | 4525 | – | 33 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | noisy | 2.75 | 2.94 | – | – | – | 106.5 | 7104 | – | 39 | – | – | – | – | 0.0 | – | 0.0 | 0.2 |
| Low Fidelity Photo | scanned | 2.07 | 2.19 | – | – | – | 226.0 | 8249 | – | 40 | – | – | – | – | 0.0 | – | 0.5 | 0.2 |
| 3 Colors | clean | 4.62 | 7.50 | 0.106 | 0.35 | 89.46 | 1.3 | 11 | 1.78 | 13 | 33 | 0.0 | 0.7 | 10577 | 33.4 | 32.3 | 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 |
| 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 | 6.92 | 10.46 | 0.848 | 2.02 | 87.75 | 4.0 | 122 | 20.33 | 0 | 0 | 10.0 | 4.0 | 25697 | 51.3 | 37.6 | 0.0 | 0.0 |
| 3 Colors | noisy | 7.21 | 9.91 | 0.112 | 0.31 | 89.49 | 1.0 | 12 | 2.00 | 0 | 0 | 0.0 | 1.0 | 25398 | 37.8 | 18.2 | 0.0 | 0.0 |
| 3 Colors | scanned | 5.86 | 6.64 | 0.814 | 1.49 | 88.54 | 4.0 | 93 | 15.50 | 0 | 0 | 8.0 | 4.0 | 25863 | 39.3 | 47.4 | 0.0 | 0.0 |
| 6 Colors | clean | 0.19 | 2.98 | 0.216 | 1.29 | 0.51 | 5.0 | 60 | 5.27 | 56 | 100 | 3.0 | 1.5 | 170 | 27.9 | 39.3 | 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 |
| 3 Colors | noisy | 0.71 | 1.81 | 0.049 | 0.23 | 0.08 | 2.0 | 9 | 1.50 | 39 | 100 | 0.0 | 0.0 | 0 | 35.7 | 9.7 | 0.0 | 0.0 |
| 3 Colors | scanned | 1.57 | 3.23 | 0.082 | 0.34 | 0.22 | 2.0 | 11 | 1.83 | 39 | 100 | 0.0 | 0.0 | 0 | 31.9 | 18.9 | 0.0 | 0.0 |
| 6 Colors | clean | 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 | aliased | 0.31 | 10.53 | 0.291 | 1.00 | 1.00 | 3.0 | 26 | 1.91 | 42 | 100 | 0.0 | 0.0 | 0 | 82.6 | 74.0 | 0.0 | 0.0 |
| 6 Colors | blurred | 0.76 | 6.94 | 0.685 | 1.52 | 1.02 | 10.0 | 204 | 22.77 | 43 | 100 | 23.5 | 6.0 | 976 | 49.1 | 39.8 | 0.0 | 0.0 |
| 6 Colors | noisy | 1.20 | 3.58 | 20.366 | 79.78 | 30.03 | 99.0 | 2064 | 245.68 | 30 | 100 | 1822.5 | 3.0 | 297 | 191.1 | 209.1 | 0.0 | 0.2 |
| 6 Colors | scanned | 1.47 | 4.15 | 9.100 | 83.47 | 0.94 | 21.5 | 835 | 91.04 | 30 | 62 | 554.5 | 3.0 | 172 | 73.9 | 112.0 | 0.0 | 0.2 |
| 16 Colors | clean | 0.12 | 3.16 | 0.279 | 0.99 | 0.45 | 7.5 | 127 | 10.37 | 61 | 100 | 7.5 | 3.0 | 321 | 59.9 | 31.8 | 0.0 | 0.0 |
| 6 Colors | blurred | 0.62 | 9.41 | 0.249 | 1.09 | 1.14 | 6.0 | 115 | 6.33 | 42 | 50 | 9.5 | 0.0 | 0 | 32.6 | 48.3 | 0.0 | 0.0 |
| 6 Colors | noisy | 0.85 | 2.50 | 0.048 | 0.26 | 0.18 | 3.0 | 28 | 2.01 | 33 | 100 | 0.0 | 0.0 | 0 | 29.7 | 22.5 | 0.0 | 0.0 |
| 6 Colors | scanned | 1.76 | 4.63 | 0.240 | 1.09 | 0.94 | 7.0 | 101 | 5.81 | 31 | 62 | 15.5 | 0.0 | 0 | 62.7 | 41.7 | 0.0 | 0.0 |
| 16 Colors | clean | 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 | aliased | 0.30 | 10.39 | 0.274 | 1.00 | 1.00 | 3.0 | 34 | 2.34 | 43 | 100 | 0.0 | 0.0 | 0 | 77.3 | 51.7 | 0.0 | 0.0 |
| 16 Colors | blurred | 0.59 | 8.94 | 0.949 | 2.58 | 1.73 | 26.5 | 421 | 35.52 | 57 | 100 | 102.5 | 10.0 | 1155 | 149.8 | 121.6 | 0.0 | 0.0 |
| 16 Colors | noisy | 1.31 | 4.52 | 17.093 | 82.18 | 4.16 | 238.5 | 4758 | 593.71 | 29 | 100 | 4008.5 | 4.5 | 607 | 313.9 | 328.8 | 0.0 | 0.2 |
| 16 Colors | scanned | 1.63 | 3.93 | 16.438 | 85.85 | 3.01 | 199.0 | 6606 | 713.67 | 29 | 88 | 5501.0 | 18.0 | 1561 | 349.0 | 394.9 | 1.5 | 0.6 |
| Shades of Gray | clean | 9.73 | 9.60 | – | – | – | 82.0 | 3181 | – | 51 | – | – | – | – | 0.0 | – | 2.2 | 0.8 |
| Shades of Gray | aliased | 9.73 | 9.58 | – | – | – | 80.0 | 2438 | – | 54 | – | – | – | – | 0.0 | – | 3.0 | 0.8 |
| Shades of Gray | blurred | 9.69 | 9.39 | – | – | – | 62.0 | 1652 | – | 50 | – | – | – | – | 0.0 | – | 1.0 | 0.7 |
| Shades of Gray | noisy | 9.79 | 9.52 | – | – | – | 132.0 | 3813 | – | 51 | – | – | – | – | 0.0 | – | 9.0 | 0.8 |
| Shades of Gray | scanned | 6.64 | 6.56 | – | – | – | 115.0 | 4832 | – | 48 | – | – | – | – | 0.0 | – | 17.5 | 0.5 |
| 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 |
| 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 |
@ -282,88 +282,89 @@ 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.08 s, 29 paths, 7947 anchors, 15 MB |
| 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 | 2.05 s, 51279 paths, 1476202 anchors, 209 MB |
| High Fidelity Photo | four-colour badge | 1 | 0.06 s, 147 paths, 1700 anchors, 111 MB |
| Low Fidelity Photo | photo-like | 1 | 0.19 s, 612 paths, 91647 anchors, 133 MB |
| Low Fidelity Photo | four-colour badge | 1 | 0.05 s, 85 paths, 548 anchors, 86 MB |
| 3 Colors | photo-like | 1 | 0.05 s, 9 paths, 5249 anchors, 22 MB |
| 3 Colors | four-colour badge | 1 | 0.04 s, 6 paths, 128 anchors, 21 MB |
| 6 Colors | photo-like | 1 | 0.05 s, 53 paths, 13454 anchors, 38 MB |
| 6 Colors | four-colour badge | 1 | 0.04 s, 7 paths, 153 anchors, 39 MB |
| 16 Colors | photo-like | 1 | 0.13 s, 398 paths, 49771 anchors, 89 MB |
| 16 Colors | four-colour badge | 1 | 0.05 s, 16 paths, 527 anchors, 89 MB |
| Shades of Gray | photo-like | 1 | 0.10 s, 447 paths, 31068 anchors, 52 MB |
| Shades of Gray | four-colour badge | 1 | 0.04 s, 36 paths, 400 anchors, 52 MB |
| Black and White Logo | photo-like | 1 | 0.06 s, 71 paths, 9067 anchors, 9 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 |
| 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 | 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 |
| 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.06 s, 5 paths, 3207 anchors, 9 MB |
| Sketched Art | four-colour badge | 1 | 0.03 s, 2 paths, 145 anchors, 8 MB |
| Silhouettes | photo-like | 1 | 0.05 s, 2 paths, 851 anchors, 11 MB |
| Silhouettes | four-colour badge | 1 | 0.03 s, 1 paths, 29 anchors, 9 MB |
| Line Art | photo-like | 1 | 0.07 s, 54 paths, 4751 anchors, 9 MB |
| Line Art | four-colour badge | 1 | 0.03 s, 1 paths, 243 anchors, 8 MB |
| Technical Drawing | photo-like | 1 | 0.07 s, 221 paths, 10646 anchors, 9 MB |
| Sketched Art | photo-like | 1 | 0.05 s, 5 paths, 3207 anchors, 9 MB |
| Sketched Art | four-colour badge | 1 | 0.02 s, 2 paths, 145 anchors, 8 MB |
| Silhouettes | photo-like | 1 | 0.04 s, 2 paths, 851 anchors, 11 MB |
| Silhouettes | four-colour badge | 1 | 0.02 s, 1 paths, 29 anchors, 9 MB |
| Line Art | photo-like | 1 | 0.05 s, 54 paths, 4751 anchors, 9 MB |
| Line Art | four-colour badge | 1 | 0.02 s, 1 paths, 243 anchors, 8 MB |
| 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.10 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 1 | 0.76 s, 5 paths, 78 anchors, 146 MB |
| Default | photo-like | 4 | 0.22 s, 48 paths, 20717 anchors, 60 MB |
| Default | four-colour badge | 4 | 0.12 s, 3 paths, 148 anchors, 64 MB |
| High Fidelity Photo | photo-like | 4 | refused after 3.98 s (over 2,000,000 anchors), 818 MB |
| High Fidelity Photo | four-colour badge | 4 | 0.22 s, 225 paths, 2758 anchors, 530 MB |
| Low Fidelity Photo | photo-like | 4 | 0.52 s, 1499 paths, 196617 anchors, 493 MB |
| Low Fidelity Photo | four-colour badge | 4 | 0.19 s, 88 paths, 780 anchors, 352 MB |
| 3 Colors | photo-like | 4 | 0.20 s, 22 paths, 13682 anchors, 88 MB |
| 3 Colors | four-colour badge | 4 | 0.14 s, 5 paths, 153 anchors, 84 MB |
| 6 Colors | photo-like | 4 | 0.21 s, 100 paths, 32737 anchors, 152 MB |
| 6 Colors | four-colour badge | 4 | 0.15 s, 7 paths, 191 anchors, 152 MB |
| 16 Colors | photo-like | 4 | 0.40 s, 1236 paths, 130559 anchors, 393 MB |
| 16 Colors | four-colour badge | 4 | 0.22 s, 17 paths, 917 anchors, 324 MB |
| Shades of Gray | photo-like | 4 | 0.37 s, 1289 paths, 83191 anchors, 199 MB |
| Shades of Gray | four-colour badge | 4 | 0.19 s, 41 paths, 450 anchors, 197 MB |
| Black and White Logo | photo-like | 4 | 0.26 s, 208 paths, 25916 anchors, 36 MB |
| Black and White Logo | four-colour badge | 4 | 0.10 s, 1 paths, 64 anchors, 36 MB |
| Sketched Art | photo-like | 4 | 0.26 s, 9 paths, 6875 anchors, 36 MB |
| Sketched Art | four-colour badge | 4 | 0.10 s, 2 paths, 279 anchors, 36 MB |
| Silhouettes | photo-like | 4 | 0.20 s, 2 paths, 2133 anchors, 44 MB |
| Silhouettes | four-colour badge | 4 | 0.10 s, 1 paths, 32 anchors, 36 MB |
| Line Art | photo-like | 4 | 0.23 s, 127 paths, 12595 anchors, 36 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 |
| 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 |
| 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 |
| 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.10 s, 1 paths, 64 anchors, 36 MB |
| Flat Logo | photo-like | 4 | refused after 0.12 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 4 | 0.73 s, 5 paths, 69 anchors, 146 MB |
| Default | photo-like | 16 | 1.19 s, 127 paths, 46199 anchors, 256 MB |
| Default | four-colour badge | 16 | 0.50 s, 3 paths, 284 anchors, 240 MB |
| High Fidelity Photo | photo-like | 16 | refused after 8.58 s (over 2,000,000 anchors), 3320 MB |
| High Fidelity Photo | four-colour badge | 16 | 1.11 s, 438 paths, 4419 anchors, 2404 MB |
| Low Fidelity Photo | photo-like | 16 | 2.12 s, 8558 paths, 699119 anchors, 1910 MB |
| Low Fidelity Photo | four-colour badge | 16 | 1.18 s, 105 paths, 1089 anchors, 1556 MB |
| 3 Colors | photo-like | 16 | 1.08 s, 87 paths, 32157 anchors, 352 MB |
| 3 Colors | four-colour badge | 16 | 0.70 s, 6 paths, 227 anchors, 336 MB |
| 6 Colors | photo-like | 16 | 0.98 s, 476 paths, 86962 anchors, 608 MB |
| 6 Colors | four-colour badge | 16 | 0.68 s, 11 paths, 290 anchors, 600 MB |
| 16 Colors | photo-like | 16 | 1.55 s, 7432 paths, 407078 anchors, 1574 MB |
| 16 Colors | four-colour badge | 16 | 0.86 s, 23 paths, 1707 anchors, 1348 MB |
| Shades of Gray | photo-like | 16 | 1.24 s, 5996 paths, 256732 anchors, 789 MB |
| Shades of Gray | four-colour badge | 16 | 1.03 s, 41 paths, 681 anchors, 684 MB |
| Black and White Logo | photo-like | 16 | 1.20 s, 1128 paths, 77629 anchors, 144 MB |
| Black and White Logo | four-colour badge | 16 | 0.46 s, 1 paths, 103 anchors, 128 MB |
| Technical Drawing | four-colour badge | 4 | 0.09 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.47 s, 2 paths, 478 anchors, 128 MB |
| Silhouettes | photo-like | 16 | 1.36 s, 3 paths, 4891 anchors, 176 MB |
| Silhouettes | four-colour badge | 16 | 0.53 s, 1 paths, 44 anchors, 144 MB |
| Line Art | photo-like | 16 | 1.20 s, 713 paths, 64481 anchors, 144 MB |
| Line Art | four-colour badge | 16 | 0.49 s, 1 paths, 786 anchors, 128 MB |
| Technical Drawing | photo-like | 16 | 1.45 s, 4578 paths, 114292 anchors, 144 MB |
| Technical Drawing | four-colour badge | 16 | 0.48 s, 1 paths, 103 anchors, 128 MB |
| Flat Logo | photo-like | 16 | refused after 0.13 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 16 | 1.09 s, 5 paths, 67 anchors, 146 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 |
| 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 |
| 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 |
## Revision history
| date | change |
|:--|:--|
| 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 |