mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 11:17:01 -06:00
Merge pull request #3948 from dotCooCoo/fix/trace-and-style-prompt-runs
MCP: the trace-and-style prompt's steps run as written
This commit is contained in:
@@ -270,7 +270,7 @@ One undo step covers the whole change; gradients and linked swatches stay linked
|
||||
PromptDef {
|
||||
name: "trace-and-style",
|
||||
title: "Trace an image and style it",
|
||||
description: "Trace a placed raster with Image Trace, then apply a live effect and paint over the result.",
|
||||
description: "Trace an image with Image Trace and expand the trace, then apply a live effect and recolor the result.",
|
||||
arguments: &[
|
||||
PromptArg { name: "path", description: "Path to the image file", required: true, default: "", values: Values::Fixed(NONE) },
|
||||
PromptArg {
|
||||
@@ -293,8 +293,13 @@ Trace the image at {path} in VectorCraft with the \"{preset}\" preset, then styl
|
||||
with the {effect} effect.
|
||||
|
||||
Method:
|
||||
1. open_file the image: it becomes a document of its own pixel size.
|
||||
2. imageTrace.make with the preset name, adjusting `params` — mode blackAndWhite, grayscale,
|
||||
1. open_file the image: it becomes a document of the image's size at the resolution it declares
|
||||
(1 px = 1 pt at 72 ppi or when it declares none), with nothing selected.
|
||||
Then run_command select.all to select the image, or pass the image's `id` (inspect_document
|
||||
lists it) to imageTrace.make.
|
||||
2. imageTrace.make with the preset name. The Default preset traces in black and white; a color
|
||||
preset such as 6 Colors or 16 Colors, or `params` with mode color, keeps the image's colors.
|
||||
Adjust `params`: mode blackAndWhite, grayscale,
|
||||
color (palette limited, fullTone for photos, automatic, or documentLibrary to trace with
|
||||
the document's swatches or a swatch `library`) or logo (flat-colour logos only; the Flat Logo
|
||||
preset; colours via logoColors), then threshold, colors (colorDetail for
|
||||
@@ -304,11 +309,16 @@ Method:
|
||||
imageTrace.presets to see every preset with its numbers.
|
||||
Lower paths and higher corners give the clean, logo-like result; high fidelity needs paths
|
||||
near 90. imageTrace.setView {view: \"outlinesWithSourceImage\"} draws the traced outlines
|
||||
over the image on screen to compare them (screenshots show it; exports keep the result).
|
||||
3. apply_effect {effect} on the traced shapes. Call apply_effect with no `effect` for the
|
||||
over the image in the app's window to compare them (screenshot {window: true} captures it);
|
||||
render_preview and screenshot without `window` draw the tracing result, as exports do.
|
||||
3. imageTrace.expand keeps only the traced paths and removes the source image.
|
||||
imageTrace.makeAndExpand traces and expands in one command, in place of steps 2 and 3.
|
||||
4. apply_effect {effect} on the traced shapes. Call apply_effect with no `effect` for the
|
||||
catalogue and each effect's parameters.
|
||||
4. set_paint over the traced shapes to give the artwork its own colours.
|
||||
5. screenshot, then remove the placed image if it is still underneath.
|
||||
5. To change the colors, run recolor.colors on the traced shapes, then recolor.apply with a
|
||||
`map` from each traced color to its new color. set_paint gives every selected shape the
|
||||
same fill or stroke.
|
||||
6. render_preview or screenshot, and compare the result with the source image.
|
||||
|
||||
The trace is real geometry: every traced shape stays selectable and editable.",
|
||||
},
|
||||
@@ -546,6 +556,26 @@ mod tests {
|
||||
assert!(e.contains("brief"), "{e}");
|
||||
}
|
||||
|
||||
/// trace-and-style selects the opened image before tracing it, expands the trace, recolors it
|
||||
/// instead of painting it one color, and says that screenshots draw the tracing result.
|
||||
#[test]
|
||||
fn trace_and_style_selects_expands_and_recolors() {
|
||||
let v = get("trace-and-style", &json!({"path": "art.png"})).unwrap();
|
||||
let text = v["messages"][0]["content"]["text"].as_str().unwrap_or_default();
|
||||
for step in ["open_file", "select.all", "imageTrace.make", "imageTrace.expand", "apply_effect", "recolor.apply", "render_preview"] {
|
||||
assert!(text.contains(step), "{step}: {text}");
|
||||
}
|
||||
let at = |s: &str| text.find(s).unwrap_or(usize::MAX);
|
||||
assert!(at("select.all") < at("imageTrace.make") && at("imageTrace.make") < at("imageTrace.expand"), "{text}");
|
||||
assert!(text.contains("black and white"), "{text}");
|
||||
assert!(!text.contains("screenshots show it"), "{text}");
|
||||
assert!(!text.contains("own pixel size") && text.contains("resolution it declares"), "{text}");
|
||||
// No step of its own paints the trace with set_paint.
|
||||
let steps: Vec<&str> = text.lines().filter(|l| is_item(l)).filter_map(|l| l.split_once(". ").map(|(_, rest)| rest)).collect();
|
||||
assert!(!steps.is_empty(), "{text}");
|
||||
assert!(steps.iter().all(|s| !s.starts_with("set_paint")), "{steps:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completions_rank_prefix_first_and_cap_at_100() {
|
||||
let mut b = b();
|
||||
|
||||
@@ -296,6 +296,60 @@ fn headless_end_to_end() {
|
||||
assert_eq!(v["error"]["code"], -32002);
|
||||
}
|
||||
|
||||
/// The trace-and-style prompt's steps, run in its order on an image drawn here: each one
|
||||
/// succeeds, the expanded trace holds no image, and its paths keep the image's colors.
|
||||
#[test]
|
||||
fn trace_and_style_prompt_steps_run_in_order() {
|
||||
let mut s = server();
|
||||
// 60 × 40 pixels: a red and a blue block on white.
|
||||
let png = tmp("trace-and-style.png");
|
||||
image::RgbImage::from_fn(60, 40, |x, y| match (x, y) {
|
||||
(5..25, 5..35) => image::Rgb([220, 30, 30]),
|
||||
(35..55, 5..35) => image::Rgb([30, 60, 200]),
|
||||
_ => image::Rgb([255, 255, 255]),
|
||||
})
|
||||
.save(&png)
|
||||
.unwrap();
|
||||
let path = png.to_str().unwrap();
|
||||
let v = rpc(&mut s, 1, "prompts/get", json!({"name": "trace-and-style", "arguments": {"path": path}}));
|
||||
let prompt = v["result"]["messages"][0]["content"]["text"].as_str().unwrap().to_string();
|
||||
|
||||
let ok = |s: &mut Server, id: u64, name: &str, args: Value| {
|
||||
let r = call(s, id, name, args);
|
||||
assert_eq!(r["isError"], false, "{name}: {r}");
|
||||
serde_json::from_str::<Value>(&text_of(&r)).unwrap_or(Value::Null)
|
||||
};
|
||||
let command = |s: &mut Server, id: u64, command: &str, params: Value| {
|
||||
assert!(prompt.contains(command), "the prompt names {command}: {prompt}");
|
||||
ok(s, id, "run_command", json!({"command": command, "params": params}))
|
||||
};
|
||||
ok(&mut s, 2, "open_file", json!({"path": path}));
|
||||
command(&mut s, 3, "select.all", json!({}));
|
||||
let traced = command(&mut s, 4, "imageTrace.make", json!({"preset": "6 Colors"}));
|
||||
assert!(traced["colors"].as_u64().unwrap() > 1, "{traced}");
|
||||
command(&mut s, 5, "imageTrace.expand", json!({}));
|
||||
ok(&mut s, 6, "apply_effect", json!({"effect": "stylize.scribble"}));
|
||||
let colors = command(&mut s, 7, "recolor.colors", json!({}));
|
||||
let from = colors["colors"].as_array().unwrap().iter().filter_map(|c| c["hex"].as_str()).find(|h| *h != "#ffffff").unwrap().to_string();
|
||||
command(&mut s, 8, "recolor.apply", json!({"map": {from.as_str(): "#00aa00"}}));
|
||||
let r = call(&mut s, 9, "render_preview", json!({}));
|
||||
assert_eq!(r["isError"], false, "{r}");
|
||||
|
||||
// No image is left, and the paths carry more than one fill color, the new one among them.
|
||||
let doc = ok(&mut s, 10, "inspect_document", json!({}));
|
||||
let (mut kinds, mut fills) = (vec![], std::collections::BTreeSet::new());
|
||||
let mut stack: Vec<&Value> = doc["layers"].as_array().unwrap().iter().collect();
|
||||
while let Some(n) = stack.pop() {
|
||||
kinds.push(n["kind"].as_str().unwrap_or_default().to_string());
|
||||
if let Some(fill) = n["fill"].as_str().filter(|f| f.starts_with('#')) {
|
||||
fills.insert(fill.to_string());
|
||||
}
|
||||
stack.extend(n["children"].as_array().into_iter().flatten());
|
||||
}
|
||||
assert!(!kinds.iter().any(|k| k == "Image"), "{kinds:?}");
|
||||
assert!(fills.len() > 1 && fills.contains("#00aa00") && !fills.contains(&from), "{fills:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headless_path_gesture_undo() {
|
||||
let mut s = server();
|
||||
|
||||
Reference in New Issue
Block a user