mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-26 11:36:58 -06:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88c5976ae2 |
@@ -785,7 +785,12 @@ impl Renderer {
|
|||||||
let fill = if let Some(t) = st.tint { Some(Fill::solid(deckcraft_model::ColorRef::rgb(t))) } else { fill };
|
let fill = if let Some(t) = st.tint { Some(Fill::solid(deckcraft_model::ColorRef::rgb(t))) } else { fill };
|
||||||
// Effects below the shape.
|
// Effects below the shape.
|
||||||
if let Some(e) = &effects {
|
if let Some(e) = &effects {
|
||||||
self.effects_below(ctx, rctx, e, fx_ph, &geo, &fill, m, w, h);
|
// A picture (or picture fill) casts its shadow from its pixels, not its frame.
|
||||||
|
let content = match (&s.kind, &fill) {
|
||||||
|
(ShapeKind::Picture { fill: pf }, _) | (_, Some(Fill::Picture(pf))) => Some(paint::resolved(rctx, pf, None).into_owned()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
self.effects_below(ctx, rctx, e, fx_ph, &geo, &fill, content.as_ref(), m, w, h);
|
||||||
}
|
}
|
||||||
let soft = effects.as_ref().and_then(|e| e.soft_edge).filter(|r| *r > 0.0);
|
let soft = effects.as_ref().and_then(|e| e.soft_edge).filter(|r| *r > 0.0);
|
||||||
if let Some(r) = soft {
|
if let Some(r) = soft {
|
||||||
@@ -959,6 +964,7 @@ impl Renderer {
|
|||||||
ph: Option<Rgba>,
|
ph: Option<Rgba>,
|
||||||
geo: &Geometry,
|
geo: &Geometry,
|
||||||
fill: &Option<Fill>,
|
fill: &Option<Fill>,
|
||||||
|
content: Option<&deckcraft_model::style::PictureFill>,
|
||||||
m: Affine,
|
m: Affine,
|
||||||
w: f64,
|
w: f64,
|
||||||
h: f64,
|
h: f64,
|
||||||
@@ -986,8 +992,24 @@ impl Renderer {
|
|||||||
// Blurred by a third of the blur radius, as PowerPoint's export shows.
|
// Blurred by a third of the blur radius, as PowerPoint's export shows.
|
||||||
ctx.push_layer(None, None, Some((c.a as f32) / 255.0), None, Some(blur(sh.blur / 3.0 * k)));
|
ctx.push_layer(None, None, Some((c.a as f32) / 255.0), None, Some(blur(sh.blur / 3.0 * k)));
|
||||||
ctx.set_transform(sm);
|
ctx.set_transform(sm);
|
||||||
ctx.set_paint(color(c.with_alpha(255), 1.0));
|
match content {
|
||||||
ctx.fill_path(&silhouette);
|
// As PowerPoint draws it: the shadow is the picture's own coverage (a logo on a
|
||||||
|
// transparent background casts the logo's shadow): the picture, clipped to the
|
||||||
|
// shape, recoloured to the shadow colour.
|
||||||
|
Some(pf) => {
|
||||||
|
ctx.push_clip_layer(&silhouette);
|
||||||
|
paint::picture(ctx, rctx.pres, pf, Rect::new(0.0, 0.0, w, h), sm, None);
|
||||||
|
ctx.push_layer(None, Some(peniko::BlendMode::new(peniko::Mix::Normal, peniko::Compose::SrcIn)), None, None, None);
|
||||||
|
ctx.set_paint(color(c.with_alpha(255), 1.0));
|
||||||
|
ctx.fill_rect(&Rect::new(0.0, 0.0, w, h).inflate(1.0, 1.0));
|
||||||
|
ctx.pop_layer();
|
||||||
|
ctx.pop_layer();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
ctx.set_paint(color(c.with_alpha(255), 1.0));
|
||||||
|
ctx.fill_path(&silhouette);
|
||||||
|
}
|
||||||
|
}
|
||||||
ctx.pop_layer();
|
ctx.pop_layer();
|
||||||
}
|
}
|
||||||
if let Some(g) = &e.glow {
|
if let Some(g) = &e.glow {
|
||||||
|
|||||||
@@ -1506,3 +1506,50 @@ fn group_child_scale_does_not_scale_line_widths() {
|
|||||||
assert!(d * 20 < drawn, "{name}: {d} differing values against {drawn} inked pixels");
|
assert!(d * 20 < drawn, "{name}: {d} differing values against {drawn} inked pixels");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_picture_casts_its_shadow_from_its_pixels() {
|
||||||
|
// PowerPoint shades only where a picture paints: a logo on a transparent background casts the
|
||||||
|
// logo's shadow, not its frame's. Here the left half of the image is transparent.
|
||||||
|
let png = {
|
||||||
|
let img = image::RgbaImage::from_fn(8, 4, |x, _| if x < 4 { image::Rgba([0, 0, 0, 0]) } else { image::Rgba([20, 160, 20, 255]) });
|
||||||
|
let mut out = std::io::Cursor::new(vec![]);
|
||||||
|
image::DynamicImage::ImageRgba8(img).write_to(&mut out, image::ImageFormat::Png).unwrap();
|
||||||
|
out.into_inner()
|
||||||
|
};
|
||||||
|
let shadow = deckcraft_model::style::Shadow {
|
||||||
|
color: ColorRef::rgb(deckcraft_color::Rgba::BLACK),
|
||||||
|
blur: 0.0,
|
||||||
|
dist: 20.0,
|
||||||
|
dir: 90.0,
|
||||||
|
inner: false,
|
||||||
|
sx: 1.0,
|
||||||
|
sy: 1.0,
|
||||||
|
kx: 0.0,
|
||||||
|
ky: 0.0,
|
||||||
|
align: String::new(),
|
||||||
|
rotate_with_shape: false,
|
||||||
|
};
|
||||||
|
for as_fill in [false, true] {
|
||||||
|
let mut p = Presentation::default();
|
||||||
|
let media = p.add_media("logo.png", "image/png", png.clone());
|
||||||
|
let pf = deckcraft_model::style::PictureFill { media, ..Default::default() };
|
||||||
|
let mut sh = Shape { id: ShapeId(600), xfrm: Some(Xfrm::new(100.0, 100.0, 400.0, 200.0)), ..Default::default() };
|
||||||
|
if as_fill {
|
||||||
|
sh.fill = Some(Fill::Picture(pf));
|
||||||
|
sh.line = Some(deckcraft_model::style::Line { fill: Some(Fill::None), ..Default::default() });
|
||||||
|
} else {
|
||||||
|
sh.kind = ShapeKind::Picture { fill: pf };
|
||||||
|
}
|
||||||
|
sh.effects = Some(deckcraft_model::Effects { outer_shadow: Some(shadow.clone()), ..Default::default() });
|
||||||
|
std::sync::Arc::make_mut(&mut p.slides[0]).shapes = vec![sh];
|
||||||
|
let img = render_slide(&p, 0, &RenderOpts { scale: 1.0, ..Default::default() });
|
||||||
|
// Below the picture, 10 pt down: shadow under the opaque right half only.
|
||||||
|
let left = img.pixel(200, 310);
|
||||||
|
let right = img.pixel(400, 310);
|
||||||
|
assert!(left[0] > 240, "no shadow under the transparent half ({as_fill}): {left:?}");
|
||||||
|
assert!(right[0] < 40, "shadow under the opaque half ({as_fill}): {right:?}");
|
||||||
|
// The transparent half shows the slide, not a shadow behind it.
|
||||||
|
assert!(img.pixel(200, 200)[0] > 240, "{as_fill}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user