From 8206f219127901d6cbbe96fd085f0229883faf30 Mon Sep 17 00:00:00 2001 From: Sven Lafebre Date: Mon, 9 Nov 2026 08:52:02 +0100 Subject: [PATCH] Follow-up to #6252: a selected colorized image gives new art its fill A grayscale image colorized with a fill color keeps that color outside its appearance, so the check for art without paint treated it like an uncolorized image and new art kept its old fill while the Fill proxy showed the color. Count a colorize paint as paint: the image then loads like any object with a fill and no stroke. --- crates/engine/src/cmd/newart.rs | 8 +++++--- crates/engine/src/tests/colorize.rs | 16 ++++++++++++++++ docs/mcp.md | 4 ++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/cmd/newart.rs b/crates/engine/src/cmd/newart.rs index c7bd4ffbf..ce8029e71 100644 --- a/crates/engine/src/cmd/newart.rs +++ b/crates/engine/src/cmd/newart.rs @@ -217,13 +217,15 @@ impl Session { /// After a command changed the selection, with New Art Has Basic Appearance on: the fill and /// stroke proxies and the weight take what the selection shows in them, so new art looks like /// it. A paint the selected objects don't share (a "?" proxy) stays as it was, and art without - /// paint (a placed image, whose None would make new art invisible) changes nothing. The - /// template is left alone, and without a stroke to show the weight stays too. + /// paint (a placed image that isn't colorized, whose None would make new art invisible) changes + /// nothing. The template is left alone, and without a stroke to show the weight stays too. pub(crate) fn load_selection_paints(&mut self) { if !self.prefs.new_art_basic { return; } - let has_paint = self.proxy_source(false).is_some_and(|(n, _)| matches!(n.kind, NodeKind::Text(_)) || !n.appearance.items.is_empty()); + let has_paint = self + .proxy_source(false) + .is_some_and(|(n, _)| matches!(n.kind, NodeKind::Text(_)) || !n.appearance.items.is_empty() || n.extras().colorize.is_some()); if !has_paint { return; } diff --git a/crates/engine/src/tests/colorize.rs b/crates/engine/src/tests/colorize.rs index 2dba3f6e0..c71014eb1 100644 --- a/crates/engine/src/tests/colorize.rs +++ b/crates/engine/src/tests/colorize.rs @@ -173,6 +173,22 @@ fn blend_recolors_a_colorized_gray_image_without_alpha() { assert_eq!(colorize(&s, middle), Some(Paint::solid(Color::rgb(0.5, 0.5, 0.5)))); } +/// A colorized image shows a fill and no stroke, so selecting it loads them for new art as any +/// object without a stroke does (the weight stays). +#[test] +fn selecting_a_colorized_image_gives_new_art_its_fill_and_no_stroke() { + let mut s = session(); + let id = image(&mut s, "gray", gray(false)); + s.execute("paint.setFill", &json!({"color": "#336699"})).unwrap(); + s.execute("select.none", &json!({})).unwrap(); + s.execute("paint.setFill", &json!({"color": "#ff0000"})).unwrap(); + s.execute("paint.setStroke", &json!({"color": "#00ff00"})).unwrap(); + s.execute("stroke.set", &json!({"weight": 5.0})).unwrap(); + s.execute("select.set", &json!({"ids": [id.0]})).unwrap(); + assert_eq!(s.paint.fill.color().map(|c| c.to_hex()), Some("#336699".into())); + assert_eq!((s.paint.stroke.clone(), s.paint.stroke_width), (Paint::None, 5.0)); +} + #[test] fn fill_proxy_invert_recolors_a_colorized_gray_image_and_undo_restores_it() { let mut s = session(); diff --git a/docs/mcp.md b/docs/mcp.md index 72c211b75..0a6df3443 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4199,8 +4199,8 @@ selected) on top of a template. With nothing selected, `stroke.set` / `stroke.se arrowheads, profile…) and `graphicStyle.apply` (every fill, stroke and effect, opacity, blend mode and the link to the style; `add: true` stacks it) set that template instead of editing art. With the Appearance panel's New Art Has Basic Appearance on (the default), a `select.*` command or `layer.target` that changes the selection loads the proxies and -the weight from it: only a paint or weight the selected objects share, and art without paint (a placed image) changes -nothing. With the option off (`appearance.setNewArtBasic {on: false}`, the preference `newArtBasic`) new art takes the +the weight from it: only a paint or weight the selected objects share, and art without paint (a placed image that isn't +colorized) changes nothing. With the option off (`appearance.setNewArtBasic {on: false}`, the preference `newArtBasic`) new art takes the whole appearance of the last selected object. `appearance.newArt` reports what the next object gets; `paint.default` resets it. `appearance.setNewArtBasic` rejects an `on` value that is not a boolean. Omitting `on` toggles the current preference.