mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 02:31:22 +00:00
Outline Stroke: random Transform effects keep the values the canvas drew
Random in a Transform effect takes its seed from the object's id. The copies of a gradient path that its Transform effect makes were given new ids before their strokes were outlined, so a random Transform on the stroke moved each outline elsewhere than the canvas drew it. The copies are now outlined first and given new ids after. A fill with a random Transform effect stayed on a copy of the path with a new id, so it drew elsewhere too. It now paints the outline its geometry effects make of the path, on a copy of its own. outlines_keep_the_random_transforms_the_canvas_drew checks Outline Stroke, Expand and Flatten Transparency against the canvas for both cases and for a random Transform that makes the copies.
This commit is contained in:
@@ -366,7 +366,7 @@ pub(crate) fn reshaped(l: &Node) -> Option<Node> {
|
||||
/// Path `l` (outline `path`, box `bounds`) as its fill `item` paints it on the canvas, with its
|
||||
/// outline and box: when the fill has visible geometry effects, `l` with the outline they make of
|
||||
/// `path` as a plain path, and that outline's box.
|
||||
fn fill_shape(l: &Node, item: &AppearanceItem, path: &PathData, bounds: Rect) -> Option<(Node, PathData, Rect)> {
|
||||
pub(crate) fn fill_shape(l: &Node, item: &AppearanceItem, path: &PathData, bounds: Rect) -> Option<(Node, PathData, Rect)> {
|
||||
use vectorcraft_render::effects::{GeomContext, apply_geometry_with, has_geometry};
|
||||
if !has_geometry(item.effects()) {
|
||||
return Some((l.clone(), path.clone(), bounds));
|
||||
@@ -595,10 +595,13 @@ fn moves_only(a: Affine) -> bool {
|
||||
(m0 - 1.0).abs() <= eps && m1.abs() <= eps && m2.abs() <= eps && (m3 - 1.0).abs() <= eps
|
||||
}
|
||||
|
||||
/// Is `e` a visible Transform effect with Random on (which takes its seed from the object's id)?
|
||||
pub(crate) fn seeded(e: &vectorcraft_doc::Effect) -> bool {
|
||||
e.visible && e.id == "distort.transform" && e.params.get("random").and_then(Value::as_bool) == Some(true)
|
||||
}
|
||||
|
||||
/// Does `n` have a visible Transform effect with Random on, its own or a fill's or stroke's?
|
||||
fn seeded_here(n: &Node) -> bool {
|
||||
let seeded =
|
||||
|e: &vectorcraft_doc::Effect| e.visible && e.id == "distort.transform" && e.params.get("random").and_then(Value::as_bool) == Some(true);
|
||||
n.appearance.effects.iter().any(seeded) || n.appearance.items.iter().any(|i| i.effects().iter().any(seeded))
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use vectorcraft_pathops as po;
|
||||
use vectorcraft_pathops::{BoolOp, PathfinderOp};
|
||||
|
||||
use super::edit::selected_roots;
|
||||
use super::expand::reshaped;
|
||||
use super::expand::{fill_shape, reshaped, seeded};
|
||||
use super::*;
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
@@ -644,8 +644,11 @@ pub(crate) fn outlined_stroke(
|
||||
/// the path, each stroke becomes its outline (or brush art), grouped when there are several.
|
||||
/// As on the canvas, the path's geometry effects ([`reshaped`]) shape the path first, and each
|
||||
/// stroke's own geometry effects then shape the path that stroke outlines; [`outlined_stroke`]
|
||||
/// gets the stroke without them. A path whose Transform effect copies it with a gradient becomes
|
||||
/// the art the canvas draws for it, each copy with its own gradient and its strokes outlined.
|
||||
/// gets the stroke without them. A fill with a random Transform effect paints the outline its
|
||||
/// geometry effects make of the path, on a copy of its own. A path whose Transform effect copies
|
||||
/// it with a gradient becomes the art the canvas draws for it, each copy with its own gradient and
|
||||
/// its strokes outlined before the art gets new ids. Random in a Transform effect takes its seed
|
||||
/// from the object's id, so these effects give the outline the values the canvas drew.
|
||||
/// `None` when `l` has no stroke to outline.
|
||||
pub(crate) fn outline_strokes(d: &mut Document, brushes: &[vectorcraft_brush::Brush], l: &Node) -> Option<Node> {
|
||||
use vectorcraft_render::effects::{GeomContext, apply_geometry_with, has_geometry, has_gradient_copies, is_geometry, reshape};
|
||||
@@ -653,9 +656,9 @@ pub(crate) fn outline_strokes(d: &mut Document, brushes: &[vectorcraft_brush::Br
|
||||
return None;
|
||||
}
|
||||
if has_gradient_copies(l) {
|
||||
let mut art = d.reid(&reshape(l, None)?);
|
||||
let mut art = reshape(l, None)?;
|
||||
outline_member_strokes(d, brushes, &mut art);
|
||||
return Some(art);
|
||||
return Some(d.reid(&art));
|
||||
}
|
||||
let reshaped = reshaped(l);
|
||||
let l = reshaped.as_ref().unwrap_or(l);
|
||||
@@ -665,7 +668,19 @@ pub(crate) fn outline_strokes(d: &mut Document, brushes: &[vectorcraft_brush::Br
|
||||
let mut fills: Vec<AppearanceItem> = vec![];
|
||||
for item in &l.appearance.items {
|
||||
match item {
|
||||
AppearanceItem::Fill(f) if f.visible && !f.paint.is_none() => fills.push(item.clone()),
|
||||
AppearanceItem::Fill(f) if f.visible && !f.paint.is_none() => {
|
||||
let shaped = f.effects.iter().any(seeded).then(|| path.bounds().and_then(|b| fill_shape(l, item, &path, b))).flatten();
|
||||
let Some((shape, ..)) = shaped else {
|
||||
fills.push(item.clone());
|
||||
continue;
|
||||
};
|
||||
if !fills.is_empty() {
|
||||
parts.push(styled_copy(d, l, std::mem::take(&mut fills)));
|
||||
}
|
||||
let mut plain = item.clone();
|
||||
plain.effects_mut().retain(|e| !is_geometry(&e.id));
|
||||
parts.push(styled_copy(d, &shape, vec![plain]));
|
||||
}
|
||||
AppearanceItem::Stroke(st) if outlinable(st) => {
|
||||
if !fills.is_empty() {
|
||||
parts.push(styled_copy(d, l, std::mem::take(&mut fills)));
|
||||
|
||||
@@ -303,6 +303,39 @@ fn each_transform_copy_keeps_its_own_gradient() {
|
||||
assert_eq!(differ, none_differ);
|
||||
}
|
||||
|
||||
/// Random in a Transform effect takes its seed from the object's id. The outline draws as the
|
||||
/// canvas drew the object with Random in a Transform effect that copies a gradient square, in a
|
||||
/// Transform effect on the stroke of such a square, and in a Transform effect on the fill of a
|
||||
/// plain square.
|
||||
#[test]
|
||||
fn outlines_keep_the_random_transforms_the_canvas_drew() {
|
||||
let random = json!({"moveH": 30, "moveV": 30, "rotate": 40, "random": true});
|
||||
let cases = [
|
||||
("random copies", json!({"gradient": {"kind": "linear"}}), json!({"moveH": 120, "rotate": 30, "copies": 1, "random": true}), None),
|
||||
("copies, random stroke", json!({"gradient": {"kind": "linear"}}), json!({"moveH": 120, "copies": 1}), Some(1)),
|
||||
("random fill", json!({"color": "#33aa55"}), json!({}), Some(0)),
|
||||
];
|
||||
let mut differ = vec![];
|
||||
for (case, fill, object, item) in &cases {
|
||||
for (command, p) in outlining() {
|
||||
let mut s = session();
|
||||
square_with(&mut s, fill.clone(), Value::Null, &[]);
|
||||
if object != &json!({}) {
|
||||
s.execute("effect.apply", &json!({"effect": "distort.transform", "params": object})).unwrap();
|
||||
}
|
||||
s.execute("stroke.set", &json!({"weight": 12})).unwrap();
|
||||
if let Some(item) = item {
|
||||
s.execute("effect.apply", &json!({"effect": "distort.transform", "params": random, "item": item})).unwrap();
|
||||
}
|
||||
let before = render(&s);
|
||||
outlined_by(&mut s, command, &p);
|
||||
differ.push((*case, command, differing(&before, &render(&s), 64)));
|
||||
}
|
||||
}
|
||||
let none_differ: Vec<_> = cases.iter().flat_map(|(case, ..)| outlining().map(|(command, _)| (*case, command, 0))).collect();
|
||||
assert_eq!(differ, none_differ);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn brushed_strokes_outline_to_their_brush_art() {
|
||||
let mut s = session();
|
||||
|
||||
Reference in New Issue
Block a user