From d46284c52c389bccfa3ac497b01f6f248c5dc5ef Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 18:10:13 -0800 Subject: [PATCH] Recolor: Global Adjust gives linked tints of a rewritten group their adjusted color A tint linked to a global swatch of the color group that recolor.apply rewrites kept its link and showed the swatch's adjusted color at its tint, which differs from the same color adjusted on its own. It now keeps the link only when the two match, and otherwise takes the adjusted color, unlinked. --- crates/color/src/recolor.rs | 6 ++++++ crates/engine/src/cmd/recolor.rs | 9 +++++---- crates/engine/src/tests/recolor.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/crates/color/src/recolor.rs b/crates/color/src/recolor.rs index 53cb12478..e7cf22c41 100644 --- a/crates/color/src/recolor.rs +++ b/crates/color/src/recolor.rs @@ -244,6 +244,12 @@ pub fn is_tint(c: Color, base: Color) -> bool { a.iter().zip(b).all(|(x, y)| (x - y * t).abs() <= 0.01) } +/// Whether `a` and `b` carry the same ink in `a`'s model, within [`is_tint`]'s tolerance. +pub fn same_ink(a: Color, b: Color) -> bool { + let (x, y) = (ink(a, a.model()), ink(b, a.model())); + x.iter().zip(y).all(|(p, q)| (p - q).abs() <= 0.01) +} + /// `c` darkened by `f` (0..1) toward black: more K for CMYK and Gray, scaled RGB and Lab. pub fn shaded(c: Color, f: f32) -> Color { let f = f.clamp(0.0, 1.0); diff --git a/crates/engine/src/cmd/recolor.rs b/crates/engine/src/cmd/recolor.rs index 75b2cc73b..1759372d0 100644 --- a/crates/engine/src/cmd/recolor.rs +++ b/crates/engine/src/cmd/recolor.rs @@ -7,7 +7,7 @@ use std::collections::{BTreeSet, HashMap}; use serde_json::{Value, json}; -use vectorcraft_color::recolor::{ColorKey, GlobalAdjust, Method, Preserve, Rng, RowMap, cluster}; +use vectorcraft_color::recolor::{ColorKey, GlobalAdjust, Method, Preserve, Rng, RowMap, cluster, same_ink}; use vectorcraft_color::{Color, Paint, Swatch}; use vectorcraft_doc::swatches::{node_colors, node_paints}; use vectorcraft_doc::{Document, Node, NodeId, NodeKind}; @@ -452,7 +452,8 @@ impl ColorMap { /// stay unlinked), a /// process swatch gives its colour in its own model; a color linked to a global swatch of the /// rewritten group that takes the row's new color keeps its link (at its tint in the row) when - /// the row gives it the new color in that color's model; + /// the row gives it the new color in that color's model and the Global Adjust gives it the + /// swatch's new color at that tint; /// other rows map the colour, which loses its link when it changes or when the group's rewrite /// would change it. True when anything changed. fn map_link(&self, c: &mut Color, link: &mut Option, tint: &mut f32) -> bool { @@ -462,9 +463,9 @@ impl ColorMap { && let Some(g) = link.as_deref().and_then(|n| self.group.iter().find(|g| g.name == n)) && self.tos.get(i) == Some(&g.color) && !row.converts(*c) - && let Some(t) = row.tint(*c) + && let Some(t) = row.tint(*c).map(|t| t.clamp(0.0, 1.0)) + && (self.adjust.is_identity() || same_ink(self.adjust.apply(row.map(*c)), g.shown.tinted(t))) { - let t = t.clamp(0.0, 1.0); let shown = g.shown.tinted(t); let changed = *tint != t || *c != shown; (*c, *tint) = (shown, t); diff --git a/crates/engine/src/tests/recolor.rs b/crates/engine/src/tests/recolor.rs index cadf9314a..cff302503 100644 --- a/crates/engine/src/tests/recolor.rs +++ b/crates/engine/src/tests/recolor.rs @@ -812,6 +812,34 @@ fn global_adjust_with_a_library_and_a_group() { assert_eq!(c, fill(&s, art), "as the art is"); } +/// Art linked to a global swatch of the rewritten group takes the same Global Adjust as unlinked +/// art of its color: at 100% it keeps the link, and a tint whose adjusted color is not the +/// swatch's new color at that tint takes the adjusted color and loses the link. +#[test] +fn global_adjust_gives_linked_tints_of_a_group_the_adjusted_color() { + let mut s = session(); + run(&mut s, "swatch.newGroup", json!({"name": "Theme", "colors": ["#ff0000"]})); + let name = s.doc().unwrap().doc.swatch_groups.iter().find(|g| g.name == "Theme").unwrap().swatches[0].name.clone(); + run(&mut s, "swatch.edit", json!({"name": name, "global": true})); + let full = rect(&mut s, json!("#000000")); + let tint = rect(&mut s, json!("#000000")); + run(&mut s, "paint.setFill", json!({"ids": [full.0], "swatch": name})); + run(&mut s, "paint.setFill", json!({"ids": [tint.0], "swatch": name, "tint": 50})); + let link = |s: &Session, id: NodeId| match s.doc().unwrap().doc.node(id).unwrap().appearance.fill_paint() { + vectorcraft_color::Paint::Solid { color, swatch, tint } => (color, swatch, tint), + p => panic!("{p:?}"), + }; + select_all(&mut s); + let tint_key = vectorcraft_color::recolor::ColorKey::of(&fill(&s, tint)).to_string(); + let rows = json!([{"from": ["rgb 255 0 0", tint_key], "to": "#ff0000"}]); + run(&mut s, "recolor.apply", json!({"map": rows, "method": "scaleTints", "group": "Theme", "globalAdjust": {"brightness": -50}})); + let (c, w, t) = link(&s, full); + assert!(close(c, Color::rgb(0.5, 0.0, 0.0)) && w.as_deref() == Some(name.as_str()) && t == 1.0, "{c:?} {w:?} {t}"); + let (c, w, _) = link(&s, tint); + assert!(close(c, Color::rgb(0.5, 0.25, 0.25)), "the tint's own color, adjusted: {c:?}"); + assert_eq!(w, None, "the adjusted tint is not a tint of the swatch's new color"); +} + /// `globalAdjust` must be an object of numbers from -100 to 100; a refusal changes nothing and /// records no undo step. A null counts as left out. #[test]