mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #3982 from dotCooCoo/feat/image-trace-preview
Image Trace: a Preview checkbox chooses whether setting changes trace at once
This commit is contained in:
@@ -310,15 +310,20 @@ fn save_prefs(app: &VectorcraftApp) {
|
||||
}
|
||||
if let Some(p) = prefs_path() {
|
||||
let _ = std::fs::create_dir_all(p.parent().unwrap_or(std::path::Path::new(".")));
|
||||
let mut ui = app.ui.clone();
|
||||
ui.engine_prefs = app.session.prefs.to_json();
|
||||
if let Ok(bytes) = serde_json::to_vec_pretty(&ui) {
|
||||
if let Ok(bytes) = prefs_bytes(app) {
|
||||
// Preferences are best effort: a failed write keeps the previous file.
|
||||
let _ = fileio::write_atomic(&p, &bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The preferences file's contents: the UI state with the engine preferences.
|
||||
fn prefs_bytes(app: &VectorcraftApp) -> serde_json::Result<Vec<u8>> {
|
||||
let mut ui = app.ui.clone();
|
||||
ui.engine_prefs = app.session.prefs.to_json();
|
||||
serde_json::to_vec_pretty(&ui)
|
||||
}
|
||||
|
||||
/// The window file dialogs belong to.
|
||||
type Parent = Option<std::sync::Arc<winit::window::Window>>;
|
||||
|
||||
@@ -1205,6 +1210,17 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_image_trace_preview_setting_is_kept_across_a_restart() {
|
||||
for preview in [false, true] {
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
|
||||
app.ui.image_trace_preview = preview;
|
||||
let saved = prefs_bytes(&app).unwrap();
|
||||
let loaded = decode_saved_prefs(&saved).unwrap().sanitized();
|
||||
assert_eq!(loaded.image_trace_preview, preview, "Preview {preview} after a restart");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_system_title_bar_preference_keeps_the_window_decorations() {
|
||||
use craft_window::{Frame, Layout};
|
||||
|
||||
@@ -381,6 +381,9 @@ pub fn handle(app: &mut VectorcraftApp, ctx: &egui::Context, req: &ControlReques
|
||||
if let Some(v) = p.get("controlBar").and_then(Value::as_bool) {
|
||||
app.ui.control_bar = v;
|
||||
}
|
||||
if let Some(v) = p.get("imageTracePreview").and_then(Value::as_bool) {
|
||||
app.ui.image_trace_preview = v;
|
||||
}
|
||||
if p.get("closePanel").is_some() {
|
||||
app.ui.open_panel = None;
|
||||
}
|
||||
@@ -511,6 +514,17 @@ mod tests {
|
||||
r
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_set_turns_the_image_trace_preview_off_and_on() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Services::default());
|
||||
assert!(app.ui.image_trace_preview);
|
||||
for on in [false, true] {
|
||||
let r = input_request(&mut app, "ui.set", json!({"imageTracePreview": on}));
|
||||
assert_eq!(r["ok"], true, "{r}");
|
||||
assert_eq!(app.ui.image_trace_preview, on);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_input_rejects_excessive_counts_before_mutation() {
|
||||
// Only modest over-limit values on the baseline: never exercise an unbounded loop.
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
//! Image Trace panel: preset, view, mode, palette (Color), threshold or colour count, and the
|
||||
//! Advanced section (open or closed as it was last left): fidelity, method, and Create: Fills /
|
||||
//! Strokes with the widest line stroked. With an Image Trace object selected, changing a setting
|
||||
//! re-traces it (one undo step per change; sliders apply when released) and changing the view
|
||||
//! redraws it without tracing again. With an image selected, Trace makes a new Image Trace object.
|
||||
//! Strokes with the widest line stroked. With an Image Trace object selected and Preview on (the
|
||||
//! default, kept with the UI preferences), changing a setting re-traces it (one undo step per
|
||||
//! change; sliders apply when released); with Preview off, the changes wait in the panel until
|
||||
//! Trace applies them, or until Preview is turned on again. Changing the view redraws the object
|
||||
//! without tracing again. With an image selected, Trace makes a new Image Trace object.
|
||||
//! The public documentation of the panel describes Preview as showing the result of the current
|
||||
//! settings (<https://web.archive.org/web/20250831090623/https://helpx.adobe.com/illustrator/using/image-trace.html>).
|
||||
|
||||
use egui::Ui;
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::{Node, NodeKind, TraceView};
|
||||
use vectorcraft_doc::{Node, NodeId, NodeKind, TraceView};
|
||||
use vectorcraft_engine::cmd::swatchlib::DOCUMENT_SWATCHES;
|
||||
|
||||
use super::{first_selected, library_panel, pstate, set_pstate, swatches};
|
||||
@@ -66,6 +70,10 @@ struct TraceUi {
|
||||
/// The selected Image Trace object's stored settings last adopted (so edits in progress
|
||||
/// aren't overwritten until the object changes).
|
||||
synced: Value,
|
||||
/// The document and object `synced` came from.
|
||||
object: Option<(u64, NodeId)>,
|
||||
/// Preview was off at the panel's last frame.
|
||||
preview_off: bool,
|
||||
}
|
||||
|
||||
/// Each preset: its name, its parameters, and those of its enhanced version (null if it has none).
|
||||
@@ -84,6 +92,11 @@ pub(crate) fn is_trace(n: &Node) -> bool {
|
||||
&& n.children().is_some_and(|c| c.first().is_some_and(|i| matches!(i.kind, NodeKind::Image(_))))
|
||||
}
|
||||
|
||||
/// The active document and the first selected object.
|
||||
fn target_key(app: &VectorcraftApp) -> Option<(u64, NodeId)> {
|
||||
Some((app.session.active()?.uid, first_selected(app)?.id))
|
||||
}
|
||||
|
||||
/// What the selection is: an Image Trace object (with its stored settings), a plain image, or neither.
|
||||
fn target(app: &VectorcraftApp) -> (bool, bool, Option<Value>) {
|
||||
let Some(n) = first_selected(app) else { return (false, false, None) };
|
||||
@@ -217,6 +230,7 @@ fn trace(app: &mut VectorcraftApp, st: &mut TraceUi) {
|
||||
// still be recognized as a change, even when it returns to the previous settings.
|
||||
if let Some(stored) = target(app).2 {
|
||||
st.synced = stored;
|
||||
st.object = target_key(app);
|
||||
}
|
||||
}
|
||||
Err(e) => app.ui.status = e,
|
||||
@@ -248,11 +262,17 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
st.params = all.first().map(|p| p.1.clone()).unwrap_or_default();
|
||||
}
|
||||
let (is_trace, is_image, stored) = target(app);
|
||||
if let Some(t) = stored.filter(|t| *t != st.synced) {
|
||||
st.preset = t["preset"].as_str().unwrap_or("Custom").to_string();
|
||||
st.params = t["params"].clone();
|
||||
let object = target_key(app);
|
||||
if let Some(t) = stored.filter(|t| *t != st.synced || object != st.object) {
|
||||
// A change of the object's view alone (here or in the Control bar) keeps the settings that
|
||||
// wait in the panel with Preview off.
|
||||
if object != st.object || t["preset"] != st.synced["preset"] || t["params"] != st.synced["params"] {
|
||||
st.preset = t["preset"].as_str().unwrap_or("Custom").to_string();
|
||||
st.params = t["params"].clone();
|
||||
}
|
||||
st.view = TraceView::of(&t);
|
||||
st.synced = t;
|
||||
st.object = object;
|
||||
}
|
||||
let mut retrace = false;
|
||||
|
||||
@@ -447,6 +467,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
);
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
let preview = app.ui.image_trace_preview;
|
||||
if widgets::check(ui, tl!("Preview"), preview, true) {
|
||||
app.ui.image_trace_preview = !preview;
|
||||
}
|
||||
let r = ui.add_enabled_ui(is_trace || is_image, |ui| widgets::flat_button(ui, tl!("Trace"), 80.0)).inner;
|
||||
if r.on_disabled_hover_text(tl!("Select an image to trace")).clicked() {
|
||||
trace(app, &mut st);
|
||||
@@ -455,7 +479,14 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
app.run("imageTrace.expand", json!({})).ok();
|
||||
}
|
||||
});
|
||||
if retrace && is_trace {
|
||||
// Preview turned on (the checkbox, or `ui.set`): the selected object shows the settings that
|
||||
// waited in the panel. With no Image Trace object selected, they wait until it is selected again.
|
||||
let preview = app.ui.image_trace_preview;
|
||||
retrace |= preview && st.preview_off && is_trace && st.params != st.synced["params"];
|
||||
if is_trace || !preview {
|
||||
st.preview_off = !preview;
|
||||
}
|
||||
if retrace && is_trace && preview {
|
||||
trace(app, &mut st);
|
||||
}
|
||||
set_pstate(ui.ctx(), "image-trace", st);
|
||||
@@ -629,6 +660,148 @@ mod tests {
|
||||
assert_eq!(state.params["threshold"], 150);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_preview_off_a_slider_change_waits_for_trace() {
|
||||
let mut app = traced_app_with_ink(100);
|
||||
trace_and_open(&mut app, "Sketched Art");
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
assert!(app.ui.image_trace_preview, "on at first");
|
||||
click(&mut app, &ctx, "Preview");
|
||||
assert!(!app.ui.image_trace_preview);
|
||||
let steps = app.session.active().unwrap().history.undo.len();
|
||||
let rail = ctx.data(|d| d.get_temp::<Rect>(egui::Id::new(("trace-slider", "Threshold")))).unwrap();
|
||||
let at = rail.left_center() + vec2(20.0, 0.0);
|
||||
for pressed in [true, false] {
|
||||
frame(
|
||||
&mut app,
|
||||
&ctx,
|
||||
vec![
|
||||
Event::PointerMoved(at),
|
||||
Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() },
|
||||
],
|
||||
);
|
||||
}
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), steps, "no trace while Preview is off");
|
||||
assert_eq!(first_selected(&app).unwrap().children().unwrap().len(), 2, "source and ink path, as before");
|
||||
let draft: TraceUi = pstate(&ctx, "image-trace");
|
||||
assert_ne!(draft.params["threshold"], target(&app).2.unwrap()["params"]["threshold"], "the change waits in the panel");
|
||||
click(&mut app, &ctx, "Trace");
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), steps + 1, "Trace applies it as one undo step");
|
||||
assert_eq!(first_selected(&app).unwrap().children().unwrap().len(), 1, "only the source remains below the ink's luminance");
|
||||
assert_eq!(target(&app).2.unwrap()["params"], pstate::<TraceUi>(&ctx, "image-trace").params);
|
||||
assert!(!app.ui.image_trace_preview, "Trace leaves Preview off");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turning_preview_on_traces_the_changes_that_waited() {
|
||||
let mut app = traced_app();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let strokes = |app: &VectorcraftApp| target(app).2.map(|t| t["params"]["strokes"].clone());
|
||||
let undo = |app: &VectorcraftApp| app.session.active().unwrap().history.undo.len();
|
||||
let steps = undo(&app);
|
||||
click(&mut app, &ctx, "Preview");
|
||||
click(&mut app, &ctx, "Preview");
|
||||
assert!(app.ui.image_trace_preview);
|
||||
assert_eq!(undo(&app), steps, "nothing waited, so nothing is traced");
|
||||
click(&mut app, &ctx, "Preview");
|
||||
click(&mut app, &ctx, "Strokes");
|
||||
assert_eq!(strokes(&app), Some(json!(false)));
|
||||
assert_eq!(undo(&app), steps);
|
||||
click(&mut app, &ctx, "Preview");
|
||||
assert_eq!(strokes(&app), Some(json!(true)), "traced again with strokes");
|
||||
assert_eq!(undo(&app), steps + 1);
|
||||
// A change of view keeps the settings that wait, and Preview turned on with `ui.set` traces them.
|
||||
click(&mut app, &ctx, "Preview");
|
||||
click(&mut app, &ctx, "Fills");
|
||||
assert_eq!(target(&app).2.unwrap()["params"]["fills"], json!(true));
|
||||
click(&mut app, &ctx, "Tracing Result");
|
||||
click(&mut app, &ctx, "Outlines");
|
||||
assert_eq!(selected_trace(&app).map(|t| t.1), Some(TraceView::Outlines));
|
||||
assert_eq!(undo(&app), steps + 2, "the view changes without a trace");
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
assert_eq!(pstate::<TraceUi>(&ctx, "image-trace").params["fills"], json!(false), "the change still waits");
|
||||
app.ui.image_trace_preview = true;
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
assert_eq!(target(&app).2.unwrap()["params"]["fills"], json!(false), "traced with the waiting change");
|
||||
assert_eq!(selected_trace(&app).map(|t| t.1), Some(TraceView::Outlines), "in the view chosen");
|
||||
assert_eq!(undo(&app), steps + 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_turned_on_with_nothing_selected_traces_when_the_object_is_selected_again() {
|
||||
let mut app = traced_app();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let a = first_selected(&app).unwrap().id;
|
||||
let undo = |app: &VectorcraftApp| app.session.active().unwrap().history.undo.len();
|
||||
click(&mut app, &ctx, "Preview");
|
||||
click(&mut app, &ctx, "Strokes");
|
||||
assert_eq!(target(&app).2.unwrap()["params"]["strokes"], json!(false), "the change waits");
|
||||
app.run("select.none", json!({})).unwrap();
|
||||
let steps = undo(&app);
|
||||
click(&mut app, &ctx, "Preview");
|
||||
assert!(app.ui.image_trace_preview);
|
||||
assert_eq!(undo(&app), steps, "nothing to trace with nothing selected");
|
||||
app.run("select.set", json!({"ids": [a.0]})).unwrap();
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
assert_eq!(target(&app).2.unwrap()["params"]["strokes"], json!(true), "traced with the change that waited");
|
||||
assert_eq!(undo(&app), steps + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_that_wait_stay_with_their_object() {
|
||||
let mut app = traced_app();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let a = first_selected(&app).unwrap().id;
|
||||
app.run("edit.duplicate", json!({})).unwrap();
|
||||
let b = first_selected(&app).unwrap().id;
|
||||
assert_ne!(a, b);
|
||||
assert_eq!(target(&app).2.unwrap()["params"]["strokes"], json!(false), "the copy has the same settings");
|
||||
app.run("select.set", json!({"ids": [a.0]})).unwrap();
|
||||
click(&mut app, &ctx, "Preview");
|
||||
click(&mut app, &ctx, "Strokes");
|
||||
assert_eq!(pstate::<TraceUi>(&ctx, "image-trace").params["strokes"], json!(true), "the change waits for A");
|
||||
app.run("select.set", json!({"ids": [b.0]})).unwrap();
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
assert_eq!(pstate::<TraceUi>(&ctx, "image-trace").params["strokes"], json!(false), "the panel shows B's own settings");
|
||||
click(&mut app, &ctx, "Trace");
|
||||
assert_eq!(target(&app).2.unwrap()["params"]["strokes"], json!(false), "B is traced with its own settings");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_is_kept_with_the_preferences_and_reset_to_default_leaves_it() {
|
||||
let mut app = traced_app();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
click(&mut app, &ctx, "Preview");
|
||||
let menu_frame = |app: &mut VectorcraftApp, events: Vec<Event>| {
|
||||
let mut out = ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| menu(app, ui));
|
||||
out.textures_delta.clear();
|
||||
crate::tests_removeanchors::shapes_text(&out.shapes.iter().map(|c| c.shape.clone()).collect::<Vec<_>>())
|
||||
};
|
||||
let texts = menu_frame(&mut app, vec![]);
|
||||
// Menu items draw their label after the check mark's column.
|
||||
let at = texts.iter().find(|(t, _)| t.trim() == "Reset to Default").map(|(_, r)| r.center()).unwrap_or_else(|| panic!("{texts:?}"));
|
||||
for pressed in [true, false] {
|
||||
menu_frame(
|
||||
&mut app,
|
||||
vec![
|
||||
Event::PointerMoved(at),
|
||||
Event::PointerButton { pos: at, button: PointerButton::Primary, pressed, modifiers: Default::default() },
|
||||
],
|
||||
);
|
||||
}
|
||||
assert!(pstate::<TraceUi>(&ctx, "image-trace").params.is_null(), "the panel's settings were reset");
|
||||
assert!(!app.ui.image_trace_preview, "Reset to Default leaves Preview");
|
||||
let saved: crate::state::UiState = serde_json::from_value(serde_json::to_value(&app.ui).unwrap()).unwrap();
|
||||
assert!(!saved.image_trace_preview);
|
||||
let old: crate::state::UiState = serde_json::from_value(json!({})).unwrap();
|
||||
assert!(old.image_trace_preview, "on for preferences saved before Preview existed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sketch_adjustment_sliders_retrace_on_release() {
|
||||
let mut app = traced_app();
|
||||
|
||||
@@ -559,6 +559,10 @@ pub struct UiState {
|
||||
/// The Image Trace panel's Advanced section is open (as it was last left).
|
||||
#[serde(default = "yes")]
|
||||
pub image_trace_advanced: bool,
|
||||
/// The Image Trace panel's Preview: a setting changed in the panel traces the selected Image
|
||||
/// Trace object again at once. Off, the changes wait in the panel until Trace applies them.
|
||||
#[serde(default = "yes")]
|
||||
pub image_trace_preview: bool,
|
||||
/// Glyphs panel preview step, kept with the other panel choices. `0` is the default 30 pt
|
||||
/// cell; `1`..=`7` is a step from smallest to largest (`crates/ui-egui/src/panels/glyphs.rs`).
|
||||
#[serde(default, skip_serializing_if = "is_zero_u8")]
|
||||
@@ -901,6 +905,7 @@ impl Default for UiState {
|
||||
layers_panel: Default::default(),
|
||||
layers_filter: Default::default(),
|
||||
image_trace_advanced: true,
|
||||
image_trace_preview: true,
|
||||
glyphs_preview: 0,
|
||||
window: None,
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ the two can still win that race.
|
||||
| `ui.move` | `{x, y}` | screen-point pointer move |
|
||||
| `ui.click` / `ui.drag` | `{x, y, button?, count?, shift?, alt?, ctrl?, cmd?}` / `{x, y, toX, toY, steps?, count?, button?, shift?, alt?, ctrl?, cmd?}` | egui pointer input; see synthetic input budgets above |
|
||||
| `ui.wheel` | `{x, y, dy?, dx?, unit?: "line"\|"point", shift?, alt?, cmd?}` | a mouse wheel turn over screen point (x, y): `dy` notches up (+) or down, `dx` sideways. Over the canvas the wheel scrolls and Cmd- or Alt-wheel (Option on the Mac) zooms about the pointer; with the `zoomWithMouseWheel` preference the wheel and Alt-wheel zoom about the pointer, Shift-wheel scrolls up and down and Cmd-wheel (Ctrl on Windows and Linux) sideways. Over a focused numeric field (click it first) each notch steps its value as Up/Down do (Shift: ten, Cmd/Ctrl: a tenth) and the panel stays put; over anything else in a panel the wheel scrolls it |
|
||||
| `ui.set` | `{brightness?, panel?, rulers?, outline?, grid?, smartGuides?, boundingBox?, controlBar?}` | |
|
||||
| `ui.set` | `{brightness?, panel?, rulers?, outline?, grid?, smartGuides?, boundingBox?, controlBar?, imageTracePreview?}` | `imageTracePreview`: the Image Trace panel's Preview (`ui.inspect` shows it as `ui.image_trace_preview`) |
|
||||
| `ui.dialog.set` / `.confirm` / `.cancel` | `{field, value}` | fill and submit the open dialog |
|
||||
| `ui.screenshot` | `{path?, data?}` | capture the window (PNG), written to `path`; `data: true` sends it back as `pngBase64` too. Needs a presented frame: with the screen locked or the window minimized/covered it fails after ~8 s with an explanatory error |
|
||||
| `ui.render` | `{path?, scale?, artboard?, data?}` | render an artboard headlessly (PNG with the pixels of `app.export {format: "png", background: "white"}` at the same scale; `artboard`: its 0-based index, default the first): written to `path`, else (or with `data: true` as well) sent back as `pngBase64` |
|
||||
|
||||
@@ -785,6 +785,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **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, large images:** images over 1500 px on a side trace with a warning in the result and the status bar; the anchor limit is 8,000,000 (2,000,000 on the web), measured at about 90 bytes an anchor (see [quality notes](image-trace-quality.md#large-images)).
|
||||
- **Image Trace, Preview:** the Image Trace panel's Preview checkbox, before Trace, is on by default, and a setting change traces the selected Image Trace object again at once. Off, changes wait in the panel until Trace applies them as one undo step, or until Preview is turned on again. Preview is kept with the UI preferences, and Reset to Default leaves it.
|
||||
- **Image Trace small contours:** closed fits too small to form an editable subpath retain their positioned outline, including holes. Source-generated island, hole/winding and stepped-region regressions cover Corners and Noise; defaults, parity scores and estimates are unchanged.
|
||||
- **Create Strokes baseline (2026-10-17, measured):** the ignored centre-lines class covers 23 exact code-drawn scenes in aa and hard variants, including junctions, crossings, fills meeting strokes and varying widths. [Baseline and metric definitions](image-trace-quality.md#centre-lines). Topology is recovered from emitted geometry; width-profile metrics remain unavailable. No behaviour change or score increase.
|
||||
- **Image Trace, collapsed loops keep few segments:** a loop whose fit collapses to one cubic keeps the fewest segments that hold its area and course, so a higher Paths gives no fewer segments; a code-drawn Paths sweep in `crates/trace` (see [quality notes](image-trace-quality.md#collapsed-loops-keep-few-segments)).
|
||||
|
||||
@@ -274,6 +274,7 @@ empty), without opening the dialog.
|
||||
| Edit Toolbar (•••): the All Tools drawer opens beside the toolbar by its button (Escape or a click outside closes it), dims the tools already in the toolbar; while it is open, tools drag from it onto the toolbar (between two tools, or onto one to join its group), from the toolbar into it, and within the toolbar; each toolbar keeps its edits | done | #1018; `window.editToolbar`, `toolbar.addTool`/`removeTool`/`reset`; the drawer's Reset (Illustrator has it in a drawer menu); dragging a toolbar button moves the tool it shows, not its whole group; New/Manage Toolbars missing; tool flyouts are as wide as their longest translated name and shortcut need (#1616) |
|
||||
| Quick Image Trace presets trace at once and leave the panel as it was; the Image Trace button (Control bar, Properties) opens the editable controls | done | Per the public documentation, choosing a preset traces the image with it straight away, and the panel is opened with the Image Trace button in the Control panel or in Properties' Image Trace section (<https://helpx.adobe.com/illustrator/using/image-trace-results-optimization.html>). The controls show Threshold, Paths, Corners and Noise with Advanced open; `panels/image_trace.rs` headless tests (`a_quick_trace_leaves_the_panel_as_it_was`, `sketch_quick_trace_reveals_all_adjustments_and_keeps_the_source` for the button's route). No algorithm parity claim. |
|
||||
| Live trace result bounds and selection highlights exclude the retained bitmap; source-image views retain it | done | All TraceView modes and rotated result bounds regression-covered; ordinary hidden group behavior retained |
|
||||
| Image Trace panel: the Preview checkbox before Trace chooses whether a setting change traces the selected Image Trace object again at once | done | Per the public documentation, Preview shows the result of the current settings and Trace traces with them (<https://web.archive.org/web/20250831090623/https://helpx.adobe.com/illustrator/using/image-trace.html>). Preview is on by default and kept with the UI preferences; Reset to Default leaves it. Off, changes wait in the panel until Trace applies them as one undo step, or until Preview is turned on again. `ui.set {imageTracePreview}`; `panels/image_trace.rs` headless tests (`with_preview_off_a_slider_change_waits_for_trace`, `turning_preview_on_traces_the_changes_that_waited`, `preview_turned_on_with_nothing_selected_traces_when_the_object_is_selected_again`, `settings_that_wait_stay_with_their_object`, `preview_is_kept_with_the_preferences_and_reset_to_default_leaves_it`) |
|
||||
| Toolbar without group labels by default, one or two columns; All Tools drawer by flyout group with shortcuts, its menu (Reset, Basic, Advanced) and Show section | done | #1751; a preference brings the labels back; `toolbar.show` sets the Fill/Stroke, Coloring, Draw Mode and Screen Mode controls |
|
||||
| Actions panel: a clicked action stays selected for Play Current Selection and Delete Selection | partial | #1853 (part of #1629); editing actions and action shortcuts remain |
|
||||
| Actions panel: Create New Set (bottom bar, ≡ › New Set…) names a new set and Set Options… renames the current one; a click on a set's name makes it current and a recording goes into the current set ([helpx: Automation with actions](https://helpx.adobe.com/illustrator/using/automation-actions.html)) | partial | #3712; the New Action dialog (name, set menu, shortcut, colour), dragging actions between sets and deleting a set remain |
|
||||
|
||||
Reference in New Issue
Block a user