-
Notifications
You must be signed in to change notification settings - Fork 8
imaging_vello_hybrid: support blurred rounded rects and filters #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -166,7 +166,7 @@ use imaging::RgbaImage; | |
| use imaging::record::{Scene, ValidateError, replay}; | ||
| use imaging::render::{ | ||
| GpuReadbackError, ImageBufferFormat, ImageBufferTarget, ImageRenderer, ImageRendererError, | ||
| ImageTargetError, RenderContentError, RenderSource, RenderUnsupportedError, | ||
| ImageTargetError, RenderContentError, RenderSource, | ||
| }; | ||
| pub use imaging_wgpu::wgpu; | ||
| use imaging_wgpu::{TextureRenderer, TextureRendererError, TextureTargetError, TextureViewTarget}; | ||
|
|
@@ -187,12 +187,8 @@ pub enum Error { | |
| InvalidScene(ValidateError), | ||
| /// An image brush was encountered on a sink path that has no renderer-backed image resolver. | ||
| UnsupportedImageBrush, | ||
| /// A filter configuration could not be translated. | ||
| UnsupportedFilter, | ||
| /// Masks are not supported by this backend yet. | ||
| UnsupportedMask, | ||
| /// Blurred rounded rect draws are not supported by this backend yet. | ||
| UnsupportedBlurredRoundedRect, | ||
| /// Vello hybrid returned a render error. | ||
| Render(RenderError), | ||
| /// An internal invariant was violated. | ||
|
|
@@ -570,16 +566,6 @@ fn map_texture_renderer_error(error: Error) -> TextureRendererError { | |
| Error::InvalidScene(error) => { | ||
| TextureRendererError::Content(RenderContentError::InvalidScene(error)) | ||
| } | ||
| Error::UnsupportedImageBrush => { | ||
| TextureRendererError::Unsupported(RenderUnsupportedError::ImageBrush) | ||
| } | ||
| Error::UnsupportedFilter => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should still be used for filters with more than one primitive which aren't supported. |
||
| TextureRendererError::Unsupported(RenderUnsupportedError::Filter) | ||
| } | ||
| Error::UnsupportedMask => TextureRendererError::Unsupported(RenderUnsupportedError::Mask), | ||
| Error::UnsupportedBlurredRoundedRect => { | ||
| TextureRendererError::Unsupported(RenderUnsupportedError::BlurredRoundedRect) | ||
| } | ||
| Error::Internal("render width too large" | "render height too large") => { | ||
| TextureRendererError::Target(TextureTargetError::DimensionsTooLarge) | ||
| } | ||
|
|
@@ -640,8 +626,10 @@ fn map_readback_image_error(error: ReadbackError) -> ImageRendererError { | |
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use imaging::{Painter, record::Scene, render::ImageTargetError}; | ||
| use kurbo::Rect; | ||
| use imaging::{ | ||
| BlurredRoundedRect, Composite, Filter, Painter, record::Scene, render::ImageTargetError, | ||
| }; | ||
| use kurbo::{Affine, Rect}; | ||
| use peniko::{Blob, Brush, Color, ImageAlphaType, ImageBrush, ImageData, ImageFormat}; | ||
| use pollster::block_on; | ||
| use std::sync::Arc; | ||
|
|
@@ -921,4 +909,64 @@ mod tests { | |
| assert_eq!(image.width, 20); | ||
| assert_eq!(image.height, 20); | ||
| } | ||
| #[test] | ||
| fn blurred_rounded_rect_blurs_beyond_source_rect() { | ||
| let Ok((device, queue)) = try_init_device_and_queue() else { | ||
| return; | ||
| }; | ||
| let mut renderer = VelloHybridRenderer::new(device, queue); | ||
|
|
||
| let mut scene = Scene::new(); | ||
| { | ||
| let mut painter = Painter::new(&mut scene); | ||
| painter.blurred_rounded_rect(BlurredRoundedRect { | ||
| transform: Affine::IDENTITY, | ||
| rect: Rect::new(8.0, 8.0, 24.0, 24.0), | ||
| color: Color::from_rgba8(0x40, 0x80, 0xc0, 0xff), | ||
| radius: 6.0, | ||
| std_dev: 3.0, | ||
| composite: Composite::default(), | ||
| }); | ||
| } | ||
|
|
||
| let native = renderer.encode_scene(&scene, 32, 32).unwrap(); | ||
| let image = renderer.render(&native, 32, 32).unwrap(); | ||
|
|
||
| let alpha_at = |x: usize, y: usize| -> u8 { image.data[(y * 32 + x) * 4 + 3] }; | ||
| assert_eq!(alpha_at(2, 2), 0); | ||
| assert!(alpha_at(6, 16) > 0); | ||
| assert!(alpha_at(16, 16) > alpha_at(6, 16)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn filtered_group_blurs_beyond_source_rect() { | ||
| let Ok((device, queue)) = try_init_device_and_queue() else { | ||
| return; | ||
| }; | ||
| let mut renderer = VelloHybridRenderer::new(device, queue); | ||
|
|
||
| let mut scene = Scene::new(); | ||
| { | ||
| let mut painter = Painter::new(&mut scene); | ||
| painter.with_group( | ||
| imaging::GroupRef::new().with_filters(&[Filter::blur(3.0)]), | ||
| |group| { | ||
| group | ||
| .fill( | ||
| Rect::new(8.0, 8.0, 24.0, 24.0), | ||
| Color::from_rgb8(0x20, 0x90, 0x60), | ||
| ) | ||
| .draw(); | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| let native = renderer.encode_scene(&scene, 32, 32).unwrap(); | ||
| let image = renderer.render(&native, 32, 32).unwrap(); | ||
|
|
||
| let alpha_at = |x: usize, y: usize| -> u8 { image.data[(y * 32 + x) * 4 + 3] }; | ||
| assert_eq!(alpha_at(2, 2), 0); | ||
| assert!(alpha_at(6, 16) > 0); | ||
| assert!(alpha_at(16, 16) > alpha_at(6, 16)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ use imaging::{ | |
| }; | ||
| use kurbo::{Affine, Shape as _}; | ||
| use peniko::{Brush, BrushRef, ImageBrush, Style}; | ||
| use std::sync::Arc; | ||
| use vello_common::filter_effects::{EdgeMode, Filter as VelloFilter, FilterGraph, FilterPrimitive}; | ||
| use vello_common::glyph::Glyph as VelloGlyph; | ||
|
|
||
| /// Borrowed adapter that streams `imaging` commands into an existing [`vello_hybrid::Scene`]. | ||
|
|
@@ -212,8 +214,73 @@ impl<'a> VelloHybridSceneSink<'a> { | |
| } | ||
| } | ||
|
|
||
| fn draw_blurred_rounded_rect(&mut self, _draw: BlurredRoundedRect) { | ||
| self.set_error_once(Error::UnsupportedBlurredRoundedRect); | ||
| fn draw_blurred_rounded_rect(&mut self, draw: BlurredRoundedRect) { | ||
| #[expect( | ||
| clippy::cast_possible_truncation, | ||
| reason = "filter sigma is stored as f32" | ||
| )] | ||
| let blur_sigma = draw.std_dev as f32; | ||
| self.scene.set_transform(draw.transform); | ||
| self.scene.set_fill_rule(peniko::Fill::NonZero); | ||
| self.scene.push_layer( | ||
| None, | ||
| Some(draw.composite.blend), | ||
| Some(draw.composite.alpha), | ||
| None, | ||
| self.filters_to_vello(&[imaging::Filter::blur(blur_sigma)]), | ||
| ); | ||
| self.scene.set_blend_mode(Composite::default().blend); | ||
| self.scene.set_paint(Brush::Solid( | ||
| draw.color.multiply_alpha(Composite::default().alpha), | ||
| )); | ||
| let path = draw | ||
| .rect | ||
| .to_rounded_rect(draw.radius) | ||
| .to_path(self.tolerance); | ||
| self.scene.fill_path(&path); | ||
| self.scene.pop_layer(); | ||
| } | ||
|
|
||
| fn filters_to_vello(&self, filters: &[imaging::Filter]) -> Option<VelloFilter> { | ||
| if filters.is_empty() { | ||
| return None; | ||
| } | ||
|
|
||
| let mut graph = FilterGraph::new(); | ||
| let mut last = None; | ||
| for filter in filters { | ||
| let primitive = match *filter { | ||
| imaging::Filter::Flood { color } => FilterPrimitive::Flood { color }, | ||
| imaging::Filter::Blur { | ||
| std_deviation_x, | ||
| std_deviation_y, | ||
| } => FilterPrimitive::GaussianBlur { | ||
| std_deviation: std_deviation_x.max(std_deviation_y), | ||
| edge_mode: EdgeMode::None, | ||
| }, | ||
| imaging::Filter::DropShadow { | ||
| dx, | ||
| dy, | ||
| std_deviation_x, | ||
| std_deviation_y, | ||
| color, | ||
| } => FilterPrimitive::DropShadow { | ||
| dx, | ||
| dy, | ||
| std_deviation: std_deviation_x.max(std_deviation_y), | ||
| color, | ||
| edge_mode: EdgeMode::None, | ||
| }, | ||
| imaging::Filter::Offset { dx, dy } => FilterPrimitive::Offset { dx, dy }, | ||
| }; | ||
| last = Some(graph.add(primitive, None)); | ||
| } | ||
| if let Some(output) = last { | ||
| graph.set_output(output); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You dropped some logic here that was in the vello_cpu adapter code... |
||
| Some(VelloFilter { | ||
| graph: Arc::new(graph), | ||
| }) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -249,10 +316,6 @@ impl PaintSink for VelloHybridSceneSink<'_> { | |
| self.set_error_once(Error::UnsupportedMask); | ||
| return; | ||
| } | ||
| if !group.filters.is_empty() { | ||
| self.set_error_once(Error::UnsupportedFilter); | ||
| return; | ||
| } | ||
| let clip_path = group.clip.map(|clip| { | ||
| let (xf, path, fill_rule) = self.clip_to_path(clip); | ||
| self.scene.set_transform(xf); | ||
|
|
@@ -262,8 +325,9 @@ impl PaintSink for VelloHybridSceneSink<'_> { | |
|
|
||
| let blend = Some(group.composite.blend); | ||
| let opacity = Some(group.composite.alpha); | ||
| let filter = self.filters_to_vello(group.filters); | ||
| self.scene | ||
| .push_layer(clip_path.as_ref(), blend, opacity, None, None); | ||
| .push_layer(clip_path.as_ref(), blend, opacity, None, filter); | ||
| self.group_depth += 1; | ||
| } | ||
|
|
||
|
|
@@ -388,12 +452,13 @@ mod tests { | |
| } | ||
|
|
||
| #[test] | ||
| fn hybrid_scene_sink_rejects_filters() { | ||
| fn hybrid_scene_sink_supports_filters() { | ||
| let mut scene = vello_hybrid::Scene::new(32, 32); | ||
| scene.reset(); | ||
| let mut sink = VelloHybridSceneSink::new(&mut scene); | ||
| sink.push_group(GroupRef::new().with_filters(&[Filter::blur(2.0)])); | ||
| assert!(matches!(sink.finish(), Err(Error::UnsupportedFilter))); | ||
| sink.pop_group(); | ||
| assert!(matches!(sink.finish(), Ok(()))); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove this? The error type still exists...