Compare commits

...
Author SHA1 Message Date
bflatastic 7af5790b71 Image Trace: levelling moves no end of a line by more than 1.6 px
Snap Curves To Lines made every straight piece within 2.5 degrees of level or
upright exactly so, whatever its length: a 200 px line 2 degrees off had each end
moved 3.5 px. Now a piece is levelled only when neither end moves more than
1.6 px; a longer line that far off is left as the image has it.

Technical Drawing (the preset with the option on) on its inputs: largest
distance from the art 4.21 -> 1.46 px (1.43 before levelling existed), mean
0.178 -> 0.126 px; on the askew inputs 98% -> 70% of the outline comes out level
or upright.
2026-10-13 12:40:24 +07:00
BFlat 5bfe774420 Merge pull request #1627 from storytold/u/trace-gradients
Image Trace: Gradients
2026-10-13 12:24:35 +07:00
BFlat e918615347 Merge pull request #1623 from storytold/fix/1592-effect-params
Effects: effect.apply and effect.setParams refuse parameters the effect doesn't take (#1592)
2026-10-13 12:19:00 +07:00
BFlat 17b4396a97 Merge pull request #1585 from p0lyg0n/fix/pdf-glyph-by-glyph-strokes
PDF/.ai import: type stroked glyph by glyph stays one object
2026-10-13 12:07:21 +07:00
BFlat 5adeada26d Merge pull request #1586 from p0lyg0n/fix/pdf-type-on-a-path-tracking
PDF/.ai import: evenly tracked type on a path keeps its length
2026-10-13 12:07:05 +07:00
bflatastic 175ad4a1e3 Effects: effect.apply and effect.setParams refuse parameters the effect doesn't take (#1592) 2026-10-13 11:53:38 +07:00
bflatastic 95304ad7f6 PDF import: a held glyph stroke takes its turn in the page's overprint list 2026-10-13 11:53:38 +07:00
Hiroshi InukaiandClaude Opus 5.5 02da9bbdc9 PDF/.ai import: evenly tracked type on a path keeps its length
Straight type takes the tracking that keeps its length as the file set
it; type on a path took none, so letter-spaced type on a curve came back
closed up to the font's advances (an "OFFICIAL ISSUED" badge sat to one
side of its band, a condensed title ran off the end of its path). Type on
a path now takes the same tracking, its length measured along the path
from the first glyph to the end of the last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 11:53:38 +07:00
Hiroshi Inukai 331fc48b24 PDF/.ai import: type stroked glyph by glyph stays one object
Type on a path is written glyph by glyph, and a stroke added to it as each
glyph's outline stroked right after the glyph. The path in between ended the
line, so each glyph came in as its own text object. Such a stroke, in the
glyph's own paint, is now held with the line and written after it, where it
becomes the type's stroke as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	crates/pdf/src/tests_import_lines.rs
2026-10-13 11:53:38 +07:00
19 changed files with 461 additions and 167 deletions

No files matched your search

