Files
designcraft/crates/render/tests/gradients.rs
moppeeerandClaude Opus 5.5 7a30e5427e fix(render): radial gradients stretch with the object's box, as in InDesign
InDesign draws a radial gradient in the object's bounding box: on a
non-square frame it's an ellipse reaching the frame's sides, and a
Gradient Swatch tool drag keeps those proportions. LYH Studio episode 6
(3:16) gives the left page's background a radial gradient: magenta in
the middle, cyan out to every edge. DesignCraft drew a circle reaching
only the longer side, so a portrait page stayed purple at its sides.

- doc: `Fill::radial_ellipse(bounds)`: centre and radii (default: the
  box's centre and half its width and height; with a tool vector, its
  start, and its length measured with the box made square).
- render: the radial paint is that circle, stretched by the paint
  transform; PDF: the shading's transform does the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:58:05 +02:00

56 lines
2.7 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Gradient fills: InDesign stretches a radial gradient with the object's box.
use designcraft_color::swatch::{Swatch, SwatchValue};
use designcraft_color::{Color, Gradient, GradientKind, GradientStop};
use designcraft_compose::Cache;
use designcraft_doc::build::NewDocument;
use designcraft_doc::{Document, Fill, Item, ItemId, Shape, SpreadRef};
use designcraft_geom::{Affine, Rect, Vec2, shapes};
use designcraft_render::{Placed, RenderOptions, Rendered, Renderer};
/// A 100×200 rectangle at (50, 20) filled with a black-to-white radial gradient.
fn portrait(vector: Option<[f64; 4]>) -> Document {
let mut d = Document::new(&NewDocument { width: 200.0, height: 240.0, facing_pages: false, ..Default::default() });
let stop = |offset: f32, color: Color| GradientStop { offset, color, opacity: 1.0, midpoint: 0.5 };
d.swatches.push(Swatch {
name: "Radial".into(),
value: SwatchValue::Gradient {
gradient: Gradient { kind: GradientKind::Radial, stops: vec![stop(0.0, Color::BLACK), stop(1.0, Color::WHITE)] },
},
locked: false,
named: true,
hidden: false,
});
let id = ItemId(d.alloc());
let mut it = Item::new(id, d.default_layer(), Shape::Rectangle, shapes::rectangle(Rect::new(50.0, 20.0, 150.0, 220.0)));
it.fill = Fill { swatch: "Radial".into(), gradient_vector: vector, ..Fill::none() };
d.insert_item(SpreadRef::Doc(0), it, None).unwrap();
d
}
fn render(d: &Document) -> Rendered {
let mut r = Renderer::new();
r.threads = 0;
let opts = RenderOptions { background: Some([255, 255, 255, 255]), ..Default::default() };
r.render(d, &Cache::new(), &[Placed { spread: SpreadRef::Doc(0), xf: Affine::translate(Vec2::ZERO) }], 200, 240, Affine::IDENTITY, &opts)
}
fn grey(img: &Rendered, x: u32, y: u32) -> u8 {
img.pixel(x, y)[0]
}
#[test]
fn a_radial_gradient_stretches_with_its_object() {
// Centred, it reaches white at the sides and at the top and bottom alike (an ellipse).
let img = render(&portrait(None));
assert!(grey(&img, 100, 120) < 20, "black at the centre: {}", grey(&img, 100, 120));
let (side, top) = (grey(&img, 52, 120), grey(&img, 100, 22));
assert!(side > 225 && top > 225, "white at the side ({side}) and the top ({top})");
// Half-way to the side and half-way to the top: the same grey.
let (hs, ht) = (grey(&img, 75, 120), grey(&img, 100, 70));
assert!(hs.abs_diff(ht) < 12, "half-way: side {hs}, top {ht}");
// A Gradient Swatch tool vector to the right side: the same ellipse.
let dragged = render(&portrait(Some([100.0, 120.0, 150.0, 120.0])));
assert!(grey(&dragged, 100, 70).abs_diff(ht) < 12 && grey(&dragged, 75, 120).abs_diff(hs) < 12);
}