From 0850de890fbbb58955c9fb859af6f4e7511c045a Mon Sep 17 00:00:00 2001 From: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> Date: Wed, 25 Sep 2024 20:22:18 +0100 Subject: [PATCH] Add `draw_blurred_rounded_rect_in` (intended for box shadows) (#700) 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 --- examples/scenes/src/test_scenes.rs | 33 ++++++++++++++----- vello/src/scene.rs | 32 ++++++++++++++++-- .../snapshots/blurred_rounded_rect.png | 4 +-- vello_tests/src/snapshot.rs | 25 ++++++++++++-- vello_tests/tests/snapshot_test_scenes.rs | 2 +- 5 files changed, 79 insertions(+), 17 deletions(-) diff --git a/examples/scenes/src/test_scenes.rs b/examples/scenes/src/test_scenes.rs index 92f513d7..233017d9 100644 --- a/examples/scenes/src/test_scenes.rs +++ b/examples/scenes/src/test_scenes.rs @@ -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::{ @@ -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, @@ -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, + ); } } diff --git a/vello/src/scene.rs b/vello/src/scene.rs index f9e79e21..7c06aac4 100644 --- a/vello/src/scene.rs +++ b/vello/src/scene.rs @@ -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 _, diff --git a/vello_tests/snapshots/blurred_rounded_rect.png b/vello_tests/snapshots/blurred_rounded_rect.png index fac0ea09..8512f629 100644 --- a/vello_tests/snapshots/blurred_rounded_rect.png +++ b/vello_tests/snapshots/blurred_rounded_rect.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:31045b3af3ada20e0a0763f453a69c9f6e9a6963a024c11603490ad6e036e68a -size 766382 +oid sha256:700b4e27b371c609d7cc47c7dad623248d57cb2eac9eb3f90548adec59fc6f66 +size 121632 diff --git a/vello_tests/src/snapshot.rs b/vello_tests/src/snapshot.rs index eaefd42f..8dd09360 100644 --- a/vello_tests/src/snapshot.rs +++ b/vello_tests/src/snapshot.rs @@ -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", ¶ms.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", ¶ms.name, params.use_cpu) { if !params.use_cpu { @@ -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\ diff --git a/vello_tests/tests/snapshot_test_scenes.rs b/vello_tests/tests/snapshot_test_scenes.rs index 126d8045..5f447acd 100644 --- a/vello_tests/tests/snapshot_test_scenes.rs +++ b/vello_tests/tests/snapshot_test_scenes.rs @@ -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); }