From df716c31189d172097dea5aa2c043bc3b9df6e78 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 26 Oct 2026 09:25:42 +0700 Subject: [PATCH] Image Trace: Document Library traces with a color group from the Swatches panel Squashed from #3975 onto main. --- crates/engine/src/cmd/buildcmds.rs | 43 ++++++- crates/engine/src/tests_build.rs | 65 ++++++++++ crates/trace/src/lib.rs | 7 +- crates/ui-egui/src/i18n/cs.tsv | 4 + crates/ui-egui/src/i18n/de.tsv | 4 + crates/ui-egui/src/i18n/el.tsv | 4 + crates/ui-egui/src/i18n/es.tsv | 4 + crates/ui-egui/src/i18n/fi.tsv | 4 + crates/ui-egui/src/i18n/fr.tsv | 4 + crates/ui-egui/src/i18n/hu.tsv | 4 + crates/ui-egui/src/i18n/it.tsv | 4 + crates/ui-egui/src/i18n/ja.tsv | 4 + crates/ui-egui/src/i18n/ka.tsv | 4 + crates/ui-egui/src/i18n/ko.tsv | 4 + crates/ui-egui/src/i18n/pl.tsv | 4 + crates/ui-egui/src/i18n/pt-br.tsv | 4 + crates/ui-egui/src/i18n/ru.tsv | 4 + crates/ui-egui/src/i18n/sv.tsv | 4 + crates/ui-egui/src/i18n/tr.tsv | 4 + crates/ui-egui/src/i18n/uk.tsv | 4 + crates/ui-egui/src/i18n/vi.tsv | 4 + crates/ui-egui/src/i18n/zh-hans.tsv | 4 + crates/ui-egui/src/i18n/zh-hant.tsv | 4 + crates/ui-egui/src/panels/image_trace.rs | 144 +++++++++++++++++++++++ docs/image-trace-adjustments.md | 15 ++- docs/target-app-parity.md | 1 + 26 files changed, 347 insertions(+), 8 deletions(-) diff --git a/crates/engine/src/cmd/buildcmds.rs b/crates/engine/src/cmd/buildcmds.rs index 413f433b8..03166a5b0 100644 --- a/crates/engine/src/cmd/buildcmds.rs +++ b/crates/engine/src/cmd/buildcmds.rs @@ -129,7 +129,7 @@ pub fn specs() -> Vec { "Make", ["Object", "Image Trace"], None, - "{id?: image or Image Trace group (default: selection), preset?: name (imageTrace.presets), enhanced?: false (the preset's enhanced version: with Auto Grouping, and with Shapes in all but High Fidelity Photo; Flat Logo has none and is traced as it is), params?: {mode: \"blackAndWhite\"|\"grayscale\"|\"color\"|\"logo\", threshold: 0-255, palette: \"limited\"|\"fullTone\"|\"automatic\"|\"documentLibrary\" (color mode; limited: the `colors` best colours, fullTone: as many as the tones need, more with colorDetail, automatic: flat art with its own colours, else fullTone, documentLibrary: at most `colors` of the library's colours, as they are), colors: 2-256, colorDetail: 0-100 (fullTone, automatic), library: \"document\" (the document's swatches) or a swatch library id or name (swatch.library.list; documentLibrary), paths: 0-100, corners: 0-100, noise: px, method: \"abutting\"|\"overlapping\", ignoreWhite (leave out white areas), ignoreColor?: \"#rrggbb\" (with ignoreWhite: the colour left out instead of white), snapCurvesToLines, shapes: false (filled outlines that are a circle, an ellipse, a rectangle or a regular polygon come out as that live shape), autoGrouping: false (each path that has others inside it is grouped with them), gradients: false (grayscale and color: an area whose colour changes evenly along one direction is one path with a linear gradient fill), fills: true, strokes: false (Create: lines up to strokeWidth wide become stroked centre lines; without fills, wider areas become stroked outlines), strokeWidth: px (10), logoColors?: [\"#rrggbb\"…] (mode logo: flat-colour logos only; colours found automatically or named here, white is always its own colour, at most ten; noise = areas under noise/10 px ignored; edges to a fraction of a pixel, sharp corners restored, lines and circles snapped; enclosed transparent areas are painted white unless ignoreWhite makes them holes; fills/strokes are ignored; gradients and photos are refused)}, view?: as imageTrace.setView (default: the traced object's own, else tracingResult), ids?: [id…] (instead of id)} → {id, paths, anchors, colors, warnings: [text…] (the image is over 1500 px on a side, so it traces more slowly; or it was traced scaled down; Flat Logo names only the size it traced a larger image at)}", + "{id?: image or Image Trace group (default: selection), preset?: name (imageTrace.presets), enhanced?: false (the preset's enhanced version: with Auto Grouping, and with Shapes in all but High Fidelity Photo; Flat Logo has none and is traced as it is), params?: {mode: \"blackAndWhite\"|\"grayscale\"|\"color\"|\"logo\", threshold: 0-255, palette: \"limited\"|\"fullTone\"|\"automatic\"|\"documentLibrary\" (color mode; limited: the `colors` best colours, fullTone: as many as the tones need, more with colorDetail, automatic: flat art with its own colours, else fullTone, documentLibrary: at most `colors` of the library's colours, as they are), colors: 2-256, colorDetail: 0-100 (fullTone, automatic), library: \"document\" (the document's swatches) or a swatch library id or name (swatch.library.list; documentLibrary), colorGroup?: name (color mode, documentLibrary with library \"document\": the colors of that document color group instead of every swatch; \"\" is every swatch), paths: 0-100, corners: 0-100, noise: px, method: \"abutting\"|\"overlapping\", ignoreWhite (leave out white areas), ignoreColor?: \"#rrggbb\" (with ignoreWhite: the colour left out instead of white), snapCurvesToLines, shapes: false (filled outlines that are a circle, an ellipse, a rectangle or a regular polygon come out as that live shape), autoGrouping: false (each path that has others inside it is grouped with them), gradients: false (grayscale and color: an area whose colour changes evenly along one direction is one path with a linear gradient fill), fills: true, strokes: false (Create: lines up to strokeWidth wide become stroked centre lines; without fills, wider areas become stroked outlines), strokeWidth: px (10), logoColors?: [\"#rrggbb\"…] (mode logo: flat-colour logos only; colours found automatically or named here, white is always its own colour, at most ten; noise = areas under noise/10 px ignored; edges to a fraction of a pixel, sharp corners restored, lines and circles snapped; enclosed transparent areas are painted white unless ignoreWhite makes them holes; fills/strokes are ignored; gradients and photos are refused)}, view?: as imageTrace.setView (default: the traced object's own, else tracingResult), ids?: [id…] (instead of id)} → {id, paths, anchors, colors, warnings: [text…] (the image is over 1500 px on a side, so it traces more slowly; or it was traced scaled down; Flat Logo names only the size it traced a larger image at)}", has_selection_or_ids, |s, p| trace_make(s, p, false) ), @@ -1000,18 +1000,49 @@ fn trace_params(c: &str, p: &Value) -> Result { if !(params.stroke_width.is_finite() && params.stroke_width > 0.0) { return Err(bad(c, "strokeWidth must be a positive number of pixels")); } + // A color group is refused unless the trace uses it: Color mode with Document Library and the + // document's swatches. + if params.color_group.as_deref().is_some_and(|g| !g.is_empty()) { + if params.palette != tr::Palette::DocumentLibrary { + return Err(bad(c, "`colorGroup` needs `palette: \"documentLibrary\"`")); + } + if params.library != super::swatchlib::DOCUMENT_SWATCHES { + return Err(bad(c, "`colorGroup` needs `library: \"document\"`")); + } + if params.mode != tr::Mode::Color { + return Err(bad(c, "`colorGroup` needs `mode: \"color\"`")); + } + } Ok(params) } -/// Document Library in Color mode: look the library's colours up into `params.swatches` → each -/// one as traced (sRGB) with the colour to fill with. Errors name command `c`. +/// Document Library with `colorGroup`: the solid colors of the active document's color group +/// `name` (so named exactly, else ignoring case). Errors name command `c`. +/// ([`trace_params`] has checked that the library is the document's swatches.) +fn trace_group_colors(c: &str, s: &Session, name: &str) -> Result> { + let groups = &s.doc()?.doc.swatch_groups; + let lower = name.to_lowercase(); + let group = groups + .iter() + .find(|g| g.name == name) + .or_else(|| groups.iter().find(|g| g.name.to_lowercase() == lower)) + .ok_or_else(|| bad(c, format!("no color group `{name}` in the document (see swatch.list)")))?; + Ok(group.swatches.iter().filter_map(|w| w.paint.color()).collect()) +} + +/// Document Library in Color mode: look the library's colors (or its color group's) up into +/// `params.swatches` → each one as traced (sRGB) with the color to fill with. Errors name command `c`. fn trace_library(c: &str, s: &Session, params: &mut tr::TraceParams) -> Result> { if params.mode != tr::Mode::Color || params.palette != tr::Palette::DocumentLibrary { return Ok(vec![]); } - let key = params.library.as_str(); - let colors = super::swatchlib::library_colors(s, key) - .ok_or_else(|| bad(c, format!("no swatch library `{key}` (see swatch.library.list; \"document\": the document's swatches)")))?; + let group = params.color_group.as_deref().filter(|g| !g.is_empty()); + let key = group.unwrap_or(params.library.as_str()); + let colors = match group { + Some(name) => trace_group_colors(c, s, name)?, + None => super::swatchlib::library_colors(s, key) + .ok_or_else(|| bad(c, format!("no swatch library `{key}` (see swatch.library.list; \"document\": the document's swatches)")))?, + }; if colors.is_empty() { return Err(bad(c, format!("`{key}` has no colours"))); } diff --git a/crates/engine/src/tests_build.rs b/crates/engine/src/tests_build.rs index 606be610e..fb16cd5e9 100644 --- a/crates/engine/src/tests_build.rs +++ b/crates/engine/src/tests_build.rs @@ -2802,6 +2802,71 @@ fn image_trace_palettes_and_swatch_libraries() { assert!(bad(json!({"palette": "rainbow"}))); } +#[test] +fn image_trace_document_library_uses_a_color_group() { + let mut s = session(); + 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) => [50, 60, 200, 255], + }); + add_image(&mut s, &r, Affine::IDENTITY); + // Ungrouped swatches of the image's own colors, and a group of colors a little off them. + s.execute("swatch.new", &json!({"colors": ["#c82828", "#28a03c", "#323cc8"]})).unwrap(); + s.execute("swatch.newGroup", &json!({"name": "Brand", "colors": ["#b43c3c", "#3c8c50", "#4650b4"]})).unwrap(); + s.execute("swatch.newGroup", &json!({"name": "Empty"})).unwrap(); + s.execute("swatch.newGroup", &json!({"name": "Été", "colors": ["#b43c3c", "#3c8c50", "#4650b4"]})).unwrap(); + let hex = |c: &Color| { + let [r, g, b, _] = c.to_rgba8(1.0); + format!("#{r:02x}{g:02x}{b:02x}") + }; + let trace = |s: &mut Session, extra: Value| { + let mut params = json!({"mode": "color", "palette": "documentLibrary", "library": "document", "colors": 3}); + params.as_object_mut().unwrap().extend(extra.as_object().unwrap().clone()); + s.execute("imageTrace.makeAndExpand", &json!({"params": params})) + }; + let fills = |s: &Session, id: &Value| -> Vec { + let g = node(s, NodeId(id.as_u64().unwrap())); + let mut v: Vec = g.children().unwrap().iter().filter_map(|n| n.appearance.fill_paint().color()).map(|c| hex(&c)).collect(); + v.sort(); + v.dedup(); + v + }; + // Every swatch: the image's own colors win. + let r = trace(&mut s, json!({})).unwrap(); + assert_eq!(fills(&s, &r["id"]), ["#28a03c", "#323cc8", "#c82828"]); + s.execute("edit.undo", &json!({})).unwrap(); + // The group: its colors only, and its name matches whatever its case. + for name in ["Brand", "brand", "Été", "été", "ÉTÉ"] { + let r = trace(&mut s, json!({"colorGroup": name})).unwrap(); + assert_eq!(fills(&s, &r["id"]), ["#3c8c50", "#4650b4", "#b43c3c"], "{name}"); + s.execute("edit.undo", &json!({})).unwrap(); + } + // An empty name is every swatch. + let r = trace(&mut s, json!({"colorGroup": ""})).unwrap(); + assert_eq!(fills(&s, &r["id"]), ["#28a03c", "#323cc8", "#c82828"]); + s.execute("edit.undo", &json!({})).unwrap(); + let err = |s: &mut Session, extra: Value| trace(s, extra).unwrap_err().to_string(); + assert!(err(&mut s, json!({"colorGroup": "Nope"})).contains("no color group `Nope`")); + assert!(err(&mut s, json!({"colorGroup": "Empty"})).contains("`Empty` has no colours")); + assert!(err(&mut s, json!({"colorGroup": "Brand", "library": "web-safe-216"})).contains("needs `library: \"document\"`")); + // Every other palette refuses a group too, in any mode; an empty name is no group. + for palette in ["limited", "fullTone", "automatic"] { + let e = err(&mut s, json!({"colorGroup": "Brand", "palette": palette})); + assert!(e.contains("`colorGroup` needs `palette: \"documentLibrary\"`"), "{palette}: {e}"); + } + let e = err(&mut s, json!({"colorGroup": "Brand", "mode": "grayscale", "palette": "limited"})); + assert!(e.contains("needs `palette: \"documentLibrary\"`"), "{e}"); + // Document Library uses the group in Color mode only. + for mode in ["blackAndWhite", "grayscale", "logo"] { + let e = err(&mut s, json!({"colorGroup": "Brand", "mode": mode})); + assert!(e.contains("`colorGroup` needs `mode: \"color\"`"), "{mode}: {e}"); + } + s.execute("imageTrace.makeAndExpand", &json!({"params": {"mode": "color", "palette": "limited", "colors": 3, "colorGroup": ""}})).unwrap(); + s.execute("edit.undo", &json!({})).unwrap(); + trace(&mut s, json!({"mode": "grayscale", "colorGroup": ""})).unwrap(); +} + #[test] fn image_trace_create_strokes_makes_stroked_centre_lines() { let mut s = session(); diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs index a95ef067e..88e882f98 100644 --- a/crates/trace/src/lib.rs +++ b/crates/trace/src/lib.rs @@ -281,8 +281,12 @@ pub struct TraceParams { /// Document Library: the swatch library, `"document"` (the document's swatches) or a library id /// or name. The caller looks its colours up into `swatches`. pub library: String, + /// Document Library with the document's swatches: the name of the color group whose colors are + /// used. `None` (or empty) uses every swatch. `imageTrace.make` refuses a name with any other + /// palette, library or mode. + pub color_group: Option, /// Document Library: the library's colours (sRGB). Not saved: they are looked up from - /// `library` at every trace. + /// `library` (and `color_group`) at every trace. #[serde(skip)] pub swatches: Vec<[u8; 3]>, /// Paths (fidelity) 0–100: higher follows the pixels more tightly. @@ -333,6 +337,7 @@ impl Default for TraceParams { colors: 6, color_detail: 50.0, library: "document".into(), + color_group: None, swatches: vec![], paths: 50.0, corners: 75.0, diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index ca98a6243..4b22a988f 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -5399,6 +5399,10 @@ mark-style Japanese Japonské Snap to Perpendicular Přichytávat ke kolmici @msg unknown snap option `{key}` (use {options}) neznámá možnost přichytávání `{key}` (použijte {options}) Gap Length: Délka mezery: +@msg `colorGroup` needs `library: "document"` `colorGroup` vyžaduje `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` vyžaduje `palette: "documentLibrary"` +@msg `colorGroup` needs `mode: "color"` `colorGroup` vyžaduje `mode: "color"` +@msg no color group `{name}` in the document (see swatch.list) skupina barev `{name}` v dokumentu neexistuje (viz swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. Režim Ploché logo vektorizoval obraz v rozlišení {_1} × {_2} pixelů, zmenšený průměrováním z {_3} × {_4}. Artboard {n} of {total} Pracovní plocha {n} z {total} File {n} of {total} Soubor {n} z {total} diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index ea3180ac2..46bc42641 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4978,6 +4978,10 @@ effect Soft Weich Snap to Perpendicular An Senkrechter ausrichten @msg unknown snap option `{key}` (use {options}) unbekannte Einrastoption `{key}` (möglich: {options}) Gap Length: Lückenlänge: +@msg `colorGroup` needs `library: "document"` `colorGroup` erfordert `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` erfordert `palette: "documentLibrary"` +@msg `colorGroup` needs `mode: "color"` `colorGroup` erfordert `mode: "color"` +@msg no color group `{name}` in the document (see swatch.list) keine Farbgruppe `{name}` im Dokument (siehe swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. Der Modus „Flaches Logo“ hat das Bild mit {_1} × {_2} Pixeln nachgezeichnet, durch Mittelung verkleinert von {_3} × {_4}. Artboard {n} of {total} Zeichenfläche {n} von {total} File {n} of {total} Datei {n} von {total} diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index f56d01c08..8a20b9b2d 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5351,6 +5351,10 @@ effect Points Σημεία Snap to Perpendicular Προσκόλληση σε κάθετο @msg unknown snap option `{key}` (use {options}) άγνωστη επιλογή προσκόλλησης `{key}` (χρησιμοποιήστε {options}) Gap Length: Μήκος κενού: +@msg `colorGroup` needs `library: "document"` το `colorGroup` απαιτεί `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` το `colorGroup` απαιτεί `palette: "documentLibrary"` +@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}. Artboard {n} of {total} Πίνακας σχεδίασης {n} από {total} File {n} of {total} Αρχείο {n} από {total} diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index afeefeca6..01f8091a0 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5341,6 +5341,10 @@ mark-style Japanese Japonesas Snap to Perpendicular Ajustar a perpendicular @msg unknown snap option `{key}` (use {options}) opción de ajuste desconocida `{key}` (use {options}) Gap Length: Longitud del hueco: +@msg `colorGroup` needs `library: "document"` `colorGroup` requiere `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` requiere `palette: "documentLibrary"` +@msg `colorGroup` needs `mode: "color"` `colorGroup` requiere `mode: "color"` +@msg no color group `{name}` in the document (see swatch.list) no existe el grupo de colores `{name}` en el documento (consulta swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. El modo Logotipo plano calcó la imagen a {_1} × {_2} píxeles, reducida promediando desde {_3} × {_4}. Artboard {n} of {total} Mesa de trabajo {n} de {total} File {n} of {total} Archivo {n} de {total} diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 37712878e..4f9592be7 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -5363,6 +5363,10 @@ mark-style Japanese Japanilainen Snap to Perpendicular Kohdista kohtisuoraan @msg unknown snap option `{key}` (use {options}) tuntematon tartuntavalinta `{key}` (käytä: {options}) Gap Length: Aukon pituus: +@msg `colorGroup` needs `library: "document"` `colorGroup` edellyttää arvoa `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` edellyttää arvoa `palette: "documentLibrary"` +@msg `colorGroup` needs `mode: "color"` `colorGroup` edellyttää arvoa `mode: "color"` +@msg no color group `{name}` in the document (see swatch.list) asiakirjassa ei ole väriryhmää `{name}` (ks. swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. Tasainen logo -tila jäljitti kuvan kokoon {_1} × {_2} kuvapistettä keskiarvoistamalla pienennettynä koosta {_3} × {_4}. Artboard {n} of {total} Piirtoalusta {n}/{total} File {n} of {total} Tiedosto {n}/{total} diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index b7450e631..cb6b7d2fa 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -5398,6 +5398,10 @@ theme Appearance Apparence Snap to Perpendicular Magnétisme de la perpendiculaire @msg unknown snap option `{key}` (use {options}) option de magnétisme inconnue `{key}` (utilisez {options}) Gap Length: Longueur de lacune : +@msg `colorGroup` needs `library: "document"` `colorGroup` nécessite `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` nécessite `palette: "documentLibrary"` +@msg `colorGroup` needs `mode: "color"` `colorGroup` nécessite `mode: "color"` +@msg no color group `{name}` in the document (see swatch.list) aucun groupe de couleurs `{name}` dans le document (voir swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. Le mode Logo en aplats a vectorisé l’image à {_1} × {_2} pixels, réduite par moyennage de {_3} × {_4}. Artboard {n} of {total} Plan de travail {n} sur {total} File {n} of {total} Fichier {n} sur {total} diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 6b1820bbe..338568215 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -5385,6 +5385,10 @@ imageTrace Outline Körvonal Snap to Perpendicular Illesztés merőlegesre @msg unknown snap option `{key}` (use {options}) ismeretlen illesztési beállítás: `{key}` (használható: {options}) Gap Length: Rés hossza: +@msg `colorGroup` needs `library: "document"` a `colorGroup` használatához `library: "document"` szükséges +@msg `colorGroup` needs `palette: "documentLibrary"` a `colorGroup` használatához `palette: "documentLibrary"` szükséges +@msg `colorGroup` needs `mode: "color"` a `colorGroup` használatához `mode: "color"` szükséges +@msg no color group `{name}` in the document (see swatch.list) nincs ilyen színcsoport a dokumentumban: `{name}` (lásd: swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. Az Egyszínű logó mód {_1} × {_2} képponton vektorizálta a képet, {_3} × {_4} méretről átlagolással kicsinyítve. Artboard {n} of {total} Rajztábla: {n} / {total} File {n} of {total} Fájl: {n} / {total} diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 4793d5ada..1aa64038c 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5012,6 +5012,10 @@ lighting Light Luce Snap to Perpendicular Aggancia alla perpendicolare @msg unknown snap option `{key}` (use {options}) opzione di aggancio sconosciuta `{key}` (usare {options}) Gap Length: Lunghezza lacuna: +@msg `colorGroup` needs `library: "document"` `colorGroup` richiede `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` richiede `palette: "documentLibrary"` +@msg `colorGroup` needs `mode: "color"` `colorGroup` richiede `mode: "color"` +@msg no color group `{name}` in the document (see swatch.list) nessun gruppo colori `{name}` nel documento (vedi swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. La modalità Logo piatto ha vettorializzato l’immagine a {_1} × {_2} pixel, ridotta per media da {_3} × {_4}. Artboard {n} of {total} Tavola da disegno {n} di {total} File {n} of {total} File {n} di {total} diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 3e76db333..a626a1495 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5052,6 +5052,10 @@ lighting Light ライト Snap to Perpendicular 垂線にスナップ @msg unknown snap option `{key}` (use {options}) 不明なスナップオプション `{key}` ({options} を使用してください) Gap Length: ギャップの長さ: +@msg `colorGroup` needs `library: "document"` `colorGroup` には `library: "document"` が必要です +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` には `palette: "documentLibrary"` が必要です +@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}. フラットロゴモードは画像を {_3} × {_4} から平均化して縮小した {_1} × {_2} ピクセルでトレースしました。 Artboard {n} of {total} アートボード {n} / {total} File {n} of {total} ファイル {n} / {total} diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index c6903ba13..9dfe36829 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5237,6 +5237,10 @@ imageTrace Outline კონტური @msg Save writes {_1}, not {_2}: use file.saveAsTemplate to write it „შენახვა“ წერს ფორმატს {_1} და არა {_2}: ამისთვის გამოიყენეთ file.saveAsTemplate @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 `colorGroup` needs `library: "document"` `colorGroup`-ს სჭირდება `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup`-ს სჭირდება `palette: "documentLibrary"` +@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 `{given}` isn't a library panel (see window.swatchLibrary) `{given}` ბიბლიოთეკის პანელი არ არის (იხ. window.swatchLibrary) @msg `{key}` must be a #rrggbb color `{key}` უნდა იყოს #rrggbb ფერი diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 99da0c022..e63f40ae3 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4975,6 +4975,10 @@ lighting Light 광원 Snap to Perpendicular 수직선에 스냅 @msg unknown snap option `{key}` (use {options}) 알 수 없는 스냅 옵션 `{key}` ({options} 중에서 사용하세요) Gap Length: 간격 길이: +@msg `colorGroup` needs `library: "document"` `colorGroup`에는 `library: "document"`가 필요합니다 +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup`에는 `palette: "documentLibrary"`가 필요합니다 +@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}. 단색 로고 모드에서 이미지를 {_3} × {_4}에서 평균을 내어 축소한 {_1} × {_2}픽셀로 추적했습니다. Artboard {n} of {total} 아트보드 {n} / {total} File {n} of {total} 파일 {n} / {total} diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index b138cb5de..5b74b91e9 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5251,6 +5251,10 @@ mark-style Japanese Japońskie Snap to Perpendicular Przyciągaj do prostopadłych @msg unknown snap option `{key}` (use {options}) nieznana opcja przyciągania `{key}` (użyj: {options}) Gap Length: Długość przerwy: +@msg `colorGroup` needs `library: "document"` `colorGroup` wymaga `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` wymaga `palette: "documentLibrary"` +@msg `colorGroup` needs `mode: "color"` `colorGroup` wymaga `mode: "color"` +@msg no color group `{name}` in the document (see swatch.list) brak grupy kolorów `{name}` w dokumencie (patrz swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. Tryb Płaskie logo obrysował obraz w rozdzielczości {_1} × {_2} pikseli, po zmniejszeniu przez uśrednienie z {_3} × {_4}. Artboard {n} of {total} Obszar kompozycji {n} z {total} File {n} of {total} Plik {n} z {total} diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 7cfddfac8..f251ba0c9 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -5316,6 +5316,10 @@ mark-style Japanese Japonesas Snap to Perpendicular Ajustar à Perpendicular @msg unknown snap option `{key}` (use {options}) opção de ajuste desconhecida `{key}` (use {options}) Gap Length: Comprimento da Lacuna: +@msg `colorGroup` needs `library: "document"` `colorGroup` requer `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` requer `palette: "documentLibrary"` +@msg `colorGroup` needs `mode: "color"` `colorGroup` requer `mode: "color"` +@msg no color group `{name}` in the document (see swatch.list) nenhum grupo de cores `{name}` no documento (veja swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. O modo Logotipo Plano traçou a imagem em {_1} × {_2} pixels, reduzida por média de {_3} × {_4}. Artboard {n} of {total} Mesa de Trabalho {n} de {total} File {n} of {total} Arquivo {n} de {total} diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 8c7badc51..1523725d4 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -5008,6 +5008,10 @@ lighting Light Свет Snap to Perpendicular Привязать к перпендикуляру @msg unknown snap option `{key}` (use {options}) неизвестный параметр привязки `{key}` (допустимо: {options}) Gap Length: Длина зазора: +@msg `colorGroup` needs `library: "document"` для `colorGroup` нужен `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` для `colorGroup` нужен `palette: "documentLibrary"` +@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}. Artboard {n} of {total} Монтажная область {n} из {total} File {n} of {total} Файл {n} из {total} diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index bb7e61cfe..cb9adb174 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -5422,6 +5422,10 @@ swatch Yellow Green Gulgrön Snap to Perpendicular Fäst mot vinkelrät @msg unknown snap option `{key}` (use {options}) okänt fästalternativ `{key}` (använd {options}) Gap Length: Lucklängd: +@msg `colorGroup` needs `library: "document"` `colorGroup` kräver `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` kräver `palette: "documentLibrary"` +@msg `colorGroup` needs `mode: "color"` `colorGroup` kräver `mode: "color"` +@msg no color group `{name}` in the document (see swatch.list) ingen färggrupp `{name}` i dokumentet (se swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. Läget Platt logotyp vektoriserade bilden vid {_1} × {_2} pixlar, nedskalad genom medelvärdesbildning från {_3} × {_4}. Artboard {n} of {total} Rityta {n} av {total} File {n} of {total} Fil {n} av {total} diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 82fac9235..2b2ba1b90 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -5393,6 +5393,10 @@ theme Appearance Görünüm @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). 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 +@msg `colorGroup` needs `mode: "color"` `colorGroup` için `mode: "color"` gerekir +@msg no color group `{name}` in the document (see swatch.list) belgede `{name}` adlı renk grubu yok (bkz. swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. Düz Logo modu görüntüyü {_3} × {_4} boyutundan ortalama alarak küçültüp {_1} × {_2} pikselde izledi. @plural {n} more export waiting|{n} more exports waiting {n} dışa aktarma daha bekliyor|{n} dışa aktarma daha bekliyor @msg Export cancelled Dışa aktarma iptal edildi diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 6de7a233f..c14fd0f8c 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4992,6 +4992,10 @@ lighting Light Світло Snap to Perpendicular Прив’язати до перпендикуляра @msg unknown snap option `{key}` (use {options}) невідомий параметр прив’язки `{key}` (використовуйте {options}) Gap Length: Довжина проміжку: +@msg `colorGroup` needs `library: "document"` для `colorGroup` потрібен `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` для `colorGroup` потрібен `palette: "documentLibrary"` +@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}. Artboard {n} of {total} Монтажна область {n} з {total} File {n} of {total} Файл {n} з {total} diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index f741c03cb..fd9a3f65a 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4992,6 +4992,10 @@ theme Appearance Giao diện Snap to Perpendicular Hút vào đường vuông góc @msg unknown snap option `{key}` (use {options}) tùy chọn hút không hợp lệ `{key}` (dùng {options}) Gap Length: Độ dài khoảng hở: +@msg `colorGroup` needs `library: "document"` `colorGroup` cần `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` cần `palette: "documentLibrary"` +@msg `colorGroup` needs `mode: "color"` `colorGroup` cần `mode: "color"` +@msg no color group `{name}` in the document (see swatch.list) không có nhóm màu `{name}` trong tài liệu (xem swatch.list) @msg Flat Logo traced the image at {_1} × {_2} pixels, averaged down from {_3} × {_4}. Chế độ Logo phẳng đã dò ảnh ở {_1} × {_2} điểm ảnh, thu nhỏ bằng cách lấy trung bình từ {_3} × {_4}. Artboard {n} of {total} Bảng vẽ {n} / {total} File {n} of {total} Tệp {n} / {total} diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 6d20becf8..7999d6ccb 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5231,6 +5231,10 @@ lighting Light 光源 Snap to Perpendicular 对齐垂线 @msg unknown snap option `{key}` (use {options}) 未知的对齐选项 `{key}`(可用:{options}) Gap Length: 间隙长度: +@msg `colorGroup` needs `library: "document"` `colorGroup` 需要 `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` 需要 `palette: "documentLibrary"` +@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} 平均缩小而来。 # Links and Attributes: files and URLs handed to other apps. @msg Edit Original opens image and document files only; “{name}” is not one 编辑原稿只能打开图像和文档文件;“{name}” 不是其中之一 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 929516460..2916e7c9e 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2377,6 +2377,10 @@ file-filter Templates 範本 @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 `colorGroup` needs `library: "document"` `colorGroup` 需要 `library: "document"` +@msg `colorGroup` needs `palette: "documentLibrary"` `colorGroup` 需要 `palette: "documentLibrary"` +@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} 平均縮小而來。 # Links and Attributes: files and URLs handed to other apps. @msg Edit Original opens image and document files only; “{name}” is not one 「編輯原稿」只能開啟影像與文件檔案;「{name}」不屬於這兩類 diff --git a/crates/ui-egui/src/panels/image_trace.rs b/crates/ui-egui/src/panels/image_trace.rs index 83ca93893..86240875b 100644 --- a/crates/ui-egui/src/panels/image_trace.rs +++ b/crates/ui-egui/src/panels/image_trace.rs @@ -61,6 +61,31 @@ fn library_list(app: &VectorcraftApp, ui: &mut Ui, current: &str) -> Option) -> Option> { + let mut chosen = menu_item(ui, "All", true, current.is_none()).then_some(None); + let groups: Vec = app.session.active().map(|d| d.doc.swatch_groups.iter().map(|g| g.name.clone()).collect()).unwrap_or_default(); + if !groups.is_empty() { + ui.separator(); + } + for g in groups { + if widgets::menu_item_name(ui, &g, true, current == Some(g.as_str())) { + chosen = Some(Some(g)); + } + } + chosen +} + +/// `params`' color group when the active document has it, found as `imageTrace.make` finds it (so +/// named exactly, else ignoring case); `None` is All. A group of another document, or a deleted +/// one, is All. +fn color_group<'a>(app: &VectorcraftApp, params: &'a Value) -> Option<&'a str> { + let name = params["colorGroup"].as_str().filter(|g| !g.is_empty())?; + let lower = name.to_lowercase(); + app.session.active()?.doc.swatch_groups.iter().any(|g| g.name.to_lowercase() == lower).then_some(name) +} + /// Panel state: the preset name, the current parameters, the view and the last result's counts. #[derive(Clone, Default)] struct TraceUi { @@ -258,6 +283,10 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) -> bool { } fn trace(app: &mut VectorcraftApp, st: &mut TraceUi) { + // A group the active document does not have traces with All, as the panel shows. + if color_group(app, &st.params).is_none() && !st.params["colorGroup"].is_null() { + st.params["colorGroup"] = Value::Null; + } let preset = if st.preset == "Custom" { "Default" } else { st.preset.as_str() }; match app.run("imageTrace.make", json!({ "preset": preset, "params": st.params, "view": st.view.id() })) { Ok(r) => { @@ -378,6 +407,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let label = MODES.iter().find(|m| m.0 == mode).map_or("Black and White", |m| m.1); let labels: Vec<&str> = MODES.iter().map(|m| m.1).collect(); if let Some(i) = widgets::dropdown(ui, "it-mode", label, &labels, 170.0) { + // Only Color mode uses a color group. + if MODES[i].0 != "color" { + st.params["colorGroup"] = Value::Null; + } st.params["mode"] = json!(MODES[i].0); st.preset = "Custom".into(); retrace = true; @@ -390,6 +423,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { widgets::dim_label(ui, tl!("Palette:")); let labels = PALETTES.map(palette_label); if let Some(p) = widgets::dropdown_names(ui, "it-palette", palette_label(&palette), &labels, 170.0).and_then(|i| PALETTES.get(i)) { + // Every other palette refuses a color group. + if *p != "documentLibrary" { + st.params["colorGroup"] = Value::Null; + } st.params["palette"] = json!(p); st.preset = "Custom".into(); retrace = true; @@ -400,11 +437,27 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { widgets::dim_label(ui, tl!("Library:")); let key = st.params["library"].as_str().unwrap_or(DOCUMENT_SWATCHES).to_string(); if let Some(k) = widgets::combo(ui, "it-library", &library_label(app, &key), 170.0, false, |ui| library_list(app, ui, &key)) { + if k != DOCUMENT_SWATCHES { + st.params["colorGroup"] = Value::Null; + } st.params["library"] = json!(k); st.preset = "Custom".into(); retrace = true; } }); + // The document's swatches: every one, or one color group's. + if st.params["library"].as_str().unwrap_or(DOCUMENT_SWATCHES) == DOCUMENT_SWATCHES { + ui.horizontal(|ui| { + widgets::dim_label(ui, tl!("Colors:")); + let group = color_group(app, &st.params).map(str::to_string); + let label = group.clone().unwrap_or_else(|| tl!("All").to_string()); + if let Some(g) = widgets::combo(ui, "it-color-group", &label, 170.0, false, |ui| color_group_list(app, ui, group.as_deref())) { + st.params["colorGroup"] = json!(g); + st.preset = "Custom".into(); + retrace = true; + } + }); + } } } // Full Tone and Automatic: how many colours (Less … More); else a count or the threshold. @@ -1250,6 +1303,97 @@ mod tests { assert!(has(&texts, "50") && !has(&texts, "Library:"), "{texts:?}"); } + #[test] + fn document_library_colors_lists_the_documents_color_groups() { + let mut app = traced_app(); + app.run("swatch.newGroup", json!({"name": "Brand", "colors": ["#000000", "#ffffff"]})).unwrap(); + app.run("imageTrace.make", json!({"preset": "6 Colors", "params": {"palette": "documentLibrary"}})).unwrap(); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let has = |texts: &[(String, Rect)], s: &str| texts.iter().any(|(t, _)| t == s); + let stored = |app: &VectorcraftApp, key: &str| target(app).2.map(|t| t["params"][key].clone()); + let texts = frame(&mut app, &ctx, vec![]); + assert!(has(&texts, "Colors:") && has(&texts, "All"), "{texts:?}"); + // The list holds All and the document's groups; a group traces again with its colors. + click(&mut app, &ctx, "All"); + assert!(has(&frame(&mut app, &ctx, vec![]), " Brand")); + let steps = app.session.active().unwrap().history.undo.len(); + click(&mut app, &ctx, " Brand"); + assert_eq!(stored(&app, "colorGroup"), Some(json!("Brand"))); + assert_eq!(target(&app).2.unwrap()["preset"], "Custom"); + assert_eq!(app.session.active().unwrap().history.undo.len(), steps + 1); + assert!(has(&frame(&mut app, &ctx, vec![]), "Brand")); + // All again. + click(&mut app, &ctx, "Brand"); + click(&mut app, &ctx, " All"); + assert_eq!(stored(&app, "colorGroup"), Some(Value::Null)); + // Another library clears the group, and the row goes. + click(&mut app, &ctx, "All"); + click(&mut app, &ctx, " Brand"); + let lib = swatches::colour_libraries(&app).into_iter().find(|l| l.submenu.is_none()).unwrap(); + click(&mut app, &ctx, "Document Swatches"); + click(&mut app, &ctx, &format!(" {}", library_panel::library_name(&lib.id, &lib.name))); + assert_eq!((stored(&app, "library"), stored(&app, "colorGroup")), (Some(json!(lib.id)), Some(Value::Null))); + assert!(!has(&frame(&mut app, &ctx, vec![]), "Colors:")); + // Another palette clears the group too, and traces again (the object stores the settings of + // a trace that was made). + click(&mut app, &ctx, library_panel::library_name(&lib.id, &lib.name)); + click(&mut app, &ctx, " Document Swatches"); + click(&mut app, &ctx, "All"); + click(&mut app, &ctx, " Brand"); + assert_eq!(stored(&app, "colorGroup"), Some(json!("Brand"))); + click(&mut app, &ctx, "Document Library"); + click(&mut app, &ctx, "Limited"); + assert_eq!((stored(&app, "palette"), stored(&app, "colorGroup")), (Some(json!("limited")), Some(Value::Null))); + // Another mode clears it too, and traces again. + click(&mut app, &ctx, "Limited"); + click(&mut app, &ctx, "Document Library"); + click(&mut app, &ctx, "All"); + click(&mut app, &ctx, " Brand"); + assert_eq!(stored(&app, "colorGroup"), Some(json!("Brand"))); + click(&mut app, &ctx, "Color"); + click(&mut app, &ctx, "Grayscale"); + assert_eq!((stored(&app, "mode"), stored(&app, "colorGroup")), (Some(json!("grayscale")), Some(Value::Null))); + } + + #[test] + fn a_color_group_the_active_document_lacks_is_all() { + let mut app = traced_app(); + app.run("swatch.newGroup", json!({"name": "Brand", "colors": ["#000000", "#ffffff"]})).unwrap(); + app.run("imageTrace.make", json!({"preset": "6 Colors", "params": {"palette": "documentLibrary"}})).unwrap(); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + click(&mut app, &ctx, "All"); + click(&mut app, &ctx, " Brand"); + assert_eq!(target(&app).2.map(|t| t["params"]["colorGroup"].clone()), Some(json!("Brand"))); + let shows_brand = |app: &mut VectorcraftApp| frame(app, &ctx, vec![]).iter().any(|(t, _)| t == "Brand"); + // Without the group the row shows All; undoing its removal shows it again. + app.run("swatch.ungroup", json!({"name": "Brand"})).unwrap(); + assert!(!shows_brand(&mut app)); + app.run("edit.undo", json!({})).unwrap(); + assert!(shows_brand(&mut app)); + // Another document without the group, with an image that is not traced yet. + app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap(); + let img = image::RgbaImage::from_fn(20, 20, |x, _| image::Rgba(if x < 10 { [0, 0, 0, 255] } else { [255, 255, 255, 255] })); + let mut png = vec![]; + img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).unwrap(); + let data = vectorcraft_format::base64_encode(&png); + app.run("file.place", json!({"name": "half.png", "dataBase64": data, "link": false})).unwrap(); + let texts = frame(&mut app, &ctx, vec![]); + let has = |s: &str| texts.iter().any(|(t, _)| t == s); + assert!(has("Colors:") && has("All") && !has("Brand"), "{texts:?}"); + // Back in the first document, its Image Trace object shows its group again. + app.session.set_active(0); + assert!(shows_brand(&mut app)); + app.session.set_active(1); + assert!(!shows_brand(&mut app)); + // Trace uses every document swatch. + click(&mut app, &ctx, "Trace"); + let stored = target(&app).2.unwrap_or_else(|| panic!("not traced: {}", app.ui.status)); + assert!(stored["params"]["colorGroup"].as_str().is_none_or(str::is_empty), "{stored}"); + assert_eq!(stored["params"]["palette"], "documentLibrary"); + } + #[test] fn create_strokes_traces_again_and_keeps_one_kind_on() { let mut app = traced_app(); diff --git a/docs/image-trace-adjustments.md b/docs/image-trace-adjustments.md index 33c538b02..4dc6199c4 100644 --- a/docs/image-trace-adjustments.md +++ b/docs/image-trace-adjustments.md @@ -32,11 +32,24 @@ white beside a cream), at most six; scattered specks of a colour add none. Adobe its own palettes are chosen; this is VectorCraft's own method (see [image-trace-quality.md](image-trace-quality.md#auto-color-keeps-rare-colours)). +**Document Library color groups.** With Palette › Document Library and Library › Document Swatches, +the panel's **Colors** menu traces with every document swatch (**All**) or with the colors of one +color group from the Swatches panel, as the public +[Image Trace panel options](https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/image-trace-panel-options.html) +page ([archived](https://web.archive.org/web/20251230195658/https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/image-trace-panel-options.html)) +describes Document Library. In the params of `imageTrace.make` or `imageTrace.makeAndExpand`, +`"colorGroup": ""` names the group. It needs `"mode": "color"`, `"palette": "documentLibrary"` and +`"library": "document"`; a `colorGroup` with a nonempty name and any other mode, palette or library is refused. An +empty name names no group and is accepted with any mode, palette or library; with Document Library and +`"library": "document"` it traces with every document swatch. A group whose name matches exactly is used first, then one whose name differs only in case. At +most `colors` of its colors are used. A missing group and a group with no solid colors are refused. Choosing another +mode, palette or library in the panel clears the group. A group the active document does not have shows as All +and traces as All. + **Document Library** traces with the library's colors as they are. Each pixel takes the library color nearest to it in lightness and hue, with a difference in hue or chroma counted three times as much as one in lightness, so a gray or a muted brown takes a black, white or other neutral library color rather than a saturated color of the same lightness. - **Abutting and Overlapping.** Abutting is the default Method, and the Default, Auto Color, 3 Colors, 6 Colors and 16 Colors presets use it. With Abutting, two neighboring colors share one exact edge. The canvas smooths the edges of each path on its own, so each of the two paths covers only part of the diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 95ed2199f..cc544d53f 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -793,6 +793,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Transparency:** opacity masks (clip/invert/disable/link), exported as SVG `` and PDF soft masks. The constant alpha soft mask that PDF and `.ai` export draw a CMYK page with transparency through is white, so applications that read every soft mask by its luminosity show the page too (#1359, part of #1286). Disable, Enable, Unlink and Link Opacity Mask and Opacity Mask Options (`transparency.setOpacityMask`) record no undo step when every mask they act on already has the flags they set. - **Image Trace, thin closed outlines:** generated `fit::snap_area_tests` verify nonzero interiors after straightening, opposite closed-boundary winding and Noise controls; see [quality notes](image-trace-quality.md#thin-closed-outlines-with-snap-curves-to-lines). Scores and hours unchanged; open-piece fitting is unchanged. +- **Image Trace, Document Library color groups:** with Document Swatches, the panel's Colors menu and `"colorGroup"` trace with one color group's colors instead of every swatch, as the public panel documentation describes Document Library; code-drawn tests in `crates/engine` and the panel. - **Image Trace, flattened slivers:** in a Color, Grayscale or Black and White trace with Fills, a traced region whose outline straightens onto one line (a single pixel, or a one-pixel row along an edge, its pieces shared with the regions beside it) is left out instead of becoming a selectable path with no area, which paints nothing. Snap Curves To Lines straightens more outlines, so it leaves out more of these. Without Fills, the region's 1 px stroke draws it, and it is kept. Code-drawn tests in `crates/trace`. - **Image Trace, Auto Color rare colours:** the Automatic palette keeps colours too rare for its area count when they are distinct (Oklab) and drawn as coherent regions, at most six, protected from mixed-colour merging and Noise; code-drawn stem, highlight, low-contrast and scattered-noise tests (see [quality notes](image-trace-quality.md#auto-color-keeps-rare-colours)). - **Image Trace, Document Library keeps neutral tones neutral:** each pixel takes the library color nearest in Oklab with differences in hue and chroma counted three times, so a gray or a muted brown traces as a neutral library color instead of a saturated one of similar lightness; a code-drawn test of gray and brown patches with a black, light and blue library in `crates/trace`.