mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 18:27:02 -06:00
Object > Rasterize, Crop Image, Flatten Transparency, Expand Appearance of raster effects and Vector Halftone draw the art with the renderer and replace it with the result. When the art held an image that could not be decoded, the renderer drew nothing for it, and the result lacked the image with no message. A raster export of the same art warns that "an image couldn't be decoded and is missing from the picture". These five commands now compare decode_failures before and after they draw, as raster exports do, and their replies carry that text as `warning`. They still make their result in one undo step. The app shows a command's `warning` in the status bar: after a menu command, after OK in a dialog with a preview, and after a tool runs a command (Crop Image's Apply). Docs: docs/mcp.md (Flatten Transparency, Expand Appearance, Document Raster Effects Settings, Vector halftone).
68 lines
3.0 KiB
Rust
68 lines
3.0 KiB
Rust
//! Object → Vector Halftone.
|
|
|
|
use serde_json::json;
|
|
|
|
use super::*;
|
|
use crate::tests_adjust::{fill_hex, node, red_rect, session};
|
|
use crate::tests_menucmds::{place_undecodable_png, warns_undecoded};
|
|
|
|
#[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}");
|
|
}
|
|
}
|