122 lines
4.4 KiB
Rust
122 lines
4.4 KiB
Rust
//! Live colour adjustments (Effect → Color Adjustments) as drawn: on paths, type, embedded images,
|
|
//! groups and symbol instances, alone and with other effects.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use serde_json::{Value, json};
|
|
use vectorcraft_color::{Color, Paint};
|
|
use vectorcraft_doc::text::{CharStyle, TextObject};
|
|
use vectorcraft_doc::{Appearance, Effect, ImageBlob, ImageObject, Node, NodeKind, Symbol};
|
|
use vectorcraft_geom::{Point, shapes};
|
|
|
|
use super::*;
|
|
|
|
fn fx(id: &str, params: Value) -> Effect {
|
|
Effect { id: id.into(), params, visible: true }
|
|
}
|
|
|
|
/// Hue +120°: red becomes green, green blue.
|
|
fn hue() -> Effect {
|
|
fx("adjust.hueSaturation", json!({"hue": 120}))
|
|
}
|
|
|
|
fn doc_with(nodes: Vec<Node>) -> Document {
|
|
let mut d = Document::new(100.0, 100.0);
|
|
let l = d.layers[0].id;
|
|
for mut n in nodes {
|
|
n.id = d.alloc_id();
|
|
d.insert(Some(l), 0, n).unwrap();
|
|
}
|
|
d
|
|
}
|
|
|
|
fn render(d: &Document) -> Rendered {
|
|
let mut r = Renderer::new();
|
|
r.threads = 0;
|
|
r.render(d, 100, 100, Affine::IDENTITY, &RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() })
|
|
}
|
|
|
|
fn red_square(x: f64, y: f64) -> Node {
|
|
Node::path(
|
|
NodeId(0),
|
|
shapes::rectangle(Rect::new(x, y, x + 30.0, y + 30.0)),
|
|
Appearance::basic(Paint::solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0),
|
|
)
|
|
}
|
|
|
|
fn red_image() -> (String, ImageBlob) {
|
|
let img = image::RgbaImage::from_pixel(30, 30, image::Rgba([255, 0, 0, 255]));
|
|
let mut png = vec![];
|
|
img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).unwrap();
|
|
("red".into(), ImageBlob::new("image/png", png))
|
|
}
|
|
|
|
#[test]
|
|
fn a_path_takes_its_adjusted_colour_and_keeps_its_other_effects() {
|
|
let mut n = red_square(10.0, 10.0);
|
|
n.appearance.effects = vec![hue(), fx("distort.transform", json!({"moveH": 40}))];
|
|
let img = render(&doc_with(vec![n]));
|
|
assert_eq!(img.pixel(65, 25), [0, 255, 0, 255], "green, and moved by the geometry effect");
|
|
assert_eq!(img.pixel(25, 25), [255, 255, 255, 255]);
|
|
// Hidden, it changes nothing.
|
|
let mut n = red_square(10.0, 10.0);
|
|
n.appearance.effects = vec![Effect { visible: false, ..hue() }];
|
|
assert_eq!(render(&doc_with(vec![n])).pixel(25, 25), [255, 0, 0, 255]);
|
|
}
|
|
|
|
#[test]
|
|
fn a_group_recolours_its_members_and_type() {
|
|
let style = CharStyle { size: 60.0, fill: Paint::solid(Color::rgb(1.0, 0.0, 0.0)), ..Default::default() };
|
|
let text = Node::new(NodeId(0), NodeKind::Text(Box::new(TextObject::point(Point::new(50.0, 90.0), "I", style))));
|
|
let mut g = Node::group(NodeId(0), vec![Arc::new(red_square(5.0, 5.0)), Arc::new(text)]);
|
|
g.appearance.effects.push(hue());
|
|
let d = doc_with(vec![g]);
|
|
let img = render(&d);
|
|
assert_eq!(img.pixel(20, 20), [0, 255, 0, 255]);
|
|
let inked: Vec<[u8; 4]> =
|
|
(50..70).flat_map(|x| (50..90).map(move |y| (x, y))).map(|(x, y)| img.pixel(x, y)).filter(|p| p[0] < 128 || p[2] < 128).collect();
|
|
assert!(inked.contains(&[0, 255, 0, 255]), "the type turns green too");
|
|
assert!(inked.iter().all(|p| p[1] == 255 && p[0] == p[2]), "no red left: {:?}", inked.iter().find(|p| p[1] != 255 || p[0] != p[2]));
|
|
}
|
|
|
|
#[test]
|
|
fn an_embedded_image_is_recoloured_and_the_original_pixels_stay() {
|
|
let mut d = doc_with(vec![]);
|
|
let (key, blob) = red_image();
|
|
d.images.insert(key.clone(), blob.clone());
|
|
let mut n = Node::new(
|
|
NodeId(0),
|
|
NodeKind::Image(Box::new(ImageObject {
|
|
key,
|
|
width: 30,
|
|
height: 30,
|
|
xf: Affine::translate((20.0, 20.0)),
|
|
link: None,
|
|
placement: Default::default(),
|
|
})),
|
|
);
|
|
n.appearance.effects.push(hue());
|
|
d = {
|
|
let l = d.layers[0].id;
|
|
n.id = d.alloc_id();
|
|
d.insert(Some(l), 0, n).unwrap();
|
|
d
|
|
};
|
|
let img = render(&d);
|
|
assert_eq!(img.pixel(35, 35), [0, 255, 0, 255]);
|
|
assert_eq!(d.images.get("red"), Some(&blob), "non-destructive");
|
|
}
|
|
|
|
#[test]
|
|
fn a_symbol_instance_recolours_its_art() {
|
|
let mut d = Document::new(100.0, 100.0);
|
|
let art = red_square(-15.0, -15.0);
|
|
d.symbols.push(Symbol { name: "Red".into(), art: Arc::new(art) });
|
|
let mut n = Node::new(NodeId(0), NodeKind::SymbolInstance { symbol: "Red".into(), xf: Affine::translate((50.0, 50.0)) });
|
|
n.appearance.effects.push(hue());
|
|
let l = d.layers[0].id;
|
|
n.id = d.alloc_id();
|
|
d.insert(Some(l), 0, n).unwrap();
|
|
assert_eq!(render(&d).pixel(50, 50), [0, 255, 0, 255]);
|
|
}
|