mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 15:49:48 +00:00
Merge pull request #1633 from samhyun/fix/partial-backdrop-at-view-edge
M8: a patch reads no backdrop past the view's edge
This commit is contained in:
2 files changed
+105
-5
No files matched your search
@@ -413,6 +413,19 @@ fn patch(last: Frame, doc: &Arc<Document>, page: NodeId, view: &View, px: (i64,
|
||||
} else if dy < 0 {
|
||||
damage.push(Px { x0: 0, y0: 0, x1: w, y1: reach - dy });
|
||||
}
|
||||
// And at the edges that moved in: a backdrop there read what is now past the edge.
|
||||
if reach > 0 {
|
||||
if dx > 0 {
|
||||
damage.push(Px { x0: 0, y0: 0, x1: reach, y1: h });
|
||||
} else if dx < 0 {
|
||||
damage.push(Px { x0: w - reach, y0: 0, x1: w, y1: h });
|
||||
}
|
||||
if dy > 0 {
|
||||
damage.push(Px { x0: 0, y0: 0, x1: w, y1: reach });
|
||||
} else if dy < 0 {
|
||||
damage.push(Px { x0: 0, y0: h - reach, x1: w, y1: h });
|
||||
}
|
||||
}
|
||||
// Layers that changed, where they were and where they are now.
|
||||
if !Arc::ptr_eq(&last.doc, doc) {
|
||||
let mut areas = Vec::new();
|
||||
@@ -528,18 +541,22 @@ struct FrameRender<'a> {
|
||||
opts: &'a Options,
|
||||
}
|
||||
|
||||
/// Render the frame pixels `d` (with `margin` more around, discarded) into `pix`.
|
||||
/// Render the frame pixels `d` (with `margin` more around, discarded) into `pix`. The margin stops
|
||||
/// at the frame's edge: background blurs and glass read what is drawn under them, and a full
|
||||
/// render's backdrops end where the frame does.
|
||||
fn render_into(frame: FrameRender<'_>, d: &Px, margin: i64, pix: &mut Pixmap) -> Option<()> {
|
||||
let FrameRender { doc, page, view, px, opts } = frame;
|
||||
let s = view.scale;
|
||||
let (ox, oy) = (d.x0 - margin, d.y0 - margin);
|
||||
let (rw, rh) = (u32::try_from(d.x1 - d.x0 + 2 * margin).ok()?, u32::try_from(d.y1 - d.y0 + 2 * margin).ok()?);
|
||||
let (w, h) = (i64::from(pix.width()), i64::from(pix.height()));
|
||||
let (ox, oy) = ((d.x0 - margin).max(0), (d.y0 - margin).max(0));
|
||||
let (ex, ey) = ((d.x1 + margin).min(w), (d.y1 + margin).min(h));
|
||||
let (rw, rh) = (u32::try_from(ex - ox).ok()?, u32::try_from(ey - oy).ok()?);
|
||||
// The same pixel grid as the frame: origin (px + offset) / scale.
|
||||
let sub = View { origin: ((px.0 + ox) as f64 / s, (px.1 + oy) as f64 / s), scale: s, width: rw, height: rh };
|
||||
let part = render_view(doc, page, &sub, opts)?;
|
||||
let inner = IntRect::from_xywh(
|
||||
i32::try_from(margin).ok()?,
|
||||
i32::try_from(margin).ok()?,
|
||||
i32::try_from(d.x0 - ox).ok()?,
|
||||
i32::try_from(d.y0 - oy).ok()?,
|
||||
u32::try_from(d.x1 - d.x0).ok()?,
|
||||
u32::try_from(d.y1 - d.y0).ok()?,
|
||||
)?;
|
||||
|
||||
@@ -456,3 +456,86 @@ fn assert_within(a: &Pixmap, b: &Pixmap, rows_down: u32, tol: u8, what: &str) {
|
||||
let worst = a.data().iter().zip(below).map(|(x, y)| x.abs_diff(*y)).max().unwrap_or(0);
|
||||
assert!(worst <= tol, "{what}: differs by {worst} levels");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backdrops_at_the_views_edge_patch_as_they_render() {
|
||||
fonts();
|
||||
let blur = |r: f64| {
|
||||
let mut e = Effect::background_blur();
|
||||
if let EffectKind::BackgroundBlur(b) = &mut e.kind {
|
||||
b.radius = r;
|
||||
}
|
||||
e
|
||||
};
|
||||
let mut glass = Effect::glass();
|
||||
if let EffectKind::Glass(g) = &mut glass.kind {
|
||||
(g.depth, g.refraction) = (30.0, 1.0);
|
||||
}
|
||||
// A layer reading its backdrop across the view's left edge, over stripes, and a square moved
|
||||
// beside it in the view and off it: what the patch reads past the edge a full render doesn't.
|
||||
for (why, fx) in [("a background blur of 60", blur(60.0)), ("glass", glass)] {
|
||||
for (from, by) in [((180.0, 100.0), (15.0, 10.0)), ((-150.0, 100.0), (20.0, 0.0))] {
|
||||
let mut d = Document::new("edge");
|
||||
let p = d.first_page().unwrap();
|
||||
for k in 0..20 {
|
||||
let s = d.create(p, NodeKind::Rectangle, "S", -200.0 + f64::from(k) * 20.0, 0.0, 10.0, 400.0).unwrap();
|
||||
solid(d.node_mut(s).unwrap(), if k % 2 == 0 { "FF0000" } else { "0000FF" });
|
||||
}
|
||||
let e = d.create(p, NodeKind::Rectangle, "E", -60.0, 50.0, 160.0, 200.0).unwrap();
|
||||
let n = d.node_mut(e).unwrap();
|
||||
n.fills = vec![Paint { opacity: 0.2, ..Paint::solid(Color::WHITE) }];
|
||||
n.effects = vec![fx.clone()];
|
||||
let m = d.create(p, NodeKind::Rectangle, "M", from.0, from.1, 40.0, 40.0).unwrap();
|
||||
solid(d.node_mut(m).unwrap(), "00AA00");
|
||||
let before = Arc::new(d);
|
||||
let after = edit(&before, |d| d.node_mut(m).unwrap().transform = Affine::translate((from.0 + by.0, from.1 + by.1)));
|
||||
let v = View { origin: (0.0, 0.0), scale: 1.0, width: 400, height: 300 };
|
||||
let mut r = Retained::default();
|
||||
r.render(&before, p, &v, &Options::default()).unwrap();
|
||||
let (shown, pix, work) = r.render(&after, p, &v, &Options::default()).unwrap();
|
||||
let pix = pix.clone();
|
||||
let full = render_view(&after, p, &shown, &Options::default()).unwrap();
|
||||
let worst = pix.data().iter().zip(full.data()).map(|(a, b)| a.abs_diff(*b)).max().unwrap_or(0);
|
||||
assert!(worst <= 2, "{why}, a square moved from {from:?}: {work:?} is {worst} levels off a full render");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pans_patch_backdrops_at_the_edges_that_moved_in() {
|
||||
fonts();
|
||||
let mut blur = Effect::background_blur();
|
||||
if let EffectKind::BackgroundBlur(b) = &mut blur.kind {
|
||||
b.radius = 10.0;
|
||||
}
|
||||
let mut glass = Effect::glass();
|
||||
if let EffectKind::Glass(g) = &mut glass.kind {
|
||||
(g.depth, g.refraction) = (30.0, 1.0);
|
||||
}
|
||||
// A layer reading its backdrop across all four edges of the view, over stripes: after a pan,
|
||||
// what it read past an edge that moved in is past the edge no more.
|
||||
for (why, fx) in [("a background blur of 10", blur), ("glass", glass)] {
|
||||
let mut d = Document::new("pan");
|
||||
let p = d.first_page().unwrap();
|
||||
for k in 0..40 {
|
||||
let s = d.create(p, NodeKind::Rectangle, "S", -200.0 + f64::from(k) * 20.0, -100.0, 10.0, 600.0).unwrap();
|
||||
solid(d.node_mut(s).unwrap(), if k % 2 == 0 { "FF0000" } else { "0000FF" });
|
||||
}
|
||||
let e = d.create(p, NodeKind::Rectangle, "E", -60.0, -60.0, 500.0, 420.0).unwrap();
|
||||
let n = d.node_mut(e).unwrap();
|
||||
n.fills = vec![Paint { opacity: 0.2, ..Paint::solid(Color::WHITE) }];
|
||||
n.effects = vec![fx];
|
||||
let doc = Arc::new(d);
|
||||
for (dx, dy) in [(30.0, 0.0), (-30.0, 0.0), (0.0, 30.0), (0.0, -30.0)] {
|
||||
let mut r = Retained::default();
|
||||
r.render(&doc, p, &View { origin: (0.0, 0.0), scale: 1.0, width: 400, height: 300 }, &Options::default()).unwrap();
|
||||
let v = View { origin: (dx, dy), scale: 1.0, width: 400, height: 300 };
|
||||
let (shown, pix, work) = r.render(&doc, p, &v, &Options::default()).unwrap();
|
||||
let pix = pix.clone();
|
||||
let full = render_view(&doc, p, &shown, &Options::default()).unwrap();
|
||||
let worst = pix.data().iter().zip(full.data()).map(|(a, b)| a.abs_diff(*b)).max().unwrap_or(0);
|
||||
assert!(partial(work), "{why}, a pan by ({dx}, {dy}): {work:?}");
|
||||
assert!(worst <= 2, "{why}, a pan by ({dx}, {dy}): {work:?} is {worst} levels off a full render");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user