Image Trace: Expand can group the traced paths by color

Squashed from #4058 onto main.
This commit is contained in:
dotCooCoo
2026-11-08 23:50:26 +07:00
committed by bflatastic
parent 743b8031d3
commit f118cf6aa6
25 changed files with 732 additions and 3 deletions
+2 -2
View File
@@ -153,7 +153,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Make and Expand",
["Object", "Image Trace"],
None,
"{…as imageTrace.make} same as imageTrace.make, then expand → {id, paths, anchors, colors}",
"{…as imageTrace.make, byColor?: false (each color's paths in a group of their own, as imageTrace.expand)} same as imageTrace.make, then expand → {id, paths, anchors, colors}",
has_selection_or_ids,
|s, p| trace_make(s, p, true)
),
@@ -171,7 +171,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Expand",
["Object", "Image Trace"],
None,
"{id?, ids?: [id…] (instead of id)} keep only the traced paths (drop the source image) → {ids}",
"{id?, ids?: [id…] (instead of id), byColor?: false (each color's paths in a group of their own, named by the swatch the color links to or as #rrggbb, gradient fills in one group named Gradients; the groups stack by their lowest path and keep their paths' order; a color whose paths overlap another color's from above and below gets a group for each part, so the art draws the same; refused for a trace made with autoGrouping and for a trace with a live Pathfinder effect)} keep only the traced paths (drop the source image) → {ids}",
has_selection_or_ids,
trace_expand
),
+346 -1
View File
@@ -4,10 +4,16 @@ use vectorcraft_color::Swatch;
use vectorcraft_doc::ImageObject;
use super::{
Appearance, AppearanceItem, Arc, Beside, Color, Document, EngineError, LineCap, LineJoin, LiveShape, Node, NodeId, NodeKind, Paint, Point,
Appearance, AppearanceItem, Arc, Beside, Color, Document, EngineError, LineCap, LineJoin, LiveShape, Node, NodeId, NodeKind, Paint, Point, Rect,
Result, Session, StrokeLayer, TRACE_NAME, TraceView, Value, bad, fill_only, ids_json, json, root_ids, shape_node, str_param, switch_or, tr,
};
#[cfg(test)]
thread_local! {
static BOX_TESTS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static CANDIDATE_VISITS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
// ---------- Image Trace ----------
fn is_trace_group(n: &Node) -> bool {
@@ -16,6 +22,8 @@ fn is_trace_group(n: &Node) -> bool {
&& n.children().is_some_and(|c| c.first().is_some_and(|i| matches!(i.kind, NodeKind::Image(_))))
}
const LIVE_PATHFINDER_BY_COLOR: &str = "byColor can't regroup a trace with a live Pathfinder effect: expand without byColor";
/// The `view` param ([`TraceView`] id), if given.
fn view_param(cmd: &str, p: &Value) -> Result<Option<TraceView>> {
let Some(v) = p.get("view").filter(|v| !v.is_null()) else { return Ok(None) };
@@ -368,6 +376,156 @@ fn grouped(d: &mut Document, nodes: Vec<Node>, parents: &[Option<usize>]) -> Vec
roots.iter().filter_map(|&r| built.get_mut(r)?.take()).chain(ring).collect()
}
/// The `byColor` param of an Expand (false when not given).
fn by_color_param(cmd: &str, p: &Value) -> Result<bool> {
match p.get("byColor").filter(|v| !v.is_null()) {
Some(v) => v.as_bool().ok_or_else(|| bad(cmd, "byColor must be true or false")),
None => Ok(false),
}
}
/// Expand by color: an expanded trace's `items` (bottom-most first) in a group for each paint, a
/// stroked line counted by its stroke. A solid color's group is named by the swatch it links to (a
/// tint with its percentage) or as `#rrggbb`, and gradient fills share one group, "Gradients". The
/// groups stack in the order of the lowest item in each, and each keeps its items' order. When the
/// paths meet, an item joins its paint's last group only if no item of a later group meets it
/// (by the boxes of everything they paint, effects included, within one point); otherwise it
/// starts another group of that paint, which keeps the stacking order of those items. Items
/// farther apart can still share antialiased pixels at small view or raster-export scales, so
/// their edge pixels may change. A group among `items` (Auto Grouping) is refused.
fn color_groups(cmd: &str, d: &mut Document, items: Vec<Arc<Node>>) -> Result<Vec<Arc<Node>>> {
use std::collections::{BTreeMap, HashMap};
use std::ops::Bound::{Excluded, Unbounded};
use vectorcraft_color::tint_percent;
// Grouped by the paint: unlinked (its #rrggbb), linked (the swatch and its exact tint), a
// gradient, or another paint (its label). The name is apart from it: a swatch may be named as
// a color's `#rrggbb` or as "Gradients".
type PaintKey = (u8, String, u32);
struct Run {
name: String,
members: Vec<Arc<Node>>,
}
// Boxes closer than a point count as overlapping: their antialiased edges can share pixels.
let meet = |a: &Rect, b: &Rect| {
#[cfg(test)]
BOX_TESTS.with(|n| n.set(n.get() + 1));
let b = b.inflate(1.0, 1.0);
a.x0 < b.x1 && b.x0 < a.x1 && a.y0 < b.y1 && b.y0 < a.y1
};
// Choose a cell size from the painted extent and path count. A box over more than eight cells
// across but at most eight down (a stripe) is indexed by its rows, one the other way round by
// its columns, and one over more than eight both ways stays in a separate list, keeping the
// index bounded even when a path covers most of the document.
let items: Vec<_> = items
.into_iter()
.map(|item| {
let bounds = vectorcraft_render::painted_bounds(d, &item);
(item, bounds)
})
.collect();
let extent = items.iter().filter_map(|(_, b)| *b).reduce(|a, b| a.union(b));
let cell_size = extent.map_or(1.0, |r| ((r.x1 - r.x0).abs() * (r.y1 - r.y0).abs() / items.len().max(1) as f64).sqrt().max(1.0));
// The first and last column and row of the cells `r` covers; `None` when it isn't finite.
let span = |r: Rect| -> Option<(i64, i64, i64, i64)> {
let coord = |v: f64| (v / cell_size).floor() as i64;
[r.x0, r.y0, r.x1, r.y1].iter().all(|v| v.is_finite()).then(|| (coord(r.x0), coord(r.y0), coord(r.x1), coord(r.y1)))
};
let count = |first: i64, last: i64| i128::from(last) - i128::from(first) + 1;
// A box over more cells than this is checked against every box.
const MAX_CELLS: i128 = 1024;
let mut groups: Vec<Run> = vec![];
let mut latest: HashMap<PaintKey, usize> = HashMap::new();
// An item can join an earlier group, so insertion order is not group-index order. Index
// boxes by group to skip older groups without scanning their entries.
let mut spatial: HashMap<(i64, i64), BTreeMap<usize, Vec<Rect>>> = HashMap::new();
let mut rows: HashMap<i64, BTreeMap<usize, Vec<Rect>>> = HashMap::new();
let mut columns: HashMap<i64, BTreeMap<usize, Vec<Rect>>> = HashMap::new();
let mut large: BTreeMap<usize, Vec<Rect>> = BTreeMap::new();
let mut all_boxes: BTreeMap<usize, Vec<Rect>> = BTreeMap::new();
for (item, bounds) in items {
if matches!(item.kind, NodeKind::Group { .. }) {
return Err(bad(cmd, "byColor can't split Auto Grouping groups: trace without Auto Grouping"));
}
let paint = match item.appearance.fill_paint() {
Paint::None => item.appearance.stroke_paint(),
fill => fill,
};
let (key, name) = match &paint {
Paint::Solid { swatch: Some(n), tint, .. } => {
let percent = tint_percent(*tint);
let name = if *tint < 1.0 { format!("{n} {percent}%") } else { n.clone() };
((1, n.clone(), tint.to_bits()), name)
}
Paint::Solid { color, .. } => ((0, color.to_hex(), 0), color.to_hex()),
Paint::Gradient(_) => ((2, String::new(), 0), "Gradients".to_string()),
other => ((3, other.label(), 0), other.label()),
};
// Joining an earlier group moves the item under every later group's items.
let hidden_by_later = |at: usize| {
bounds.is_some_and(|b| {
let overlaps_later = |boxes: &BTreeMap<usize, Vec<Rect>>| {
boxes.range((Excluded(at), Unbounded)).rev().any(|(_, group_boxes)| {
group_boxes.iter().rev().any(|box_bounds| {
#[cfg(test)]
CANDIDATE_VISITS.with(|n| n.set(n.get() + 1));
meet(box_bounds, &b)
})
})
};
match span(b) {
Some((x0, y0, x1, y1)) if count(x0, x1) * count(y0, y1) <= MAX_CELLS => {
(y0..=y1).flat_map(|y| (x0..=x1).map(move |x| (x, y))).filter_map(|c| spatial.get(&c)).any(overlaps_later)
|| (y0..=y1).filter_map(|y| rows.get(&y)).any(overlaps_later)
|| (x0..=x1).filter_map(|x| columns.get(&x)).any(overlaps_later)
|| overlaps_later(&large)
}
_ => overlaps_later(&all_boxes),
}
})
};
let at = latest.get(&key).copied().filter(|&at| !hidden_by_later(at));
let mut started = Run { name, members: vec![] };
let joins = at.and_then(|at| groups.get_mut(at));
let new = joins.is_none();
let g = joins.unwrap_or(&mut started);
g.members.push(item);
if new {
groups.push(started);
latest.insert(key, groups.len() - 1);
}
if let Some(b) = bounds {
let index = if new { groups.len() - 1 } else { at.unwrap_or_default() };
all_boxes.entry(index).or_default().push(b);
match span(b.inflate(1.0, 1.0)) {
Some((x0, y0, x1, y1)) if count(x0, x1) <= 8 && count(y0, y1) <= 8 => {
for place in (y0..=y1).flat_map(|y| (x0..=x1).map(move |x| (x, y))) {
spatial.entry(place).or_default().entry(index).or_default().push(b);
}
}
Some((_, y0, _, y1)) if count(y0, y1) <= 8 => {
for y in y0..=y1 {
rows.entry(y).or_default().entry(index).or_default().push(b);
}
}
Some((x0, _, x1, _)) if count(x0, x1) <= 8 => {
for x in x0..=x1 {
columns.entry(x).or_default().entry(index).or_default().push(b);
}
}
_ => large.entry(index).or_default().push(b),
}
}
}
Ok(groups
.into_iter()
.map(|run| {
let mut g = Node::group(d.alloc_id(), run.members);
g.name = Some(run.name);
Arc::new(g)
})
.collect())
}
/// What a trace depends on: the object traced (an image, or an Image Trace object traced again),
/// its image, and the settings it is traced with. The tracer makes the same paths of the same
/// inputs, so a trace made away from the command ([`TraceRun`]) stands in for the command's own.
@@ -398,6 +556,10 @@ struct Prepared {
fn prepare(s: &Session, c: &str, p: &Value) -> Result<Prepared> {
let mut params = trace_params(s, c, p)?;
let view = view_param(c, p)?;
let by_color = c == "imageTrace.makeAndExpand" && by_color_param(c, p)?;
if by_color && params.auto_grouping {
return Err(bad(c, "byColor can't split Auto Grouping groups: trace without Auto Grouping"));
}
let params_json = serde_json::to_value(&params).map_err(|e| EngineError::Other(e.to_string()))?;
// The preset's name if the parameters are exactly that preset's or its enhanced version's (a
// saved preset's under its saved name), else "Custom".
@@ -428,6 +590,9 @@ fn prepare(s: &Session, c: &str, p: &Value) -> Result<Prepared> {
})
.ok_or_else(|| bad(c, "select an image (or an Image Trace group)"))?
};
if by_color && s.doc()?.doc.node(target).is_some_and(vectorcraft_render::effects::has_pathfinder) {
return Err(bad(c, LIVE_PATHFINDER_BY_COLOR));
}
let blob = s.doc()?.doc.images.get(&image.key).ok_or_else(|| EngineError::Other(format!("image data `{}` is missing", image.key)))?;
let bytes = Arc::clone(&blob.bytes);
let library = trace_library(c, s, &mut params)?;
@@ -533,6 +698,7 @@ impl TraceRun {
pub(super) fn trace_make(s: &mut Session, p: &Value, expand: bool) -> Result<Value> {
let c = if expand { "imageTrace.makeAndExpand" } else { "imageTrace.make" };
let Prepared { inputs, trace, view, params_json, preset_label, library, bytes } = prepare(s, c, p)?;
let group_by_color = expand && by_color_param(c, p)?;
// A saved preset's settings join the journal entry, so a replay traces as this run did after
// the preset is changed, renamed or deleted.
let named = str_param(p, "preset").unwrap_or("Default");
@@ -608,6 +774,9 @@ pub(super) fn trace_make(s: &mut Session, p: &Value, expand: bool) -> Result<Val
traced.push(n);
}
children.extend(grouped(d, traced, &res.parents));
if group_by_color {
children = color_groups(c, d, children)?;
}
let gid = d.alloc_id();
let mut g = Node::group(gid, children);
g.name = if expand { None } else { Some(TRACE_NAME.into()) };
@@ -706,7 +875,20 @@ pub(super) fn trace_release(s: &mut Session, p: &Value) -> Result<Value> {
}
pub(super) fn trace_expand(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "imageTrace.expand";
let by_color = by_color_param(C, p)?;
let targets = trace_targets(s, p)?;
if by_color {
// A trace made with Auto Grouping is refused even when none of its paths were grouped.
let d = &s.doc()?.doc;
let grouped = |n: &Node| n.extras().trace.as_deref().and_then(|t| t.pointer("/params/autoGrouping")).and_then(Value::as_bool) == Some(true);
if targets.iter().any(|g| d.node(*g).is_some_and(grouped)) {
return Err(bad(C, "byColor can't split Auto Grouping groups: trace without Auto Grouping"));
}
if targets.iter().any(|g| d.node(*g).is_some_and(vectorcraft_render::effects::has_pathfinder)) {
return Err(bad(C, LIVE_PATHFINDER_BY_COLOR));
}
}
s.edit("Expand Image Trace", |d, sel| {
for g in &targets {
let n = d.node_mut(*g).ok_or(EngineError::NoNode(*g))?;
@@ -715,6 +897,15 @@ pub(super) fn trace_expand(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(ch) = n.children_mut() {
ch.retain(|c| !matches!(c.kind, NodeKind::Image(_)));
}
if by_color {
let items = d.node(*g).and_then(Node::children).cloned().unwrap_or_default();
// The paths may have been moved or reshaped since the trace was made, so any trace's
// paths are taken to overlap.
let groups = color_groups(C, d, items)?;
if let Some(ch) = d.node_mut(*g).and_then(Node::children_mut) {
*ch = groups;
}
}
}
sel.set(targets.iter().copied());
Ok(())
@@ -722,6 +913,160 @@ pub(super) fn trace_expand(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({ "ids": ids_json(&targets) }))
}
#[cfg(test)]
mod color_group_tests {
use super::*;
use vectorcraft_geom::{PathData, SubPath};
fn area(d: &mut Document, x0: f64, y0: f64, x1: f64, y1: f64, color: Color) -> Arc<Node> {
let path = PathData::single(SubPath::polyline(&[Point::new(x0, y0), Point::new(x1, y0), Point::new(x1, y1), Point::new(x0, y1)], true));
Arc::new(Node::path(d.alloc_id(), path, fill_only(Paint::solid(color))))
}
#[test]
fn abutting_color_groups_preserve_order_at_shared_edges() {
let mut d = Document::new(100.0, 100.0);
let a = Color::rgb(1.0, 0.0, 0.0);
let b = Color::rgb(0.0, 0.0, 1.0);
let items = vec![area(&mut d, 0.0, 0.0, 20.0, 20.0, a), area(&mut d, 20.0, 0.0, 40.0, 20.0, b), area(&mut d, 40.0, 0.0, 60.0, 20.0, a)];
let original: Vec<_> = items.iter().map(|n| n.id).collect();
let groups = color_groups("imageTrace.makeAndExpand", &mut d, items).unwrap();
let actual: Vec<_> = groups.iter().flat_map(|g| g.children().unwrap().iter().map(|n| n.id)).collect();
assert_eq!(actual, original, "shared-edge paths changed stacking order");
}
#[test]
fn dense_color_groups_use_bounded_box_tests() {
let mut d = Document::new(200.0, 200.0);
let a = Color::rgb(1.0, 0.0, 0.0);
let b = Color::rgb(0.0, 0.0, 1.0);
let items = (0..4000)
.map(|i| {
if i % 2 == 0 {
let x = 100.0 + (i % 40) as f64 * 0.01;
area(&mut d, x, 100.0, x + 0.1, 100.1, a)
} else {
let x = if i % 4 == 1 { 0.0 } else { 198.0 };
area(&mut d, x, x, x + 2.0, x + 2.0, b)
}
})
.collect();
BOX_TESTS.with(|n| n.set(0));
let groups = color_groups("imageTrace.makeAndExpand", &mut d, items).unwrap();
let tests = BOX_TESTS.with(std::cell::Cell::get);
assert_eq!(groups.iter().map(|g| g.children().map_or(0, Vec::len)).sum::<usize>(), 4000);
assert!(tests < 4000 * 32, "{tests} box tests for 4000 paths");
}
#[test]
fn overlapping_alternating_colors_visit_bounded_candidates() {
const COUNT: usize = 3000;
let mut d = Document::new(100.0, 100.0);
let colors = [Color::rgb(1.0, 0.0, 0.0), Color::rgb(0.0, 0.0, 1.0)];
let items = (0..COUNT).map(|i| area(&mut d, 10.0, 10.0, 20.0, 20.0, colors[i % 2])).collect();
CANDIDATE_VISITS.with(|n| n.set(0));
let groups = color_groups("imageTrace.makeAndExpand", &mut d, items).unwrap();
let visits = CANDIDATE_VISITS.with(std::cell::Cell::get);
assert_eq!(groups.len(), COUNT);
assert!(visits < COUNT * 16, "{visits} candidate visits for {COUNT} paths");
}
#[test]
fn separate_stripes_of_alternating_colors_visit_bounded_candidates() {
const COUNT: usize = 2000;
let mut d = Document::new(1000.0, 4000.0);
let colors = [Color::rgb(1.0, 0.0, 0.0), Color::rgb(0.0, 0.0, 1.0)];
for tall in [false, true] {
let items = (0..COUNT)
.map(|i| {
let at = i as f64 * 2.0;
if tall { area(&mut d, at, 0.0, at + 0.5, 1000.0, colors[i % 2]) } else { area(&mut d, 0.0, at, 1000.0, at + 0.5, colors[i % 2]) }
})
.collect();
CANDIDATE_VISITS.with(|n| n.set(0));
let groups = color_groups("imageTrace.makeAndExpand", &mut d, items).unwrap();
let visits = CANDIDATE_VISITS.with(std::cell::Cell::get);
assert_eq!(groups.len(), 2, "tall {tall}");
assert!(visits < COUNT * 32, "tall {tall}: {visits} candidate visits for {COUNT} stripes");
}
}
#[test]
fn spatial_color_groups_match_simple_scan() {
let colors = [Color::rgb(1.0, 0.0, 0.0), Color::rgb(0.0, 0.0, 1.0), Color::rgb(0.0, 1.0, 0.0)];
for layout in 0..6 {
let mut d = Document::new(200.0, 200.0);
let items: Vec<_> = (0..90)
.map(|i| {
let x = match layout {
0 => i as f64 * 2.0,
1 => (i % 10) as f64 * 5.0,
2 => (i % 7) as f64 * 3.0,
3 => (i % 13) as f64 * 9.0,
4 => (i % 4) as f64 * 10.0,
_ => i as f64 * 1.5,
};
let y = match layout {
0 => 0.0,
1 => (i / 10) as f64 * 5.0,
2 => (i / 7) as f64 * 3.0,
3 => (i / 13) as f64 * 9.0,
4 => i as f64 * 1.5,
_ => (i % 4) as f64 * 10.0,
};
// Layouts 4 and 5: wide and tall stripes, some meeting their neighbors.
let (w, h) = match layout {
4 => (150.0, 1.0),
5 => (1.0, 150.0),
_ => (3.0, 3.0),
};
area(&mut d, x, y, x + w, y + h, colors[i % 3])
})
.collect();
let mut expected: Vec<(String, Vec<(NodeId, Rect)>)> = vec![];
for item in &items {
let key = item.appearance.fill_paint().color().unwrap().to_hex();
let bounds = vectorcraft_render::painted_bounds(&d, item).unwrap();
let previous = expected.iter().rposition(|(k, _)| *k == key);
let can_join = previous.filter(|&at| {
!expected.iter().skip(at + 1).any(|(_, boxes)| {
boxes.iter().any(|(_, m)| {
let b = bounds.inflate(1.0, 1.0);
m.x0 < b.x1 && b.x0 < m.x1 && m.y0 < b.y1 && b.y0 < m.y1
})
})
});
match can_join.and_then(|at| expected.get_mut(at)) {
Some((_, members)) => members.push((item.id, bounds)),
None => expected.push((key, vec![(item.id, bounds)])),
}
}
let actual = color_groups("imageTrace.makeAndExpand", &mut d, items).unwrap();
let actual: Vec<Vec<_>> = actual.iter().map(|g| g.children().unwrap().iter().map(|n| n.id).collect()).collect();
let expected: Vec<Vec<_>> = expected.into_iter().map(|(_, members)| members.into_iter().map(|(id, _)| id).collect()).collect();
assert_eq!(actual, expected, "layout {layout}");
}
}
#[test]
fn an_abutting_gradient_stays_above_the_solid_area_it_borders() {
use vectorcraft_color::{Gradient, GradientKind, GradientPaint, GradientStop};
let stops = vec![GradientStop::new(0.0, Color::rgb(1.0, 0.0, 0.0)), GradientStop::new(1.0, Color::rgb(0.0, 0.0, 1.0))];
let ramp = || Paint::Gradient(Box::new(GradientPaint::new(Gradient::new(GradientKind::Linear, stops.clone()))));
let mut d = Document::new(100.0, 100.0);
let mut area = |x0: f64, x1: f64, paint: Paint| {
let path =
PathData::single(SubPath::polyline(&[Point::new(x0, 0.0), Point::new(x1, 0.0), Point::new(x1, 60.0), Point::new(x0, 60.0)], true));
Arc::new(Node::path(d.alloc_id(), path, fill_only(paint)))
};
// A gradient, a solid area beside it and a second gradient beside that, bottom to top.
let items = vec![area(0.0, 50.0, ramp()), area(50.0, 80.0, Paint::solid(Color::rgb(0.2, 0.6, 0.2))), area(80.0, 100.0, ramp())];
let groups = color_groups("imageTrace.makeAndExpand", &mut d, items).unwrap();
let names: Vec<_> = groups.iter().map(|g| g.name.clone().unwrap_or_default()).collect();
assert_eq!(names, ["Gradients", "#339933", "Gradients"]);
}
}
#[cfg(test)]
mod trace_limit_tests {
use super::{MAX_TRACE_ANCHORS, trace_warnings};
+310
View File
@@ -3221,6 +3221,316 @@ fn image_trace_release_and_expand() {
assert!(s.execute("imageTrace.release", &json!({})).is_err());
}
/// Expand by color: each color's paths in a group of their own, named by the color, the groups
/// stacked by the lowest path in each and the art drawn as before; Release to Layers then gives a
/// sublayer for each color.
#[test]
#[allow(clippy::cognitive_complexity)] // Exercises several color and stacking cases in one trace fixture.
fn image_trace_expand_by_color_groups_each_colors_paths() {
// A light field with a black ring and a red square on it.
let art = vectorcraft_trace::Raster::from_fn(200, 150, |x, y| {
let r = (f64::from(x) + 0.5 - 70.0).hypot(f64::from(y) + 0.5 - 75.0);
if (34.0..=40.0).contains(&r) {
[0, 0, 0, 255]
} else if (130..170).contains(&x) && (55..95).contains(&y) {
[200, 40, 50, 255]
} else {
[240, 240, 220, 255]
}
});
let names = |n: &Node| n.children().unwrap().iter().map(|c| c.name.clone().unwrap_or_default()).collect::<Vec<_>>();
let hex = |n: &Node| n.appearance.fill_paint().color().unwrap().to_hex();
let shot = |s: &Session| {
let img = render_doc(s, false);
(0..200).flat_map(|x| (0..150).map(move |y| (x, y))).map(|(x, y)| img.pixel(x, y)).collect::<Vec<_>>()
};
for method in ["abutting", "overlapping"] {
let params = json!({"mode": "color", "palette": "limited", "colors": 3, "method": method});
let expand = |by_color: bool| {
let mut s = session();
add_image(&mut s, &art, Affine::IDENTITY);
let r = s.execute("imageTrace.makeAndExpand", &json!({"params": params, "byColor": by_color})).unwrap();
let g = NodeId(r["id"].as_u64().unwrap());
(s, g)
};
let (flat, fg) = expand(false);
let (mut s, g) = expand(true);
let paths = node(&flat, fg).children().unwrap().clone();
// Without byColor (the default) the paths sit in the trace's group, with no group of their own.
assert!(paths.iter().all(|p| !matches!(p.kind, NodeKind::Group { .. })), "{method}");
let (default, dg) = {
let mut s = session();
add_image(&mut s, &art, Affine::IDENTITY);
let r = s.execute("imageTrace.makeAndExpand", &json!({"params": params})).unwrap();
(s, NodeId(r["id"].as_u64().unwrap()))
};
assert!(*node(&default, dg).children().unwrap() == paths, "{method}: byColor is false when not given");
// The colors in the order they first come in, bottom to top.
let mut order: Vec<String> = vec![];
for p in &paths {
if !order.contains(&hex(p)) {
order.push(hex(p));
}
}
let mut sorted = order.clone();
sorted.sort();
assert_eq!(sorted, ["#000000", "#c82832", "#f0f0dc"], "{method}");
let n = node(&s, g);
assert_eq!(names(&n), order, "{method}");
// Each group holds its color's paths, and in turn they are the paths of the trace without
// byColor, in their order.
let groups = n.children().unwrap();
for c in groups {
assert!(matches!(c.kind, NodeKind::Group { .. }), "{method}");
assert!(c.children().unwrap().iter().all(|m| Some(hex(m)) == c.name), "{method}: {:?}", c.name);
}
let members: Vec<Arc<Node>> = groups.iter().flat_map(|c| c.children().unwrap().iter().cloned()).collect();
assert!(members == paths, "{method}");
assert!(shot(&s) == shot(&flat), "{method}: the art changed");
// Release to Layers: a sublayer for each color, in the same order.
let r = s.execute("layer.releaseToLayers", &json!({"id": g.0})).unwrap();
let subs = ids(&r);
assert_eq!(subs.len(), 3, "{method}");
for (sub, name) in subs.iter().zip(&order) {
let l = node(&s, *sub);
assert!(matches!(l.kind, NodeKind::Layer { .. }), "{method}");
assert_eq!(names(&l), std::slice::from_ref(name), "{method}");
}
}
// imageTrace.expand takes it too, as one undo step.
let mut s = session();
add_image(&mut s, &art, Affine::IDENTITY);
let params = json!({"mode": "color", "palette": "limited", "colors": 3});
let g = NodeId(s.execute("imageTrace.make", &json!({"params": params})).unwrap()["id"].as_u64().unwrap());
let steps = s.doc().unwrap().history.undo.len();
s.execute("imageTrace.expand", &json!({"byColor": true})).unwrap();
assert_eq!(s.doc().unwrap().history.undo.len(), steps + 1);
let mut got = names(&node(&s, g));
got.sort();
assert_eq!(got, ["#000000", "#c82832", "#f0f0dc"]);
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(node(&s, g).name.as_deref(), Some("Image Trace"));
// A trace of one color gives one group holding every path.
let mut one = session();
let bar =
vectorcraft_trace::Raster::from_fn(80, 40, |x, y| if (10..70).contains(&x) && (10..30).contains(&y) { [0, 0, 0, 255] } else { [255; 4] });
add_image(&mut one, &bar, Affine::IDENTITY);
let r = one.execute("imageTrace.makeAndExpand", &json!({"params": {"ignoreWhite": true}, "byColor": true})).unwrap();
let og = node(&one, NodeId(r["id"].as_u64().unwrap()));
assert_eq!(names(&og), ["#000000"]);
let only = &og.children().unwrap()[0];
assert!(!only.children().unwrap().is_empty() && only.children().unwrap().iter().all(|m| hex(m) == "#000000"));
// Auto Grouping's groups are not split: refused, and the trace stays as it was.
let mut grouped = params.clone();
grouped["autoGrouping"] = json!(true);
let e = s.execute("imageTrace.makeAndExpand", &json!({"id": g.0, "params": grouped, "byColor": true})).unwrap_err().to_string();
assert!(e.contains("Auto Grouping"), "{e}");
let g = NodeId(s.execute("imageTrace.make", &json!({"id": g.0, "params": grouped})).unwrap()["id"].as_u64().unwrap());
let e = s.execute("imageTrace.expand", &json!({"byColor": true})).unwrap_err().to_string();
assert!(e.contains("Auto Grouping"), "{e}");
assert_eq!(node(&s, g).name.as_deref(), Some("Image Trace"));
let e = s.execute("imageTrace.expand", &json!({"byColor": "yes"})).unwrap_err().to_string();
assert!(e.contains("true or false"), "{e}");
// Refused too when Auto Grouping grouped nothing, as with side-by-side stripes.
let mut s = session();
let stripes = vectorcraft_trace::Raster::from_fn(60, 60, |x, _| match x / 20 {
0 => [200, 40, 40, 255],
1 => [40, 160, 60, 255],
_ => [40, 70, 200, 255],
});
add_image(&mut s, &stripes, Affine::IDENTITY);
let g = NodeId(s.execute("imageTrace.make", &json!({"params": grouped})).unwrap()["id"].as_u64().unwrap());
assert!(node(&s, g).children().unwrap().iter().all(|c| !matches!(c.kind, NodeKind::Group { .. })));
let e = s.execute("imageTrace.expand", &json!({"byColor": true})).unwrap_err().to_string();
assert!(e.contains("Auto Grouping"), "{e}");
assert_eq!(node(&s, g).name.as_deref(), Some("Image Trace"));
// A color linked to a global swatch goes by the swatch's name, a tint with its percentage.
let mut s = session();
s.execute("swatch.new", &json!({"name": "Ink", "color": "#c82828", "global": true})).unwrap();
s.execute("swatch.new", &json!({"name": "Sky", "color": "#2846c8", "global": true})).unwrap();
let r = vectorcraft_trace::Raster::from_fn(60, 60, |x, y| match (x < 30, y < 30) {
(true, _) => [200, 40, 40, 255],
(false, true) => [40, 160, 60, 255],
(false, false) => [40, 70, 200, 255],
});
add_image(&mut s, &r, Affine::IDENTITY);
let limited = json!({"mode": "color", "palette": "limited", "colors": 3});
let g = NodeId(s.execute("imageTrace.make", &json!({"params": limited})).unwrap()["id"].as_u64().unwrap());
let paths: Vec<(u64, String)> =
node(&s, g).children().unwrap().iter().filter(|c| !matches!(c.kind, NodeKind::Image(_))).map(|c| (c.id.0, hex(c))).collect();
let mut colors: Vec<&str> = paths.iter().map(|(_, h)| h.as_str()).collect();
colors.sort();
colors.dedup();
assert_eq!(colors, ["#2846c8", "#28a03c", "#c82828"]);
// The red paths link to Ink and the blue ones to Sky at 50%; green stays a plain color.
let pick = |want: &str| paths.iter().filter(|(_, h)| h == want).map(|(id, _)| *id).collect::<Vec<_>>();
s.execute("paint.setFill", &json!({"ids": pick("#c82828"), "swatch": "Ink"})).unwrap();
s.execute("paint.setFill", &json!({"ids": pick("#2846c8"), "swatch": "Sky", "tint": 50})).unwrap();
s.execute("imageTrace.expand", &json!({"id": g.0, "byColor": true})).unwrap();
let mut got = names(&node(&s, g));
got.sort();
assert_eq!(got, ["#28a03c", "Ink", "Sky 50%"]);
// A swatch named as another color's #rrggbb still gets a group of its own.
s.execute("edit.undo", &json!({})).unwrap();
s.execute("swatch.edit", &json!({"name": "Ink", "newName": "#28a03c"})).unwrap();
s.execute("imageTrace.expand", &json!({"id": g.0, "byColor": true})).unwrap();
let mut got = names(&node(&s, g));
got.sort();
assert_eq!(got, ["#28a03c", "#28a03c", "Sky 50%"]);
// Gradient fills share one group, and a stroked line goes by its stroke.
let ramp = vectorcraft_trace::Raster::from_fn(100, 60, |x, _| {
let t = (f64::from(x) + 0.5) / 100.0;
[(20.0 + 200.0 * t) as u8, 40, (220.0 - 180.0 * t) as u8, 255]
});
let line =
vectorcraft_trace::Raster::from_fn(100, 100, |x, y| if (10..90).contains(&x) && (49..52).contains(&y) { [0, 0, 0, 255] } else { [255; 4] });
for (art, params, want) in [
(ramp, json!({"preset": "6 Colors", "params": {"gradients": true}, "byColor": true}), "Gradients"),
(line, json!({"params": {"ignoreWhite": true, "strokes": true, "strokeWidth": 5}, "byColor": true}), "#000000"),
] {
let mut s = session();
add_image(&mut s, &art, Affine::IDENTITY);
let res = s.execute("imageTrace.makeAndExpand", &params).unwrap();
assert_eq!(names(&node(&s, NodeId(res["id"].as_u64().unwrap()))), [want]);
}
// Flat Logo stacks its shapes biggest first without cutting holes under white: a black ring's
// outline, a white disc on it and a black dot on that. Black keeps a group below white and one
// above it, and the art draws as before, through Make and Expand and through Expand.
let target = vectorcraft_trace::Raster::from_fn(120, 120, |x, y| {
let r = (f64::from(x) + 0.5 - 60.0).hypot(f64::from(y) + 0.5 - 60.0);
if r < 15.0 || (30.0..50.0).contains(&r) {
[0, 0, 0, 255]
} else if r < 30.0 {
[255, 255, 255, 255]
} else {
[0, 0, 0, 0]
}
});
let logo = json!({"mode": "logo", "logoColors": ["#000000"]});
let traced = |by_color: bool, expand_after: bool| {
let mut s = session();
add_image(&mut s, &target, Affine::IDENTITY);
let g = if expand_after {
let g = s.execute("imageTrace.make", &json!({"params": logo})).unwrap()["id"].as_u64().unwrap();
s.execute("imageTrace.expand", &json!({"byColor": by_color})).unwrap();
g
} else {
s.execute("imageTrace.makeAndExpand", &json!({"params": logo, "byColor": by_color})).unwrap()["id"].as_u64().unwrap()
};
(s, NodeId(g))
};
for expand_after in [false, true] {
let (flat, fg) = traced(false, expand_after);
let (s, g) = traced(true, expand_after);
let paths = node(&flat, fg).children().unwrap().clone();
assert_eq!(paths.iter().map(|p| hex(p)).collect::<Vec<_>>(), ["#000000", "#ffffff", "#000000"]);
assert_eq!(names(&node(&s, g)), ["#000000", "#ffffff", "#000000"], "expand after: {expand_after}");
let members: Vec<Arc<Node>> = node(&s, g).children().unwrap().iter().flat_map(|c| c.children().unwrap().iter().cloned()).collect();
assert!(members == paths, "expand after: {expand_after}");
assert!(shot(&s) == shot(&flat), "expand after: {expand_after}: the art changed");
}
// An Abutting trace's top path, given the bottom path's color and moved onto the middle one (by
// a move or by a Transform effect), moved clear of it with a drop shadow cast onto it, or moved
// to share a pixel at its edge, before Expand, still draws on top of it.
let stripes = vectorcraft_trace::Raster::from_fn(90, 30, |x, _| match x / 30 {
0 => [200, 40, 40, 255],
1 => [40, 160, 60, 255],
_ => [40, 70, 200, 255],
});
let edited = |by_color: bool, how: &str| {
let mut s = session();
add_image(&mut s, &stripes, Affine::IDENTITY);
let limited = json!({"mode": "color", "palette": "limited", "colors": 3, "method": "abutting"});
let g = NodeId(s.execute("imageTrace.make", &json!({"params": limited})).unwrap()["id"].as_u64().unwrap());
let paths: Vec<Arc<Node>> = node(&s, g).children().unwrap().iter().filter(|c| !matches!(c.kind, NodeKind::Image(_))).cloned().collect();
assert_eq!(paths.len(), 3);
let (first, middle, last) = (&paths[0], &paths[1], &paths[2]);
s.execute("paint.setFill", &json!({"ids": [last.id.0], "color": hex(first)})).unwrap();
let (to, from) = (middle.visual_bounds().unwrap(), last.visual_bounds().unwrap());
let (dx, dy) = (to.x0 - from.x0, to.y0 - from.y0);
match how {
"beside" => {
// The middle path and the moved top path end and start inside one pixel.
s.execute("object.move", &json!({"ids": [middle.id.0], "dx": -0.5, "dy": 0})).unwrap();
s.execute("object.move", &json!({"ids": [last.id.0], "dx": -0.3, "dy": 0})).unwrap()
}
"move" => s.execute("object.move", &json!({"ids": [last.id.0], "dx": dx, "dy": dy})).unwrap(),
"transform" => {
s.execute("effect.apply", &json!({"ids": [last.id.0], "effect": "distort.transform", "params": {"moveH": dx, "moveV": dy}})).unwrap()
}
_ => {
s.execute("object.move", &json!({"ids": [last.id.0], "dx": 40, "dy": 0})).unwrap();
s.execute("effect.apply", &json!({"ids": [last.id.0], "effect": "stylize.dropShadow", "params": {"x": -70, "y": 0, "blur": 0}}))
.unwrap()
}
};
s.execute("imageTrace.expand", &json!({"id": g.0, "byColor": by_color})).unwrap();
(s, g)
};
for how in ["move", "transform", "shadow", "beside"] {
let (flat, _) = edited(false, how);
let (s, g) = edited(true, how);
assert_eq!(names(&node(&s, g)).len(), 3, "{how}");
assert!(shot(&s) == shot(&flat), "{how}: the edited art changed");
}
// Two tints of one swatch that round to the same percentage are still two colors.
let mut s = session();
s.execute("swatch.new", &json!({"name": "Sky", "color": "#2846c8", "global": true})).unwrap();
add_image(&mut s, &stripes, Affine::IDENTITY);
let limited = json!({"mode": "color", "palette": "limited", "colors": 3});
let g = NodeId(s.execute("imageTrace.make", &json!({"params": limited})).unwrap()["id"].as_u64().unwrap());
let paths: Vec<u64> = node(&s, g).children().unwrap().iter().filter(|c| !matches!(c.kind, NodeKind::Image(_))).map(|c| c.id.0).collect();
assert_eq!(paths.len(), 3);
s.execute("paint.setFill", &json!({"ids": [paths[0]], "swatch": "Sky", "tint": 50.1})).unwrap();
s.execute("paint.setFill", &json!({"ids": [paths[1], paths[2]], "swatch": "Sky", "tint": 50.4})).unwrap();
s.execute("imageTrace.expand", &json!({"id": g.0, "byColor": true})).unwrap();
let groups = node(&s, g).children().unwrap().clone();
assert_eq!(names(&node(&s, g)), ["Sky 50%", "Sky 50%"]);
assert_eq!(groups.iter().map(|c| c.children().unwrap().len()).collect::<Vec<_>>(), [1, 2]);
}
#[test]
fn image_trace_expand_by_color_preserves_live_pathfinder_art() {
let mut s = session();
let art = vectorcraft_trace::Raster::from_fn(100, 60, |x, y| {
if (10..40).contains(&y) && ((10..35).contains(&x) || (65..90).contains(&x)) { [0, 0, 0, 255] } else { [255, 255, 255, 255] }
});
add_image(&mut s, &art, Affine::IDENTITY);
let g = NodeId(s.execute("imageTrace.make", &json!({"params": {"ignoreWhite": true}})).unwrap()["id"].as_u64().unwrap());
let paths: Vec<NodeId> = node(&s, g).children().unwrap().iter().filter(|c| c.path_data().is_some()).map(|c| c.id).collect();
assert_eq!(paths.len(), 2);
s.execute("select.set", &json!({"ids": [paths[1].0]})).unwrap();
s.execute("object.move", &json!({"dx": -45, "dy": 0})).unwrap();
s.execute("effect.apply", &json!({"ids": [g.0], "effect": "pathfinder.subtract"})).unwrap();
let before = render_doc(&s, false);
let before_pixels: Vec<_> = (0..100).flat_map(|x| (0..60).map(move |y| (x, y))).map(|(x, y)| before.pixel(x, y)).collect();
let original = node(&s, g);
let steps = s.doc().unwrap().history.undo.len();
let result = s.execute("imageTrace.expand", &json!({"id": g.0, "byColor": true}));
if let Err(e) = &result {
assert!(e.to_string().contains("Pathfinder"), "{e}");
assert_eq!(node(&s, g), original);
assert_eq!(s.doc().unwrap().history.undo.len(), steps);
}
let after = render_doc(&s, false);
let after_pixels: Vec<_> = (0..100).flat_map(|x| (0..60).map(move |y| (x, y))).map(|(x, y)| after.pixel(x, y)).collect();
let changed = after_pixels.iter().zip(&before_pixels).filter(|(a, b)| a != b).count();
assert_eq!(changed, 0, "byColor changed {changed} live Pathfinder pixels: {result:?}");
assert!(result.is_err(), "byColor must refuse a live Pathfinder operand group");
let error = s.execute("imageTrace.makeAndExpand", &json!({"id": g.0, "params": {"ignoreWhite": true}, "byColor": true})).unwrap_err();
assert!(error.to_string().contains("Pathfinder"), "{error}");
assert_eq!(node(&s, g), original);
assert_eq!(s.doc().unwrap().history.undo.len(), steps);
}
#[test]
fn image_trace_make_and_expand_and_color_presets() {
let mut s = session();
+3
View File
@@ -6127,6 +6127,9 @@ anchor Points: Punts:
CMYK Images Imatges CMYK
Grayscale Images Imatges en escala de grisos
@msg `kind` must be spotColors, colorEffects, rgbImages, cmykImages or grayImages `kind` ha de ser spotColors, colorEffects, rgbImages, cmykImages o grayImages
@msg byColor must be true or false byColor ha de ser true o false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor no pot separar els grups de l'agrupació automàtica: traça sense l'agrupació automàtica
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor no pot tornar a agrupar un traçat amb un efecte Pathfinder actiu: expandeix-lo sense byColor
# Color Effects add-on: Vector Halftone (Effect › Color Effects).
Dot Color from Art Color dels punts de la il·lustració
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5711,6 +5711,9 @@ mark-style Japanese Japonské
@msg too many library panels were opened in this session v této relaci bylo otevřeno příliš mnoho panelů knihoven
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Obraz má {_1} × {_2} pixelů, na jedné straně více než {LARGE_TRACE_SIDE}: vektorizuje se, ale pomaleji. Rychleji se vektorizuje s předvolbou nižší věrnosti nebo menší kopie obrazu.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Obraz byl vektorizován v rozlišení {_1} × {_2} pixelů, zmenšený z {_3} × {_4} (nejvýše {_5} megapixelů).
@msg byColor must be true or false byColor musí být true nebo false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor nerozdělí skupiny z Automatického seskupení: vektorizujte bez Automatického seskupení
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor nemůže přeskupit trasování s živým efektem Pathfinder: rozbalte bez byColor
Snapping Přichytávání
Snap to Tangent Přichytávat k tečně
Snap to Perpendicular Přichytávat ke kolmici
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5290,6 +5290,9 @@ effect Soft Weich
@msg too many library panels were opened in this session in dieser Sitzung wurden zu viele Bibliotheksbedienfelder geöffnet
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Das Bild hat {_1} × {_2} Pixel, mehr als {LARGE_TRACE_SIDE} auf einer Seite: Es wird nachgezeichnet, aber langsamer. Eine Vorgabe mit geringerer Detailtreue oder eine kleinere Kopie des Bildes geht schneller.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Das Bild wurde mit {_1} × {_2} Pixeln nachgezeichnet, verkleinert von {_3} × {_4} (höchstens {_5} Megapixel).
@msg byColor must be true or false byColor muss true oder false sein
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor kann Gruppen aus Automatisch gruppieren nicht aufteilen: ohne Automatisch gruppieren nachzeichnen
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor kann eine Nachzeichnung mit einem aktiven Pathfinder-Effekt nicht neu gruppieren: ohne byColor umwandeln
Snapping Einrasten
Snap to Tangent An Tangente ausrichten
Snap to Perpendicular An Senkrechter ausrichten
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5662,6 +5662,9 @@ effect Points Σημεία
@msg too many library panels were opened in this session άνοιξαν πάρα πολλοί πίνακες βιβλιοθηκών σε αυτή τη συνεδρία
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Η εικόνα είναι {_1} × {_2} εικονοστοιχεία, πάνω από {LARGE_TRACE_SIDE} σε μία πλευρά: ανιχνεύεται, αλλά πιο αργά. Μια προρύθμιση χαμηλότερης πιστότητας ή ένα μικρότερο αντίγραφο της εικόνας ανιχνεύεται γρηγορότερα.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Η εικόνα ανιχνεύτηκε σε {_1} × {_2} εικονοστοιχεία, σμικρυμένη από {_3} × {_4} (το πολύ {_5} megapixel).
@msg byColor must be true or false το byColor πρέπει να είναι true ή false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping το byColor δεν μπορεί να χωρίσει τις ομάδες της Αυτόματης ομαδοποίησης: ανιχνεύστε χωρίς Αυτόματη ομαδοποίηση
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor το byColor δεν μπορεί να αναομαδοποιήσει μια ανίχνευση με ενεργό εφέ Pathfinder: επεκτείνετε χωρίς byColor
Snapping Προσκόλληση
Snap to Tangent Προσκόλληση σε εφαπτομένη
Snap to Perpendicular Προσκόλληση σε κάθετο
Can't render this file because it contains an unexpected character in line 168 and column 5.
+3
View File
@@ -5652,6 +5652,9 @@ mark-style Japanese Japonesas
@msg too many library panels were opened in this session se abrieron demasiados paneles de biblioteca en esta sesión
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. La imagen tiene {_1} × {_2} píxeles, más de {LARGE_TRACE_SIDE} en un lado: se calcará, pero más despacio. Un ajuste preestablecido de menor fidelidad o una copia más pequeña de la imagen se calcan más rápido.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). La imagen se calcó a {_1} × {_2} píxeles, reducida desde {_3} × {_4} ({_5} megapíxeles como máximo).
@msg byColor must be true or false byColor debe ser true o false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor no puede dividir los grupos de Agrupación automática: calque sin Agrupación automática
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor no puede reagrupar un calco con un efecto Pathfinder activo: expanda sin byColor
Snapping Ajuste
Snap to Tangent Ajustar a tangente
Snap to Perpendicular Ajustar a perpendicular
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5674,6 +5674,9 @@ mark-style Japanese Japanilainen
@msg too many library panels were opened in this session tässä istunnossa on avattu liian monta kirjastopaneelia
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Kuva on {_1} × {_2} kuvapistettä, yhdeltä sivultaan yli {LARGE_TRACE_SIDE}: se jäljitetään, mutta hitaammin. Pienemmän tarkkuuden esiasetus tai kuvan pienempi kopio jäljittyy nopeammin.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Kuva jäljitettiin kokoon {_1} × {_2} kuvapistettä pienennettynä koosta {_3} × {_4} (enintään {_5} megapikseliä).
@msg byColor must be true or false byColor-arvon on oltava true tai false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor ei voi jakaa Automaattisen ryhmittelyn ryhmiä: jäljitä ilman Automaattista ryhmittelyä
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor ei voi ryhmitellä uudelleen jäljitystä, jossa on aktiivinen Pathfinder-tehoste: laajenna ilman byColor-asetusta
Snapping Tartunta
Snap to Tangent Kohdista tangenttiin
Snap to Perpendicular Kohdista kohtisuoraan
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5706,6 +5706,9 @@ theme Appearance Apparence
@msg too many library panels were opened in this session trop de panneaux de bibliothèque ont été ouverts dans cette session
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. L’image fait {_1} × {_2} pixels, plus de {LARGE_TRACE_SIDE} sur un côté : elle est vectorisée, mais plus lentement. Un paramètre prédéfini moins fidèle ou une copie plus petite de l’image se vectorise plus vite.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). L’image a été vectorisée à {_1} × {_2} pixels, réduite de {_3} × {_4} ({_5} mégapixels au plus).
@msg byColor must be true or false byColor doit être true ou false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor ne peut pas scinder les groupes du Groupement automatique : vectorisez sans Groupement automatique
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor ne peut pas regrouper un tracé avec un effet Pathfinder actif : développez sans byColor
Snapping Magnétisme
Snap to Tangent Magnétisme de la tangente
Snap to Perpendicular Magnétisme de la perpendiculaire
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5694,6 +5694,9 @@ imageTrace Outline Körvonal
@msg too many library panels were opened in this session ebben a munkamenetben túl sok könyvtárpanel nyílt meg
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. A kép {_1} × {_2} képpont, egyik oldalán több mint {LARGE_TRACE_SIDE}: a vektorizálás megtörténik, de lassabban. Kisebb hűségű előbeállítással vagy a kép kisebb másolatával gyorsabb.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). A kép vektorizálása {_1} × {_2} képponton történt, {_3} × {_4} méretről kicsinyítve (legfeljebb {_5} megapixel).
@msg byColor must be true or false a byColor értéke true vagy false lehet
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping a byColor nem bontja fel az Automatikus csoportosítás csoportjait: vektorizáljon Automatikus csoportosítás nélkül
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor a byColor nem csoportosíthatja át az élő Pathfinder-effektussal rendelkező vektorizálást: bővítse ki byColor nélkül
Snapping Illesztés
Snap to Tangent Illesztés érintőre
Snap to Perpendicular Illesztés merőlegesre
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5324,6 +5324,9 @@ lighting Light Luce
@msg too many library panels were opened in this session in questa sessione sono stati aperti troppi pannelli libreria
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. L’immagine è di {_1} × {_2} pixel, più di {LARGE_TRACE_SIDE} su un lato: viene vettorializzata, ma più lentamente. Un predefinito a fedeltà più bassa o una copia più piccola dell’immagine è più veloce.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). L’immagine è stata vettorializzata a {_1} × {_2} pixel, ridotta da {_3} × {_4} (al massimo {_5} megapixel).
@msg byColor must be true or false byColor deve essere true o false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor non può dividere i gruppi di Raggruppamento automatico: vettorializza senza Raggruppamento automatico
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor non può raggruppare di nuovo una traccia con un effetto Pathfinder attivo: espandi senza byColor
Snapping Aggancio
Snap to Tangent Aggancia alla tangente
Snap to Perpendicular Aggancia alla perpendicolare
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5367,6 +5367,9 @@ lighting Light ライト
@msg too many library panels were opened in this session このセッションで開いたライブラリパネルが多すぎます
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. 画像は {_1} × {_2} ピクセルで、一辺が {LARGE_TRACE_SIDE} を超えています。トレースはできますが、時間がかかります。忠実度の低いプリセットや画像の小さいコピーを使うと速くなります。
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). 画像は {_3} × {_4} から縮小した {_1} × {_2} ピクセルでトレースされました (最大 {_5} メガピクセル)。
@msg byColor must be true or false byColor は true または false にしてください
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor は自動グループ化のグループを分割できません: 自動グループ化なしでトレースしてください
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor はライブ Pathfinder 効果のあるトレースを再グループ化できません: byColor なしで拡張してください
Snapping スナップ
Snap to Tangent 接線にスナップ
Snap to Perpendicular 垂線にスナップ
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5557,6 +5557,9 @@ imageTrace Outline კონტური
@msg `colorGroup` needs `mode: "color"` `colorGroup`-ს სჭირდება `mode: "color"`
@msg no color group `{name}` in the document (see swatch.list) დოკუმენტში ფერების ჯგუფი `{name}` არ არის (იხ. swatch.list)
@msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. ბრტყელი ლოგოს რეჟიმში გამოსახულება ვექტორიზდა {_1} × {_2} პიქსელზე, საშუალოს გამოთვლით შემცირდა {_3} × {_4}-დან.
@msg byColor must be true or false byColor უნდა იყოს true ან false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor ვერ დაყოფს ავტომატური დაჯგუფების ჯგუფებს: ვექტორიზება გააკეთეთ ავტომატური დაჯგუფების გარეშე
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor ვერ გადააჯგუფებს ტრასირებას აქტიური Pathfinder ეფექტით: გააფართოვეთ byColor-ის გარეშე
@msg `{given}` isn't a library panel (see window.swatchLibrary) `{given}` ბიბლიოთეკის პანელი არ არის (იხ. window.swatchLibrary)
@msg `{key}` must be a #rrggbb color `{key}` უნდა იყოს #rrggbb ფერი
@msg a key object sits on that point ამ წერტილზე საკვანძო ობიექტი მდებარეობს
Can't render this file because it is too large.
+3
View File
@@ -5285,6 +5285,9 @@ lighting Light 광원
@msg too many library panels were opened in this session 이 세션에서 라이브러리 패널을 너무 많이 열었습니다
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. 이미지가 {_1} × {_2}픽셀로 한 변이 {LARGE_TRACE_SIDE}을 넘습니다. 추적은 되지만 더 느립니다. 충실도가 낮은 사전 설정이나 이미지의 작은 사본을 사용하면 더 빠릅니다.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). 이미지를 {_3} × {_4}에서 축소한 {_1} × {_2}픽셀로 추적했습니다(최대 {_5}메가픽셀).
@msg byColor must be true or false byColor는 true 또는 false여야 합니다
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor는 자동 그룹화 그룹을 나눌 수 없습니다. 자동 그룹화 없이 추적하세요
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor는 라이브 Pathfinder 효과가 있는 추적을 다시 그룹화할 수 없습니다. byColor 없이 확장하세요
Snapping 스냅
Snap to Tangent 접선에 스냅
Snap to Perpendicular 수직선에 스냅
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5564,6 +5564,9 @@ mark-style Japanese Japońskie
@msg too many library panels were opened in this session w tej sesji otwarto zbyt wiele paneli bibliotek
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Obraz ma {_1} × {_2} pikseli, ponad {LARGE_TRACE_SIDE} na jednym boku: zostanie obrysowany, ale wolniej. Ustawienie wstępne o niższej wierności lub mniejsza kopia obrazu obrysuje się szybciej.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Obraz obrysowano w rozdzielczości {_1} × {_2} pikseli, po zmniejszeniu z {_3} × {_4} (najwyżej {_5} megapikseli).
@msg byColor must be true or false byColor musi mieć wartość true lub false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor nie może podzielić grup Automatycznego grupowania: obrysuj bez Automatycznego grupowania
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor nie może ponownie zgrupować obrysu z aktywnym efektem Pathfinder: rozwiń bez byColor
Snapping Przyciąganie
Snap to Tangent Przyciągaj do stycznych
Snap to Perpendicular Przyciągaj do prostopadłych
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5626,6 +5626,9 @@ mark-style Japanese Japonesas
@msg too many library panels were opened in this session foram abertos painéis de biblioteca demais nesta sessão
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. A imagem tem {_1} × {_2} pixels, mais de {LARGE_TRACE_SIDE} em um lado: ela é traçada, mas mais devagar. Uma predefinição de menor fidelidade ou uma cópia menor da imagem traça mais rápido.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). A imagem foi traçada em {_1} × {_2} pixels, reduzida de {_3} × {_4} (no máximo {_5} megapixels).
@msg byColor must be true or false byColor deve ser true ou false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor não pode dividir os grupos de Agrupamento Automático: trace sem Agrupamento Automático
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor não pode reagrupar um traçado com um efeito Pathfinder ativo: expanda sem byColor
Snapping Ajuste
Snap to Tangent Ajustar à Tangente
Snap to Perpendicular Ajustar à Perpendicular
Can't render this file because it contains an unexpected character in line 6 and column 47.
+3
View File
@@ -5319,6 +5319,9 @@ lighting Light Свет
@msg too many library panels were opened in this session в этом сеансе открыто слишком много панелей библиотек
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Изображение {_1} × {_2} пикселей, больше {LARGE_TRACE_SIDE} по одной стороне: трассировка выполняется, но медленнее. Стиль с меньшей точностью или уменьшенная копия изображения трассируются быстрее.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Изображение трассировано в размере {_1} × {_2} пикселей, уменьшенное с {_3} × {_4} (не более {_5} мегапикселей).
@msg byColor must be true or false byColor должен иметь значение true или false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor не может разделить группы автогруппировки: трассируйте без автогруппировки
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor не может перегруппировать трассировку с действующим эффектом Pathfinder: разверните без byColor
Snapping Привязка
Snap to Tangent Привязать к касательной
Snap to Perpendicular Привязать к перпендикуляру
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5733,6 +5733,9 @@ swatch Yellow Green Gulgrön
@msg “{name}” is not a file ”{name}” är inte en fil
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Bilden är {_1} × {_2} pixlar, mer än {LARGE_TRACE_SIDE} på en sida: den vektoriseras, men långsammare. En förinställning med lägre återgivning eller en mindre kopia av bilden går snabbare.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Bilden vektoriserades vid {_1} × {_2} pixlar, nedskalad från {_3} × {_4} (högst {_5} megapixlar).
@msg byColor must be true or false byColor måste vara true eller false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor kan inte dela upp grupper från Automatisk gruppering: vektorisera utan Automatisk gruppering
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor kan inte gruppera om en kalkering med en aktiv Pathfinder-effekt: expandera utan byColor
Snapping Fäst
Snap to Tangent Fäst mot tangent
Snap to Perpendicular Fäst mot vinkelrät
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -5712,6 +5712,9 @@ theme Appearance Görünüm
@msg library panel `{given}` isn't missing its library `{given}` kitaplık panelinin kitaplığı eksik değil
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Görüntü {_1} × {_2} piksel, bir kenarı {LARGE_TRACE_SIDE} değerinden büyük: izlenir, ancak daha yavaş. Daha düşük doğruluklu bir ön ayar veya görüntünün daha küçük bir kopyası daha hızlı izlenir.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Görüntü {_3} × {_4} boyutundan küçültülerek {_1} × {_2} pikselde izlendi (en fazla {_5} megapiksel).
@msg byColor must be true or false byColor true veya false olmalıdır
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor, Otomatik Gruplama gruplarını bölemez: Otomatik Gruplama olmadan izleyin
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor, canlı Pathfinder efekti olan bir izlemeyi yeniden gruplayamaz: byColor olmadan genişletin
Gap Length: Boşluk Uzunluğu:
@msg `colorGroup` needs `library: "document"` `colorGroup` için `library: "document"` gerekir
@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` için `palette: "documentLibrary"` gerekir
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -5304,6 +5304,9 @@ lighting Light Світло
@msg too many library panels were opened in this session у цьому сеансі відкрито забагато панелей бібліотек
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Зображення {_1} × {_2} пікселів, понад {LARGE_TRACE_SIDE} з одного боку: трасування виконується, але повільніше. Стиль із нижчою точністю або менша копія зображення трасуються швидше.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Зображення трасовано в розмірі {_1} × {_2} пікселів, зменшене з {_3} × {_4} (щонайбільше {_5} мегапікселів).
@msg byColor must be true or false byColor має мати значення true або false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor не може розділити групи автогрупування: трасуйте без автогрупування
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor не може перегрупувати трасування з активним ефектом Pathfinder: розгорніть без byColor
Snapping Прив’язка
Snap to Tangent Прив’язати до дотичної
Snap to Perpendicular Прив’язати до перпендикуляра
Can't render this file because it contains an unexpected character in line 182 and column 5.
+3
View File
@@ -5305,6 +5305,9 @@ theme Appearance Giao diện
@msg too many library panels were opened in this session đã mở quá nhiều bảng thư viện trong phiên này
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. Ảnh có kích thước {_1} × {_2} điểm ảnh, hơn {LARGE_TRACE_SIDE} ở một cạnh: ảnh vẫn được dò, nhưng chậm hơn. Một thiết lập sẵn có độ trung thực thấp hơn hoặc một bản sao nhỏ hơn của ảnh sẽ dò nhanh hơn.
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). Ảnh đã được dò ở {_1} × {_2} điểm ảnh, thu nhỏ từ {_3} × {_4} (tối đa {_5} megapixel).
@msg byColor must be true or false byColor phải là true hoặc false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor không thể tách các nhóm của Tự động nhóm: hãy dò mà không bật Tự động nhóm
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor không thể nhóm lại bản dò có hiệu ứng Pathfinder đang hoạt động: mở rộng mà không dùng byColor
Snapping Hút
Snap to Tangent Hút vào tiếp tuyến
Snap to Perpendicular Hút vào đường vuông góc
Can't render this file because it contains an unexpected character in line 204 and column 5.
+3
View File
@@ -5542,6 +5542,9 @@ lighting Light 光源
@msg too many library panels were opened in this session 本次会话中打开的库面板过多
@msg The image is {_1} × {_2} pixels, more than {LARGE_TRACE_SIDE} on a side: it is traced, but more slowly. A lower-fidelity preset or a smaller copy of the image traces faster. 图像为 {_1} × {_2} 像素,某一边超过 {LARGE_TRACE_SIDE}:会进行描摹,但速度较慢。使用保真度较低的预设或图像的较小副本会更快。
@msg The image was traced at {_1} × {_2} pixels, scaled down from {_3} × {_4} ({_5} megapixels at most). 图像已按 {_1} × {_2} 像素描摹,由 {_3} × {_4} 缩小而来(最多 {_5} 百万像素)。
@msg byColor must be true or false byColor 必须是 true 或 false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor 无法拆分自动编组生成的编组:请在不使用自动编组的情况下描摹
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor 无法重新编组带有实时 Pathfinder 效果的描摹:请不使用 byColor 进行扩展
Snapping 对齐
Snap to Tangent 对齐切线
Snap to Perpendicular 对齐垂线
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -2553,6 +2553,9 @@ file-filter Templates 範本
@msg `colorGroup` needs `mode: "color"` `colorGroup` 需要 `mode: "color"`
@msg no color group `{name}` in the document (see swatch.list) 文件中沒有顏色群組 `{name}` (請參閱 swatch.list)
@msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. 「扁平標誌」模式已以 {_1} × {_2} 像素描圖,由 {_3} × {_4} 平均縮小而來。
@msg byColor must be true or false byColor 必須是 true 或 false
@msg byColor can't split Auto Grouping groups: trace without Auto Grouping byColor 無法拆分自動群組產生的群組:請不使用自動群組來描圖
@msg byColor can't regroup a trace with a live Pathfinder effect: expand without byColor byColor 無法重新編組帶有即時 Pathfinder 效果的描圖:請不使用 byColor 進行擴充
# Links and Attributes: files and URLs handed to other apps.
@msg Edit Original opens image and document files only; “{name}” is not one 「編輯原稿」只能開啟影像與文件檔案;「{name}」不屬於這兩類
@msg only http, https and mailto URLs open in the browser, not “{url}” 只有 http、https 和 mailto URL 會在瀏覽器中開啟,「{url}」不會
Can't render this file because it contains an unexpected character in line 6 and column 47.
+11
View File
@@ -2015,6 +2015,17 @@ already walks Image Trace. Short agent entry points:
- Image Trace: `imageTrace.presets`, `imageTrace.make {id?, preset?, params?, view?}` (or `imageTrace.makeAndExpand`),
`imageTrace.setView`, `imageTrace.expand`, `imageTrace.release`. A key inside `params` that is not one of the trace
settings, such as `colours`, is an error that names it and the nearest setting (`colors`), as for a command's own keys.
With `byColor: true`, `imageTrace.expand` and `imageTrace.makeAndExpand` put each color's paths in a group of their
own, named by the global swatch the color links to (a tint with its percentage, as `Sky 50%`) or as `#rrggbb`;
gradient fills share a group named `Gradients`. The groups stack in the order of the lowest path in each, and each
keeps its paths' order. For either Expand command, a path that would move under a path of another
color whose painted bounds, effects and shadows included, come within one document point of it starts another group
of its color instead of joining an earlier one. Paths whose painted bounds come within one document point of an
intervening color stay in separate groups to preserve their stacking order; at small view or raster-export scales,
regrouped paths farther apart can still share antialiased pixels, so edge pixels may differ. In a Flat Logo trace of a
black ring around a white disc around a black dot, black gets two groups. `layer.releaseToLayers` on the expanded group then
gives each group a sublayer. A trace made with `autoGrouping` is refused. Expanding by color also refuses a trace
with a live Pathfinder effect.
`imageTrace.presets.save {name?, newName?, preset?, enhanced?, params?}` saves tracing settings as a preset kept with
the preferences: `params` over `preset`'s (default: the saved preset `name`, else Default), checked as `imageTrace.make`
checks them; without `name` it is a new "Tracing Preset N", and `newName` renames a saved preset (the reply's