Merge pull request #1504 from storytold/u/trace-palette

Image Trace: colour presets trace flat art with its own colours (no shattering, no slivers)
This commit is contained in:
BFlat authored and GitHub committed 2026-10-13 03:55:45 +07:00
commit ce0c86f91a
4 files changed
+556 -223

No files matched your search

+4 -1
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
+322 -78
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;
}
}
+85
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);
}
+145 -144
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 |