mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 14:41:19 +00:00
Image Trace: Expand can group the traced paths by color
Squashed from #4058 onto main.
This commit is contained in:
@@ -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
|
||||
),
|
||||
|
||||
@@ -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(¶ms).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};
|
||||
|
||||
@@ -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", ¶ms).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();
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user