mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 15:57:49 +00:00
The Gradient panel's Method menu gets two methods of VectorCraft's own beside Classic and Perceptual. They mix a linear, radial, conic or diamond gradient's colors around the hue wheel: hue, saturation and brightness each change evenly from one stop to the next, and the hue angle keeps one direction, as the fixed-direction rules of https://www.w3.org/TR/css-color-4/#hue-interpolation do. Hue Clockwise lowers the angle (red, magenta, blue), which is clockwise on the color wheels, and Hue Counterclockwise raises it (red, yellow, green). Two stops whose hues are less than half a color level apart count as one hue and keep it, so a color and a tint or shade of it do not turn the whole wheel. A stop whose RGB numbers are less than half a color level apart is gray and takes the hue of the stop it mixes with, and black takes that stop's saturation too. - `paint.editGradient` and `paint.setFill` take `method: hue-clockwise` or `hue-counterclockwise`, in any case, with a space or an underscore for the hyphen or without it; other spellings are an error. The native file writes `HueClockwise` and `HueCounterclockwise`. - The canvas and raster exports draw the gradient through stops on its curve, within half a color level. - Reverse turns the method the other way with the stops, and a Reflect spread is repeated from the curve's stops, so both keep the colors. - SVG and PDF write the gradient as an RGB image, CMYK and spot color stops included, with the spot warning. EPS writes a shading in process colors with a warning for spot stops. A legacy .ai file and the .ai editing data get the colors between the stops as stops of their own, in process colors, at most 256, with a warning. CSS gets those stops too. - The `method` error names the four values, in every catalog. - The Method menu is as wide as its longest name in the interface language, within the panel. Docs: docs/mcp.md, docs/plugins.md, docs/target-app-parity.md, ROADMAP.
118 lines
4.7 KiB
Rust
118 lines
4.7 KiB
Rust
//! Command params written by tools and panels: the inverse of the engine's paint parsers, so a
|
|
//! paint read from the document and sent back through a command arrives unchanged.
|
|
|
|
use serde_json::{Value, json};
|
|
use vectorcraft_color::{Color, Freeform, FreeformPoint, GradientPaint, GradientStop};
|
|
|
|
/// A colour as a `color` param in its own model: `[r,g,b]`, `{c,m,y,k}`, `{gray}` or `{l,a,b}`.
|
|
pub fn color_json(c: &Color) -> Value {
|
|
match *c {
|
|
Color::Rgb { r, g, b } => json!([r, g, b]),
|
|
Color::Cmyk { c, m, y, k } => json!({"c": c, "m": m, "y": y, "k": k}),
|
|
Color::Gray { k } => json!({"gray": k}),
|
|
Color::Lab { l, a, b } => json!({"l": l, "a": a, "b": b}),
|
|
}
|
|
}
|
|
|
|
/// Gradient stops as the `stops` param of `paint.editGradient` / a `gradient` paint (a linked
|
|
/// stop also carries its `swatch` and `tint` %).
|
|
pub fn stops_json(stops: &[GradientStop]) -> Value {
|
|
Value::Array(
|
|
stops
|
|
.iter()
|
|
.map(|s| {
|
|
let mut v = json!({"offset": s.offset, "color": color_json(&s.color), "opacity": s.opacity, "midpoint": s.midpoint});
|
|
if let Some(n) = &s.swatch {
|
|
v["swatch"] = json!(n);
|
|
v["tint"] = json!(s.tint * 100.0);
|
|
if let Some(p) = s.plates {
|
|
v["plates"] = json!(p);
|
|
}
|
|
}
|
|
v
|
|
})
|
|
.collect(),
|
|
)
|
|
}
|
|
|
|
/// A freeform point as its `paint.freeform.addPoint` / `freeform` param fields.
|
|
pub fn freeform_point_json(p: &FreeformPoint) -> Value {
|
|
json!({"at": [p.at.x, p.at.y], "color": color_json(&p.color), "opacity": p.opacity, "spread": p.spread})
|
|
}
|
|
|
|
/// A freeform gradient as the `freeform` param of a `gradient` paint: points, lines and Draw mode.
|
|
pub fn freeform_json(f: &Freeform) -> Value {
|
|
json!({
|
|
"points": f.points.iter().map(freeform_point_json).collect::<Vec<_>>(),
|
|
"lines": f.lines,
|
|
"mode": f.mode.label().to_lowercase(),
|
|
})
|
|
}
|
|
|
|
/// A gradient paint as the `gradient` param of `paint.setFill` / `swatch.new`: kind, stops (with
|
|
/// opacity and midpoint), angle, the linked swatch and, once placed, the vector, aspect (%) and
|
|
/// focal point, and the freeform points.
|
|
pub fn gradient_params(g: &GradientPaint) -> Value {
|
|
let mut v = json!({
|
|
"kind": g.gradient.kind.label().to_lowercase(),
|
|
"angle": g.angle,
|
|
"stops": stops_json(&g.gradient.stops),
|
|
});
|
|
if let Some(geom) = g.geom {
|
|
v["start"] = json!([geom.start.x, geom.start.y]);
|
|
v["end"] = json!([geom.end.x, geom.end.y]);
|
|
v["aspect"] = json!(geom.aspect * 100.0);
|
|
if let Some(f) = geom.focal {
|
|
v["focal"] = json!([f.x, f.y]);
|
|
}
|
|
}
|
|
if let Some(s) = &g.swatch {
|
|
v["swatch"] = json!(s);
|
|
}
|
|
if let Some(f) = &g.freeform {
|
|
v["freeform"] = freeform_json(f);
|
|
}
|
|
if g.gradient.dither {
|
|
v["dither"] = json!(true);
|
|
}
|
|
if !g.gradient.method.is_classic() {
|
|
v["method"] = json!(g.gradient.method.id());
|
|
}
|
|
// A freeform gradient has no ends to spread past (`paint.setFill` refuses a spread for one).
|
|
if g.gradient.is_spread() {
|
|
v["spread"] = json!(g.gradient.spread.label().to_lowercase());
|
|
}
|
|
// A gradient that pads keeps its count for later.
|
|
if g.gradient.repeat_count() != 1 {
|
|
v["repeats"] = json!(g.gradient.repeat_count());
|
|
}
|
|
v
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use vectorcraft_color::{Gradient, GradientGeom, GradientKind};
|
|
use vectorcraft_geom::Point;
|
|
|
|
#[test]
|
|
fn gradient_params_carry_placement_only_when_placed() {
|
|
let mut g = GradientPaint::new(Gradient { kind: GradientKind::Radial, ..Default::default() });
|
|
let v = gradient_params(&g);
|
|
assert_eq!(v["kind"], "radial");
|
|
assert!(v.get("start").is_none() && v.get("aspect").is_none());
|
|
assert_eq!(v["stops"][0]["midpoint"], json!(0.5));
|
|
g.geom = Some(GradientGeom { start: Point::new(1.0, 2.0), end: Point::new(3.0, 4.0), aspect: 0.5, focal: None });
|
|
g.swatch = Some("Sky".into());
|
|
let v = gradient_params(&g);
|
|
assert_eq!((v["start"].clone(), v["end"].clone(), v["aspect"].clone()), (json!([1.0, 2.0]), json!([3.0, 4.0]), json!(50.0)));
|
|
assert_eq!(v["swatch"], "Sky");
|
|
assert!(v["stops"][0].get("swatch").is_none(), "unlinked stops carry no link");
|
|
g.gradient.stops[1].swatch = Some("Ink".into());
|
|
g.gradient.stops[1].tint = 0.25;
|
|
let v = gradient_params(&g);
|
|
assert_eq!((v["stops"][1]["swatch"].clone(), v["stops"][1]["tint"].clone()), (json!("Ink"), json!(25.0)));
|
|
assert_eq!(color_json(&Color::gray(0.25)), json!({"gray": 0.25}));
|
|
}
|
|
}
|