Files
vectorcraft/crates/engine/src/tests/halftone.rs
T
dotCooCoo b967408a09 Vector Halftone: a parameter of the wrong type is refused
object.vectorHalftone read frequency, angle, invert, clip and
keepOriginal with readers that fall back to the default for a value of
the wrong type, so {"frequency": "60"} drew a 20 lpi screen. shape and
mode given as a non-string also fell back to circle and mono. Each of
these is now an error and the document is left as it was. A null
value, color included, still stands for the default.
2026-11-11 15:39:28 -08:00

94 lines
4.0 KiB
Rust

//! Object → Vector Halftone.
use serde_json::json;
use crate::tests_adjust::{fill_hex, node, red_rect, session};
use crate::tests_menucmds::{place_undecodable_png, warns_undecoded};
use crate::*;
#[test]
fn halftone_turns_art_into_dots_by_tone() {
let mut s = session();
let id = red_rect(&mut s);
s.execute("paint.setFill", &json!({"color": "#000000"})).unwrap();
let undo = s.doc().unwrap().history.undo.len();
let r = s.execute("object.vectorHalftone", &json!({"frequency": 18})).unwrap();
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1);
assert!(s.doc().unwrap().doc.node(id).is_none(), "the original is replaced");
let g = node(&s, NodeId(r["id"].as_u64().unwrap()));
let dots = r["dots"].as_u64().unwrap();
// 60 pt at 18 lpi (4 pt cells) turned 45°: about 225 cells, all black.
assert!((180..=320).contains(&dots), "{dots}");
let ch = g.children().unwrap();
assert_eq!(ch.len(), 1);
let (pd, b) = (ch[0].path_data().unwrap(), ch[0].geometric_bounds().unwrap());
assert!(pd.subpaths.len() as u64 >= dots / 2);
// Clipped to the art.
assert!(b.x0 >= 19.9 && b.y0 >= 19.9 && b.x1 <= 80.1 && b.y1 <= 80.1, "{b:?}");
}
#[test]
fn halftone_says_when_an_image_could_not_be_decoded() {
for mode in ["mono", "cmyk"] {
let mut s = session();
let id = red_rect(&mut s);
s.execute("paint.setFill", &json!({"color": "#000000"})).unwrap();
let cut = place_undecodable_png(&mut s);
s.execute("select.set", &json!({"ids": [id.0, cut.0]})).unwrap();
let r = s.execute("object.vectorHalftone", &json!({"frequency": 18, "mode": mode})).unwrap();
assert!(warns_undecoded(&r), "{mode}: {r}");
}
}
#[test]
fn halftone_options() {
let mut s = session();
let id = red_rect(&mut s);
// Red art in CMYK: magenta and yellow screens, no cyan or black.
let r = s.execute("object.vectorHalftone", &json!({"mode": "cmyk", "shape": "square", "keepOriginal": true})).unwrap();
assert!(s.doc().unwrap().doc.node(id).is_some(), "the original is kept");
let g = node(&s, NodeId(r["id"].as_u64().unwrap()));
let inks: Vec<String> = g.children().unwrap().iter().map(|c| fill_hex(c)).collect();
assert_eq!(inks.len(), 2, "{inks:?}");
assert!(g.children().unwrap().iter().all(|c| c.blend == vectorcraft_color::BlendMode::Multiply));
// Light art gets no dots; inverted, it does.
let mut s = session();
let id = red_rect(&mut s);
s.execute("paint.setFill", &json!({"color": "#ffffff"})).unwrap();
assert!(s.execute("object.vectorHalftone", &json!({})).is_err());
s.execute("select.set", &json!({"ids": [id.0]})).unwrap();
assert!(s.execute("object.vectorHalftone", &json!({"invert": true, "shape": "line"})).is_ok());
// Bad input is refused.
for bad in [json!({"frequency": 0}), json!({"shape": "star"}), json!({"mode": "rgb"}), json!({"color": "nope"})] {
let mut s = session();
red_rect(&mut s);
assert!(s.execute("object.vectorHalftone", &bad).is_err(), "{bad}");
}
}
#[test]
fn halftone_refuses_a_param_of_the_wrong_type() {
for bad in [
json!({"frequency": "60"}),
json!({"angle": "30"}),
json!({"shape": 1}),
json!({"mode": true}),
json!({"invert": "true"}),
json!({"clip": "false"}),
json!({"keepOriginal": 1}),
json!({"color": 0}),
] {
let mut s = session();
let id = red_rect(&mut s);
let before = s.doc().unwrap().doc.clone();
assert!(s.execute("object.vectorHalftone", &bad).is_err(), "{bad}");
assert_eq!(s.doc().unwrap().doc, before, "{bad} changed the document");
assert!(s.doc().unwrap().doc.node(id).is_some());
}
// Null stands for an omitted value.
let mut s = session();
red_rect(&mut s);
let p = json!({"frequency": null, "angle": null, "shape": null, "mode": null, "color": null, "invert": null, "clip": null, "keepOriginal": null});
assert!(s.execute("object.vectorHalftone", &p).is_ok());
}