+1 -1
View File
@@ -249,7 +249,7 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
"distort.freeDistort",
"Free Distort…",
DT,
"{corners: [[x,y]×4] new positions of the bounding-box corners TL, TR, BR, BL in unit box coordinates (identity [[0,0],[1,0],[1,1],[0,1]])}",
"{corners: [[x,y]×4] (new positions of the bounding-box corners TL, TR, BR, BL in unit box coordinates; identity [[0,0],[1,0],[1,1],[0,1]])}",
json!({"corners": [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]}),
),
g("distort.puckerBloat", "Pucker & Bloat…", DT, "{amount: % (-200 pucker … 200 bloat, default 0)}", json!({"amount": 0.0})),
+15 -1
View File
@@ -280,6 +280,20 @@ fn move_effect(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({ "ids": ids_json(&ids), "index": index, "item": item.flatten() }))
}
/// The ids of the effects `effect.setParams` with `p` edits, for the check of their parameters
/// (none when `p` doesn't say which: running it reports why).
pub(crate) fn set_params_effects(s: &Session, p: &Value) -> Vec<String> {
const C: &str = "effect.setParams";
let (Ok(index), Ok(item), Ok(ids), Ok(st)) = (index_param(p, "index", C), item_target(s, p, C), appearance_targets(s, p), s.doc()) else {
return vec![];
};
let effect = |id: &NodeId| {
let ap = &st.doc.node(*id)?.appearance;
Some(ap.effects_at(item.effects_item(ap, C).ok()?)?.get(index)?.id.clone())
};
ids.iter().filter_map(effect).collect()
}
fn set_params(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "effect.setParams";
let index = index_param(p, "index", C)?;
@@ -566,7 +580,7 @@ mod tests {
for other in ["threeD.revolve", "threeD.extrude", "threeD.rotate"] {
assert!(s.execute("effect.apply", &json!({"effect": other, "ids": [id.0], "item": null})).is_err(), "{effect} + {other}");
}
s.execute("effect.setParams", &json!({"index": 0, "params": {"rotationY": 50, "depth": 120}, "ids": [id.0], "item": null})).unwrap();
s.execute("effect.setParams", &json!({"index": 0, "params": {"rotationY": 50, "rotationX": 10}, "ids": [id.0], "item": null})).unwrap();
assert_ne!(effects::solid_art(&node(&s, id)).unwrap(), art, "{effect}: options change the art");
assert!(s.execute("effect.setParams", &json!({"index": 0, "params": {"bevel": "nope"}, "ids": [id.0], "item": null})).is_err());
let svg = s.execute("document.serialize", &json!({"format": "svg"})).unwrap();
+72 -8
View File
@@ -10,8 +10,10 @@
//!
//! Callers outside the app (the control channel, MCP, the CLI) are answered with a parameter error
//! for a key the description doesn't name ([`check_params`]): a misspelt `widht` no longer runs the
//! command with its default. [`Session::execute`] itself takes what it is given, so the journal of
//! an older version still replays.
//! command with its default. So are the `params` of an effect (#1592): `effect.apply` and
//! `effect.setParams` answer a key the effect doesn't take, or a value of another type than its
//! default, with a parameter error ([`check_effect_params`]). [`Session::execute`] itself takes
//! what it is given, so the journal of an older version still replays.
use serde_json::Value;
@@ -189,15 +191,56 @@ pub fn check_keys(id: &str, doc: &'static str, params: &Value) -> Result<()> {
return Ok(());
}
let Some(keys) = accepted(doc, 0) else { return Ok(()) };
let Some(key) = given.keys().find(|k| !keys.contains(&k.as_str())) else { return Ok(()) };
Err(bad(
match given.keys().find(|k| !keys.contains(&k.as_str())) {
Some(key) => Err(unknown(id, key, &keys)),
None => Ok(()),
}
}
/// The parameter error of command `id` for `key`, which isn't one of `keys`: with the nearest of
/// them, or all of them.
fn unknown(id: &str, key: &str, keys: &[&str]) -> crate::EngineError {
bad(
id,
match nearest(key, &keys) {
match nearest(key, keys) {
Some(near) => format!("unknown parameter `{key}`; did you mean `{near}`?"),
None if keys.is_empty() => format!("unknown parameter `{key}`; the command takes no parameters"),
None => format!("unknown parameter `{key}`; the command takes: {}", keys.join(", ")),
},
))
)
}
/// `params` of built-in effect `effect`, as command `id` was given them (#1592): only keys the
/// effect takes (those of its dialog defaults and of its params description), each of its
/// default's type. Numbers out of range pass: the effect clamps them when it draws. An effect
/// plug-in resolves its own against its manifest, and an unknown effect is the command's to report.
pub fn check_effect_params(id: &str, effect: &str, params: &Value) -> Result<()> {
let Value::Object(given) = params else { return Ok(()) };
if effect.starts_with(vectorcraft_plugins::effect::PREFIX) {
return Ok(());
}
let Some(info) = vectorcraft_render::effects::effect_info(effect) else { return Ok(()) };
let defaults = info.defaults.as_object();
let mut keys: Vec<&str> = defaults.into_iter().flat_map(|d| d.keys().map(String::as_str)).collect();
// A description that doesn't parse names no more keys (the tests rule that out).
for key in doc_keys(info.params).map(|d| d.keys).unwrap_or_default() {
if !keys.contains(&key) {
keys.push(key);
}
}
for (key, v) in given {
if !keys.contains(&key.as_str()) {
return Err(unknown(id, key, &keys));
}
match defaults.and_then(|d| d.get(key)) {
Some(Value::Number(_)) if !v.is_number() => return Err(bad(id, format!("`{key}` must be a number"))),
Some(Value::Bool(_)) if !v.is_boolean() => return Err(bad(id, format!("`{key}` must be true or false"))),
// (A colour is also taken as `[r, g, b]` or as a colour object.)
Some(Value::String(_)) if v.is_number() || v.is_boolean() => return Err(bad(id, format!("`{key}` must be a string"))),
_ => {}
}
}
Ok(())
}
/// Commands whose params aren't checked yet (#1442): they read keys in ways the audit of the
@@ -333,10 +376,16 @@ pub const UNCHECKED: &[&str] = &[
"webExport.settings",
];
/// [`check_keys`] for an engine command, the steps of a `command.batch` included. A command the
/// engine doesn't have passes: running it reports that.
/// [`check_keys`] for an engine command, the steps of a `command.batch` included, and
/// [`check_effect_params`] for the effect `effect.apply` adds. A command the engine doesn't have
/// passes: running it reports that.
pub fn check_params(id: &str, params: &Value) -> Result<()> {
let Some(spec) = find_command(id) else { return Ok(()) };
if let ("effect.apply" | "appearance.addEffect", Some(effect), Some(given)) =
(id, params.get("effect").or_else(|| params.get("id")).and_then(Value::as_str), params.get("params"))
{
check_effect_params(id, effect, given)?;
}
if UNCHECKED.contains(&id) {
return Ok(());
}
@@ -350,3 +399,18 @@ pub fn check_params(id: &str, params: &Value) -> Result<()> {
}
Ok(())
}
impl crate::Session {
/// [`check_params`], and [`check_effect_params`] for the effects `effect.setParams` edits in
/// this session's document (not as a step of a `command.batch`: what it edits is only there
/// once the steps before it ran).
pub fn check_params(&self, id: &str, params: &Value) -> Result<()> {
check_params(id, params)?;
if let ("effect.setParams", Some(given)) = (id, params.get("params")) {
for effect in super::effectcmd::set_params_effects(self, params) {
check_effect_params(id, &effect, given)?;
}
}
Ok(())
}
}
+1 -1
View File
@@ -1317,7 +1317,7 @@ impl Session {
// The crate's own tests are the audit of the params descriptions (#1442): a key a test
// passes that its command's description doesn't name fails the test.
#[cfg(test)]
cmd::params::check_params(id, params)?;
self.check_params(id, params)?;
// Clear's menu needs a selection; an agent supplying explicit targets only needs a
// document. The command validates those targets before making any edit.
let enabled = if id == "edit.clear" && params.get("ids").is_some() {
+45 -1
View File
@@ -2,7 +2,7 @@
use serde_json::json;
use crate::cmd::params::{UNCHECKED, check_keys, check_params, doc_keys};
use crate::cmd::params::{UNCHECKED, check_effect_params, check_keys, check_params, doc_keys};
use crate::{EngineError, Session, command_specs, find_command};
fn keys(doc: &str) -> Vec<&str> {
@@ -95,6 +95,50 @@ fn a_batch_checks_its_steps() {
assert_eq!(refusal("command.batch", json!({"steps": []})), "unknown parameter `steps`; the command takes: label, commands, artboard");
}
/// #1592: the `params` of an effect are held to the effect's own keys and types, where the effect
/// is named (`effect.apply`) and where the document says which it is (`effect.setParams`).
#[test]
fn effect_params_are_the_effects_own() {
use vectorcraft_render::effects::effect_catalog;
for e in effect_catalog() {
doc_keys(e.params).unwrap_or_else(|why| panic!("{}: {why}", e.id));
check_effect_params("effect.apply", e.id, &e.defaults).unwrap_or_else(|why| panic!("{}'s defaults: {why}", e.id));
}
let apply = |effect: &str, params| json!({"effect": effect, "params": params});
assert_eq!(refusal("effect.apply", apply("blur.gaussian", json!({"blurr": 3}))), "unknown parameter `blurr`; the command takes: radius");
assert_eq!(
refusal("appearance.addEffect", apply("stylize.dropShadow", json!({"opacty": 50}))),
"unknown parameter `opacty`; did you mean `opacity`?"
);
assert_eq!(refusal("effect.apply", apply("blur.gaussian", json!({"radius": "big"}))), "`radius` must be a number");
assert_eq!(refusal("effect.apply", apply("threeD.revolve", json!({"shade": 1}))), "`shade` must be true or false");
assert_eq!(refusal("effect.apply", apply("threeD.revolve", json!({"edge": 1}))), "`edge` must be a string");
// Out of range is the effect's to clamp; a key only its description names is one it takes;
// a plug-in's are its manifest's; an unknown effect is the command's to report.
for (effect, params) in [
("stylize.dropShadow", json!({"opacity": 500, "x": -3, "color": "#ff0000"})),
("stylize.dropShadow", json!({"color": [1, 0, 0]})),
("threeD.revolve", json!({"segments": 100000})),
("path.outlineStroke", json!({"width": 2})),
("plugin.anything", json!({"whatever": 1})),
("no.such", json!({"whatever": 1})),
] {
assert!(check_params("effect.apply", &apply(effect, params.clone())).is_ok(), "{effect} {params}");
}
// `effect.setParams` names an index: the session knows the effect there.
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 50, "height": 40})).unwrap();
s.execute("effect.apply", &apply("blur.gaussian", json!({"radius": 2}))).unwrap();
for (params, why) in [(json!({"blurr": 3}), "unknown parameter `blurr`"), (json!({"radius": true}), "`radius` must be a number")] {
let e = s.execute("effect.setParams", &json!({"index": 0, "params": params})).unwrap_err().to_string();
assert!(e.contains(why), "{e}");
}
s.execute("effect.setParams", &json!({"index": 0, "params": {"radius": 1e9}, "visible": false})).unwrap();
// No effect at the index: the command says so, not the check.
assert!(s.check_params("effect.setParams", &json!({"index": 7, "params": {"blurr": 3}})).is_ok());
}
/// What a command notes for its journal entry is a key its description names, so the entry
/// passes the check a caller's params get.
#[test]
+12 -3
View File
@@ -106,10 +106,19 @@ fn listed_applied_edited_and_undone() {
assert_eq!((&e["raster"], &e["menu"]), (&json!(true), &json!(["Effect", menu])), "{id}");
}
}
// Junk parameters are stored as given and read clamped; the canvas still draws.
// Numbers far out of range are stored as given and read clamped; the canvas still draws.
for (id, _) in EFFECTS {
s.execute("effect.apply", &json!({"effect": id, "ids": [g], "params": {"amount": 1e308, "radius": -1e308, "threshold": "x", "quality": 7}}))
.unwrap();
let defaults = vectorcraft_render::effects::default_params(id).unwrap();
let junk: serde_json::Map<String, Value> = defaults
.as_object()
.unwrap()
.iter()
.filter(|(_, v)| v.is_number())
.enumerate()
.map(|(i, (k, _))| (k.clone(), json!(if i % 2 == 0 { 1e308 } else { -1e308 })))
.collect();
assert!(!junk.is_empty(), "{id}");
s.execute("effect.apply", &json!({"effect": id, "ids": [g], "params": junk})).unwrap();
}
let _ = render(&s);
let n = s.doc().unwrap().doc.node(NodeId(g)).unwrap().clone();
+22
View File
@@ -22,6 +22,28 @@ fn a_journal_with_a_key_no_longer_read_still_replays() {
assert_eq!((st.doc.artboards[0].rect.width(), st.doc.layers[0].children().unwrap().len()), (300.0, 1));
}
/// So do an effect's own parameters (#1592): a journal or a file with a key the effect doesn't
/// take, or a value of another type, replays and opens; the effect reads what it knows, clamped.
#[test]
fn effect_params_no_longer_taken_still_replay_and_open() {
let mut s = Session::new();
s.execute("file.new", &json!({})).unwrap();
s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 50, "height": 40})).unwrap();
let old = [
("effect.apply", json!({"effect": "blur.gaussian", "params": {"blurr": 3, "radius": "big"}})),
("effect.setParams", json!({"index": 0, "params": {"amount": 1e308}})),
];
for (id, params) in &old {
assert!(s.check_params(id, params).is_err(), "{id}");
s.execute(id, params).unwrap();
}
let doc = &s.active().unwrap().doc;
let back = vectorcraft_format::load(&vectorcraft_format::save(doc, false)).unwrap();
let stored = |d: &vectorcraft_doc::Document| d.layers[0].children().unwrap()[0].appearance.effects[0].params.clone();
assert_eq!(stored(&back), stored(doc));
assert_eq!(stored(&back)["blurr"], json!(3));
}
/// What a command adds to its own journal entry passes the check, so a recorded journal can be
/// sent back through the control channel or MCP.
#[test]
+1 -1
View File
@@ -128,7 +128,7 @@ impl Headless {
// A key the command's description doesn't name is an error, not a default (#1442).
match HOST_COMMANDS.iter().find(|c| c.0 == id) {
Some((_, _, doc)) => vectorcraft_engine::check_keys(id, doc, params),
None => vectorcraft_engine::check_params(id, params),
None => self.session.check_params(id, params),
}
.map_err(|e| e.to_string())?;
match id {
+48 -5
View File
@@ -1241,11 +1241,50 @@ impl<'p> Builder<'p> {
}
fn flush_text(&mut self) {
let Some((t, opacity, facts)) = self.text.take().and_then(TextLine::finish) else { return };
let mut n = Node::new(self.id(), NodeKind::Text(Box::new(t)));
n.opacity = opacity;
self.lines.insert(n.id, facts);
self.push_node(n);
let Some(mut line) = self.text.take() else { return };
let held = std::mem::take(&mut line.held);
if let Some((t, opacity, facts)) = line.finish() {
let mut n = Node::new(self.id(), NodeKind::Text(Box::new(t)));
n.opacity = opacity;
self.lines.insert(n.id, facts);
self.push_node(n);
}
// The strokes of its glyphs' outlines, right after it ([`import_lines`] makes them its stroke).
for n in held {
self.push_node(n);
}
}
/// A stroke of the outline of the glyph just drawn, in the glyph's own paint, while every glyph
/// before it on the open line was stroked so too, is held with the line instead of ending it
/// (an editor writes type on a path glyph by glyph, each followed by the stroke added to the
/// type). `false`: it isn't one.
fn hold_glyph_stroke(&mut self, path: &BezPath, transform: Affine, paint: &hayro_interpret::Paint<'_>, draw_mode: &PathDrawMode) -> bool {
let PathDrawMode::Stroke(props) = draw_mode else { return false };
if self.mask.is_some() || self.blend != BlendMode::Normal || matches!(paint, hayro_interpret::Paint::Pattern(_)) {
return false;
}
let (p, opacity) = self.paint(paint, true);
let Some(line) = &self.text else { return false };
let Some(ink) = line.ink.last() else { return false };
if line.held.len() + 1 != line.ink.len() || opacity < 0.999 || !line.last_fill().is_some_and(|f| *f == p) {
return false;
}
let mut bp = path.clone();
bp.apply_affine(transform);
let b = bp.bounding_box();
let tol = 0.01 * (ink.width() + ink.height()) + 0.01;
if ![(b.x0, ink.x0), (b.y0, ink.y0), (b.x1, ink.x1), (b.y1, ink.y1)].iter().all(|(a, c)| (a - c).abs() <= tol) {
return false;
}
let mut st = stroke_layer(p, opacity, props, mean_scale(transform));
// Every painted path takes its turn in the scan's overprint list, a held one too.
st.overprint = self.path_overprint();
let n = Node::path(self.id(), PathData::from_bezpath(&bp), Appearance { items: vec![AppearanceItem::Stroke(st)], ..Default::default() });
if let Some(line) = &mut self.text {
line.held.push(n);
}
true
}
/// Convert a hayro paint to a VectorCraft paint and opacity (`stroke`: for a stroke).
@@ -1667,6 +1706,10 @@ impl<'a> Device<'a> for Builder<'_> {
fn draw_path(&mut self, path: &BezPath, transform: Affine, paint: &hayro_interpret::Paint<'a>, draw_mode: &PathDrawMode) {
self.flush_glyphs();
if self.hold_glyph_stroke(path, transform, paint, draw_mode) {
self.pending = false;
return;
}
self.flush_text();
self.pending = false;
let overprint = self.path_overprint();
+24 -1
View File
@@ -11,7 +11,7 @@ use std::collections::HashMap;
use kurbo::{Affine, BezPath, Point, Rect, Vec2};
use vectorcraft_color::Paint;
use vectorcraft_doc::{CharStyle, ParaDirection, TextKind, TextObject, TextRun};
use vectorcraft_doc::{CharStyle, Node, ParaDirection, TextKind, TextObject, TextRun};
use vectorcraft_text::TextLayout;
/// One glyph's placement: its baseline origin, advance direction, size and horizontal scale.
@@ -144,6 +144,9 @@ pub(crate) struct TextLine {
gaps: Vec<(usize, f64)>,
/// Each glyph's advance in the file's font (points), with [`Self::baseline`].
advances: Vec<f64>,
/// The strokes of its glyphs' outlines drawn between them, one per glyph with ink so far
/// (written right after the line).
pub held: Vec<Node>,
}
/// What a finished line of type tells beyond its text object (see [`crate::import_lines`]).
@@ -177,9 +180,15 @@ impl TextLine {
ink: vec![],
gaps: vec![],
advances: vec![],
held: vec![],
}
}
/// The paint the last glyph is filled with.
pub fn last_fill(&self) -> Option<&Paint> {
self.runs.last()?.0.fill.as_ref()
}
/// Add glyph `text` set vertically (`upright`) if it continues this column (or makes this
/// line's single glyph a column): below the last one, in the same column. `false`: it starts
/// another line.
@@ -350,6 +359,20 @@ impl TextLine {
t.runs = tracked_each(std::mem::take(&mut t.runs), own, self.at.size);
}
if let Some(path) = path {
// Evenly spaced type keeps its length along the path: the path runs through the
// glyphs' origins, one curve each, to the end of the last glyph, and tracking makes up
// the difference from the advances.
if own.is_none() {
let length: f64 = path.segments().take(self.baseline.len()).map(|seg| kurbo::ParamCurveArclen::arclen(&seg, 1e-3)).sum();
let laid = vectorcraft_text::layout(db, &t);
let natural: f64 = laid.glyphs.iter().map(|g| g.advance).sum();
let chars: usize = t.runs.iter().map(|r| r.text.chars().count()).sum();
let size = self.at.size;
if chars > 1 && length.is_finite() && (length - natural).abs() > size * 0.01 {
let (tracking, spaces) = spacing(&laid, &[], length - natural, chars, size);
t.runs = tracked(std::mem::take(&mut t.runs), tracking, &spaces);
}
}
t.kind = TextKind::OnPath { path: vectorcraft_geom::PathData::from_bezpath(&path), start: 0.0, end: None };
t.xf = Affine::IDENTITY;
t.cached_bounds = Some(vectorcraft_text::layout(db, &t).bounds);
+19
View File
@@ -153,6 +153,25 @@ fn a_stroke_on_the_type_of_a_moved_ai_page_is_still_the_types_stroke() {
assert!(matches!(art[1].appearance.items.as_slice(), [AppearanceItem::Stroke(_)]), "{:?}", art[1].appearance);
}
#[test]
fn glyphs_each_followed_by_their_stroked_outline_stay_one_object() {
// Type on a path is written glyph by glyph: each glyph, then its outline stroked in the same
// colour (a shadow under the type).
let glyph = |c: char, x: f64, stroke: &str| format!("BT 0 0 1 rg /F1 24 Tf {x} 50 Td ({c}) Tj ET {stroke} 0.5 w {}", outline(c, x));
let i_at = 10.0 + 0.722 * 24.0;
let d = import(&page(&(glyph('H', 10.0, "0 0 1 RG") + &glyph('i', i_at, "0 0 1 RG")))).unwrap();
let art = d.layers[0].children().unwrap();
assert_eq!(art.len(), 1, "one object: {art:?}");
let NodeKind::Text(t) = &art[0].kind else { panic!("{:?}", art[0].kind) };
assert_eq!(t.plain_text(), "Hi");
let [AppearanceItem::Stroke(st)] = art[0].appearance.items.as_slice() else { panic!("{:?}", art[0].appearance) };
assert_eq!(st.paint.color(), Some(Color::rgb(0.0, 0.0, 1.0)));
// A stroke of another colour drawn over each glyph in turn would also cover the next one's
// neighbour as one object's stroke: the glyphs stay as the file draws them.
let d = import(&page(&(glyph('H', 10.0, "1 1 1 RG") + &glyph('i', i_at, "1 1 1 RG")))).unwrap();
assert_eq!(texts(&d).len(), 2);
}
/// A 300 × 200 pt page with `lines` of (x, y from the top, size, text) in Source Sans 3.
fn source_sans(lines: &[(f32, f32, f32, &str)]) -> Vec<u8> {
crate::tests_live_text::text_pdf(lines, false)
+19
View File
@@ -424,3 +424,22 @@ fn glyphs_spaced_each_their_own_way_keep_their_place() {
}
});
}
/// Evenly tracked type on a path comes back with its tracking: each glyph where it was along the
/// curve, not closed up to the font's advances.
#[test]
fn evenly_tracked_type_on_a_path_keeps_its_length() {
let mut arc = BezPath::new();
arc.move_to((40.0, 200.0));
arc.curve_to((100.0, 80.0), (260.0, 80.0), (320.0, 200.0));
let mut t = TextObject::point(Point::ORIGIN, "ARCHING TYPE", CharStyle { tracking: 80.0, ..style(24.0) });
t.kind = TextKind::OnPath { path: PathData::from_bezpath(&arc), start: 0.0, end: None };
let d = doc(vec![t]);
let want = origins(&d);
let back = import_as(&pdf(&d, false).bytes, TextAs::default());
let got = origins(&back);
assert_eq!(got.len(), want.len(), "{:?}", texts(&back));
for (g, w) in got.iter().zip(&want) {
assert!(g.distance(*w) < 0.3, "{got:?}\nvs {want:?}");
}
}
+10 -1
View File
@@ -54,9 +54,13 @@ fn before(a: Point, b: Point) -> std::cmp::Ordering {
/// Lines within this of level or upright are made exactly so (the tangent of 2.5°).
const NEARLY_UPRIGHT: f64 = 0.0437;
/// The farthest (px) levelling a line may move either of its ends.
const LEVEL_REACH: f64 = 1.6;
/// Snap Curves To Lines: the straight pieces of `segs` (a chain; `closed` when its end joins its
/// start) that are within 2.5° of level or upright and at least 2 px long are made exactly level
/// or upright, by moving their ends across them: both to their mean, or, in an open chain, the
/// or upright (unless that moves an end by more than [`LEVEL_REACH`]), by moving their ends
/// across them: both to their mean, or, in an open chain, the
/// free end to the end that is fixed (with `fixed_ends`, a chain's own ends stay where they are:
/// they are junctions other regions share). The pieces next to a moved end follow it.
fn snap_upright(segs: &mut [Cubic], closed: bool, fixed_ends: bool) {
@@ -96,6 +100,11 @@ fn snap_upright(segs: &mut [Cubic], closed: bool, fixed_ends: bool) {
(false, true) => pick(b),
(false, false) => (pick(a) + pick(b)) / 2.0,
};
// Not when an end would go farther than a pixel and a half or so from where the image has it: a long line a
// degree or two off is that line, not a level one drawn badly.
if (pick(a) - to).abs().max((pick(b) - to).abs()) > LEVEL_REACH {
continue;
}
let moved = |p: Point| if level { Point::new(p.x, to) } else { Point::new(to, p.y) };
if !fixed(ia) {
put(segs, ia, moved(a));
+15
View File
@@ -1250,3 +1250,18 @@ fn a_hard_edged_circle_is_a_circle() {
let [TracedPath { shape: Some(TracedShape::Ellipse { center, rx, ry, .. }), .. }] = res.paths.as_slice() else { panic!("{:?}", res.paths) };
assert!(rx == ry && (rx - 30.0).abs() < 0.3 && center.distance(Point::new(50.0, 50.0)) < 0.1, "{center:?} {rx} {ry}");
}
/// Levelling moves no end far: a long line two degrees off level stays where the image has it.
#[test]
fn a_long_line_two_degrees_askew_is_not_levelled() {
let slope = 2f64.to_radians().tan();
let img = Raster::from_fn(240, 40, |x, y| {
let top = 12.0 + slope * (f64::from(x) - 20.0);
let inside = (20..220).contains(&x) && (f64::from(y) + 0.5 - top - 4.0).abs() <= 4.0;
if inside { [0, 0, 0, 255] } else { [255, 255, 255, 255] }
});
let res = trace(&img, &TraceParams { ignore_white: true, snap_curves_to_lines: true, ..Default::default() });
let b = res.paths[0].path.bounds().unwrap();
// 200 px at 2° rises 7 px: levelled, the bar would be 8 px tall, not 15.
assert!(b.height() > 13.0, "{b:?}");
}
+1 -1
View File
@@ -144,7 +144,7 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context, req: &ControlReques
let params = p.get("params").cloned().unwrap_or(json!({}));
let params = if params.is_null() { json!({}) } else { params };
// A key the command's description doesn't name is an error, not a default (#1442).
if let Err(e) = crate::menus::check_params(id, &params) {
if let Err(e) = crate::menus::check_params(&app.session, id, &params) {
return err(e);
}
wrap(app.run(id, params))
+1 -1
View File
@@ -623,7 +623,7 @@ impl VectorcraftApp {
// In debug builds the app's own calls are held to the params descriptions too (#1442): a
// panel passing a key its command doesn't read shows up in the tests.
if cfg!(debug_assertions) {
menus::check_params(id, &params)?;
menus::check_params(&self.session, id, &params)?;
}
if let Some(r) = menus::run_ui_command(self, id, &params) {
return r;
+2 -2
View File
@@ -905,11 +905,11 @@ pub(crate) fn opt_bool(p: &Value, key: &str) -> Result<Option<bool>, String> {
/// `params` has only keys the params description of engine command `id` names, or the parameter
/// error naming the one it doesn't (#1442). The UI's own commands aren't checked yet: their
/// descriptions haven't been audited against what they read.
pub fn check_params(id: &str, params: &Value) -> Result<(), String> {
pub fn check_params(session: &vectorcraft_engine::Session, id: &str, params: &Value) -> Result<(), String> {
if UI_COMMANDS.iter().any(|c| c.0 == id) {
return Ok(());
}
vectorcraft_engine::check_params(id, params).map_err(|e| e.to_string())
session.check_params(id, params).map_err(|e| e.to_string())
}
pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<Value, String>> {
+139 -138
View File
@@ -1,6 +1,6 @@
# Image Trace quality
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (Gradients: on linear gradients 6 Colors goes from ΔE 2.12 in 6.4 paths to 0.62 in 3.4)
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (levelling moves no line end more than 1.6 px: Technical Drawing's largest distance 4.21 px to 1.46)
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
@@ -81,7 +81,7 @@ Each preset on the inputs it is meant for, all sizes and variants.
| Technical Drawing | polygons, star, letter A, lines, askew shapes and lines | 2.73 | 13.67 | 0.210 | 1.43 | 105.76 | 4.7 | 31 | 2.01 | 100 | 9 | 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 | 10 | 80 | 237.0 | 0.0 | 0 | 13.8 | – | 59.1 | 0.2 | – | – | – |
### Now (with Gradients)
### Now (with levelling bounded)
| preset | inputs | ΔE | edge ΔE | off mean | off max | missed max | paths | anchors | × art | lines % | upright % | corners % | kinks | odd colours | odd px | gap px | overlap px | specks | outside px | centre off | width off | anchors a stroke | shapes % | grouped % | gradient paths | found % | angle off | ramp off |
|:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
@@ -97,7 +97,7 @@ Each preset on the inputs it is meant for, all sizes and variants.
| Sketched Art | circle, ellipses, badge with a hole, letter D, lines | 2.71 | 12.02 | 0.185 | 1.00 | 105.61 | 1.8 | 16 | 1.42 | 19 | 8 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Silhouettes | circle, ellipses, badge with a hole, letter D | 2.98 | 19.30 | 0.524 | 2.19 | 2.19 | 1.2 | 14 | 1.64 | 12 | 9 | 93 | 0.4 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Line Art | lines | 2.62 | 9.49 | 0.056 | 0.99 | 105.72 | 5.4 | 43 | 2.14 | 40 | 0 | – | – | – | – | – | – | 0.1 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | polygons, star, letter A, lines, askew shapes and lines | 2.71 | 12.14 | 0.178 | 4.21 | 105.64 | 4.7 | 38 | 2.55 | 81 | 24 | 78 | 0.8 | – | – | – | – | 0.5 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | polygons, star, letter A, lines, askew shapes and lines | 2.65 | 10.88 | 0.126 | 1.46 | 105.64 | 4.7 | 38 | 2.55 | 81 | 19 | 78 | 0.8 | – | – | – | – | 0.5 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Outline | lines | 2.60 | 9.16 | 0.055 | 2.94 | 105.75 | 5.0 | 14 | 0.71 | 95 | 4 | – | – | – | – | – | – | 0.0 | 0.0 | 0.053 | 0.02 | 2.4 | – | – | 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 | 10 | 80 | 237.0 | 0.0 | 0 | 13.8 | – | 59.1 | 0.2 | – | – | – | – | – | 0.0 | – | – | – |
| Default with Shapes | plain shapes | 3.18 | 9.53 | 0.102 | 1.00 | 1.00 | 6.0 | 40 | 1.32 | 41 | 27 | – | – | – | – | 15.8 | 11.5 | 0.0 | 0.0 | – | – | – | 57 | – | 0.0 | – | – | – |
@@ -110,7 +110,7 @@ Each preset on the inputs it is meant for, all sizes and variants.
| High Fidelity Photo, no Gradients | two-stop gradient, three-stop gradient in a disc | 0.79 | 2.98 | – | – | – | 38.1 | 3827 | – | 31 | 30 | – | – | – | – | 0.0 | – | 1.1 | 0.0 | – | – | – | – | – | 0.0 | 0 | – | – |
| High Fidelity Photo with Gradients | two-stop gradient, three-stop gradient in a disc | 0.46 | 1.31 | – | – | – | 20.6 | 759 | – | 9 | 5 | – | – | – | – | 0.0 | – | 2.9 | 0.0 | – | – | – | – | – | 1.0 | 86 | 1.5 | 21 |
The sections below are the numbers now (with Gradients).
The sections below are the numbers now (with levelling bounded).
## 1. Each preset on the inputs it is meant for
@@ -130,7 +130,7 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
| Sketched Art | circle, ellipses, badge with a hole, letter D, lines | 2.71 | 12.02 | 0.185 | 1.00 | 105.61 | 1.8 | 16 | 1.42 | 19 | 8 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Silhouettes | circle, ellipses, badge with a hole, letter D | 2.98 | 19.30 | 0.524 | 2.19 | 2.19 | 1.2 | 14 | 1.64 | 12 | 9 | 93 | 0.4 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Line Art | lines | 2.62 | 9.49 | 0.056 | 0.99 | 105.72 | 5.4 | 43 | 2.14 | 40 | 0 | – | – | – | – | – | – | 0.1 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | polygons, star, letter A, lines, askew shapes and lines | 2.71 | 12.14 | 0.178 | 4.21 | 105.64 | 4.7 | 38 | 2.55 | 81 | 24 | 78 | 0.8 | – | – | – | – | 0.5 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | polygons, star, letter A, lines, askew shapes and lines | 2.65 | 10.88 | 0.126 | 1.46 | 105.64 | 4.7 | 38 | 2.55 | 81 | 19 | 78 | 0.8 | – | – | – | – | 0.5 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Outline | lines | 2.60 | 9.16 | 0.055 | 2.94 | 105.75 | 5.0 | 14 | 0.71 | 95 | 4 | – | – | – | – | – | – | 0.0 | 0.0 | 0.053 | 0.02 | 2.4 | – | – | 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 | 10 | 80 | 237.0 | 0.0 | 0 | 13.8 | – | 59.1 | 0.2 | – | – | – | – | – | 0.0 | – | – | – |
| Default with Shapes | plain shapes | 3.18 | 9.53 | 0.102 | 1.00 | 1.00 | 6.0 | 40 | 1.32 | 41 | 27 | – | – | – | – | 15.8 | 11.5 | 0.0 | 0.0 | – | – | – | 57 | – | 0.0 | – | – | – |
@@ -214,7 +214,7 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
| Black and White Logo | curved shapes | 0.03 | 1.48 | 0.024 | 0.16 | 0.16 | 1.2 | 20 | 2.12 | 21 | 21 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Black and White Logo | polygons | 0.06 | 2.36 | 0.040 | 0.47 | 1.26 | 1.7 | 14 | 1.24 | 93 | 8 | 95 | 0.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Black and White Logo | thin lines | 0.59 | 6.00 | 0.105 | 0.50 | 77.21 | 10.3 | 44 | 2.18 | 90 | 10 | – | – | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Black and White Logo | askew shapes | 0.89 | 20.99 | 0.613 | 4.21 | 9.63 | 4.0 | 25 | 1.03 | 98 | 98 | – | – | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Black and White Logo | askew shapes | 0.49 | 11.14 | 0.234 | 1.46 | 9.63 | 4.0 | 25 | 1.03 | 98 | 70 | – | – | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Black and White Logo | plain shapes | 0.08 | 1.56 | 0.033 | 0.31 | 0.30 | 6.0 | 55 | 1.84 | 58 | 52 | – | – | – | – | – | – | 0.0 | 0.0 | – | – | – | 0 | – | 0.0 | – | – | – |
| Black and White Logo | flat colour art | 19.90 | 17.76 | 0.157 | 0.30 | 179.51 | 1.0 | 18 | 1.12 | 23 | 23 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Black and White Logo | gradient, photo | 38.97 | 38.53 | – | – | – | 9.0 | 634 | – | 73 | 54 | – | – | – | – | – | – | 1.2 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
@@ -246,7 +246,7 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
| Technical Drawing | curved shapes | 0.03 | 1.48 | 0.024 | 0.16 | 0.16 | 1.2 | 20 | 2.12 | 21 | 21 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | polygons | 0.06 | 2.36 | 0.040 | 0.47 | 1.26 | 1.7 | 14 | 1.23 | 94 | 8 | 95 | 0.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | thin lines | 0.50 | 5.46 | 0.109 | 0.50 | 55.93 | 15.3 | 62 | 3.12 | 86 | 14 | – | – | – | – | – | – | 3.3 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | askew shapes | 0.88 | 20.92 | 0.611 | 4.21 | 4.19 | 4.3 | 26 | 1.08 | 98 | 98 | – | – | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | askew shapes | 0.48 | 11.07 | 0.233 | 1.46 | 2.11 | 4.3 | 26 | 1.08 | 98 | 70 | – | – | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | plain shapes | 0.08 | 1.63 | 0.035 | 0.33 | 0.33 | 6.0 | 55 | 1.82 | 60 | 52 | – | – | – | – | – | – | 0.0 | 0.0 | – | – | – | 0 | – | 0.0 | – | – | – |
| Technical Drawing | flat colour art | 19.90 | 17.76 | 0.157 | 0.30 | 179.51 | 1.0 | 18 | 1.12 | 23 | 23 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | gradient, photo | 38.94 | 38.51 | – | – | – | 20.0 | 659 | – | 73 | 51 | – | – | – | – | – | – | 11.7 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
@@ -404,10 +404,10 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
| Line Art | blurred | 1.56 | 16.06 | 0.061 | 0.99 | 105.72 | 3.0 | 53 | 2.65 | 22 | 0 | – | – | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Line Art | noisy | 1.03 | 4.10 | 0.035 | 0.44 | 56.07 | 8.0 | 50 | 2.50 | 43 | 0 | – | – | – | – | – | – | 1.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Line Art | scanned | 13.54 | 19.55 | 0.062 | 0.92 | 105.68 | 3.0 | 51 | 2.55 | 8 | 0 | – | – | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | clean | 0.31 | 6.70 | 0.168 | 4.21 | 55.93 | 4.9 | 26 | 1.58 | 93 | 27 | 95 | 0.0 | – | – | – | – | 0.7 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | aliased | 0.56 | 20.39 | 0.275 | 2.42 | 55.44 | 6.2 | 31 | 1.85 | 96 | 28 | 94 | 0.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | blurred | 1.12 | 17.47 | 0.168 | 2.41 | 105.64 | 2.4 | 49 | 3.84 | 47 | 19 | 30 | 2.7 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | noisy | 1.68 | 6.90 | 0.160 | 2.13 | 31.79 | 7.0 | 34 | 1.98 | 93 | 26 | 95 | 0.0 | – | – | – | – | 1.4 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | clean | 0.23 | 4.73 | 0.092 | 1.46 | 55.93 | 4.9 | 26 | 1.58 | 93 | 21 | 95 | 0.0 | – | – | – | – | 0.7 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | aliased | 0.50 | 19.60 | 0.238 | 1.37 | 55.44 | 6.2 | 31 | 1.85 | 96 | 23 | 94 | 0.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | blurred | 1.06 | 16.51 | 0.130 | 1.16 | 105.64 | 2.4 | 49 | 3.84 | 47 | 13 | 30 | 2.7 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | noisy | 1.59 | 5.74 | 0.103 | 1.17 | 31.79 | 7.0 | 34 | 1.98 | 93 | 21 | 95 | 0.0 | – | – | – | – | 1.4 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Technical Drawing | scanned | 14.69 | 20.15 | 0.136 | 1.17 | 105.57 | 2.4 | 73 | 5.42 | 49 | 12 | 39 | 3.0 | – | – | – | – | 0.0 | 0.0 | – | – | – | – | – | 0.0 | – | – | – |
| Outline | clean | 0.52 | 3.89 | 0.038 | 2.94 | 105.73 | 3.3 | 7 | 0.35 | 100 | 7 | – | – | – | – | – | – | 0.0 | 0.0 | 0.056 | 0.02 | 2.1 | – | – | 0.0 | – | – | – |
| Outline | aliased | 0.62 | 13.33 | 0.169 | 0.88 | 55.55 | 11.0 | 51 | 2.55 | 64 | 9 | – | – | – | – | – | – | 0.0 | 0.0 | 0.154 | 0.01 | 4.6 | – | – | 0.0 | – | – | – |
@@ -505,155 +505,156 @@ 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.05 s, 29 paths, 6278 anchors, 12 MB |
| Default | four-colour badge | 1 | 0.03 s, 3 paths, 64 anchors, 12 MB |
| Auto Color | photo-like | 1 | 0.18 s, 732 paths, 45265 anchors, 25 MB |
| Auto Color | four-colour badge | 1 | 0.05 s, 5 paths, 84 anchors, 12 MB |
| High Fidelity Photo | photo-like | 1 | 0.23 s, 968 paths, 117041 anchors, 170 MB |
| High Fidelity Photo | four-colour badge | 1 | 0.07 s, 6 paths, 172 anchors, 23 MB |
| Low Fidelity Photo | photo-like | 1 | 0.21 s, 129 paths, 23014 anchors, 103 MB |
| Low Fidelity Photo | four-colour badge | 1 | 0.06 s, 6 paths, 136 anchors, 23 MB |
| 3 Colors | photo-like | 1 | 0.09 s, 9 paths, 3341 anchors, 18 MB |
| 3 Colors | four-colour badge | 1 | 0.05 s, 4 paths, 88 anchors, 13 MB |
| Default | photo-like | 1 | 0.04 s, 29 paths, 6278 anchors, 12 MB |
| Default | four-colour badge | 1 | 0.02 s, 3 paths, 64 anchors, 12 MB |
| Auto Color | photo-like | 1 | 0.16 s, 732 paths, 45265 anchors, 23 MB |
| Auto Color | four-colour badge | 1 | 0.06 s, 5 paths, 84 anchors, 12 MB |
| High Fidelity Photo | photo-like | 1 | 0.25 s, 968 paths, 117041 anchors, 164 MB |
| High Fidelity Photo | four-colour badge | 1 | 0.06 s, 6 paths, 172 anchors, 23 MB |
| Low Fidelity Photo | photo-like | 1 | 0.15 s, 129 paths, 23014 anchors, 104 MB |
| Low Fidelity Photo | four-colour badge | 1 | 0.07 s, 6 paths, 136 anchors, 23 MB |
| 3 Colors | photo-like | 1 | 0.11 s, 9 paths, 3341 anchors, 18 MB |
| 3 Colors | four-colour badge | 1 | 0.07 s, 4 paths, 88 anchors, 13 MB |
| 6 Colors | photo-like | 1 | 0.10 s, 78 paths, 9076 anchors, 24 MB |
| 6 Colors | four-colour badge | 1 | 0.06 s, 5 paths, 84 anchors, 12 MB |
| 16 Colors | photo-like | 1 | 0.12 s, 178 paths, 20470 anchors, 31 MB |
| 16 Colors | four-colour badge | 1 | 0.06 s, 5 paths, 88 anchors, 12 MB |
| Shades of Gray | photo-like | 1 | 0.10 s, 81 paths, 6698 anchors, 40 MB |
| Shades of Gray | four-colour badge | 1 | 0.08 s, 10 paths, 725 anchors, 35 MB |
| 6 Colors | four-colour badge | 1 | 0.07 s, 5 paths, 84 anchors, 12 MB |
| 16 Colors | photo-like | 1 | 0.18 s, 178 paths, 20470 anchors, 32 MB |
| 16 Colors | four-colour badge | 1 | 0.07 s, 5 paths, 88 anchors, 12 MB |
| Shades of Gray | photo-like | 1 | 0.11 s, 81 paths, 6698 anchors, 40 MB |
| Shades of Gray | four-colour badge | 1 | 0.08 s, 10 paths, 725 anchors, 34 MB |
| Black and White Logo | photo-like | 1 | 0.05 s, 71 paths, 4933 anchors, 8 MB |
| Black and White Logo | four-colour badge | 1 | 0.02 s, 1 paths, 47 anchors, 6 MB |
| Sketched Art | photo-like | 1 | 0.04 s, 5 paths, 2873 anchors, 8 MB |
| Sketched Art | photo-like | 1 | 0.05 s, 5 paths, 2873 anchors, 8 MB |
| Sketched Art | four-colour badge | 1 | 0.02 s, 2 paths, 114 anchors, 6 MB |
| Silhouettes | photo-like | 1 | 0.04 s, 2 paths, 556 anchors, 9 MB |
| Silhouettes | four-colour badge | 1 | 0.02 s, 1 paths, 24 anchors, 6 MB |
| Line Art | photo-like | 1 | 0.04 s, 54 paths, 4495 anchors, 8 MB |
| Line Art | photo-like | 1 | 0.05 s, 54 paths, 4495 anchors, 8 MB |
| Line Art | four-colour badge | 1 | 0.02 s, 1 paths, 38 anchors, 6 MB |
| Technical Drawing | photo-like | 1 | 0.05 s, 221 paths, 6258 anchors, 8 MB |
| Technical Drawing | four-colour badge | 1 | 0.02 s, 1 paths, 47 anchors, 6 MB |
| Outline | photo-like | 1 | 0.05 s, 45 paths, 4110 anchors, 16 MB |
| Outline | photo-like | 1 | 0.06 s, 45 paths, 4110 anchors, 16 MB |
| Outline | four-colour badge | 1 | 0.03 s, 1 paths, 38 anchors, 11 MB |
| Flat Logo | photo-like | 1 | refused after 0.10 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 1 | 0.69 s, 5 paths, 78 anchors, 146 MB |
| Default with Shapes | photo-like | 1 | 0.04 s, 29 paths, 6278 anchors, 12 MB |
| Default with Shapes | four-colour badge | 1 | 0.03 s, 3 paths, 63 anchors, 12 MB |
| 6 Colors with Shapes | photo-like | 1 | 0.24 s, 78 paths, 9076 anchors, 24 MB |
| 6 Colors with Shapes | four-colour badge | 1 | 0.06 s, 5 paths, 84 anchors, 12 MB |
| 6 Colors with Auto Grouping | photo-like | 1 | 0.10 s, 78 paths, 9076 anchors, 24 MB |
| 6 Colors with Auto Grouping | four-colour badge | 1 | 0.05 s, 5 paths, 84 anchors, 12 MB |
| Shades of Gray with Auto Grouping | photo-like | 1 | 0.10 s, 81 paths, 6698 anchors, 44 MB |
| Flat Logo | photo-like | 1 | refused after 0.11 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 1 | 0.80 s, 5 paths, 78 anchors, 146 MB |
| Default with Shapes | photo-like | 1 | 0.11 s, 29 paths, 6278 anchors, 12 MB |
| Default with Shapes | four-colour badge | 1 | 0.04 s, 3 paths, 63 anchors, 12 MB |
| 6 Colors with Shapes | photo-like | 1 | 0.09 s, 78 paths, 9076 anchors, 24 MB |
| 6 Colors with Shapes | four-colour badge | 1 | 0.05 s, 5 paths, 84 anchors, 12 MB |
| 6 Colors with Auto Grouping | photo-like | 1 | 0.08 s, 78 paths, 9076 anchors, 24 MB |
| 6 Colors with Auto Grouping | four-colour badge | 1 | 0.05 s, 5 paths, 84 anchors, 13 MB |
| Shades of Gray with Auto Grouping | photo-like | 1 | 0.14 s, 81 paths, 6698 anchors, 40 MB |
| Shades of Gray with Auto Grouping | four-colour badge | 1 | 0.07 s, 10 paths, 725 anchors, 35 MB |
| High Fidelity Photo with Auto Grouping | photo-like | 1 | 0.28 s, 968 paths, 117041 anchors, 167 MB |
| High Fidelity Photo with Auto Grouping | four-colour badge | 1 | 0.09 s, 6 paths, 172 anchors, 23 MB |
| 6 Colors, no Gradients | photo-like | 1 | 0.10 s, 78 paths, 9076 anchors, 24 MB |
| High Fidelity Photo with Auto Grouping | photo-like | 1 | 0.25 s, 968 paths, 117041 anchors, 169 MB |
| High Fidelity Photo with Auto Grouping | four-colour badge | 1 | 0.08 s, 6 paths, 172 anchors, 23 MB |
| 6 Colors, no Gradients | photo-like | 1 | 0.49 s, 78 paths, 9076 anchors, 24 MB |
| 6 Colors, no Gradients | four-colour badge | 1 | 0.05 s, 5 paths, 84 anchors, 12 MB |
| 6 Colors with Gradients | photo-like | 1 | 0.14 s, 78 paths, 9076 anchors, 32 MB |
| 6 Colors with Gradients | four-colour badge | 1 | 0.07 s, 5 paths, 84 anchors, 12 MB |
| High Fidelity Photo, no Gradients | photo-like | 1 | 0.24 s, 968 paths, 117041 anchors, 169 MB |
| 6 Colors with Gradients | photo-like | 1 | 0.18 s, 78 paths, 9076 anchors, 32 MB |
| 6 Colors with Gradients | four-colour badge | 1 | 0.11 s, 5 paths, 84 anchors, 12 MB |
| High Fidelity Photo, no Gradients | photo-like | 1 | 0.23 s, 968 paths, 117041 anchors, 163 MB |
| High Fidelity Photo, no Gradients | four-colour badge | 1 | 0.07 s, 6 paths, 172 anchors, 23 MB |
| High Fidelity Photo with Gradients | photo-like | 1 | 0.43 s, 968 paths, 117041 anchors, 166 MB |
| High Fidelity Photo with Gradients | four-colour badge | 1 | 0.09 s, 6 paths, 172 anchors, 23 MB |
| Default | photo-like | 4 | 0.25 s, 48 paths, 14438 anchors, 49 MB |
| Default | four-colour badge | 4 | 0.16 s, 3 paths, 72 anchors, 48 MB |
| Auto Color | photo-like | 4 | 0.72 s, 1469 paths, 149389 anchors, 113 MB |
| Auto Color | four-colour badge | 4 | 0.30 s, 5 paths, 92 anchors, 46 MB |
| High Fidelity Photo | photo-like | 4 | 1.05 s, 4479 paths, 337884 anchors, 638 MB |
| High Fidelity Photo | four-colour badge | 4 | 0.35 s, 6 paths, 200 anchors, 96 MB |
| Low Fidelity Photo | photo-like | 4 | 0.54 s, 299 paths, 61601 anchors, 392 MB |
| Low Fidelity Photo | four-colour badge | 4 | 0.33 s, 6 paths, 152 anchors, 97 MB |
| 3 Colors | photo-like | 4 | 0.38 s, 20 paths, 8473 anchors, 72 MB |
| 3 Colors | four-colour badge | 4 | 0.28 s, 4 paths, 122 anchors, 53 MB |
| 6 Colors | photo-like | 4 | 0.42 s, 115 paths, 23295 anchors, 100 MB |
| 6 Colors | four-colour badge | 4 | 0.27 s, 5 paths, 92 anchors, 46 MB |
| 16 Colors | photo-like | 4 | 0.36 s, 353 paths, 52770 anchors, 129 MB |
| 16 Colors | four-colour badge | 4 | 0.24 s, 5 paths, 96 anchors, 47 MB |
| Shades of Gray | photo-like | 4 | 0.34 s, 137 paths, 18823 anchors, 158 MB |
| Shades of Gray | four-colour badge | 4 | 0.27 s, 10 paths, 1256 anchors, 135 MB |
| Black and White Logo | photo-like | 4 | 0.21 s, 208 paths, 13546 anchors, 32 MB |
| Black and White Logo | four-colour badge | 4 | 0.09 s, 1 paths, 57 anchors, 24 MB |
| Sketched Art | photo-like | 4 | 0.26 s, 9 paths, 5387 anchors, 32 MB |
| Sketched Art | four-colour badge | 4 | 0.13 s, 2 paths, 196 anchors, 24 MB |
| Silhouettes | photo-like | 4 | 0.32 s, 2 paths, 1465 anchors, 36 MB |
| Silhouettes | four-colour badge | 4 | 0.13 s, 1 paths, 28 anchors, 24 MB |
| Line Art | photo-like | 4 | 0.30 s, 127 paths, 11330 anchors, 32 MB |
| Line Art | four-colour badge | 4 | 0.12 s, 1 paths, 42 anchors, 24 MB |
| Technical Drawing | photo-like | 4 | 0.31 s, 931 paths, 20628 anchors, 32 MB |
| Technical Drawing | four-colour badge | 4 | 0.13 s, 1 paths, 57 anchors, 24 MB |
| Outline | photo-like | 4 | 0.39 s, 113 paths, 10815 anchors, 64 MB |
| Outline | four-colour badge | 4 | 0.20 s, 1 paths, 42 anchors, 44 MB |
| Flat Logo | photo-like | 4 | refused after 0.19 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 4 | 1.31 s, 5 paths, 69 anchors, 146 MB |
| Default with Shapes | photo-like | 4 | 0.30 s, 48 paths, 14438 anchors, 49 MB |
| Default with Shapes | four-colour badge | 4 | 0.15 s, 3 paths, 72 anchors, 48 MB |
| 6 Colors with Shapes | photo-like | 4 | 0.45 s, 115 paths, 23295 anchors, 100 MB |
| 6 Colors with Shapes | four-colour badge | 4 | 0.31 s, 5 paths, 92 anchors, 46 MB |
| 6 Colors with Auto Grouping | photo-like | 4 | 0.44 s, 115 paths, 23295 anchors, 100 MB |
| 6 Colors with Auto Grouping | four-colour badge | 4 | 0.30 s, 5 paths, 92 anchors, 47 MB |
| Shades of Gray with Auto Grouping | photo-like | 4 | 0.47 s, 137 paths, 18823 anchors, 162 MB |
| Shades of Gray with Auto Grouping | four-colour badge | 4 | 0.39 s, 10 paths, 1256 anchors, 137 MB |
| High Fidelity Photo with Auto Grouping | photo-like | 4 | 1.34 s, 4479 paths, 337884 anchors, 615 MB |
| High Fidelity Photo with Gradients | photo-like | 1 | 0.32 s, 968 paths, 117041 anchors, 168 MB |
| High Fidelity Photo with Gradients | four-colour badge | 1 | 0.10 s, 6 paths, 172 anchors, 23 MB |
| Default | photo-like | 4 | 0.21 s, 48 paths, 14438 anchors, 49 MB |
| Default | four-colour badge | 4 | 0.11 s, 3 paths, 72 anchors, 48 MB |
| Auto Color | photo-like | 4 | 0.44 s, 1469 paths, 149389 anchors, 115 MB |
| Auto Color | four-colour badge | 4 | 0.21 s, 5 paths, 92 anchors, 46 MB |
| High Fidelity Photo | photo-like | 4 | 0.78 s, 4479 paths, 337884 anchors, 623 MB |
| High Fidelity Photo | four-colour badge | 4 | 0.29 s, 6 paths, 200 anchors, 97 MB |
| Low Fidelity Photo | photo-like | 4 | 0.41 s, 299 paths, 61601 anchors, 406 MB |
| Low Fidelity Photo | four-colour badge | 4 | 0.24 s, 6 paths, 152 anchors, 97 MB |
| 3 Colors | photo-like | 4 | 0.31 s, 20 paths, 8473 anchors, 73 MB |
| 3 Colors | four-colour badge | 4 | 0.24 s, 4 paths, 122 anchors, 53 MB |
| 6 Colors | photo-like | 4 | 0.34 s, 115 paths, 23295 anchors, 100 MB |
| 6 Colors | four-colour badge | 4 | 0.22 s, 5 paths, 92 anchors, 46 MB |
| 16 Colors | photo-like | 4 | 0.37 s, 353 paths, 52770 anchors, 130 MB |
| 16 Colors | four-colour badge | 4 | 0.22 s, 5 paths, 96 anchors, 46 MB |
| Shades of Gray | photo-like | 4 | 0.50 s, 137 paths, 18823 anchors, 162 MB |
| Shades of Gray | four-colour badge | 4 | 0.33 s, 10 paths, 1256 anchors, 137 MB |
| Black and White Logo | photo-like | 4 | 0.25 s, 208 paths, 13546 anchors, 32 MB |
| Black and White Logo | four-colour badge | 4 | 0.11 s, 1 paths, 57 anchors, 24 MB |
| Sketched Art | photo-like | 4 | 0.24 s, 9 paths, 5387 anchors, 32 MB |
| Sketched Art | four-colour badge | 4 | 0.11 s, 2 paths, 196 anchors, 24 MB |
| Silhouettes | photo-like | 4 | 0.24 s, 2 paths, 1465 anchors, 36 MB |
| Silhouettes | four-colour badge | 4 | 0.09 s, 1 paths, 28 anchors, 24 MB |
| Line Art | photo-like | 4 | 0.22 s, 127 paths, 11330 anchors, 32 MB |
| Line Art | four-colour badge | 4 | 0.09 s, 1 paths, 42 anchors, 24 MB |
| Technical Drawing | photo-like | 4 | 0.21 s, 931 paths, 20628 anchors, 32 MB |
| Technical Drawing | four-colour badge | 4 | 0.08 s, 1 paths, 57 anchors, 24 MB |
| Outline | photo-like | 4 | 0.25 s, 113 paths, 10815 anchors, 64 MB |
| Outline | four-colour badge | 4 | 0.13 s, 1 paths, 42 anchors, 44 MB |
| Flat Logo | photo-like | 4 | refused after 0.10 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 4 | 0.67 s, 5 paths, 69 anchors, 146 MB |
| Default with Shapes | photo-like | 4 | 0.20 s, 48 paths, 14438 anchors, 49 MB |
| Default with Shapes | four-colour badge | 4 | 0.10 s, 3 paths, 72 anchors, 48 MB |
| 6 Colors with Shapes | photo-like | 4 | 0.31 s, 115 paths, 23295 anchors, 100 MB |
| 6 Colors with Shapes | four-colour badge | 4 | 0.21 s, 5 paths, 92 anchors, 46 MB |
| 6 Colors with Auto Grouping | photo-like | 4 | 0.40 s, 115 paths, 23295 anchors, 100 MB |
| 6 Colors with Auto Grouping | four-colour badge | 4 | 0.22 s, 5 paths, 92 anchors, 46 MB |
| Shades of Gray with Auto Grouping | photo-like | 4 | 0.38 s, 137 paths, 18823 anchors, 165 MB |
| Shades of Gray with Auto Grouping | four-colour badge | 4 | 0.29 s, 10 paths, 1256 anchors, 138 MB |
| High Fidelity Photo with Auto Grouping | photo-like | 4 | 1.06 s, 4479 paths, 337884 anchors, 622 MB |
| High Fidelity Photo with Auto Grouping | four-colour badge | 4 | 0.28 s, 6 paths, 200 anchors, 97 MB |
| 6 Colors, no Gradients | photo-like | 4 | 0.35 s, 115 paths, 23295 anchors, 100 MB |
| 6 Colors, no Gradients | photo-like | 4 | 0.36 s, 115 paths, 23295 anchors, 100 MB |
| 6 Colors, no Gradients | four-colour badge | 4 | 0.26 s, 5 paths, 92 anchors, 46 MB |
| 6 Colors with Gradients | photo-like | 4 | 0.66 s, 115 paths, 23295 anchors, 128 MB |
| 6 Colors with Gradients | four-colour badge | 4 | 0.39 s, 5 paths, 92 anchors, 46 MB |
| High Fidelity Photo, no Gradients | photo-like | 4 | 0.79 s, 4479 paths, 337884 anchors, 617 MB |
| High Fidelity Photo, no Gradients | four-colour badge | 4 | 0.27 s, 6 paths, 200 anchors, 97 MB |
| High Fidelity Photo with Gradients | photo-like | 4 | 1.14 s, 4479 paths, 337884 anchors, 636 MB |
| High Fidelity Photo with Gradients | four-colour badge | 4 | 0.40 s, 6 paths, 200 anchors, 97 MB |
| Default | photo-like | 16 | 1.15 s, 127 paths, 34034 anchors, 199 MB |
| Default | four-colour badge | 16 | 0.64 s, 3 paths, 88 anchors, 184 MB |
| Auto Color | photo-like | 16 | 2.06 s, 5390 paths, 454670 anchors, 280 MB |
| Auto Color | four-colour badge | 16 | 1.31 s, 5 paths, 112 anchors, 185 MB |
| High Fidelity Photo | photo-like | 16 | 5.28 s, 31139 paths, 1192715 anchors, 880 MB |
| High Fidelity Photo | four-colour badge | 16 | 1.36 s, 6 paths, 264 anchors, 257 MB |
| Low Fidelity Photo | photo-like | 16 | 3.01 s, 2036 paths, 182778 anchors, 754 MB |
| Low Fidelity Photo | four-colour badge | 16 | 1.55 s, 6 paths, 180 anchors, 263 MB |
| 3 Colors | photo-like | 16 | 1.82 s, 76 paths, 23024 anchors, 290 MB |
| 3 Colors | four-colour badge | 16 | 1.30 s, 4 paths, 190 anchors, 205 MB |
| 6 Colors | photo-like | 16 | 1.76 s, 470 paths, 60831 anchors, 298 MB |
| 6 Colors | four-colour badge | 16 | 1.27 s, 5 paths, 112 anchors, 185 MB |
| 16 Colors | photo-like | 16 | 1.93 s, 1973 paths, 145642 anchors, 303 MB |
| 16 Colors | four-colour badge | 16 | 1.26 s, 5 paths, 114 anchors, 185 MB |
| Shades of Gray | photo-like | 16 | 2.01 s, 686 paths, 53654 anchors, 407 MB |
| Shades of Gray | four-colour badge | 16 | 1.76 s, 9 paths, 2208 anchors, 270 MB |
| Black and White Logo | photo-like | 16 | 1.45 s, 1128 paths, 45273 anchors, 129 MB |
| Black and White Logo | four-colour badge | 16 | 0.88 s, 1 paths, 69 anchors, 88 MB |
| Sketched Art | photo-like | 16 | 1.60 s, 15 paths, 11486 anchors, 128 MB |
| Sketched Art | four-colour badge | 16 | 0.75 s, 2 paths, 361 anchors, 88 MB |
| Silhouettes | photo-like | 16 | 2.21 s, 3 paths, 3350 anchors, 144 MB |
| Silhouettes | four-colour badge | 16 | 0.51 s, 1 paths, 32 anchors, 95 MB |
| Line Art | photo-like | 16 | 1.21 s, 713 paths, 37370 anchors, 129 MB |
| Line Art | four-colour badge | 16 | 0.44 s, 1 paths, 49 anchors, 88 MB |
| Technical Drawing | photo-like | 16 | 1.52 s, 4577 paths, 76559 anchors, 130 MB |
| Technical Drawing | four-colour badge | 16 | 0.54 s, 1 paths, 69 anchors, 88 MB |
| 6 Colors with Gradients | photo-like | 4 | 0.73 s, 115 paths, 23295 anchors, 128 MB |
| 6 Colors with Gradients | four-colour badge | 4 | 0.40 s, 5 paths, 92 anchors, 46 MB |
| High Fidelity Photo, no Gradients | photo-like | 4 | 0.98 s, 4479 paths, 337884 anchors, 626 MB |
| High Fidelity Photo, no Gradients | four-colour badge | 4 | 0.29 s, 6 paths, 200 anchors, 97 MB |
| High Fidelity Photo with Gradients | photo-like | 4 | 1.22 s, 4479 paths, 337884 anchors, 629 MB |
| High Fidelity Photo with Gradients | four-colour badge | 4 | 0.43 s, 6 paths, 200 anchors, 97 MB |
| Default | photo-like | 16 | 1.24 s, 127 paths, 34034 anchors, 199 MB |
| Default | four-colour badge | 16 | 1.54 s, 3 paths, 88 anchors, 184 MB |
| Auto Color | photo-like | 16 | 2.44 s, 5390 paths, 454670 anchors, 272 MB |
| Auto Color | four-colour badge | 16 | 1.08 s, 5 paths, 112 anchors, 184 MB |
| High Fidelity Photo | photo-like | 16 | 4.70 s, 31139 paths, 1192715 anchors, 880 MB |
| High Fidelity Photo | four-colour badge | 16 | 1.27 s, 6 paths, 264 anchors, 257 MB |
| Low Fidelity Photo | photo-like | 16 | 2.21 s, 2036 paths, 182778 anchors, 754 MB |
| Low Fidelity Photo | four-colour badge | 16 | 1.25 s, 6 paths, 180 anchors, 257 MB |
| 3 Colors | photo-like | 16 | 1.38 s, 76 paths, 23024 anchors, 290 MB |
| 3 Colors | four-colour badge | 16 | 0.93 s, 4 paths, 190 anchors, 205 MB |
| 6 Colors | photo-like | 16 | 1.38 s, 470 paths, 60831 anchors, 298 MB |
| 6 Colors | four-colour badge | 16 | 0.91 s, 5 paths, 112 anchors, 184 MB |
| 16 Colors | photo-like | 16 | 1.40 s, 1973 paths, 145642 anchors, 304 MB |
| 16 Colors | four-colour badge | 16 | 0.99 s, 5 paths, 114 anchors, 184 MB |
| Shades of Gray | photo-like | 16 | 1.94 s, 686 paths, 53654 anchors, 407 MB |
| Shades of Gray | four-colour badge | 16 | 1.59 s, 9 paths, 2208 anchors, 270 MB |
| Black and White Logo | photo-like | 16 | 1.32 s, 1128 paths, 45273 anchors, 129 MB |
| Black and White Logo | four-colour badge | 16 | 0.54 s, 1 paths, 69 anchors, 88 MB |
| Sketched Art | photo-like | 16 | 1.30 s, 15 paths, 11486 anchors, 128 MB |
| Sketched Art | four-colour badge | 16 | 0.56 s, 2 paths, 361 anchors, 88 MB |
| Silhouettes | photo-like | 16 | 1.94 s, 3 paths, 3350 anchors, 144 MB |
| Silhouettes | four-colour badge | 16 | 0.61 s, 1 paths, 32 anchors, 95 MB |
| Line Art | photo-like | 16 | 1.55 s, 713 paths, 37370 anchors, 129 MB |
| Line Art | four-colour badge | 16 | 0.60 s, 1 paths, 49 anchors, 88 MB |
| Technical Drawing | photo-like | 16 | 1.78 s, 4577 paths, 76559 anchors, 130 MB |
| Technical Drawing | four-colour badge | 16 | 0.59 s, 1 paths, 69 anchors, 88 MB |
| Outline | photo-like | 16 | 1.83 s, 632 paths, 34884 anchors, 256 MB |
| Outline | four-colour badge | 16 | 0.91 s, 1 paths, 49 anchors, 176 MB |
| Flat Logo | photo-like | 16 | refused after 0.19 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 16 | 1.04 s, 5 paths, 67 anchors, 146 MB |
| Default with Shapes | photo-like | 16 | 1.38 s, 127 paths, 34034 anchors, 199 MB |
| Default with Shapes | four-colour badge | 16 | 0.64 s, 3 paths, 88 anchors, 184 MB |
| 6 Colors with Shapes | photo-like | 16 | 1.66 s, 470 paths, 60831 anchors, 298 MB |
| 6 Colors with Shapes | four-colour badge | 16 | 1.19 s, 5 paths, 112 anchors, 185 MB |
| 6 Colors with Auto Grouping | photo-like | 16 | 2.10 s, 470 paths, 60831 anchors, 298 MB |
| 6 Colors with Auto Grouping | four-colour badge | 16 | 1.08 s, 5 paths, 112 anchors, 184 MB |
| Shades of Gray with Auto Grouping | photo-like | 16 | 3.20 s, 686 paths, 53654 anchors, 407 MB |
| Shades of Gray with Auto Grouping | four-colour badge | 16 | 2.32 s, 9 paths, 2208 anchors, 270 MB |
| High Fidelity Photo with Auto Grouping | photo-like | 16 | 10.42 s, 31139 paths, 1192715 anchors, 880 MB |
| High Fidelity Photo with Auto Grouping | four-colour badge | 16 | 1.11 s, 6 paths, 264 anchors, 257 MB |
| 6 Colors, no Gradients | photo-like | 16 | 1.21 s, 470 paths, 60831 anchors, 298 MB |
| 6 Colors, no Gradients | four-colour badge | 16 | 0.86 s, 5 paths, 112 anchors, 184 MB |
| 6 Colors with Gradients | photo-like | 16 | 3.03 s, 470 paths, 60831 anchors, 512 MB |
| 6 Colors with Gradients | four-colour badge | 16 | 1.28 s, 5 paths, 112 anchors, 185 MB |
| High Fidelity Photo, no Gradients | photo-like | 16 | 4.74 s, 31139 paths, 1192715 anchors, 880 MB |
| High Fidelity Photo, no Gradients | four-colour badge | 16 | 1.12 s, 6 paths, 264 anchors, 257 MB |
| High Fidelity Photo with Gradients | photo-like | 16 | 7.70 s, 31139 paths, 1192715 anchors, 880 MB |
| High Fidelity Photo with Gradients | four-colour badge | 16 | 2.65 s, 6 paths, 264 anchors, 257 MB |
| Flat Logo | photo-like | 16 | refused after 0.20 s (not flat art), 40 MB |
| Flat Logo | four-colour badge | 16 | 1.17 s, 5 paths, 67 anchors, 146 MB |
| Default with Shapes | photo-like | 16 | 1.89 s, 127 paths, 34034 anchors, 199 MB |
| Default with Shapes | four-colour badge | 16 | 1.81 s, 3 paths, 88 anchors, 184 MB |
| 6 Colors with Shapes | photo-like | 16 | 3.30 s, 470 paths, 60831 anchors, 298 MB |
| 6 Colors with Shapes | four-colour badge | 16 | 1.73 s, 5 paths, 112 anchors, 185 MB |
| 6 Colors with Auto Grouping | photo-like | 16 | 2.14 s, 470 paths, 60831 anchors, 299 MB |
| 6 Colors with Auto Grouping | four-colour badge | 16 | 1.50 s, 5 paths, 112 anchors, 185 MB |
| Shades of Gray with Auto Grouping | photo-like | 16 | 2.71 s, 686 paths, 53654 anchors, 407 MB |
| Shades of Gray with Auto Grouping | four-colour badge | 16 | 1.85 s, 9 paths, 2208 anchors, 270 MB |
| High Fidelity Photo with Auto Grouping | photo-like | 16 | 10.82 s, 31139 paths, 1192715 anchors, 880 MB |
| High Fidelity Photo with Auto Grouping | four-colour badge | 16 | 1.69 s, 6 paths, 264 anchors, 257 MB |
| 6 Colors, no Gradients | photo-like | 16 | 1.81 s, 470 paths, 60831 anchors, 299 MB |
| 6 Colors, no Gradients | four-colour badge | 16 | 1.26 s, 5 paths, 112 anchors, 185 MB |
| 6 Colors with Gradients | photo-like | 16 | 4.20 s, 470 paths, 60831 anchors, 512 MB |
| 6 Colors with Gradients | four-colour badge | 16 | 1.76 s, 5 paths, 112 anchors, 185 MB |
| High Fidelity Photo, no Gradients | photo-like | 16 | 5.32 s, 31139 paths, 1192715 anchors, 880 MB |
| High Fidelity Photo, no Gradients | four-colour badge | 16 | 1.66 s, 6 paths, 264 anchors, 257 MB |
| High Fidelity Photo with Gradients | photo-like | 16 | 7.38 s, 31139 paths, 1192715 anchors, 880 MB |
| High Fidelity Photo with Gradients | four-colour badge | 16 | 1.79 s, 6 paths, 264 anchors, 263 MB |
## Revision history
| date | change |
|:--|:--|
| 2026-10-13 | Snap Curves To Lines moves no end of a line by more than 1.6 px: long lines a degree or two off stay as the image has them (Technical Drawing: largest distance 4.21 px to 1.46) |
| 2026-10-13 | Gradients (off unless asked for): areas that are a linear gradient are found before the palette is made and traced as one path with a gradient fill; linear-gradient inputs with known lines and stops, and four columns for them |
| 2026-10-13 | Auto Grouping (off unless asked for): each path that has others inside it is grouped with them; a "grouped %" column, and three presets measured with the option on |
| 2026-10-13 | Shapes (off unless asked for): filled outlines that are a circle, an ellipse, a rectangle or a regular polygon come out as that shape; corners whose pixel is mostly outside the shape are corners; plain-shape inputs and a "shapes %" column |
+14 -2
View File
@@ -675,8 +675,20 @@ is refused before the command runs:
→ invalid parameters for `file.new`: unknown parameter `widht`; did you mean `width`?
```
Only the top level is checked: the keys of an object inside a value (`artboardLayout: {…}`, an effect's `params`) are
the command's to read. A description with a `…` entry takes more keys than it lists: those of the command it names
Only the top level is checked: the keys of an object inside a value (`artboardLayout: {…}`) are the command's to
read. The one exception is an effect's `params`: `effect.apply` (and `appearance.addEffect`) and `effect.setParams`
check them against the effect's own entry in `effect.list`, its `defaults` and the keys its `params` text names. A key
the effect doesn't take, or a value of another type than its default (a string for a number, a number for a switch),
is refused the same way:
```json
{"id": "effect.apply", "params": {"effect": "blur.gaussian", "params": {"blurr": 3}}}
→ invalid parameters for `effect.apply`: unknown parameter `blurr`; the command takes: radius
```
A number outside the range the effect documents is still stored as given and clamped when the effect draws. A
plug-in effect's parameters are resolved against its manifest instead, and `effect.setParams` as a step of
`command.batch` isn't checked (the effect it edits may only exist once the steps before it ran). A description with a `…` entry takes more keys than it lists: those of the command it names
(`…settings as file.new`), or any key when it names none, as `document.export` and `document.save` do for the options
of the format they write (one object can carry several formats' options). A command that takes its own reply back
(`document.setup`, `text.areaOptions`) says so the same way. The app's own journal isn't checked: an action or