Merge pull request #4680 from storytold/feat/addon-color

Add-ons: Color Effects — live exposure, desaturate and invert, apply as filter, panel
This commit is contained in:
BFlat
2026-10-30 14:32:20 +07:00
committed by GitHub
43 changed files with 973 additions and 7 deletions
+6
View File
@@ -0,0 +1,6 @@
//! Effects of VectorCraft's built-in add-ons, one module per add-on.
//!
//! - [`color`]: Color Effects' live Exposure, Desaturate and Invert Colors, colour adjustments
//! evaluated with the others ([`crate::adjust`]).
pub mod color;
+132
View File
@@ -0,0 +1,132 @@
//! Color Effects (a built-in add-on): live Exposure, Desaturate and Invert Colors, in Effect › Color
//! Effects. They are colour adjustments ([`crate::is_adjustment`]): they recolour what an object
//! paints as the others do, in stack order with them.
use serde_json::{Value, json};
use crate::catalog::{EffectInfo, Lengths};
use crate::util::num;
/// Exposure: brighten or darken in stops of light, with an offset and a gamma.
pub const EXPOSURE: &str = "addon.color.exposure";
/// Desaturate: every colour to the grey of its lightness.
pub const DESATURATE: &str = "addon.color.desaturate";
/// Invert Colors: every colour to its RGB inverse.
pub const INVERT: &str = "addon.color.invert";
/// The add-on's effects, in menu order.
pub const EFFECTS: [&str; 3] = [DESATURATE, EXPOSURE, INVERT];
/// Is `id` one of the add-on's effects?
pub fn is_color_effect(id: &str) -> bool {
EFFECTS.contains(&id)
}
/// The add-on's catalogue entries.
pub(crate) fn catalog() -> Vec<EffectInfo> {
const MENU: &[&str] = &["Effect", "Color Effects"];
let e = |id, label, params, defaults| EffectInfo { id, label, menu: MENU, params, defaults, raster: false, lengths: Lengths::default() };
vec![
e(DESATURATE, "Desaturate", "{} turns every colour the object paints to the grey of its lightness (each colour keeps its model)", json!({})),
e(
EXPOSURE,
"Exposure…",
"{exposure: stops -20..20 (0; mostly the highlights: each stop doubles or halves the light), offset: -0.5..0.5 (0; lifts or sinks the shadows and midtones), gamma: 0.1..10 (1; above 1 lightens the midtones)} recolours as Brightness/Contrast does, in linear light",
json!({"exposure": 0.0, "offset": 0.0, "gamma": 1.0}),
),
e(INVERT, "Invert Colors", "{} turns every colour the object paints to its RGB inverse (each colour keeps its model)", json!({})),
]
}
/// One of the add-on's adjustments, its parameters normalised.
#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Adjust {
/// Linear light times `gain`, plus `offset`, to the power 1 / `gamma`.
Exposure {
gain: f32,
offset: f32,
gamma: f32,
},
Desaturate,
Invert,
}
impl Adjust {
pub(crate) fn parse(id: &str, p: &Value) -> Option<Self> {
Some(match id {
EXPOSURE => Adjust::Exposure {
gain: 2f64.powf(num(p, "exposure", 0.0).clamp(-20.0, 20.0)) as f32,
offset: num(p, "offset", 0.0).clamp(-0.5, 0.5) as f32,
gamma: num(p, "gamma", 1.0).clamp(0.1, 10.0) as f32,
},
DESATURATE => Adjust::Desaturate,
INVERT => Adjust::Invert,
_ => return None,
})
}
pub(crate) fn apply(&self, rgb: [f32; 3]) -> [f32; 3] {
match self {
Adjust::Exposure { gain, offset, gamma } => rgb.map(|v| to_srgb((to_linear(v) * gain + offset).max(0.0).powf(1.0 / gamma))),
Adjust::Desaturate => {
let [r, g, b] = rgb;
[(r.max(g).max(b) + r.min(g).min(b)) / 2.0; 3]
}
Adjust::Invert => rgb.map(|v| 1.0 - v),
}
}
}
fn to_linear(v: f32) -> f32 {
let v = v.clamp(0.0, 1.0);
if v <= 0.04045 { v / 12.92 } else { ((v + 0.055) / 1.055).powf(2.4) }
}
fn to_srgb(v: f32) -> f32 {
let v = v.clamp(0.0, 1.0);
if v <= 0.003_130_8 { v * 12.92 } else { 1.055 * v.powf(1.0 / 2.4) - 0.055 }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::color_map;
use vectorcraft_doc::Effect;
fn map(id: &str, params: Value) -> crate::ColorMap {
color_map(&[Effect { id: id.into(), params, visible: true }]).unwrap()
}
fn close(a: [f32; 3], b: [f32; 3]) -> bool {
a.iter().zip(b).all(|(x, y)| (x - y).abs() < 0.01)
}
#[test]
fn the_effects_are_colour_adjustments_in_their_own_menu() {
for id in EFFECTS {
assert!(crate::is_adjustment(id), "{id}");
assert_eq!(crate::effect_info(id).unwrap().menu, ["Effect", "Color Effects"]);
}
}
#[test]
fn exposure_works_in_stops_of_linear_light() {
let grey = [0.5; 3];
assert!(close(map(EXPOSURE, json!({})).apply_rgb(grey), grey), "neutral by default");
// One stop doubles the light: sRGB 0.5 (linear 0.214) becomes linear 0.428, sRGB 0.686.
assert!(close(map(EXPOSURE, json!({"exposure": 1})).apply_rgb(grey), [0.686; 3]));
assert!(close(map(EXPOSURE, json!({"exposure": -20})).apply_rgb([1.0; 3]), [0.0; 3]));
assert!(map(EXPOSURE, json!({"offset": 0.1})).apply_rgb([0.0; 3])[0] > 0.3, "the offset lifts the shadows");
assert!(map(EXPOSURE, json!({"gamma": 2})).apply_rgb(grey)[0] > 0.6);
assert!(close(map(EXPOSURE, json!({"exposure": 1e300, "gamma": 0})).apply_rgb(grey), [1.0; 3]), "out of range values are clamped");
}
#[test]
fn desaturate_and_invert() {
assert!(close(map(DESATURATE, json!({})).apply_rgb([1.0, 0.0, 0.0]), [0.5; 3]));
assert!(close(map(DESATURATE, json!({})).apply_rgb([1.0, 1.0, 0.0]), [0.5; 3]), "lightness is kept, not luminance");
assert!(close(map(INVERT, json!({})).apply_rgb([1.0, 0.25, 0.0]), [0.0, 0.75, 1.0]));
let both = map(INVERT, json!({})).then(&map(INVERT, json!({})));
assert!(close(both.apply_rgb([0.2, 0.4, 0.6]), [0.2, 0.4, 0.6]));
}
}
+5 -2
View File
@@ -27,7 +27,7 @@ pub const ADJUSTMENTS: [&str; 6] =
/// Is `id` a colour adjustment effect?
pub fn is_adjustment(id: &str) -> bool {
ADJUSTMENTS.contains(&id)
ADJUSTMENTS.contains(&id) || crate::addons::color::is_color_effect(id)
}
fn visible_adjustment(e: &Effect) -> bool {
@@ -88,6 +88,8 @@ enum Adjust {
ShiftToColor { target: [f32; 3], amount: f32, preserve: bool },
/// Warm/cool and magenta/green shifts, -1..1.
TemperatureTint { temperature: f32, tint: f32 },
/// One of the Color Effects add-on's.
Addon(crate::addons::color::Adjust),
}
/// The colour adjustments of an effect list, composed in order.
@@ -175,7 +177,7 @@ impl Adjust {
preserve: flag(p, "preserveLightness", true),
},
"adjust.temperatureTint" => Adjust::TemperatureTint { temperature: amount(p, "temperature"), tint: amount(p, "tint") },
_ => return None,
_ => Adjust::Addon(crate::addons::color::Adjust::parse(id, p)?),
})
}
@@ -225,6 +227,7 @@ impl Adjust {
let (t, m) = (temperature * 0.3, tint * 0.3);
[r * (1.0 + t), g * (1.0 - m), b * (1.0 - t)]
}
Adjust::Addon(a) => a.apply([r, g, b]),
}
}
}
+1
View File
@@ -791,6 +791,7 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
v.push(g(id, label, PATHFINDER, "{} (groups and layers: live Pathfinder over the members)", json!({})));
}
}
v.extend(crate::addons::color::catalog());
v.push(g(TRAP, "Trap…", PATHFINDER, trap::EFFECT_PARAMS_DOC, trap::effect_defaults()));
v.push(g(
CROP_MARKS,
+1
View File
@@ -34,6 +34,7 @@
//! anchored at the object's centre.
#![forbid(unsafe_code)]
pub mod addons;
mod adjust;
mod bake;
mod clip;
+4 -1
View File
@@ -2,10 +2,13 @@
//! with commands under `addon.<name>.*` (see `docs/mcp.md`).
mod cleanup;
mod color;
use super::CommandSpec;
/// The commands of every built-in add-on.
pub fn specs() -> Vec<CommandSpec> {
cleanup::specs()
let mut v = cleanup::specs();
v.extend(color::specs());
v
}
+49
View File
@@ -0,0 +1,49 @@
//! Color Effects (`addon.color.*`), a built-in add-on: its live effects (Exposure, Desaturate and
//! Invert Colors, in Effect › Color Effects) are the effects crate's; here, Apply as Filter writes
//! any colour adjustment into the colours themselves. Its panel and its submenu, Object › Plug-ins ›
//! Color Effects, are the UI's.
#[cfg(test)]
mod tests;
use serde_json::{Value, json};
use vectorcraft_render::effects;
use super::super::colorcmds::recolor;
use super::super::{CommandSpec, bad, checked_ids_or_selection, cmd, has_selection, text_param};
use crate::{Result, Session};
pub fn specs() -> Vec<CommandSpec> {
vec![cmd!(
"addon.color.filter",
"Apply as Filter",
["Object", "Plug-ins", "Color Effects"],
None,
"{effect: a colour adjustment's effect id (adjust.brightnessContrast, adjust.curves, adjust.hueSaturation, adjust.levels, adjust.shiftToColor, adjust.temperatureTint, addon.color.exposure, addon.color.desaturate or addon.color.invert), params?: {…} (as effect.apply; missing keys take the dialog defaults), fill?: true, stroke?: true, includeImages?: true, includePatterns?: true, ids?: [id…] (default: the selection)} recolour the objects and everything inside them as the live effect would draw them, but in their colours, as edit.colors.invert does (fills, strokes, type, gradient meshes, embedded images and pattern tiles), as one undo step; their effects stay → {changed}",
has_selection,
filter
)]
}
fn filter(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "addon.color.filter";
let id = text_param(p, "effect", C)?.ok_or_else(|| bad(C, "missing `effect`"))?;
if !effects::is_adjustment(id) {
return Err(bad(C, format!("`{id}` is not a colour adjustment")));
}
let params = match p.get("params") {
None | Some(Value::Null) => json!({}),
Some(v @ Value::Object(_)) => v.clone(),
Some(_) => return Err(bad(C, "`params` must be an object")),
};
let fx = effects::new_effect(id, &params).ok_or_else(|| bad(C, format!("unknown effect `{id}`")))?;
let ids = checked_ids_or_selection(s, p, C)?;
// Settings that change nothing still make the pass, which then leaves every colour as it was.
let Some(map) = effects::color_map(&[fx]) else { return Ok(json!({ "changed": 0 })) };
let mut given = p.clone();
if let Some(o) = given.as_object_mut() {
o.insert("ids".into(), json!(ids.iter().map(|i| i.0).collect::<Vec<_>>()));
}
let label = effects::effect_info(id).map_or("Apply as Filter", |e| e.label.trim_end_matches('…'));
recolor(s, &given, label, &|c| map.apply(c))
}
@@ -0,0 +1,89 @@
//! Color Effects: its live effects through effect.apply, and Apply as Filter.
use serde_json::json;
use vectorcraft_doc::NodeId;
use vectorcraft_geom::Rect;
use crate::Session;
/// A document with a selected red rectangle, unstroked.
fn red_rect() -> (Session, NodeId) {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
let id = NodeId(s.execute("shape.rectangle", &json!({"x": 20, "y": 20, "width": 60, "height": 60})).unwrap()["id"].as_u64().unwrap());
s.execute("paint.setFill", &json!({"color": "#ff0000"})).unwrap();
s.execute("paint.setStroke", &json!({"none": true})).unwrap();
(s, id)
}
fn fill_hex(s: &Session, id: NodeId) -> String {
s.doc().unwrap().doc.node(id).unwrap().appearance.fill_paint().color().unwrap().to_hex()
}
fn pixel(s: &Session) -> [u8; 4] {
vectorcraft_render::Renderer::new().render_region(&s.doc().unwrap().doc, Rect::new(0.0, 0.0, 100.0, 100.0), 1.0, true).pixel(50, 50)
}
#[test]
fn the_live_effects_draw_without_changing_the_colours() {
for (effect, params, drawn) in [
("addon.color.invert", json!({}), [0, 255, 255, 255]),
("addon.color.desaturate", json!({}), [128, 128, 128, 255]),
("addon.color.exposure", json!({"exposure": -20}), [0, 0, 0, 255]),
] {
let (mut s, id) = red_rect();
s.execute("effect.apply", &json!({"effect": effect, "params": params})).unwrap();
assert_eq!(fill_hex(&s, id), "#ff0000", "{effect}");
assert_eq!(pixel(&s), drawn, "{effect}");
}
}
#[test]
fn a_filter_recolours_the_object_as_one_undo_step() {
let (mut s, id) = red_rect();
let undo = s.doc().unwrap().history.undo.len();
let v = s.execute("addon.color.filter", &json!({"effect": "addon.color.invert"})).unwrap();
assert_eq!(v["changed"], 1);
assert_eq!(fill_hex(&s, id), "#00ffff");
let n = s.doc().unwrap().doc.node(id).unwrap().clone();
assert!(n.appearance.effects.is_empty(), "no effect is added");
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1);
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(fill_hex(&s, id), "#ff0000");
}
#[test]
fn a_filter_takes_any_colour_adjustment_with_its_params() {
let (mut s, id) = red_rect();
s.execute("addon.color.filter", &json!({"effect": "adjust.hueSaturation", "params": {"hue": 120}})).unwrap();
assert_eq!(fill_hex(&s, id), "#00ff00");
// Live effects already on the object stay live, on top of the new colours.
s.execute("effect.apply", &json!({"effect": "addon.color.invert"})).unwrap();
s.execute("addon.color.filter", &json!({"effect": "addon.color.desaturate", "ids": [id.0]})).unwrap();
assert_eq!(fill_hex(&s, id), "#808080");
assert_eq!(s.doc().unwrap().doc.node(id).unwrap().appearance.effects.len(), 1);
// The stroke only: the fill stays.
s.execute("addon.color.filter", &json!({"effect": "addon.color.invert", "fill": false})).unwrap();
assert_eq!(fill_hex(&s, id), "#808080");
}
#[test]
fn bad_requests_are_refused() {
let (mut s, id) = red_rect();
let undo = s.doc().unwrap().history.undo.len();
for p in [
json!({}),
json!({"effect": 3}),
json!({"effect": "distort.roughen"}),
json!({"effect": "adjust.nothing"}),
json!({"effect": "addon.color.invert", "params": [1]}),
json!({"effect": "addon.color.invert", "ids": [999_999]}),
json!({"effect": "addon.color.invert", "ids": ["x"]}),
] {
assert!(s.execute("addon.color.filter", &p).is_err(), "{p}");
}
assert_eq!(fill_hex(&s, id), "#ff0000");
assert_eq!(s.doc().unwrap().history.undo.len(), undo);
s.execute("select.none", &json!({})).unwrap();
assert!(s.execute("addon.color.filter", &json!({"effect": "addon.color.invert"})).is_err(), "nothing selected");
}
+2 -1
View File
@@ -2,10 +2,11 @@
//! Object › Plug-ins, and acts through its engine commands (`addon.<name>.*`).
pub mod cleanup;
pub mod color;
use crate::menus::Item;
/// The add-ons' submenus, then a separator: the head of Object › Plug-ins.
pub fn plugin_menu() -> Vec<Item> {
vec![Item::Sub("Artwork Cleanup", cleanup::menu()), Item::Sep]
vec![Item::Sub("Artwork Cleanup", cleanup::menu()), Item::Sub("Color Effects", color::menu()), Item::Sep]
}
+149
View File
@@ -0,0 +1,149 @@
//! Color Effects' panel and submenu (Object › Plug-ins › Color Effects). The panel picks a colour
//! adjustment and its values, then adds it to the selection as a live effect or applies it as a
//! filter, into the colours themselves (`addon.color.filter`). The submenu applies the add-on's
//! live effects (Effect › Color Effects has them too) and opens the panel.
use egui::Ui;
use serde_json::{Map, Value, json};
use crate::VectorcraftApp;
use crate::menus::Item;
use crate::panels::{empty_state, pstate, set_pstate};
use crate::widgets;
/// The panel's id (`window.panel {panel: "colorEffects"}`).
pub const ID: &str = "colorEffects";
/// Where the panel keeps its choice and values (panel state).
const STATE: &str = "color-effects";
/// A value of an adjustment: (parameter, label, range, decimals, default).
type Field = (&'static str, &'static str, (f64, f64), usize, f64);
/// The adjustments the panel offers: (effect, label, values). Curves and Shift to Color, which take
/// a curve or a colour, keep to their dialogs (Effect › Color Adjustments).
const ADJUSTMENTS: [(&str, &str, &[Field]); 7] = [
(
"adjust.brightnessContrast",
"Brightness/Contrast",
&[("brightness", "Brightness", (-100.0, 100.0), 0, 0.0), ("contrast", "Contrast", (-100.0, 100.0), 0, 0.0)],
),
(
"adjust.hueSaturation",
"Hue/Saturation",
&[
("hue", "Hue", (-180.0, 180.0), 0, 0.0),
("saturation", "Saturation", (-100.0, 100.0), 0, 0.0),
("lightness", "Lightness", (-100.0, 100.0), 0, 0.0),
],
),
(
"adjust.levels",
"Levels",
&[
("inputBlack", "Input Black", (0.0, 255.0), 0, 0.0),
("inputWhite", "Input White", (0.0, 255.0), 0, 255.0),
("gamma", "Gamma", (0.1, 10.0), 2, 1.0),
("outputBlack", "Output Black", (0.0, 255.0), 0, 0.0),
("outputWhite", "Output White", (0.0, 255.0), 0, 255.0),
],
),
(
"adjust.temperatureTint",
"Temperature/Tint",
&[("temperature", "Temperature", (-100.0, 100.0), 0, 0.0), ("tint", "Tint", (-100.0, 100.0), 0, 0.0)],
),
(
"addon.color.exposure",
"Exposure",
&[("exposure", "Exposure", (-20.0, 20.0), 2, 0.0), ("offset", "Offset", (-0.5, 0.5), 3, 0.0), ("gamma", "Gamma", (0.1, 10.0), 2, 1.0)],
),
("addon.color.desaturate", "Desaturate", &[]),
("addon.color.invert", "Invert Colors", &[]),
];
/// Adjustment `i` of [`ADJUSTMENTS`] (the first past the end).
fn adjustment(i: usize) -> (&'static str, &'static str, &'static [Field]) {
ADJUSTMENTS.get(i).copied().unwrap_or(ADJUSTMENTS[0])
}
/// Object › Plug-ins › Color Effects.
pub fn menu() -> Vec<Item> {
vec![
Item::Cmd("Color Effects Panel", "window.panel", json!({ "panel": ID })),
Item::Sep,
Item::Cmd("Exposure…", "effect.dialog", json!({ "effect": "addon.color.exposure" })),
Item::Cmd("Desaturate", "effect.apply", json!({ "effect": "addon.color.desaturate" })),
Item::Cmd("Invert Colors", "effect.apply", json!({ "effect": "addon.color.invert" })),
]
}
/// The panel's choice and the values set for each adjustment.
#[derive(Clone, Default)]
struct Picked {
/// Index in [`ADJUSTMENTS`].
index: usize,
/// Values set, by effect id then parameter.
values: Map<String, Value>,
}
impl Picked {
/// The params of the adjustment picked: its values, the defaults where none is set.
fn params(&self) -> (&'static str, Value) {
let (id, _, fields) = adjustment(self.index);
let set = self.values.get(id).and_then(Value::as_object);
let p: Map<String, Value> =
fields.iter().map(|(k, _, _, _, default)| (k.to_string(), set.and_then(|m| m.get(*k)).cloned().unwrap_or(json!(default)))).collect();
(id, Value::Object(p))
}
}
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
if app.session.active().is_none_or(|d| d.selection.is_empty()) {
empty_state(ui, "sun", tl!("No selection"), tl!("Select objects to adjust their colors."));
return;
}
let mut st: Picked = pstate(ui.ctx(), STATE);
let labels: Vec<&str> = ADJUSTMENTS.iter().map(|a| a.1).collect();
let (id, label, fields) = adjustment(st.index);
const LABEL_W: f32 = 84.0;
widgets::label_row(ui, tl!("Adjustment"), LABEL_W, |ui| {
if let Some(i) = widgets::dropdown(ui, "color-effects-pick", label, &labels, 150.0) {
st.index = i;
}
});
let (_, params) = st.params();
for (key, name, (min, max), decimals, _) in fields {
let v = params.get(*key).and_then(Value::as_f64);
widgets::label_row(ui, tl!(name), LABEL_W, |ui| {
if let Some(n) = widgets::range_field(ui, ("color-effects", id, key), v, *min..=*max, "", *decimals, 70.0) {
let set = st.values.entry(id).or_insert_with(|| json!({}));
if let Some(m) = set.as_object_mut() {
m.insert(key.to_string(), json!(n));
}
}
});
}
ui.add_space(4.0);
let (id, params) = st.params();
ui.horizontal(|ui| {
let run = |app: &mut VectorcraftApp, cmd: &str| {
if let Err(e) = app.run(cmd, json!({ "effect": id, "params": params })) {
app.status(e);
}
};
if widgets::flat_button(ui, tl!("Add as Effect"), 100.0).clicked() {
run(app, "effect.apply");
}
if widgets::flat_button(ui, tl!("Apply as Filter"), 100.0).clicked() {
run(app, "addon.color.filter");
}
if widgets::flat_button(ui, tl!("Reset"), 56.0).clicked() {
st.values.remove(id);
}
});
set_pstate(ui.ctx(), STATE, st);
}
#[cfg(test)]
mod tests;
+61
View File
@@ -0,0 +1,61 @@
use serde_json::json;
use vectorcraft_engine::Session;
use super::{ADJUSTMENTS, ID, Picked, STATE, show};
use crate::panels::set_pstate;
use crate::tests_labels::painted_text;
use crate::{VectorcraftApp, menus};
/// An app with a selected red rectangle.
fn app() -> VectorcraftApp {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 200, "height": 200})).unwrap();
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 40, "height": 40})).unwrap();
app.run("paint.setFill", json!({"color": "#ff0000"})).unwrap();
app
}
#[test]
fn the_menus_list_the_effects_and_open_the_panel() {
let mut app = app();
let entries = menus::menu_entries(&app);
let at = |path: &[&str], label: &str| entries.iter().find(|e| e.path == path && e.label == label);
let plugins = ["Object", "Plug-ins", "Color Effects"];
let item = at(&plugins, "Color Effects Panel").expect("listed");
for label in ["Exposure…", "Desaturate", "Invert Colors"] {
assert!(at(&plugins, label).is_some(), "Object › Plug-ins › Color Effects › {label}");
assert!(at(&["Effect", "Color Effects"], label).is_some(), "Effect › Color Effects › {label}");
}
// The existing submenus are as they were.
assert!(at(&["Effect", "Color Adjustments"], "Exposure…").is_none());
menus::invoke(&mut app, "window.panel", item.params.clone());
assert!(crate::panel_docking::showing(&app.ui, ID), "the panel opens");
}
#[test]
fn every_adjustment_offered_is_a_colour_adjustment_with_its_defaults() {
for (i, (id, _, fields)) in ADJUSTMENTS.iter().enumerate() {
assert!(vectorcraft_effects::is_adjustment(id), "{id}");
let defaults = vectorcraft_effects::effect_info(id).unwrap().defaults;
let (picked, params) = Picked { index: i, ..Picked::default() }.params();
assert_eq!(picked, *id);
for (key, ..) in fields.iter() {
assert_eq!(params[key], defaults[key], "{id} {key}");
}
}
}
#[test]
fn the_panel_shows_the_values_and_its_buttons() {
let mut app = app();
let exposure = ADJUSTMENTS.iter().position(|a| a.0 == "addon.color.exposure").unwrap();
let text = painted_text(&mut app, |app, ui| {
set_pstate(ui.ctx(), STATE, Picked { index: exposure, ..Picked::default() });
show(app, ui);
});
for s in ["Exposure", "Offset", "Gamma", "Add as Effect", "Apply as Filter"] {
assert!(text.contains(s), "{s} in {text}");
}
app.run("select.none", json!({})).unwrap();
assert!(painted_text(&mut app, show).contains("No selection"));
}
+2 -1
View File
@@ -161,7 +161,8 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
// Plug-in effects get fields from their parameter schema; colour adjustments their sliders.
let changed = match vectorcraft_plugins::effect::installed(&id) {
Some(plugin) => form::schema_fields(app, ui, d, plugin.manifest()),
None if vectorcraft_effects::is_adjustment(&id) => adjust_fields(ui, d),
// The Color Effects add-on's take the plain form of their parameters.
None if vectorcraft_effects::is_adjustment(&id) && !vectorcraft_effects::addons::color::is_color_effect(&id) => adjust_fields(ui, d),
None if id == GLASS => glass_fields(app, ui, d, GLASS),
None if id == TEXTURIZER => glass_fields(app, ui, d, TEXTURIZER),
None if id == TRANSFORM => transform_fields(ui, d, app.session.general_unit()),
+17
View File
@@ -5637,3 +5637,20 @@ anchor Points: Точки:
Simplify Anchors (Keep Shape) Опростяване на опорните точки (запазване на формата)
Deep Ungroup Разгрупиране на всички нива
Rejoin Path Pieces Повторно свързване на части от пътища
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Цветови ефекти
Color Effects Panel Панел „Цветови ефекти“
Exposure Експонация
Exposure… Експонация…
Desaturate Обезцветяване
Adjustment Корекция
Add as Effect Добавяне като ефект
Apply as Filter Прилагане като филтър
Select objects to adjust their colors. Изберете обекти, за да коригирате цветовете им.
Brightness/Contrast Яркост/Контраст
Hue/Saturation Оттенък/Наситеност
Temperature/Tint Температура/Нюанс
@msg `{id}` is not a colour adjustment `{id}` не е цветова корекция
@msg missing `effect` липсва `effect`
@msg `params` must be an object `params` трябва да бъде обект
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5738,3 +5738,20 @@ anchor Points: Punts:
Simplify Anchors (Keep Shape) Simplifica els punts d’ancoratge (mantén la forma)
Deep Ungroup Desagrupa tots els nivells
Rejoin Path Pieces Torna a unir els trossos de traçat
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Efectes de color
Color Effects Panel Tauler Efectes de color
Exposure Exposició
Exposure… Exposició…
Desaturate Dessaturar
Adjustment Ajust
Add as Effect Afegeix com a efecte
Apply as Filter Aplica com a filtre
Select objects to adjust their colors. Seleccioneu objectes per ajustar-ne els colors.
Brightness/Contrast Lluentor/Contrast
Hue/Saturation To/saturació
Temperature/Tint Temperatura/To
@msg `{id}` is not a colour adjustment `{id}` no és un ajust de color
@msg missing `effect` falta `effect`
@msg `params` must be an object `params` ha de ser un objecte
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5747,3 +5747,20 @@ anchor Points: Body:
Simplify Anchors (Keep Shape) Zjednodušit kotevní body (zachovat tvar)
Deep Ungroup Oddělit všechny úrovně
Rejoin Path Pieces Znovu spojit části cest
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Barevné efekty
Color Effects Panel Panel Barevné efekty
Exposure Expozice
Exposure… Expozice…
Desaturate Odbarvit
Adjustment Úprava
Add as Effect Přidat jako efekt
Apply as Filter Použít jako filtr
Select objects to adjust their colors. Vyberte objekty, jejichž barvy chcete upravit.
Brightness/Contrast Jas/kontrast
Hue/Saturation Odstín/sytost
Temperature/Tint Teplota/nádech
@msg `{id}` is not a colour adjustment `{id}` není úprava barev
@msg missing `effect` chybí `effect`
@msg `params` must be an object `params` musí být objekt
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5652,3 +5652,20 @@ anchor Points: Punkter:
Simplify Anchors (Keep Shape) Forenkl ankerpunkter (bevar form)
Deep Ungroup Afgruppér alle niveauer
Rejoin Path Pieces Forbind kurvestykker igen
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Farveeffekter
Color Effects Panel Panelet Farveeffekter
Exposure Eksponering
Exposure… Eksponering…
Desaturate Afmæt
Adjustment Justering
Add as Effect Tilføj som effekt
Apply as Filter Anvend som filter
Select objects to adjust their colors. Markér objekter for at justere deres farver.
Brightness/Contrast Lysstyrke/kontrast
Hue/Saturation Farvetone/mætning
Temperature/Tint Temperatur/toning
@msg `{id}` is not a colour adjustment `{id}` er ikke en farvejustering
@msg missing `effect` `effect` mangler
@msg `params` must be an object `params` skal være et objekt
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5329,3 +5329,20 @@ anchor Points: Punkte:
Simplify Anchors (Keep Shape) Ankerpunkte vereinfachen (Form erhalten)
Deep Ungroup Alle Gruppierungsebenen aufheben
Rejoin Path Pieces Pfadstücke wieder zusammenfügen
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Farbeffekte
Color Effects Panel Farbeffekte-Bedienfeld
Exposure Belichtung
Exposure… Belichtung…
Desaturate Sättigung verringern
Adjustment Korrektur
Add as Effect Als Effekt hinzufügen
Apply as Filter Als Filter anwenden
Select objects to adjust their colors. Wählen Sie Objekte aus, um ihre Farben anzupassen.
Brightness/Contrast Helligkeit/Kontrast
Hue/Saturation Farbton/Sättigung
Temperature/Tint Temperatur/Farbstich
@msg `{id}` is not a colour adjustment `{id}` ist keine Farbkorrektur
@msg missing `effect` `effect` fehlt
@msg `params` must be an object `params` muss ein Objekt sein
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5699,3 +5699,20 @@ anchor Points: Σημεία:
Simplify Anchors (Keep Shape) Απλοποίηση σημείων αγκύρωσης (διατήρηση σχήματος)
Deep Ungroup Κατάργηση ομαδοποίησης σε όλα τα επίπεδα
Rejoin Path Pieces Επανένωση τμημάτων διαδρομών
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Εφέ χρώματος
Color Effects Panel Πλαίσιο Εφέ χρώματος
Exposure Έκθεση
Exposure… Έκθεση…
Desaturate Αποκορεσμός
Adjustment Διόρθωση
Add as Effect Προσθήκη ως εφέ
Apply as Filter Εφαρμογή ως φίλτρο
Select objects to adjust their colors. Επιλέξτε αντικείμενα για να προσαρμόσετε τα χρώματά τους.
Brightness/Contrast Φωτεινότητα/Αντίθεση
Hue/Saturation Απόχρωση/Κορεσμός
Temperature/Tint Θερμοκρασία/Απόχρωση
@msg `{id}` is not a colour adjustment Το `{id}` δεν είναι διόρθωση χρώματος
@msg missing `effect` λείπει το `effect`
@msg `params` must be an object Το `params` πρέπει να είναι αντικείμενο
Can't render this file because it contains an unexpected character in line 164 and column 5.
+17
View File
@@ -5689,3 +5689,20 @@ anchor Points: Puntos:
Simplify Anchors (Keep Shape) Simplificar puntos de ancla (mantener forma)
Deep Ungroup Desagrupar todos los niveles
Rejoin Path Pieces Volver a unir fragmentos de trazado
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Efectos de color
Color Effects Panel Panel Efectos de color
Exposure Exposición
Exposure… Exposición…
Desaturate Desaturar
Adjustment Ajuste
Add as Effect Añadir como efecto
Apply as Filter Aplicar como filtro
Select objects to adjust their colors. Seleccione objetos para ajustar sus colores.
Brightness/Contrast Brillo/contraste
Hue/Saturation Tono/saturación
Temperature/Tint Temperatura/tinte
@msg `{id}` is not a colour adjustment `{id}` no es un ajuste de color
@msg missing `effect` falta `effect`
@msg `params` must be an object `params` debe ser un objeto
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5778,3 +5778,20 @@ anchor Points: Pisteet:
Simplify Anchors (Keep Shape) Yksinkertaista ankkuripisteet (säilytä muoto)
Deep Ungroup Pura kaikki ryhmitystasot
Rejoin Path Pieces Yhdistä polun palat uudelleen
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Väritehosteet
Color Effects Panel Väritehosteet-paneeli
Exposure Valotus
Exposure… Valotus…
Desaturate Poista värikylläisyys
Adjustment Säätö
Add as Effect Lisää tehosteena
Apply as Filter Käytä suodattimena
Select objects to adjust their colors. Valitse objektit, joiden värejä haluat säätää.
Brightness/Contrast Kirkkaus/kontrasti
Hue/Saturation Sävy/kylläisyys
Temperature/Tint Lämpötila/sävy
@msg `{id}` is not a colour adjustment `{id}` ei ole värisäätö
@msg missing `effect` `effect` puuttuu
@msg `params` must be an object `params` on oltava objekti
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5746,3 +5746,20 @@ anchor Points: Points :
Simplify Anchors (Keep Shape) Simplifier les points d’ancrage (conserver la forme)
Deep Ungroup Dissocier tous les niveaux
Rejoin Path Pieces Rejoindre les morceaux de tracé
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Effets de couleur
Color Effects Panel Panneau Effets de couleur
Exposure Exposition
Exposure… Exposition…
Desaturate Désaturer
Adjustment Réglage
Add as Effect Ajouter comme effet
Apply as Filter Appliquer comme filtre
Select objects to adjust their colors. Sélectionnez des objets pour régler leurs couleurs.
Brightness/Contrast Luminosité/contraste
Hue/Saturation Teinte/saturation
Temperature/Tint Température/teinte
@msg `{id}` is not a colour adjustment `{id}` n'est pas un réglage de couleur
@msg missing `effect` `effect` manquant
@msg `params` must be an object `params` doit être un objet
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5733,3 +5733,20 @@ anchor Points: Pontok:
Simplify Anchors (Keep Shape) Horgonypontok egyszerűsítése (alak megtartása)
Deep Ungroup Csoportbontás minden szinten
Rejoin Path Pieces Útvonaldarabok újraegyesítése
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Színeffektusok
Color Effects Panel Színeffektusok panel
Exposure Expozíció
Exposure… Expozíció…
Desaturate Telítettség csökkentése
Adjustment Korrekció
Add as Effect Hozzáadás effektusként
Apply as Filter Alkalmazás szűrőként
Select objects to adjust their colors. Jelöljön ki objektumokat a színeik módosításához.
Brightness/Contrast Fényesség/kontraszt
Hue/Saturation Színárnyalat/telítettség
Temperature/Tint Színhőmérséklet/tónus
@msg `{id}` is not a colour adjustment `{id}` nem színkorrekció
@msg missing `effect` hiányzik: `effect`
@msg `params` must be an object `params`: objektum kell
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5652,3 +5652,20 @@ anchor Points: Titik:
Simplify Anchors (Keep Shape) Sederhanakan titik jangkar (pertahankan bentuk)
Deep Ungroup Buka grup di semua tingkat
Rejoin Path Pieces Sambungkan kembali potongan jalur
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Efek Warna
Color Effects Panel Panel Efek Warna
Exposure Eksposur
Exposure… Eksposur…
Desaturate Hilangkan Saturasi
Adjustment Penyesuaian
Add as Effect Tambahkan sebagai Efek
Apply as Filter Terapkan sebagai Filter
Select objects to adjust their colors. Pilih objek untuk menyesuaikan warnanya.
Brightness/Contrast Kecerahan/kontras
Hue/Saturation Rona/saturasi
Temperature/Tint Suhu/tint
@msg `{id}` is not a colour adjustment `{id}` bukan penyesuaian warna
@msg missing `effect` `effect` tidak ada
@msg `params` must be an object `params` harus berupa objek
Can't render this file because it contains an unexpected character in line 2169 and column 5.
+17
View File
@@ -5363,3 +5363,20 @@ anchor Points: Punti:
Simplify Anchors (Keep Shape) Semplifica punti di ancoraggio (mantieni forma)
Deep Ungroup Separa tutti i livelli
Rejoin Path Pieces Ricongiungi parti di tracciato
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Effetti colore
Color Effects Panel Pannello Effetti colore
Exposure Esposizione
Exposure… Esposizione…
Desaturate Desatura
Adjustment Regolazione
Add as Effect Aggiungi come effetto
Apply as Filter Applica come filtro
Select objects to adjust their colors. Seleziona gli oggetti per regolarne i colori.
Brightness/Contrast Luminosità/contrasto
Hue/Saturation Tonalità/saturazione
Temperature/Tint Temperatura/tinta
@msg `{id}` is not a colour adjustment `{id}` non è una regolazione del colore
@msg missing `effect` manca `effect`
@msg `params` must be an object `params` deve essere un oggetto
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5432,3 +5432,20 @@ anchor Points: ポイント:
Simplify Anchors (Keep Shape) アンカーポイントを単純化(形状を維持)
Deep Ungroup すべての階層のグループ解除
Rejoin Path Pieces パスの断片を再連結
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects カラー効果
Color Effects Panel カラー効果パネル
Exposure 露光量
Exposure… 露光量…
Desaturate 彩度を下げる
Adjustment 補正
Add as Effect 効果として追加
Apply as Filter フィルターとして適用
Select objects to adjust their colors. カラーを補正するオブジェクトを選択してください。
Brightness/Contrast 明るさ・コントラスト
Hue/Saturation 色相・彩度
Temperature/Tint 色温度・色かぶり
@msg `{id}` is not a colour adjustment `{id}` はカラー補正ではありません
@msg missing `effect` `effect` が指定されていません
@msg `params` must be an object `params` にはオブジェクトを指定してください
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5736,3 +5736,20 @@ anchor Points: წერტილები:
Simplify Anchors (Keep Shape) საყრდენი წერტილების გამარტივება (ფორმის შენარჩუნებით)
Deep Ungroup დაჯგუფების მოხსნა ყველა დონეზე
Rejoin Path Pieces ტრაექტორიის ნაწილების ხელახლა შეერთება
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects ფერის ეფექტები
Color Effects Panel ფერის ეფექტების პანელი
Exposure ექსპოზიცია
Exposure… ექსპოზიცია…
Desaturate გაუფერულება
Adjustment კორექცია
Add as Effect ეფექტად დამატება
Apply as Filter ფილტრად გამოყენება
Select objects to adjust their colors. აირჩიეთ ობიექტები მათი ფერების დასარეგულირებლად.
Brightness/Contrast სიკაშკაშე/კონტრასტი
Hue/Saturation ფერის ტონი/გაჯერება
Temperature/Tint ტემპერატურა/ტონირება
@msg `{id}` is not a colour adjustment `{id}` ფერის კორექცია არ არის
@msg missing `effect` აკლია `effect`
@msg `params` must be an object `params` უნდა იყოს ობიექტი
Can't render this file because it is too large.
+17
View File
@@ -5326,3 +5326,20 @@ anchor Points: 포인트:
Simplify Anchors (Keep Shape) 앵커 포인트 단순화(모양 유지)
Deep Ungroup 모든 단계 그룹 해제
Rejoin Path Pieces 패스 조각 다시 연결
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects 색상 효과
Color Effects Panel 색상 효과 패널
Exposure 노출
Exposure… 노출…
Desaturate 채도 감소
Adjustment 조정
Add as Effect 효과로 추가
Apply as Filter 필터로 적용
Select objects to adjust their colors. 색상을 조정할 개체를 선택하십시오.
Brightness/Contrast 밝기/대비
Hue/Saturation 색조/채도
Temperature/Tint 색온도/색조
@msg `{id}` is not a colour adjustment `{id}`은(는) 색상 조정이 아닙니다
@msg missing `effect` `effect`이 없습니다
@msg `params` must be an object `params`는 개체여야 합니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5696,3 +5696,20 @@ anchor Points: Punten:
Simplify Anchors (Keep Shape) Ankerpunten vereenvoudigen (vorm behouden)
Deep Ungroup Alle groeperingsniveaus opheffen
Rejoin Path Pieces Padstukken opnieuw verbinden
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Kleureffecten
Color Effects Panel Deelvenster Kleureffecten
Exposure Belichting
Exposure… Belichting…
Desaturate Desatureren
Adjustment Aanpassing
Add as Effect Toevoegen als effect
Apply as Filter Toepassen als filter
Select objects to adjust their colors. Selecteer objecten om hun kleuren aan te passen.
Brightness/Contrast Helderheid/contrast
Hue/Saturation Kleurtoon/verzadiging
Temperature/Tint Temperatuur/tint
@msg `{id}` is not a colour adjustment `{id}` is geen kleuraanpassing
@msg missing `effect` `effect` ontbreekt
@msg `params` must be an object `params` moet een object zijn
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5601,3 +5601,20 @@ anchor Points: Punkty:
Simplify Anchors (Keep Shape) Uprość punkty kontrolne (zachowaj kształt)
Deep Ungroup Rozgrupuj wszystkie poziomy
Rejoin Path Pieces Ponownie połącz fragmenty ścieżek
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Efekty koloru
Color Effects Panel Panel Efekty koloru
Exposure Ekspozycja
Exposure… Ekspozycja…
Desaturate Usuń nasycenie
Adjustment Korekta
Add as Effect Dodaj jako efekt
Apply as Filter Zastosuj jako filtr
Select objects to adjust their colors. Zaznacz obiekty, aby skorygować ich kolory.
Brightness/Contrast Jasność/kontrast
Hue/Saturation Barwa/nasycenie
Temperature/Tint Temperatura/odcień
@msg `{id}` is not a colour adjustment `{id}` nie jest korektą koloru
@msg missing `effect` brak `effect`
@msg `params` must be an object `params` musi być obiektem
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5664,3 +5664,20 @@ anchor Points: Pontos:
Simplify Anchors (Keep Shape) Simplificar Pontos de Ancoragem (Manter Forma)
Deep Ungroup Desagrupar Todos os Níveis
Rejoin Path Pieces Reunir Partes de Caminho
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Efeitos de cor
Color Effects Panel Painel Efeitos de cor
Exposure Exposição
Exposure… Exposição…
Desaturate Dessaturar
Adjustment Ajuste
Add as Effect Adicionar como efeito
Apply as Filter Aplicar como filtro
Select objects to adjust their colors. Selecione objetos para ajustar suas cores.
Brightness/Contrast Brilho/Contraste
Hue/Saturation Matiz/Saturação
Temperature/Tint Temperatura/Tom
@msg `{id}` is not a colour adjustment `{id}` não é um ajuste de cor
@msg missing `effect` falta `effect`
@msg `params` must be an object `params` deve ser um objeto
Can't render this file because it contains an unexpected character in line 6 and column 47.
+17
View File
@@ -5725,3 +5725,20 @@ anchor Points: Точки:
Simplify Anchors (Keep Shape) Упростить опорные точки (сохранить форму)
Deep Ungroup Разгруппировать все уровни
Rejoin Path Pieces Соединить части контуров
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Цветовые эффекты
Color Effects Panel Панель «Цветовые эффекты»
Exposure Экспозиция
Exposure… Экспозиция…
Desaturate Обесцветить
Adjustment Коррекция
Add as Effect Добавить как эффект
Apply as Filter Применить как фильтр
Select objects to adjust their colors. Выделите объекты, чтобы настроить их цвета.
Brightness/Contrast Яркость/контраст
Hue/Saturation Оттенок/насыщенность
Temperature/Tint Температура/оттенок
@msg `{id}` is not a colour adjustment `{id}` не является цветокоррекцией
@msg missing `effect` отсутствует `effect`
@msg `params` must be an object `params` должно быть объектом
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5771,3 +5771,20 @@ anchor Points: Punkter:
Simplify Anchors (Keep Shape) Förenkla ankarpunkter (behåll formen)
Deep Ungroup Dela upp grupper på alla nivåer
Rejoin Path Pieces Foga samman banbitar igen
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Färgeffekter
Color Effects Panel Panelen Färgeffekter
Exposure Exponering
Exposure… Exponering…
Desaturate Avmätta
Adjustment Justering
Add as Effect Lägg till som effekt
Apply as Filter Använd som filter
Select objects to adjust their colors. Markera objekt för att justera deras färger.
Brightness/Contrast Ljusstyrka/kontrast
Hue/Saturation Nyans/mättnad
Temperature/Tint Temperatur/färgton
@msg `{id}` is not a colour adjustment `{id}` är ingen färgjustering
@msg missing `effect` `effect` saknas
@msg `params` must be an object `params` måste vara ett objekt
Can't render this file because it contains an unexpected character in line 5 and column 25.
+17
View File
@@ -5705,3 +5705,20 @@ anchor Points: Noktalar:
Simplify Anchors (Keep Shape) Bağlantı Noktalarını Basitleştir (Şekli Koru)
Deep Ungroup Tüm Düzeylerde Grubu Çöz
Rejoin Path Pieces Yol Parçalarını Yeniden Birleştir
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Renk Efektleri
Color Effects Panel Renk Efektleri Paneli
Exposure Pozlama
Exposure… Pozlama…
Desaturate Doygunluğu Kaldır
Adjustment Ayar
Add as Effect Efekt Olarak Ekle
Apply as Filter Filtre Olarak Uygula
Select objects to adjust their colors. Renklerini ayarlamak için nesneleri seçin.
Brightness/Contrast Parlaklık/Kontrast
Hue/Saturation Ton/Doygunluk
Temperature/Tint Sıcaklık/Renk Tonu
@msg `{id}` is not a colour adjustment `{id}` bir renk ayarı değil
@msg missing `effect` "isim" eksik
@msg `params` must be an object "gradyan" bir nesne olmalıdır
Can't render this file because it contains an unexpected character in line 5 and column 25.
+17
View File
@@ -5343,3 +5343,20 @@ anchor Points: Точки:
Simplify Anchors (Keep Shape) Спростити опорні точки (зберегти форму)
Deep Ungroup Розгрупувати всі рівні
Rejoin Path Pieces З’єднати частини контурів
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Колірні ефекти
Color Effects Panel Панель «Колірні ефекти»
Exposure Експозиція
Exposure… Експозиція…
Desaturate Знебарвити
Adjustment Корекція
Add as Effect Додати як ефект
Apply as Filter Застосувати як фільтр
Select objects to adjust their colors. Виділіть об’єкти, щоб налаштувати їхні кольори.
Brightness/Contrast Яскравість/контраст
Hue/Saturation Відтінок/насиченість
Temperature/Tint Температура/відтінок
@msg `{id}` is not a colour adjustment `{id}` не є корекцією кольору
@msg missing `effect` відсутня `effect`
@msg `params` must be an object `params` має бути об’єктом
Can't render this file because it contains an unexpected character in line 178 and column 5.
+17
View File
@@ -5345,3 +5345,20 @@ anchor Points: Điểm:
Simplify Anchors (Keep Shape) Đơn giản hóa điểm neo (giữ hình dạng)
Deep Ungroup Bỏ nhóm mọi cấp
Rejoin Path Pieces Nối lại các mảnh đường dẫn
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects Hiệu ứng màu
Color Effects Panel Bảng Hiệu ứng màu
Exposure Phơi sáng
Exposure… Phơi sáng…
Desaturate Khử bão hòa
Adjustment Điều chỉnh
Add as Effect Thêm làm hiệu ứng
Apply as Filter Áp dụng làm bộ lọc
Select objects to adjust their colors. Chọn đối tượng để điều chỉnh màu của chúng.
Brightness/Contrast Độ sáng/Tương phản
Hue/Saturation Sắc độ/Độ bão hòa
Temperature/Tint Nhiệt độ màu/Tông
@msg `{id}` is not a colour adjustment `{id}` không phải là điều chỉnh màu
@msg missing `effect` thiếu `effect`
@msg `params` must be an object `params` phải là một đối tượng
Can't render this file because it contains an unexpected character in line 200 and column 5.
+17
View File
@@ -5586,3 +5586,20 @@ anchor Points: 点:
Simplify Anchors (Keep Shape) 简化锚点(保持形状)
Deep Ungroup 取消所有层级编组
Rejoin Path Pieces 重新连接路径片段
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects 颜色效果
Color Effects Panel 颜色效果面板
Exposure 曝光度
Exposure… 曝光度…
Desaturate 去色
Adjustment 调整
Add as Effect 添加为效果
Apply as Filter 作为滤镜应用
Select objects to adjust their colors. 选择对象以调整其颜色。
Brightness/Contrast 亮度/对比
Hue/Saturation 色相/饱和度
Temperature/Tint 色温/色调
@msg `{id}` is not a colour adjustment `{id}` 不是颜色调整
@msg missing `effect` 缺少 `effect`
@msg `params` must be an object `params` 必须是对象
Can't render this file because it contains an unexpected character in line 2 and column 73.
+17
View File
@@ -5382,3 +5382,20 @@ anchor Points: 點:
Simplify Anchors (Keep Shape) 簡化錨點(保持形狀)
Deep Ungroup 解散所有層級群組
Rejoin Path Pieces 重新連接路徑片段
# Color Effects add-on (Object › Plug-ins › Color Effects, Effect › Color Effects): its effects, panel and messages.
Color Effects 顏色效果
Color Effects Panel 顏色效果面板
Exposure 曝光度
Exposure… 曝光度…
Desaturate 去除飽和度
Adjustment 調整
Add as Effect 加入為效果
Apply as Filter 套用為濾鏡
Select objects to adjust their colors. 選取物件以調整其顏色。
Brightness/Contrast 亮度/對比
Hue/Saturation 色相/飽和度
Temperature/Tint 色溫/色調
@msg `{id}` is not a colour adjustment `{id}` 不是顏色調整
@msg missing `effect` 未提供 `effect`
@msg `params` must be an object `params` 必須是物件
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -134,6 +134,7 @@ pub(super) fn effect_menu() -> Vec<Item> {
let order = [
"3D and Materials",
"Color Adjustments",
"Color Effects",
"Convert to Shape",
"Crop Marks",
"Distort & Transform",
+1
View File
@@ -180,6 +180,7 @@ pub fn show_icon_panel(app: &mut VectorcraftApp, ui: &mut Ui, id: &str) {
asset_export::ID => asset_export::show(app, ui),
css_properties::ID => css_properties::show(app, ui),
crate::addons::cleanup::ID => crate::addons::cleanup::show(app, ui),
crate::addons::color::ID => crate::addons::color::show(app, ui),
id if library_panel::parse(id).is_some() => library_panel::show(app, ui, id),
_ => {
dim_label(ui, tl!("This panel is on the roadmap (see the parity plan)."));
+1
View File
@@ -198,6 +198,7 @@ pub const ICON_PANELS: &[(&str, &str, &str)] = &[
(crate::panels::css_properties::ID, "CSS Properties", "globe"),
(crate::panels::variables::ID, "Variables", "chart-column"),
(crate::addons::cleanup::ID, "Artwork Cleanup", "sparkles"),
(crate::addons::color::ID, "Color Effects", "sun"),
];
/// Groups of icon panels separated by dividers in the collapsed column.
+28
View File
@@ -5708,3 +5708,31 @@ topmost piece.
{"name":"run_command","arguments":{"command":"addon.cleanup.fix","params":{"kinds":["strayPoints","needlessGroups"]}}}
{"name":"run_command","arguments":{"command":"addon.cleanup.simplify","params":{"tolerance":40,"protectCorners":true}}}
```
## Color Effects add-on
Color Effects is a built-in add-on: its effects are colour adjustments in Effect › Color Effects, its command is
`addon.color.filter`, and in the desktop app it also lives in Object › Plug-ins › Color Effects and in its panel
(`window.panel {panel: "colorEffects"}`).
Its live effects go on through `effect.apply` like any other and recolour what the object paints, in stack order with
the other colour adjustments (Effect › Color Adjustments); each colour keeps its colour model:
| Effect | Parameters | Does |
|---|---|---|
| `addon.color.exposure` (Exposure…) | `exposure` stops −20…20 (0), `offset` −0.5…0.5 (0), `gamma` 0.1…10 (1) | Scales the light in linear light (each stop doubles or halves it, mostly in the highlights), adds the offset (shadows and midtones) and applies the gamma |
| `addon.color.desaturate` (Desaturate) | none | Turns every colour to the grey of its lightness |
| `addon.color.invert` (Invert Colors) | none | Turns every colour to its RGB inverse |
`addon.color.filter {effect, params?, fill?, stroke?, includeImages?, includePatterns?, ids?}` applies any colour
adjustment, the add-on's or one of Effect › Color Adjustments', to the colours themselves instead of as a live effect:
fills, strokes, type, gradient meshes, embedded images and pattern tiles, as `edit.colors.invert` reaches them, as one
undo step. Missing `params` take the effect's dialog defaults; the objects' own effects stay as they are.
```json
{"name":"run_command","arguments":{"command":"effect.apply","params":{"effect":"addon.color.exposure","params":{"exposure":1.5}}}}
{"name":"run_command","arguments":{"command":"addon.color.filter","params":{"effect":"adjust.hueSaturation","params":{"hue":120}}}}
```
The panel picks an adjustment (Brightness/Contrast, Hue/Saturation, Levels, Temperature/Tint, Exposure, Desaturate or
Invert Colors), sets its values, and then Add as Effect (`effect.apply`) or Apply as Filter (`addon.color.filter`).
+13 -1
View File
@@ -1,6 +1,6 @@
# Parity with Adobe Illustrator
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Artwork Cleanup, a built-in add-on beyond Illustrator; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Color Effects, a built-in add-on beyond Illustrator; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
## Feature submission evidence
@@ -1216,10 +1216,22 @@ Show, Fix Selected and Fix All (each one undo step), Deep Ungroup and Rejoin Pat
alone; Fix All as one step; scopes; rejoin within the gap only) and `addons::cleanup::tests` in `vectorcraft-ui-egui`
(the submenu opens the panel; Check lists what it finds). Commands: `docs/mcp.md` › Artwork Cleanup add-on.
## Color Effects add-on (2026-10-30)
A VectorCraft addition, not an Illustrator feature, so no area score moves. Effect › Color Effects adds live Exposure
(stops of linear light, offset and gamma), Desaturate and Invert Colors as colour adjustments evaluated with those of
Effect › Color Adjustments (`vectorcraft_effects::addons::color`), and `addon.color.filter` applies any colour adjustment
to the colours themselves through the Edit Colors pass. Object › Plug-ins › Color Effects and its panel reach them.
Tests: `addons::color` in `vectorcraft-effects` (exposure in stops, desaturate keeps lightness, invert twice is
identity), `cmd::addons::color::tests` in `vectorcraft-engine` (drawn live without changing the colours; a filter as one
undo step, with any adjustment and its params; bad requests refused) and `addons::color::tests` in
`vectorcraft-ui-egui` (the menus and the panel). Commands: `docs/mcp.md` › Color Effects add-on.
## Revision history
| Date | Change | Summary |
|---|---|---|
| 2026-10-30 | minor | Color Effects, a built-in add-on beyond Illustrator; scores and estimates unchanged |
| 2026-10-30 | minor | Artwork Cleanup add-on (a VectorCraft addition): evidence added; scores unchanged. |
| 2026-10-30 | minor | Hardware: a second finger cancels the tool press the first finger began, and a finger landing while another is down presses nothing, so a pinch only navigates (#4198); evidence added; scores unchanged (estimated). |
| 2026-10-30 | minor | File formats: SVG export writes axis-aligned rectangles as `<rect>`, circles and ellipses as `<circle>`/`<ellipse>`, straight lines as `<line>` and other straight-segment paths as `<polygon>`/`<polyline>`, keeping `<path>` where a shape element would draw differently (#4652); evidence added; scores and hours unchanged (estimated). |
+3 -1
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Artwork Cleanup add-on: its panel and Object › Plug-ins submenu; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (Color Effects add-on: its effects, panel and Object › Plug-ins submenu; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
## Feature submission evidence
@@ -322,6 +322,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
| Tabs panel: a stop drags from where the press landed, a stop just added included | done | #1458 |
| Outline view keeps template layers in Preview, dimmed | done | #1486; a picture placed to be traced stays visible under the outlines |
| Artwork Cleanup add-on (VectorCraft addition): Object › Plug-ins › Artwork Cleanup opens its panel, which checks the selection or the document for 16 kinds of issue, lists them with counts, and shows or fixes the picked rows or all of them, each fix one undo step; Simplify Anchors (Keep Shape), Rejoin Path Pieces and Deep Ungroup open their options first | done | `addon.cleanup.*`, `crates/ui-egui/src/addons/cleanup.rs` and its tests; not an Illustrator feature, so it is not scored |
| Color Effects add-on (VectorCraft addition): live Exposure, Desaturate and Invert Colors in Effect › Color Effects and Object › Plug-ins › Color Effects; its panel picks a colour adjustment and its values and adds it as a live effect or applies it as a filter, into the colours, as one undo step | done | `addon.color.*`, `crates/ui-egui/src/addons/color.rs` and its tests; not an Illustrator feature, so it is not scored |
## Look and feel
@@ -362,6 +363,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
| Date | Change | Summary |
|---|---|---|
| 2026-10-30 | minor | Color Effects add-on: its effects, panel and Object › Plug-ins submenu; scores and estimates unchanged |
| 2026-10-30 | minor | Artwork Cleanup add-on (a VectorCraft addition): its panel and Object › Plug-ins › Artwork Cleanup; scores and estimates unchanged |
| 2026-10-30 | minor | Spinners: a held arrow repeats after a pause (#4267); scores and estimates unchanged |
| 2026-10-30 | minor | Control bar: a Replace menu swaps the selected symbol instances' symbol (#4121); scores and estimates unchanged |