mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-25 10:56:48 -06:00
Fixes #430 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
406 lines
19 KiB
Rust
406 lines
19 KiB
Rust
//! Effects behaviour, written from the public documentation of the design tools FigCraft follows:
|
||
//! drop and inner shadows (offset, blur, spread, colour, showing behind transparent areas),
|
||
//! layer and background blurs, effects on groups, the order effects stack in and what exports
|
||
//! include. Each case says in one line why it expects what it does; where the documentation is
|
||
//! silent the case says so and pins today's behaviour. Scenes are small and synthetic, drawn on
|
||
//! a white page at 1×.
|
||
|
||
use figcraft_color::{Color, Paint};
|
||
use figcraft_doc::{Blur, BlurType, Document, Effect, EffectKind, NodeId, NodeKind, Shadow, StrokeAlign};
|
||
|
||
use super::paint_behaviour_tests::{Probes, Pt, Px, hex, near, page, px, rect, render, solid_at};
|
||
use super::*;
|
||
|
||
const WHITE: [u8; 3] = [255, 255, 255];
|
||
const BLACK: [u8; 3] = [0, 0, 0];
|
||
const RED: [u8; 3] = [255, 0, 0];
|
||
|
||
fn fx(kind: EffectKind) -> Effect {
|
||
Effect { kind, visible: true }
|
||
}
|
||
|
||
/// A sharp, opaque black shadow.
|
||
fn sharp(offset: (f64, f64), spread: f64) -> Shadow {
|
||
Shadow { color: Color::BLACK, offset, radius: 0.0, spread, ..Shadow::default() }
|
||
}
|
||
|
||
/// A 40 × 40 rectangle at (50, 50) with `fills` and `effects`.
|
||
fn square(fills: Vec<Paint>, effects: Vec<Effect>) -> (Document, NodeId) {
|
||
let (mut d, p) = page();
|
||
let r = rect(&mut d, p, (50.0, 50.0, 40.0, 40.0), fills);
|
||
d.get_mut(r).unwrap().effects = effects;
|
||
(d, r)
|
||
}
|
||
|
||
/// The standard normal distribution function (Abramowitz–Stegun 7.1.26 for erf).
|
||
fn phi(z: f64) -> f64 {
|
||
let x = z.abs() / std::f64::consts::SQRT_2;
|
||
let t = 1.0 / (1.0 + 0.327_591_1 * x);
|
||
let poly = t * (0.254_829_592 + t * (-0.284_496_736 + t * (1.421_413_741 + t * (-1.453_152_027 + t * 1.061_405_429))));
|
||
let erf = 1.0 - poly * (-x * x).exp();
|
||
if z >= 0.0 { 0.5 * (1.0 + erf) } else { 0.5 * (1.0 - erf) }
|
||
}
|
||
|
||
#[test]
|
||
fn new_effects_start_from_the_documented_defaults() {
|
||
// A new shadow: offset 0, 4, blur 4, spread 0, black at 25%, normal blend, not shown behind
|
||
// transparent areas; a new blur: 4, uniform.
|
||
let shadow = Shadow { color: Color::new(0.0, 0.0, 0.0, 0.25), offset: (0.0, 4.0), radius: 4.0, spread: 0.0, ..Shadow::default() };
|
||
for (ty, want) in [
|
||
("dropShadow", EffectKind::DropShadow(shadow.clone())),
|
||
("innerShadow", EffectKind::InnerShadow(shadow.clone())),
|
||
("layerBlur", EffectKind::LayerBlur(Blur::uniform(4.0))),
|
||
("backgroundBlur", EffectKind::BackgroundBlur(Blur::uniform(4.0))),
|
||
] {
|
||
let e = Effect::of_type(ty).unwrap();
|
||
assert_eq!(e.kind, want, "{ty}");
|
||
assert!(e.visible, "{ty} starts visible");
|
||
}
|
||
assert!(!shadow.show_behind && shadow.blend == figcraft_color::BlendMode::Normal);
|
||
assert_eq!(Blur::default().blur_type, BlurType::Uniform);
|
||
}
|
||
|
||
#[test]
|
||
fn drop_shadows_move_by_the_offset_and_grow_by_the_spread() {
|
||
// (why, offset, spread, colour, probes). The red square covers 50..90 on both axes.
|
||
let rows: Vec<(&str, Pt, f64, Color, Probes)> = vec![
|
||
(
|
||
"x moves the shadow right; the layer paints over it",
|
||
(10.0, 0.0),
|
||
0.0,
|
||
Color::BLACK,
|
||
vec![((95, 70), BLACK), ((45, 70), WHITE), ((70, 70), RED)],
|
||
),
|
||
("y moves it down", (0.0, 10.0), 0.0, Color::BLACK, vec![((70, 95), BLACK), ((70, 45), WHITE)]),
|
||
("negative offsets move it up and left", (-10.0, -10.0), 0.0, Color::BLACK, vec![((45, 45), BLACK), ((95, 95), WHITE)]),
|
||
(
|
||
"spread grows the shadow on every side",
|
||
(10.0, 0.0),
|
||
5.0,
|
||
Color::BLACK,
|
||
vec![((102, 70), BLACK), ((107, 70), WHITE), ((70, 47), BLACK), ((70, 43), WHITE)],
|
||
),
|
||
(
|
||
"negative spread shrinks it (here, out of sight under the layer)",
|
||
(0.0, 0.0),
|
||
-5.0,
|
||
Color::BLACK,
|
||
vec![((48, 70), WHITE), ((92, 70), WHITE)],
|
||
),
|
||
("the colour's alpha is the shadow's opacity", (10.0, 0.0), 0.0, Color::new(0.0, 0.0, 0.0, 0.25), vec![((95, 70), [191, 191, 191])]),
|
||
("the shadow takes its colour", (10.0, 0.0), 0.0, hex("0000FF"), vec![((95, 70), [0, 0, 255])]),
|
||
];
|
||
for (why, offset, spread, color, probes) in rows {
|
||
let (d, _) = square(vec![Paint::solid(hex("FF0000"))], vec![fx(EffectKind::DropShadow(Shadow { color, ..sharp(offset, spread) }))]);
|
||
let pix = render(&d);
|
||
for ((x, y), want) in probes {
|
||
let got = px(&pix, x, y);
|
||
assert!(near(got, want, 2), "{why} at ({x}, {y}): {got:?} want {want:?}");
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn blurs_spread_by_a_gaussian_of_half_the_radius() {
|
||
// The blur value is the CSS blur radius (what code inspection shows for it), which CSS turns
|
||
// into a Gaussian with a standard deviation of half the radius: across a straight edge the
|
||
// coverage falls off as the normal distribution function. Radius 20: sigma 10.
|
||
let sigma = 10.0;
|
||
let edge = |d: f64| ((1.0 - phi(-d / sigma)) * 255.0).round() as u8;
|
||
// Drop shadow of a white 120 × 120 square at (40, 40), black, no offset.
|
||
let (mut d, p) = page();
|
||
let r = rect(&mut d, p, (40.0, 40.0, 120.0, 120.0), vec![Paint::solid(Color::WHITE)]);
|
||
d.get_mut(r).unwrap().effects = vec![fx(EffectKind::DropShadow(Shadow { radius: 20.0, ..sharp((0.0, 0.0), 0.0) }))];
|
||
let shadow = render(&d);
|
||
// Layer blur of a black square, same place.
|
||
let (mut d, p) = page();
|
||
let r = rect(&mut d, p, (40.0, 40.0, 120.0, 120.0), vec![Paint::solid(Color::BLACK)]);
|
||
d.get_mut(r).unwrap().effects = vec![fx(EffectKind::LayerBlur(Blur::uniform(20.0)))];
|
||
let layer = render(&d);
|
||
// Background blur: black left of x = 160 seen through a clear pane across the edge.
|
||
let (mut d, p) = page();
|
||
rect(&mut d, p, (0.0, 0.0, 160.0, 200.0), vec![Paint::solid(Color::BLACK)]);
|
||
let pane = rect(&mut d, p, (110.0, 40.0, 90.0, 120.0), vec![]);
|
||
d.get_mut(pane).unwrap().effects = vec![fx(EffectKind::BackgroundBlur(Blur::uniform(20.0)))];
|
||
let back = render(&d);
|
||
// (blur, pixel column, its centre's distance past the edge at x = 160)
|
||
let rows = [
|
||
("drop shadow", &shadow, 160, 0.5),
|
||
("drop shadow", &shadow, 170, 10.5),
|
||
("drop shadow", &shadow, 180, 20.5),
|
||
("layer blur", &layer, 150, -9.5),
|
||
("layer blur", &layer, 160, 0.5),
|
||
("layer blur", &layer, 170, 10.5),
|
||
("background blur", &back, 150, -9.5),
|
||
("background blur", &back, 160, 0.5),
|
||
("background blur", &back, 170, 10.5),
|
||
];
|
||
for (why, pix, x, dist) in rows {
|
||
let (got, want) = (px(pix, x, 100)[1], edge(dist));
|
||
assert!(got.abs_diff(want) <= 8, "{why} at x {x}: {got} want {want}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn shadows_show_behind_transparent_areas_only_when_asked() {
|
||
// Off (the default), a drop shadow doesn't show through the layer: the parts it covers,
|
||
// translucent ones included, hide the shadow. On, the shadow shows through them. The shadow
|
||
// is as strong as the layer's pixels are opaque (silent, pinned): a 50% fill casts a 50% one.
|
||
let stroke_only = |d: &mut Document, r: NodeId| {
|
||
let n = d.get_mut(r).unwrap();
|
||
n.strokes = vec![Paint::solid(hex("FF0000"))];
|
||
(n.stroke.weight, n.stroke.align) = (4.0, StrokeAlign::Inside);
|
||
};
|
||
type Setup = fn(&mut Document, NodeId);
|
||
let half = [128, 128, 128];
|
||
// (why, fill opacity (none: no fill), layer opacity, show behind, offset, extra, probes)
|
||
type Row<'a> = (&'a str, Option<f32>, f32, bool, Pt, Option<Setup>, Probes);
|
||
let rows: Vec<Row> = vec![
|
||
("off: a 50% fill shows the page, not the shadow, through it", Some(0.5), 1.0, false, (0.0, 0.0), None, vec![((70, 70), [255, 128, 128])]),
|
||
("on: the shadow shows through the 50% fill", Some(0.5), 1.0, true, (0.0, 0.0), None, vec![((70, 70), [191, 64, 64])]),
|
||
(
|
||
"off: outside the layer the shadow shows as ever",
|
||
Some(0.5),
|
||
1.0,
|
||
false,
|
||
(10.0, 0.0),
|
||
None,
|
||
vec![((95, 70), half), ((85, 70), [255, 128, 128])],
|
||
),
|
||
("on: offset, it shows under the layer too", Some(0.5), 1.0, true, (10.0, 0.0), None, vec![((95, 70), half), ((85, 70), [191, 64, 64])]),
|
||
(
|
||
"layer opacity fades the layer and its shadow together",
|
||
Some(1.0),
|
||
0.5,
|
||
false,
|
||
(10.0, 0.0),
|
||
None,
|
||
vec![((70, 70), [255, 128, 128]), ((95, 70), [128, 128, 128])],
|
||
),
|
||
(
|
||
"a stroke without a fill: the empty middle isn't part of the layer",
|
||
None,
|
||
1.0,
|
||
false,
|
||
(10.0, 0.0),
|
||
Some(stroke_only),
|
||
vec![((62, 70), BLACK), ((52, 70), RED)],
|
||
),
|
||
];
|
||
for (why, fill, layer_op, show_behind, offset, extra, probes) in rows {
|
||
let fills = fill.map(|o| vec![solid_at("FF0000", o)]).unwrap_or_default();
|
||
let (mut d, r) = square(fills, vec![fx(EffectKind::DropShadow(Shadow { show_behind, ..sharp(offset, 0.0) }))]);
|
||
d.get_mut(r).unwrap().opacity = layer_op;
|
||
if let Some(f) = extra {
|
||
f(&mut d, r);
|
||
}
|
||
let pix = render(&d);
|
||
for ((x, y), want) in probes {
|
||
let got = px(&pix, x, y);
|
||
assert!(near(got, want, 2), "{why} at ({x}, {y}): {got:?} want {want:?}");
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn effects_on_a_group_apply_to_the_group_as_a_whole() {
|
||
// A red square over a blue one, shadow (10, 10). On the group the pair casts one shadow;
|
||
// on each layer the top one's shadow falls on the bottom one.
|
||
for (why, on_group, want) in
|
||
[("on the group: one shadow, under both", true, [0, 0, 255]), ("on each layer: the top layer shadows the bottom one", false, BLACK)]
|
||
{
|
||
let (mut d, p) = page();
|
||
let g = d.create(p, NodeKind::Group, "G", 0.0, 0.0, 200.0, 200.0).unwrap();
|
||
let blue = rect(&mut d, g, (60.0, 60.0, 40.0, 40.0), vec![Paint::solid(hex("0000FF"))]);
|
||
let red = rect(&mut d, g, (40.0, 40.0, 40.0, 40.0), vec![Paint::solid(hex("FF0000"))]);
|
||
let shadow = vec![fx(EffectKind::DropShadow(sharp((10.0, 10.0), 0.0)))];
|
||
if on_group {
|
||
d.get_mut(g).unwrap().effects = shadow;
|
||
} else {
|
||
for id in [blue, red] {
|
||
d.get_mut(id).unwrap().effects = shadow.clone();
|
||
}
|
||
}
|
||
let pix = render(&d);
|
||
// (85, 70): on the blue square, inside the red one's shadow, outside the red square.
|
||
assert!(near(px(&pix, 85, 70), want, 2), "{why}: {:?}", px(&pix, 85, 70));
|
||
assert!(near(px(&pix, 105, 105), BLACK, 2), "{why}: the blue square's own shadow");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn inner_shadows_paint_inside_over_the_fills_under_strokes_and_children() {
|
||
let green = [0, 255, 0];
|
||
type Setup = fn(&mut Document, NodeId);
|
||
let inside_stroke: Setup = |d, r| {
|
||
let n = d.get_mut(r).unwrap();
|
||
n.strokes = vec![Paint::solid(hex("00FF00"))];
|
||
(n.stroke.weight, n.stroke.align) = (4.0, StrokeAlign::Inside);
|
||
};
|
||
let child: Setup = |d, r| {
|
||
rect(d, r, (0.0, 0.0, 40.0, 3.0), vec![Paint::solid(hex("0000FF"))]);
|
||
};
|
||
// (why, kind, offset, spread, extra, probes) on a white 40 × 40 square at (50, 50).
|
||
type Row<'a> = (&'a str, NodeKind, Pt, f64, Option<Setup>, Probes);
|
||
let rows: Vec<Row> = vec![
|
||
(
|
||
"y moves the outside in from the top edge",
|
||
NodeKind::Rectangle,
|
||
(0.0, 10.0),
|
||
0.0,
|
||
None,
|
||
vec![((70, 55), BLACK), ((70, 65), WHITE), ((70, 85), WHITE)],
|
||
),
|
||
("x moves it in from the left edge", NodeKind::Rectangle, (10.0, 0.0), 0.0, None, vec![((55, 70), BLACK), ((65, 70), WHITE)]),
|
||
(
|
||
"spread grows it in from every edge",
|
||
NodeKind::Rectangle,
|
||
(0.0, 0.0),
|
||
5.0,
|
||
None,
|
||
vec![((52, 70), BLACK), ((57, 70), WHITE), ((70, 52), BLACK), ((87, 70), BLACK)],
|
||
),
|
||
("it never paints outside the layer", NodeKind::Rectangle, (0.0, 10.0), 0.0, None, vec![((70, 45), WHITE), ((70, 95), WHITE)]),
|
||
("strokes paint over it", NodeKind::Rectangle, (0.0, 0.0), 5.0, Some(inside_stroke), vec![((51, 70), green), ((54, 70), BLACK)]),
|
||
("a frame's children paint over it", NodeKind::Frame, (0.0, 0.0), 5.0, Some(child), vec![((70, 51), [0, 0, 255]), ((70, 54), BLACK)]),
|
||
];
|
||
for (why, kind, offset, spread, extra, probes) in rows {
|
||
let (mut d, p) = page();
|
||
let r = d.create(p, kind, "S", 50.0, 50.0, 40.0, 40.0).unwrap();
|
||
let n = d.get_mut(r).unwrap();
|
||
n.fills = vec![Paint::solid(Color::WHITE)];
|
||
n.effects = vec![fx(EffectKind::InnerShadow(sharp(offset, spread)))];
|
||
if let Some(f) = extra {
|
||
f(&mut d, r);
|
||
}
|
||
let pix = render(&d);
|
||
for ((x, y), want) in probes {
|
||
let got = px(&pix, x, y);
|
||
assert!(near(got, want, 2), "{why} at ({x}, {y}): {got:?} want {want:?}");
|
||
}
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn effects_stack_in_list_order() {
|
||
let red = Shadow { color: hex("FF0000"), ..sharp((10.0, 0.0), 0.0) };
|
||
let blue = Shadow { color: hex("0000FF"), ..sharp((10.0, 0.0), 0.0) };
|
||
let hidden = Effect { visible: false, ..fx(EffectKind::DropShadow(sharp((10.0, 0.0), 0.0))) };
|
||
// (why, effects, pixel, check)
|
||
type Check = fn([u8; 3]) -> bool;
|
||
let rows: Vec<(&str, Vec<Effect>, Px, Check)> = vec![
|
||
(
|
||
"two shadows: the later one in the list paints over the earlier (silent, pinned like paints)",
|
||
vec![fx(EffectKind::DropShadow(red)), fx(EffectKind::DropShadow(blue))],
|
||
(95, 70),
|
||
|c| c == [0, 0, 255],
|
||
),
|
||
("a hidden effect does nothing", vec![hidden], (95, 70), |c| c == WHITE),
|
||
(
|
||
"a layer blur blurs the layer before it casts its shadow (silent, pinned)",
|
||
vec![fx(EffectKind::DropShadow(sharp((20.0, 0.0), 0.0))), fx(EffectKind::LayerBlur(Blur::uniform(10.0)))],
|
||
(108, 70),
|
||
|c| (30..=220).contains(&c[0]),
|
||
),
|
||
];
|
||
for (why, effects, (x, y), ok) in rows {
|
||
let (d, _) = square(vec![Paint::solid(hex("808080"))], effects);
|
||
let got = px(&render(&d), x, y);
|
||
assert!(ok(got), "{why}: {got:?}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn background_blur_shows_through_the_fill_inside_the_shape() {
|
||
// Black left of x = 100 under a pane (50..150) with a background blur of 20: at x = 110 the
|
||
// blurred backdrop is 218 (sigma 10, 10.5 px past the edge).
|
||
let blurred = 218;
|
||
// (why, pane fills, pixel, colour there)
|
||
let rows: Vec<(&str, Vec<Paint>, Px, [u8; 3])> = vec![
|
||
("no fill: the blurred backdrop", vec![], (110, 100), [blurred; 3]),
|
||
("a 0% fill: the blurred backdrop", vec![solid_at("FF0000", 0.0)], (110, 100), [blurred; 3]),
|
||
("a 50% fill paints over the blurred backdrop", vec![solid_at("FF0000", 0.5)], (110, 100), [236, 109, 109]),
|
||
("an opaque fill hides it", vec![Paint::solid(hex("FF0000"))], (110, 100), RED),
|
||
("outside the shape nothing is blurred", vec![], (110, 160), WHITE),
|
||
("outside the shape nothing is blurred (dark side)", vec![], (90, 160), BLACK),
|
||
];
|
||
for (why, fills, (x, y), want) in rows {
|
||
let (mut d, p) = page();
|
||
rect(&mut d, p, (0.0, 0.0, 100.0, 200.0), vec![Paint::solid(Color::BLACK)]);
|
||
let pane = rect(&mut d, p, (50.0, 50.0, 100.0, 100.0), fills);
|
||
d.get_mut(pane).unwrap().effects = vec![fx(EffectKind::BackgroundBlur(Blur::uniform(20.0)))];
|
||
let got = px(&render(&d), x, y);
|
||
assert!(near(got, want, 8), "{why}: {got:?} want {want:?}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn exports_include_what_effects_paint_outside_the_layer() {
|
||
let size = |effects: Vec<Effect>| {
|
||
let (d, r) = square(vec![Paint::solid(hex("FF0000"))], effects);
|
||
let p = render_node(&d, r, 1.0).unwrap();
|
||
(p.width(), p.height())
|
||
};
|
||
// (why, effects, grows the export)
|
||
let rows = [
|
||
("nothing: the layer's box", vec![], false),
|
||
("a drop shadow reaches out", vec![fx(EffectKind::DropShadow(sharp((0.0, 10.0), 0.0)))], true),
|
||
("a layer blur reaches out", vec![fx(EffectKind::LayerBlur(Blur::uniform(4.0)))], true),
|
||
("an inner shadow stays inside", vec![fx(EffectKind::InnerShadow(sharp((0.0, 10.0), 0.0)))], false),
|
||
("a background blur stays inside", vec![fx(EffectKind::BackgroundBlur(Blur::uniform(4.0)))], false),
|
||
("noise stays on the layer's pixels", vec![fx(EffectKind::Noise(figcraft_doc::Noise::default()))], false),
|
||
("texture roughens past the edge", vec![fx(EffectKind::Texture(figcraft_doc::Texture::default()))], true),
|
||
(
|
||
"texture clipped to the shape stays inside",
|
||
vec![fx(EffectKind::Texture(figcraft_doc::Texture { clip_to_shape: true, ..Default::default() }))],
|
||
false,
|
||
),
|
||
("glass stays inside", vec![fx(EffectKind::Glass(figcraft_doc::Glass::default()))], false),
|
||
];
|
||
for (why, effects, grows) in rows {
|
||
let (w, h) = size(effects);
|
||
// Effects grow the export only on the sides they reach (a shadow falling down grows its height).
|
||
assert!(w >= 40 && h >= 40, "{why}: {w} × {h}");
|
||
assert_eq!(w > 40 || h > 40, grows, "{why}: {w} × {h}");
|
||
}
|
||
// The export holds the shadow where the canvas draws it, pixel for pixel.
|
||
let (d, r) = square(vec![Paint::solid(hex("FF0000"))], vec![fx(EffectKind::DropShadow(Shadow { radius: 6.0, ..sharp((0.0, 10.0), 2.0) }))]);
|
||
let b = export_bounds(&d, r).unwrap();
|
||
let export = render_node(&d, r, 1.0).unwrap();
|
||
let mut canvas = Pixmap::new(export.width(), export.height()).unwrap();
|
||
render_nodes_into(&d, &[r], &View { origin: (b.x0, b.y0), scale: 1.0, width: export.width(), height: export.height() }, &mut canvas);
|
||
assert_eq!(export.data(), canvas.data(), "export and canvas agree");
|
||
assert!(b.y1 >= 90.0 + 10.0 + 2.0 + 6.0, "the shadow's blur and spread are inside the export: {b:?}");
|
||
// A clipping frame exports its shadow too.
|
||
let (mut d, p) = page();
|
||
let f = d.create(p, NodeKind::Frame, "F", 50.0, 50.0, 40.0, 40.0).unwrap();
|
||
d.get_mut(f).unwrap().effects = vec![fx(EffectKind::DropShadow(sharp((0.0, 10.0), 0.0)))];
|
||
assert!(render_node(&d, f, 1.0).unwrap().height() >= 50, "frame shadow exported");
|
||
}
|
||
|
||
/// A black 200 × 200 frame with a uniform layer blur of `radius`, drawn on a 512 × 512 view at 1×.
|
||
fn blurred_frame(radius: f64) -> Pixmap {
|
||
let mut d = Document::new("Blur range");
|
||
let p = d.first_page().unwrap();
|
||
let f = d.create(p, NodeKind::Frame, "Square", 156.0, 156.0, 200.0, 200.0).unwrap();
|
||
let n = d.get_mut(f).unwrap();
|
||
n.fills = vec![Paint::solid(Color::BLACK)];
|
||
n.effects = vec![fx(EffectKind::LayerBlur(Blur::uniform(radius)))];
|
||
let view = View { origin: (0.0, 0.0), scale: 1.0, width: 512, height: 512 };
|
||
render_view(&d, p, &view, &Options::default()).unwrap()
|
||
}
|
||
|
||
#[test]
|
||
fn layer_blur_radii_up_to_the_documented_limit_all_draw() {
|
||
// The documented blur range runs to 10,000: a larger radius in it blurs further, it doesn't
|
||
// stop at some smaller one.
|
||
let differ = |a: &Pixmap, b: &Pixmap| a.data().chunks_exact(4).zip(b.data().chunks_exact(4)).filter(|(a, b)| a != b).count();
|
||
let sharp = blurred_frame(0.0);
|
||
let small = blurred_frame(100.0);
|
||
let medium = blurred_frame(1_000.0);
|
||
let large = blurred_frame(10_000.0);
|
||
assert!(differ(&sharp, &small) > 0, "radius 0 draws the sharp shape");
|
||
assert!(differ(&small, &medium) > 0, "100 vs 1000");
|
||
assert!(differ(&medium, &large) > 0, "radii 1000 and 10000 draw the same pixels");
|
||
}
|