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]