Render: gradients with rotWithShape=0 keep to the slide's axes
A gradient fill marked not to rotate with its shape turned and flipped with it anyway. PowerPoint lays such a gradient out upright, over the box the rotated or flipped shape covers on the slide (a 90 degree gradient on a quarter-turned triangle still runs top to bottom). Linear and radial gradients now do; rect and outline path gradients still follow the shape. Wild synthetic set: gradients on rotated/flipped shapes 5.48 -> 1.34, gradient angles slide 6.75 -> 1.95. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fa1db7809a
commit
bbba07cb39
|
|
@ -35,6 +35,18 @@ pub fn fill_path(
|
|||
if g.stops.is_empty() {
|
||||
return;
|
||||
}
|
||||
// `rotWithShape="0"`: the gradient keeps to the slide's axes when the shape is
|
||||
// rotated or flipped, laid out over the box the shape covers on the slide.
|
||||
let c = m.as_coeffs();
|
||||
let turned = c[1].abs() > 1e-9 || c[2].abs() > 1e-9 || c[0] < 0.0 || c[3] < 0.0;
|
||||
let path_kind = matches!(&g.shape, GradientShape::Path { path: k, .. } if matches!(k.as_str(), "rect" | "shape"));
|
||||
if !g.rotate_with_shape && turned && !path_kind && m.determinant().abs() > 1e-12 {
|
||||
let on_slide = m.transform_rect_bbox(bounds);
|
||||
let upright = deckcraft_model::style::Gradient { rotate_with_shape: true, ..g.clone() };
|
||||
let paint_path = m * path.clone();
|
||||
fill_path(ctx, rctx, &Fill::Gradient(upright), ph, &paint_path, on_slide, Affine::IDENTITY, mode, alpha, pres);
|
||||
return;
|
||||
}
|
||||
// A linear gradient spans its tile rectangle (the shape's box unless `tileRect` moves
|
||||
// its edges); a radial one ends on the circle around it, from a focus placed in the
|
||||
// shape's own box (PowerPoint's "from corner" presets centre the tile on the corner).
|
||||
|
|
|
|||
|
|
@ -632,3 +632,33 @@ fn a_large_pictures_shadow_is_whole() {
|
|||
}
|
||||
assert!(counts[1] > 3000 && (counts[0] as i64 - counts[1] as i64).abs() < 300, "{counts:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_gradient_not_rotating_with_its_shape_keeps_to_the_slide() {
|
||||
use deckcraft_model::style::{Gradient, GradientShape, GradientStop};
|
||||
let blue = Rgba::rgb(0, 0, 255);
|
||||
for rotate_with_shape in [true, false] {
|
||||
let g = Gradient {
|
||||
stops: vec![GradientStop { pos: 0.0, color: ColorRef::rgb(RED) }, GradientStop { pos: 1.0, color: ColorRef::rgb(blue) }],
|
||||
shape: GradientShape::Linear { angle: 0.0, scaled: false },
|
||||
rotate_with_shape,
|
||||
tile: [0.0; 4],
|
||||
};
|
||||
// 200 × 100 turned a quarter: it covers 150..250 across and 50..250 down.
|
||||
let sh = Shape {
|
||||
id: ShapeId(28),
|
||||
xfrm: Some(Xfrm { rot: 90.0, ..Xfrm::new(100.0, 100.0, 200.0, 100.0) }),
|
||||
fill: Some(Fill::Gradient(g)),
|
||||
line: no_line(),
|
||||
..Default::default()
|
||||
};
|
||||
let img = render(vec![sh]);
|
||||
let (left, top) = (img.pixel(152, 150), img.pixel(200, 52));
|
||||
if rotate_with_shape {
|
||||
assert!(top[0] > 200 && top[2] < 60, "turned with the shape: red at the top {top:?}");
|
||||
} else {
|
||||
assert!(left[0] > 200 && left[2] < 60, "upright: red at the left {left:?}");
|
||||
assert!(img.pixel(248, 150)[2] > 200, "blue at the right");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue