mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 00:44:21 +00:00
Render: a filter layer's offscreen culls art and crops long strokes to its own area
On a multithreaded context, a drop shadow or glow drawn through a filter layer is drawn into an offscreen that covers the art plus the blur's spread, but its frame kept the view's visible rect. Art outside the view but within the spread was culled, so it cast no shadow, and a long stroke was outlined only near the view, so its far parts no longer fed the blur. The offscreen's frame now takes its visible rect from its own area, and the frame of content kept from an earlier split takes the window it is drawn over.
This commit is contained in:
@@ -54,7 +54,7 @@ impl Renderer {
|
||||
let split_frame;
|
||||
let (f, screen) = match content.kept.and_then(|k| k.frame.map(|frame| (k.window, frame))) {
|
||||
Some((window, (view, px))) => {
|
||||
split_frame = Frame { view, px, ..*f };
|
||||
split_frame = Frame { view, visible: window, px, ..*f };
|
||||
(&split_frame, view.transform_rect_bbox(window))
|
||||
}
|
||||
None => (f, Rect::new(0.0, 0.0, ctx.width() as f64, ctx.height() as f64)),
|
||||
@@ -580,7 +580,10 @@ impl Renderer {
|
||||
None => (x0, y0, w, h, k),
|
||||
};
|
||||
let mut off = f.offscreen_context(w, h);
|
||||
let shifted = Frame { mt: false, view: Affine::scale(k) * Affine::translate((-x0, -y0)) * f.view, px: f.px / k, ..*f };
|
||||
let view = Affine::scale(k) * Affine::translate((-x0, -y0)) * f.view;
|
||||
// Art is culled against, and long strokes outlined around, the offscreen's own area.
|
||||
let visible = view.inverse().transform_rect_bbox(Rect::new(0.0, 0.0, f64::from(w), f64::from(h)));
|
||||
let shifted = Frame { mt: false, view, visible, px: f.px / k, ..*f };
|
||||
self.inside_layer(|r| draw(r, &mut off, &shifted, None));
|
||||
off.flush();
|
||||
let mut pm = vello_cpu::Pixmap::new(w, h);
|
||||
|
||||
@@ -98,6 +98,65 @@ fn a_long_stroke_draws_the_same_pixels_outlined_near_the_view() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A zigzag of 3,000 anchors 8 pt apart between y = 60 and y = 550, through the bottom of a view
|
||||
/// on y = 0.
|
||||
fn tall_zigzag() -> BezPath {
|
||||
let mut bp = BezPath::new();
|
||||
let at = |i: usize| Point::new(i as f64 * 8.0, if i.is_multiple_of(2) { 60.0 } else { 550.0 });
|
||||
bp.move_to(at(0));
|
||||
for i in 1..3000 {
|
||||
bp.line_to(at(i));
|
||||
}
|
||||
bp
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_long_stroke_under_a_wide_blur_draws_the_same_pixels_while_dragging_on_threads() {
|
||||
// While dragging on threads, a drop shadow this large is drawn through a filter layer whose
|
||||
// offscreen reaches 602 px past the view: the parts of the stroke that far away feed the blur.
|
||||
let mut d = Document::new(1000.0, 1000.0);
|
||||
let shadow = serde_json::json!({"x": 0, "y": 0, "blur": 400, "opacity": 100, "mode": "normal"});
|
||||
let look = Appearance { items: vec![AppearanceItem::Stroke(stroke(20.0, LineJoin::Round, LineCap::Butt))], ..Default::default() };
|
||||
let n = Node::path(d.alloc_id(), PathData::from_bezpath(&tall_zigzag()), look);
|
||||
// The shadow is the group's: the path itself draws as a plain path.
|
||||
let mut g = Node::group(d.alloc_id(), vec![n.into()]);
|
||||
g.appearance.effects.push(vectorcraft_doc::Effect { id: "stylize.dropShadow".into(), params: shadow, visible: true });
|
||||
let l = d.layers[0].id;
|
||||
d.insert(Some(l), 0, g).unwrap();
|
||||
let at = Point::new(12_000.0, 0.0);
|
||||
let opts = RenderOptions { background: Some([255, 255, 255, 255]), dragging: true, ..Default::default() };
|
||||
let draw = |crops: bool| {
|
||||
CROP_STROKES.with(|c| c.set(crops));
|
||||
let mut r = Renderer::new();
|
||||
r.threads = 2;
|
||||
let img = r.render(&d, SIZE, SIZE, view_at(at), &opts);
|
||||
CROP_STROKES.with(|c| c.set(true));
|
||||
(img, r.strokes.values().any(|e| e.crop.is_some()))
|
||||
};
|
||||
let ((near, cropped), (all, _)) = (draw(true), draw(false));
|
||||
assert!(cropped, "outlined near the view");
|
||||
assert!(all.pixel(100, 0)[0] < 250, "the shadow shows above the stroke: {:?}", all.pixel(100, 0));
|
||||
assert!(near.pixels == all.pixels, "the same pixels as the whole outline");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_member_below_the_view_casts_its_wide_shadow_while_dragging_on_threads() {
|
||||
// A 400 pt shadow of a group whose only member, a rectangle, lies 50 pt below the view.
|
||||
let mut d = Document::new(1000.0, 1000.0);
|
||||
let shadow = serde_json::json!({"x": 0, "y": 0, "blur": 400, "opacity": 100, "mode": "normal"});
|
||||
let look = Appearance { items: vec![AppearanceItem::Fill(vectorcraft_doc::FillLayer::new(Paint::solid(Color::BLACK)))], ..Default::default() };
|
||||
let n = Node::path(d.alloc_id(), PathData::from_bezpath(&Rect::new(-1000.0, 150.0, 1000.0, 550.0).to_path(0.1)), look);
|
||||
let mut g = Node::group(d.alloc_id(), vec![n.into()]);
|
||||
g.appearance.effects.push(vectorcraft_doc::Effect { id: "stylize.dropShadow".into(), params: shadow, visible: true });
|
||||
let l = d.layers[0].id;
|
||||
d.insert(Some(l), 0, g).unwrap();
|
||||
let opts = RenderOptions { background: Some([255, 255, 255, 255]), dragging: true, ..Default::default() };
|
||||
let mut r = Renderer::new();
|
||||
r.threads = 2;
|
||||
let img = r.render(&d, SIZE, SIZE, view_at(Point::ZERO), &opts);
|
||||
assert!(img.pixel(100, 199)[0] < 200, "the shadow shows at the bottom of the view: {:?}", img.pixel(100, 199));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_long_stroke_is_outlined_near_the_view_only() {
|
||||
let d = doc(&zigzag(), stroke(6.0, LineJoin::Miter, LineCap::Square));
|
||||
|
||||
Reference in New Issue
Block a user