mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 17:40:14 -06:00
Recolor Artwork: a slider edit changes the color mode of its own row only
The color sliders now mark the row they edit with `model: "new"` instead of a dialog-wide flag, and `recolor.apply` reads a row's own `model` before the command's. Rows the sliders did not edit keep each color's model, so with RGB art and a CMYK color group, a slider edit of one row no longer turns the other rows' art CMYK. Another edit of the row's new color, Reset and rows reduced again remove the mark, and `recolor.randomize` moves it with its new color.
This commit is contained in:
@@ -263,6 +263,41 @@ struct Row {
|
||||
/// The tint (0..1) of a global or spot swatch the new color is (with `limitTo`): art linked to
|
||||
/// the swatch takes this share of the tint the method gives it.
|
||||
tint: f32,
|
||||
/// The row's own `model`, which takes the place of the command's for its colors.
|
||||
model: Option<ModelRule>,
|
||||
}
|
||||
|
||||
/// The color model a row's colors take (`model`): each color's own, the new color's, or the
|
||||
/// document's.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum ModelRule {
|
||||
Keep,
|
||||
New,
|
||||
Document,
|
||||
}
|
||||
|
||||
impl ModelRule {
|
||||
/// The `model` of `v` (a row or the command's parameters); a given `model` must be a string.
|
||||
fn param(v: &Value, cmd: &str) -> Result<Option<Self>> {
|
||||
match v.get("model") {
|
||||
None => Ok(None),
|
||||
Some(Value::String(m)) => match m.as_str() {
|
||||
"keep" => Ok(Some(Self::Keep)),
|
||||
"new" => Ok(Some(Self::New)),
|
||||
"document" => Ok(Some(Self::Document)),
|
||||
other => Err(bad(cmd, format!("unknown model `{other}` (keep, new or document)"))),
|
||||
},
|
||||
Some(other) => Err(bad(cmd, format!("bad model {other} (keep, new or document)"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn id(self) -> &'static str {
|
||||
match self {
|
||||
Self::Keep => "keep",
|
||||
Self::New => "new",
|
||||
Self::Document => "document",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Row {
|
||||
@@ -284,7 +319,7 @@ fn parse_rows(p: &Value, cmd: &str) -> Result<Vec<Row>> {
|
||||
let cur = |v: &Value| Match::of(v).ok_or_else(|| bad(cmd, format!("bad current colour {v}")));
|
||||
match p.get("map") {
|
||||
Some(Value::Object(m)) => {
|
||||
m.iter().map(|(k, v)| Ok(Row { from: vec![cur(&json!(k))?], to: new(Some(v))?, exclude: false, tint: 1.0 })).collect()
|
||||
m.iter().map(|(k, v)| Ok(Row { from: vec![cur(&json!(k))?], to: new(Some(v))?, exclude: false, tint: 1.0, model: None })).collect()
|
||||
}
|
||||
Some(Value::Array(rows)) => rows
|
||||
.iter()
|
||||
@@ -295,7 +330,8 @@ fn parse_rows(p: &Value, cmd: &str) -> Result<Vec<Row>> {
|
||||
None => return Err(bad(cmd, "each row needs `from`")),
|
||||
};
|
||||
let tint = (super::number_or(r, "tint", 100.0, cmd)? / 100.0).clamp(0.0, 1.0) as f32;
|
||||
Ok(Row { from, to: new(r.get("to"))?, exclude: switch_or(r, "exclude", false, cmd)?, tint })
|
||||
let model = ModelRule::param(r, cmd)?;
|
||||
Ok(Row { from, to: new(r.get("to"))?, exclude: switch_or(r, "exclude", false, cmd)?, tint, model })
|
||||
})
|
||||
.collect(),
|
||||
_ => Err(bad(cmd, "missing `map` (rows [{from, to}] or {current: new})")),
|
||||
@@ -338,8 +374,9 @@ struct ColorMap {
|
||||
|
||||
impl ColorMap {
|
||||
/// `links`: the swatch of each of `rows`' new colours (as many as `rows`, or none); `group`: the
|
||||
/// global swatches of the color group being rewritten.
|
||||
fn new(rows: &[Row], method: Method, own_model: bool, links: &[Option<Link>], group: Vec<GroupSwatch>) -> Self {
|
||||
/// global swatches of the color group being rewritten; `model`: the model rule of a row without
|
||||
/// its own.
|
||||
fn new(rows: &[Row], method: Method, model: ModelRule, links: &[Option<Link>], group: Vec<GroupSwatch>) -> Self {
|
||||
let mut out =
|
||||
Self { rows: vec![], links: vec![], tos: vec![], group, keys: HashMap::new(), hexes: HashMap::new(), converted: Default::default() };
|
||||
for (n, r) in rows.iter().enumerate().filter(|(_, r)| !r.from.is_empty()) {
|
||||
@@ -349,7 +386,7 @@ impl ColorMap {
|
||||
let link = links.get(n).cloned().flatten();
|
||||
// Colors that can't link to the swatch take the row's tint of it too.
|
||||
let mapped = link.as_ref().filter(|l| l.global && l.tint < 1.0).map_or(to, |l| to.tinted(l.tint));
|
||||
out.rows.push(RowMap::new(method, &colors, mapped).with_own_model(own_model));
|
||||
out.rows.push(RowMap::new(method, &colors, mapped).with_own_model(r.model.unwrap_or(model) != ModelRule::Keep));
|
||||
out.tos.push(to);
|
||||
out.links.push(link);
|
||||
for m in &r.from {
|
||||
@@ -453,26 +490,14 @@ fn apply(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let scope = Scope { fill: true, stroke: true, ..Scope::of(p) };
|
||||
let label = if ids.is_empty() { "Edit Color Group" } else { "Recolor Artwork" };
|
||||
// `model`: keep each colour's model (default), take the new colour's own, or the document's.
|
||||
// A given `model` must be a string; only a missing one means "keep".
|
||||
let model = match p.get("model") {
|
||||
None => "keep",
|
||||
Some(Value::String(m)) => m.as_str(),
|
||||
Some(other) => return Err(bad(C, format!("bad model {other} (keep, new or document)"))),
|
||||
};
|
||||
let mut doc_model = None;
|
||||
let own_model = match model {
|
||||
"keep" => false,
|
||||
"new" => true,
|
||||
"document" => {
|
||||
let m = s.doc()?.doc.color_mode.model();
|
||||
doc_model = Some(m);
|
||||
for r in &mut rows {
|
||||
r.to = r.to.map(|c| c.in_model(m));
|
||||
}
|
||||
true
|
||||
}
|
||||
other => return Err(bad(C, format!("unknown model `{other}` (keep, new or document)"))),
|
||||
};
|
||||
// A given `model` must be a string; only a missing one means "keep". A row's own `model`
|
||||
// takes its place for that row.
|
||||
let model = ModelRule::param(p, C)?.unwrap_or(ModelRule::Keep);
|
||||
let mode_model = s.doc()?.doc.color_mode.model();
|
||||
for r in rows.iter_mut().filter(|r| r.model.unwrap_or(model) == ModelRule::Document) {
|
||||
r.to = r.to.map(|c| c.in_model(mode_model));
|
||||
}
|
||||
let doc_model = (model == ModelRule::Document).then_some(mode_model);
|
||||
// The colours a colour group takes follow the model too (`document` converts them).
|
||||
let in_doc_model = |c: Color| doc_model.map_or(c, |m| c.in_model(m));
|
||||
let group_colors = match p.get("groupColors") {
|
||||
@@ -528,7 +553,7 @@ fn apply(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let map = ColorMap::new(&rows, method, own_model, &links, kept);
|
||||
let map = ColorMap::new(&rows, method, model, &links, kept);
|
||||
let f = |c: Color| map.map(c);
|
||||
let lf = |c: &mut Color, link: &mut Option<String>, tint: &mut f32| map.map_link(c, link, tint);
|
||||
let changed = Recolor::with_links(scope, &f, &lf).run(d, &ids);
|
||||
@@ -555,17 +580,18 @@ fn randomize(_: &mut Session, p: &Value) -> Result<Value> {
|
||||
let rows = parse_rows(p, C)?;
|
||||
let mut rng = Rng::new(super::menucmds::whole_or(p, "seed", 0, C)?);
|
||||
let (order, saturation_brightness) = (switch_or(p, "order", true, C)?, switch_or(p, "saturationBrightness", false, C)?);
|
||||
// Each new colour with its row's tint: a tint moves with its colour, and goes when the colour changes.
|
||||
let mut news: Vec<(Color, f32)> = rows.iter().filter_map(|r| r.new_color().map(|c| (c, r.tint))).collect();
|
||||
// Each new colour with its row's tint and model: a tint moves with its colour, and goes when
|
||||
// the colour changes; a model moves with its colour.
|
||||
let mut news: Vec<(Color, f32, Option<ModelRule>)> = rows.iter().filter_map(|r| r.new_color().map(|c| (c, r.tint, r.model))).collect();
|
||||
if order {
|
||||
rng.shuffle(&mut news);
|
||||
}
|
||||
if saturation_brightness {
|
||||
news = news
|
||||
.into_iter()
|
||||
.map(|(c, tint)| {
|
||||
.map(|(c, tint, model)| {
|
||||
let n = rng.saturation_brightness(c);
|
||||
(n, if ColorKey::of(&n) == ColorKey::of(&c) { tint } else { 1.0 })
|
||||
(n, if ColorKey::of(&n) == ColorKey::of(&c) { tint } else { 1.0 }, model)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
@@ -573,7 +599,8 @@ fn randomize(_: &mut Session, p: &Value) -> Result<Value> {
|
||||
let map: Vec<Value> = rows
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let (to, tint) = if r.new_color().is_some() { news.next().map_or((None, 1.0), |(c, t)| (Some(c), t)) } else { (r.to, r.tint) };
|
||||
let (to, tint, model) =
|
||||
if r.new_color().is_some() { news.next().map_or((None, 1.0, None), |(c, t, m)| (Some(c), t, m)) } else { (r.to, r.tint, r.model) };
|
||||
let mut row = json!({"from": r.from.iter().map(Match::to_json).collect::<Vec<_>>(), "to": to.as_ref().map(key_str)});
|
||||
if r.exclude {
|
||||
row["exclude"] = json!(true);
|
||||
@@ -581,6 +608,9 @@ fn randomize(_: &mut Session, p: &Value) -> Result<Value> {
|
||||
if tint < 1.0 {
|
||||
row["tint"] = json!(f64::from((tint * 10_000.0).round()) / 100.0);
|
||||
}
|
||||
if let Some(m) = model {
|
||||
row["model"] = json!(m.id());
|
||||
}
|
||||
row
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -624,6 +624,40 @@ fn model_param_must_be_a_string_when_given() {
|
||||
assert_eq!(fill(&s, art).to_hex(), "#1a1a1a", "nothing changed");
|
||||
}
|
||||
|
||||
/// A row's own `model` takes the place of the command's for that row's colors; a bad one fails
|
||||
/// before editing; `recolor.randomize` moves it with its new color.
|
||||
#[test]
|
||||
fn a_rows_model_applies_to_its_own_colors() {
|
||||
let mut s = session();
|
||||
let (a, b) = (rect(&mut s, json!("#ff00ff")), rect(&mut s, json!("#00ffff")));
|
||||
select_all(&mut s);
|
||||
let row = |from: &str, to: Value| json!({"from": [from], "to": to});
|
||||
let mut first = row("#ff00ff", json!({"c": 0, "m": 1, "y": 0, "k": 0}));
|
||||
first["model"] = json!("new");
|
||||
let map = json!([first, row("#00ffff", json!({"c": 1, "m": 0, "y": 0, "k": 0}))]);
|
||||
run(&mut s, "recolor.apply", json!({"map": map}));
|
||||
assert_eq!(fill(&s, a), Color::cmyk(0.0, 1.0, 0.0, 0.0), "the row with `new` takes the CMYK color");
|
||||
assert_eq!(fill(&s, b).model(), vectorcraft_color::cms::Model::Rgb, "the other row keeps RGB");
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
let mut keep = map.clone();
|
||||
keep[0]["model"] = json!("keep");
|
||||
run(&mut s, "recolor.apply", json!({"map": keep, "model": "new"}));
|
||||
assert_eq!(fill(&s, a).model(), vectorcraft_color::cms::Model::Rgb, "the row's `keep` wins over the command's `new`");
|
||||
assert_eq!(fill(&s, b), Color::cmyk(1.0, 0.0, 0.0, 0.0));
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
for bad in [json!("nope"), json!(42)] {
|
||||
let mut m = map.clone();
|
||||
m[0]["model"] = bad.clone();
|
||||
assert!(s.execute("recolor.apply", &json!({"map": m})).is_err(), "{bad}");
|
||||
}
|
||||
assert_eq!(fill(&s, a).to_hex(), "#ff00ff", "nothing changed");
|
||||
let r = run(&mut s, "recolor.randomize", json!({"map": map, "seed": 1}));
|
||||
let out = r["map"].as_array().unwrap();
|
||||
let moved = out.iter().find(|r| r.get("model").is_some()).expect("the model stays on a row");
|
||||
assert_eq!((&moved["to"], &moved["model"]), (&json!("cmyk 0 100 0 0"), &json!("new")), "{r}");
|
||||
assert_eq!(out.iter().filter(|r| r.get("model").is_some()).count(), 1, "{r}");
|
||||
}
|
||||
|
||||
/// Recolor Artwork's commands refuse a parameter of the wrong form, such as `limitTo: 5` or
|
||||
/// `includeImages: "false"`, with an error that names it; the art keeps its color and no undo
|
||||
/// step is recorded. A null counts as a value left out.
|
||||
|
||||
@@ -73,8 +73,6 @@ const SLIDER_MODES: [Mode; 4] = [Mode::Hsb, Mode::Rgb, Mode::Cmyk, Mode::Lab];
|
||||
/// Widths of a color slider and of its number field.
|
||||
const SLIDER_W: f32 = 170.0;
|
||||
const SLIDER_FIELD_W: f32 = 48.0;
|
||||
/// Set once the sliders have edited a new color in a mode: OK then sends `model: "new"`.
|
||||
const SLID: &str = "__slid";
|
||||
/// The slider values last shown: `{row, key, mode, v}` (HSB keeps its hue at no saturation).
|
||||
const COMPS: &str = "__comps";
|
||||
|
||||
@@ -180,7 +178,6 @@ fn sync(app: &mut VectorcraftApp, d: &mut Dialog) {
|
||||
d.fields.insert("rows".into(), Value::Array(rows));
|
||||
d.fields.insert("__sel".into(), json!([]));
|
||||
// The new rows hold no slider edit.
|
||||
d.fields.remove(SLID);
|
||||
d.fields.remove(COMPS);
|
||||
}
|
||||
Err(e) => app.status(e.to_string()),
|
||||
@@ -210,11 +207,13 @@ fn to_color(row: &Value) -> Option<Color> {
|
||||
color_value(&row["to"])
|
||||
}
|
||||
|
||||
/// Give `row` the new color `c`, without a tint of the spot color it had.
|
||||
/// Give `row` the new color `c`, without a tint of the spot color it had or the model a slider
|
||||
/// edit gave it.
|
||||
fn set_to(row: &mut Value, c: &Color) {
|
||||
row["to"] = key(c);
|
||||
if let Some(r) = row.as_object_mut() {
|
||||
r.remove("tint");
|
||||
r.remove("model");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,16 +221,19 @@ fn set_to(row: &mut Value, c: &Color) {
|
||||
const SHUFFLED: &str = "__shuffled";
|
||||
|
||||
/// Give each row of `rows` whose current colors and new color are those of the same row of
|
||||
/// `before` that row's tint again, when it has none: a change that snaps back to the same new
|
||||
/// color keeps the tint of it.
|
||||
/// `before` that row's tint and model again, when it has none: a change that snaps back to the
|
||||
/// same new color keeps the tint of it and the model a slider edit gave it.
|
||||
fn keep_tints(before: &[Value], rows: &mut [Value]) {
|
||||
for (r, b) in rows.iter_mut().zip(before) {
|
||||
if r["from"] == b["from"]
|
||||
&& r["to"] == b["to"]
|
||||
&& r.get("tint").is_none()
|
||||
&& let Some(t) = b.get("tint")
|
||||
{
|
||||
r["tint"] = t.clone();
|
||||
if r["from"] != b["from"] || r["to"] != b["to"] {
|
||||
continue;
|
||||
}
|
||||
for k in ["tint", "model"] {
|
||||
if r.get(k).is_none()
|
||||
&& let Some(v) = b.get(k)
|
||||
{
|
||||
r[k] = v.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,11 +258,8 @@ fn apply_params(d: &Dialog) -> Value {
|
||||
let rename = (!name.trim().is_empty() && name != g).then_some(name);
|
||||
json!({"group": g, "groupColors": colors, "rename": rename})
|
||||
});
|
||||
// A row edited with the sliders carries `model: "new"`: its art takes the mode they showed.
|
||||
let mut p = json!({"map": rs, "method": d.str("method"), "limitTo": d.str("limitTo")});
|
||||
// The art takes a color edited with the sliders in the mode they showed.
|
||||
if d.bool(SLID) {
|
||||
p["model"] = json!("new");
|
||||
}
|
||||
if let (Some(Value::Object(g)), Some(p)) = (group, p.as_object_mut()) {
|
||||
p.extend(g.into_iter().filter(|(_, v)| !v.is_null()));
|
||||
}
|
||||
@@ -522,8 +521,12 @@ fn assign_tab(app: &mut VectorcraftApp, ui: &mut Ui, d: &mut Dialog, rows: &mut
|
||||
}
|
||||
if widgets::icon_button(ui, "rotate-ccw", tl!("Reset the rows to the artwork's colors"), false, 24.0).clicked() {
|
||||
d.fields.remove("__reduced");
|
||||
d.fields.remove(SLID);
|
||||
d.fields.remove(COMPS);
|
||||
// The rows keep no slider edit, also when they cannot be reduced again.
|
||||
let saved = d.fields.get_mut("rows").and_then(Value::as_array_mut).into_iter().flatten();
|
||||
for r in rows.iter_mut().chain(saved).filter_map(Value::as_object_mut) {
|
||||
r.remove("model");
|
||||
}
|
||||
}
|
||||
let target = SliderTarget::of(app, d, rows);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| mode_menu(ui, d, &target));
|
||||
@@ -780,7 +783,7 @@ fn sliders(app: &VectorcraftApp, ui: &mut Ui, d: &mut Dialog, rows: &mut [Value]
|
||||
d.fields.insert("sliderMode".into(), json!(mode_id(mode)));
|
||||
set_to(&mut rows[i], &from_components(mode, &c2));
|
||||
d.fields.insert(COMPS.into(), json!({"row": i, "key": rows[i]["to"], "mode": mode_id(mode), "v": c2}));
|
||||
d.fields.insert(SLID.into(), json!(true));
|
||||
rows[i]["model"] = json!("new");
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -497,7 +497,7 @@ fn reset_after_a_slider_edit_keeps_the_arts_model() {
|
||||
set(h.state_mut(), "__sel", json!([0]));
|
||||
h.run_steps(2);
|
||||
click_slider(&mut h, "C", 0.5);
|
||||
assert_eq!(field(h.state(), "__slid"), json!(true), "the sliders edited the row");
|
||||
assert_eq!(field(h.state(), "rows")[0]["model"], json!("new"), "the sliders edited the row");
|
||||
h.get_by_role_and_label(egui::accesskit::Role::Button, "Reset the rows to the artwork's colors").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(field(h.state(), "rows")[0]["from"], json!(["rgb 255 0 255"]), "Reset reads the art's colors, not the preview's");
|
||||
@@ -518,17 +518,47 @@ fn a_new_color_count_after_a_slider_edit_keeps_the_arts_model() {
|
||||
set(h.state_mut(), "__sel", json!([0]));
|
||||
h.run_steps(2);
|
||||
click_slider(&mut h, "C", 0.5);
|
||||
assert_eq!(field(h.state(), "__slid"), json!(true), "the sliders edited the row");
|
||||
assert_eq!(field(h.state(), "rows")[0]["model"], json!("new"), "the sliders edited the row");
|
||||
set(h.state_mut(), "colors", json!(1));
|
||||
h.run_steps(3);
|
||||
assert_eq!(field(h.state(), "rows").as_array().map(Vec::len), Some(1), "the rows are rebuilt");
|
||||
assert_eq!(field(h.state(), "__slid"), Value::Null, "the slider edit is discarded");
|
||||
assert_eq!(field(h.state(), "rows")[0].get("model"), None, "the slider edit is discarded");
|
||||
h.get_by_label("OK").click();
|
||||
h.run_steps(2);
|
||||
assert!(h.state().ui.dialog.is_none());
|
||||
assert!(matches!(first_fill(h.state()).color(), Some(Color::Rgb { .. })), "the art stays RGB: {:?}", first_fill(h.state()));
|
||||
}
|
||||
|
||||
/// The fill of the `n`th object of the document.
|
||||
fn fill_of(app: &VectorcraftApp, n: usize) -> vectorcraft_color::Paint {
|
||||
let d = &app.session.doc().unwrap().doc;
|
||||
let id = d.layers[0].children().unwrap()[n].id;
|
||||
d.node(id).unwrap().appearance.fill_paint()
|
||||
}
|
||||
|
||||
/// A slider edit of one row gives only that row's art the slider's mode: with RGB art and a CMYK
|
||||
/// color group, OK makes the edited row's art CMYK and leaves the other row's art RGB.
|
||||
#[test]
|
||||
fn a_slider_edit_changes_the_model_of_its_own_row_only() {
|
||||
let mut app = app();
|
||||
app.run("swatch.newGroup", json!({"name": "Mix", "colors": ["cmyk 0 100 0 0", "cmyk 100 0 0 0"], "keepModel": true})).unwrap();
|
||||
art(&mut app, &["#ff00ff", "#00ffff"]);
|
||||
let mut h = assign_harness(app, json!({"group": "Mix"}));
|
||||
let rows = field(h.state(), "rows");
|
||||
let magenta = (0..2).find(|i| rows[*i]["from"] == json!(["rgb 255 0 255"])).expect("a row of the magenta art");
|
||||
set(h.state_mut(), "__sel", json!([magenta]));
|
||||
h.run_steps(2);
|
||||
click_slider(&mut h, "C", 0.5);
|
||||
let rows = field(h.state(), "rows");
|
||||
assert_eq!(rows[magenta]["model"], json!("new"), "the edited row: {rows}");
|
||||
assert_eq!(rows[1 - magenta].get("model"), None, "the other row: {rows}");
|
||||
h.get_by_label("OK").click();
|
||||
h.run_steps(2);
|
||||
assert!(h.state().ui.dialog.is_none());
|
||||
assert!(matches!(fill_of(h.state(), 0).color(), Some(Color::Cmyk { .. })), "the edited row's art is CMYK: {:?}", fill_of(h.state(), 0));
|
||||
assert!(matches!(fill_of(h.state(), 1).color(), Some(Color::Rgb { .. })), "the other row's art stays RGB: {:?}", fill_of(h.state(), 1));
|
||||
}
|
||||
|
||||
/// The dialog keeps its height when the tab or the sliders' color mode changes.
|
||||
#[test]
|
||||
fn switching_tabs_or_slider_modes_keeps_the_dialog_height() {
|
||||
|
||||
@@ -253,12 +253,13 @@ and spot colours exist; the dialog then stays open.
|
||||
|
||||
Recolor Artwork: `ui.recolorDialog {colors?, library?, group?}` opens the `recolor` dialog on the selected art
|
||||
(`colors`: a count for an n-colour job, or colours to assign as the new colours; `library`: Limit to Library, a library
|
||||
or `"document"`, `""` for the first library; `group`: a colour group, Edit or Apply Color Group). Fields: `rows` (`[{from: [keys], to: key, exclude?, tint?}]`
|
||||
or `"document"`, `""` for the first library; `group`: a colour group, Edit or Apply Color Group). Fields: `rows` (`[{from: [keys], to: key, exclude?, tint?, model?}]`
|
||||
as `recolor.reduce` returns them), `colors` (null for Auto), `method`, `preserveWhite`, `preserveBlack`,
|
||||
`preserveGrays`, `limitTo`, `groupName`, `tab` (`assign` or `edit`), `rule` (harmony id), `linked`, `preview`,
|
||||
`sliderMode` (the mode of the color sliders under the rows: `hsb`, `rgb`, `cmyk`, `lab`, or `tint` for a row whose new
|
||||
color is a spot color of the library; unset, they show the new color's own model). The sliders edit the first selected
|
||||
row's new color; once they have, the preview and confirm send `recolor.apply` with `model: "new"`. Setting
|
||||
row's new color and give that row `model: "new"`, so the preview and confirm give only that row's art the color in
|
||||
the sliders' mode; another edit of the row's new color, Reset and rows reduced again remove it. Setting
|
||||
`colors`, a preserve flag or `limitTo` reduces the rows again (also on `ui.dialog.confirm`). The dialog previews
|
||||
`recolor.apply` on the canvas; confirm keeps it as one undo step (with a group, the group is rewritten in the same
|
||||
step; with no art selected only the group changes). In the Swatches panel, double-clicking a colour group's folder,
|
||||
|
||||
+5
-4
@@ -1919,9 +1919,10 @@ tints of a global swatch share its row; White and Black are preserved by default
|
||||
`[{from: [keys], to: key}]` to edit and pass to `recolor.apply {map, method?, model?, limitTo?, group?, groupColors?, rename?}`.
|
||||
For scripts and MCP, `model` selects the replacement color model: `keep` (default) retains each
|
||||
original color's model, `new` takes the replacement's model, and `document` converts replacements
|
||||
and edited color groups to the document's model. Invalid values fail before editing. The Recolor
|
||||
Artwork dialog sends `new` once its color sliders have edited a row's new color in a color mode. A
|
||||
returned `note` reports a model conversion in `keep` mode.
|
||||
and edited color groups to the document's model. A row's own `model` (the same values) takes the
|
||||
place of the command's for that row's colors. Invalid values fail before editing. The Recolor
|
||||
Artwork dialog gives a row `model: "new"` when its color sliders edit the row's new color in a color
|
||||
mode. A returned `note` reports a model conversion in `keep` mode.
|
||||
|
||||
`method` says how a row's colours take its new colour: `exact`, `preserveTints` (tints of the row's darkest colour
|
||||
stay tints), `scaleTints` (every colour becomes a tint as light, relative to the darkest), `tintsShades` (lighter and
|
||||
@@ -1930,7 +1931,7 @@ the others turn by the same hue). `limitTo` snaps new colours to a swatch librar
|
||||
`recolor.randomize {map, order?, saturationBrightness?, seed?}` shuffles or varies new colours; a row with
|
||||
`exclude: true` keeps its colours. With `limitTo`, a row's `tint` (a percentage, default 100) gives the art
|
||||
linked to the global or spot swatch its new color is that share of its tint; `tint` must be a number. `recolor.randomize`
|
||||
moves a row's `tint` with its new color and removes it from a color it changes. `swatch.editGroup {group, colors, rename?}` rewrites a colour group in one undo
|
||||
moves a row's `tint` with its new color and removes it from a color it changes, and moves a row's `model` with its new color. `swatch.editGroup {group, colors, rename?}` rewrites a colour group in one undo
|
||||
step (art linked to its global swatches follows), and `recolor.apply` with `group` recolours the art and rewrites the
|
||||
group together. Art linked to one of the group's global swatches stays linked when the swatch is rewritten with its
|
||||
row's new color (the default without `groupColors` when the rows follow the group's order), so a later `swatch.edit` still
|
||||
|
||||
Reference in New Issue
Block a user