From ee71879f80036e52a4a9c7a25b927729e37ca1a7 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 13 Oct 2026 07:31:17 -0400 Subject: [PATCH] Render: pictures reflect their pixels A reflection on a picture (PowerPoint's "reflected" picture styles) drew nothing: the reflection painted the shape's fill, and a picture has none. It now mirrors the picture itself, clipped to its outline, under the same fade. Wild synthetic set: picture effects slide 16.77 -> 16.00 (the rest is 3-D camera rotation, not modelled). Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/render/src/lib.rs | 15 +++++++++++++-- crates/render/src/tests_wild.rs | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 3ad77fd..c044b05 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -1284,9 +1284,20 @@ impl Renderer { ctx.set_transform(Affine::IDENTITY); ctx.push_layer(None, None, Some(rf.start_alpha.clamp(0.0, 1.0) as f32 * 0.6), None, Some(blur(rf.blur * 0.5 * k))); ctx.set_transform(mm); - if let Some(fl) = fill { - paint::fill_path(ctx, rctx, fl, ph, &silhouette, Rect::new(0.0, 0.0, w, h), mm, FillMode::Norm, 1.0, rctx.pres); + match content { + // A picture reflects its pixels (it has no fill of its own). + Some(pf) => { + ctx.push_clip_layer(&silhouette); + paint::picture(ctx, rctx.pres, pf, Rect::new(0.0, 0.0, w, h), mm, None); + ctx.pop_layer(); + } + None => { + if let Some(fl) = fill { + paint::fill_path(ctx, rctx, fl, ph, &silhouette, Rect::new(0.0, 0.0, w, h), mm, FillMode::Norm, 1.0, rctx.pres); + } + } } + ctx.set_transform(mm); // Fade mask. ctx.push_layer(None, Some(peniko::BlendMode::new(peniko::Mix::Normal, peniko::Compose::DestIn)), None, None, None); let grad = peniko::Gradient::new_linear(Point::new(0.0, h), Point::new(0.0, h * (1.0 - rf.end_pos.clamp(0.01, 1.0)))) diff --git a/crates/render/src/tests_wild.rs b/crates/render/src/tests_wild.rs index fd3013a..4aea690 100644 --- a/crates/render/src/tests_wild.rs +++ b/crates/render/src/tests_wild.rs @@ -710,3 +710,18 @@ fn an_upright_picture_fill_stays_upright_in_a_turned_shape() { } } } + +#[test] +fn a_picture_reflects_its_pixels() { + let mut p = picture_deck(None); + let s = std::sync::Arc::make_mut(&mut p.slides[0]); + if let Some(sh) = s.shapes.first_mut() { + let rf = deckcraft_model::style::Reflection { blur: 0.0, start_alpha: 1.0, end_alpha: 0.0, end_pos: 0.5, dist: 0.0 }; + sh.effects = Some(deckcraft_model::Effects { reflection: Some(rf), ..Default::default() }); + } + let img = render_slide(&p, 0, &RenderOpts { scale: 1.0, ..Default::default() }); + // The blue picture spans 100..200 down; just below it, its reflection tints the page blue. + let below = img.pixel(200, 204); + assert!(below[2] > 200 && below[0] < 200, "{below:?}"); + assert!(close(img.pixel(200, 290), WHITE, 10), "faded out by half its height"); +}