Skip to content

Commit

Permalink
Add draw_blurred_rounded_rect_in (intended for box shadows) (#700)
Browse files Browse the repository at this point in the history
This avoids needing a clip layer for a rounded rectangle which doesn't
need to draw the inner parts (e.g. a transparent background with a box
shadow).

The previous test image was oversized, so this also fixes that (and
makes any future such image fail the tests as expected). I imagine that
the author of that test manually copied the value into the place, not
realising the size of the image.

---------

Co-authored-by: Raph Levien <[email protected]>
  • Loading branch information
DJMcNab and raphlinus authored Sep 25, 2024
1 parent 5ad79d0 commit 0850de8
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 17 deletions.
33 changes: 24 additions & 9 deletions examples/scenes/src/test_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ mod impls {
use std::f64::consts::PI;

use crate::SceneParams;
use kurbo::RoundedRect;
use rand::Rng;
use rand::{rngs::StdRng, SeedableRng};
use vello::kurbo::{
Expand Down Expand Up @@ -1719,15 +1720,6 @@ mod impls {
params.time.sin() * 50.0 + 50.0,
);

// Stretch affine transformation.
scene.draw_blurred_rounded_rect(
Affine::translate((600.0, 600.0)) * Affine::scale_non_uniform(2.2, 0.9),
rect,
Color::BLACK,
radius,
params.time.sin() * 50.0 + 50.0,
);

// Circle.
scene.draw_blurred_rounded_rect(
Affine::IDENTITY,
Expand All @@ -1745,5 +1737,28 @@ mod impls {
150.0,
params.time.sin() * 50.0 + 50.0,
);

// An emulated box shadow, to demonstrate the use of `draw_blurred_rounded_rect_in`.
let std_dev = params.time.sin() * 50.0 + 50.0;
let kernel_size = 2.5 * std_dev;

// TODO: Add utils to Kurbo for ad-hoc composed shapes
let shape = BezPath::from_iter(
rect.inflate(kernel_size, kernel_size)
.path_elements(0.1)
.chain(
RoundedRect::from_rect(rect, radius)
.to_path(0.1)
.reverse_subpaths(),
),
);
scene.draw_blurred_rounded_rect_in(
&shape,
Affine::translate((600.0, 600.0)) * Affine::scale_non_uniform(2.2, 0.9),
rect,
Color::BLACK,
radius,
std_dev,
);
}
}
32 changes: 29 additions & 3 deletions vello/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,39 @@ impl Scene {
// For performance reason we cut off the filter at some extent where the response is close to zero.
let kernel_size = 2.5 * std_dev;

let t = Transform::from_kurbo(&transform.pre_translate(rect.center().to_vec2()));
let shape: Rect = rect.inflate(kernel_size, kernel_size);
self.draw_blurred_rounded_rect_in(&shape, transform, rect, brush, radius, std_dev);
}

/// Draw a rounded rectangle blurred with a gaussian filter in `shape`.
///
/// For performance reasons, `shape` should not extend more than approximately 2.5 times
/// `std_dev` away from the edges of `rect` (as any such points will not be perceptably painted to,
/// but calculations will still be performed for them).
///
/// This method effectively draws the blurred rounded rectangle clipped to the given shape.
/// If just the blurred rounded rectangle is desired without clipping,
/// use the simpler [`Self::draw_blurred_rounded_rect`].
/// For many users, that method will be easier to use.
pub fn draw_blurred_rounded_rect_in(
&mut self,
shape: &impl Shape,
transform: Affine,
rect: Rect,
brush: Color,
radius: f64,
std_dev: f64,
) {
let t = Transform::from_kurbo(&transform);
self.encoding.encode_transform(t);

let shape: Rect =
Rect::from_center_size((0.0, 0.0), rect.size()).inflate(kernel_size, kernel_size);
self.encoding.encode_fill_style(Fill::NonZero);
if self.encoding.encode_shape(&shape, true) {
let brush_transform =
Transform::from_kurbo(&transform.pre_translate(rect.center().to_vec2()));
if self.encoding.encode_transform(brush_transform) {
self.encoding.swap_last_path_tags();
}
self.encoding.encode_blurred_rounded_rect(
brush,
rect.width() as _,
Expand Down
4 changes: 2 additions & 2 deletions vello_tests/snapshots/blurred_rounded_rect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 23 additions & 2 deletions vello_tests/src/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,23 @@ pub fn snapshot_test_image(
.with_extension(update_extension);

let expected_data = match image::open(&reference_path) {
Ok(contents) => contents.into_rgb8(),
Ok(contents) => {
let size = std::fs::metadata(&reference_path).map(|it| it.len())?;
if size > directory.max_size_in_bytes()
// If we expect to be updating the test, there's no need to fail here.
&& !env_var_relates_to("VELLO_TEST_UPDATE", &params.name, params.use_cpu)
{
bail!(
"Stored result for {test_name} is too large.\n\
Expected {max} bytes, got {size} bytes in {reference_path}",
max = directory.max_size_in_bytes(),
test_name = params.name,
reference_path = reference_path.display()
);
}

contents.into_rgb8()
}
Err(ImageError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => {
if env_var_relates_to("VELLO_TEST_CREATE", &params.name, params.use_cpu) {
if !params.use_cpu {
Expand Down Expand Up @@ -210,7 +226,12 @@ pub fn snapshot_test_image(
params,
});
} else {
write_png_to_file(params, &update_path, &raw_rendered, None)?;
write_png_to_file(
params,
&update_path,
&raw_rendered,
Some(directory.max_size_in_bytes()),
)?;
bail!(
"Couldn't find snapshot for test {}. Searched at {:?}\n\
Test result written to {:?}\n\
Expand Down
2 changes: 1 addition & 1 deletion vello_tests/tests/snapshot_test_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,6 @@ fn snapshot_many_clips() {
#[cfg_attr(skip_gpu_tests, ignore)]
fn snapshot_blurred_rounded_rect() {
let test_scene = test_scenes::blurred_rounded_rect();
let params = TestParams::new("blurred_rounded_rect", 1200, 1200);
let params = TestParams::new("blurred_rounded_rect", 400, 400);
snapshot_test_scene(test_scene, params);
}

0 comments on commit 0850de8

Please sign in to comment.