diff --git a/imaging_snapshot_tests/src/cases/masks.rs b/imaging_snapshot_tests/src/cases/masks.rs index 03f4031..008a646 100644 --- a/imaging_snapshot_tests/src/cases/masks.rs +++ b/imaging_snapshot_tests/src/cases/masks.rs @@ -122,7 +122,7 @@ impl SnapshotCase for GmMaskAlpha { } fn supports_backend(&self, backend: &str) -> bool { - matches!(backend, "skia" | "tiny_skia" | "vello_cpu") + matches!(backend, "skia" | "tiny_skia" | "vello_cpu" | "vello") } fn run(&self, sink: &mut dyn PaintSink, width: f64, height: f64) { diff --git a/imaging_snapshot_tests/tests/snapshots/vello/gm_mask_alpha.png b/imaging_snapshot_tests/tests/snapshots/vello/gm_mask_alpha.png new file mode 100644 index 0000000..20e9ec1 Binary files /dev/null and b/imaging_snapshot_tests/tests/snapshots/vello/gm_mask_alpha.png differ diff --git a/imaging_vello/src/scene_sink.rs b/imaging_vello/src/scene_sink.rs index 91c5285..989fcfc 100644 --- a/imaging_vello/src/scene_sink.rs +++ b/imaging_vello/src/scene_sink.rs @@ -9,7 +9,7 @@ use imaging::{ record::{Scene, replay_transformed}, }; use kurbo::{Affine, Rect}; -use peniko::{Brush, BrushRef, Fill}; +use peniko::{BlendMode, Brush, BrushRef, Color, Compose, Fill, Mix}; use std::boxed::Box; /// Borrowed adapter that streams `imaging` commands into an existing [`crate::vello::Scene`]. @@ -120,15 +120,21 @@ impl<'a> VelloSceneSink<'a> { glyph_run: GlyphRunRef<'_>, glyphs: &mut dyn Iterator, ) { - if glyph_run.composite.blend != peniko::BlendMode::default() { - self.set_error_once(Error::UnsupportedGlyphBlend); - return; - } - let Some(paint) = self.brush_to_brush(glyph_run.brush, glyph_run.composite) else { return; }; + let blended = glyph_run.composite.blend != BlendMode::default(); + if blended { + self.scene.push_layer( + Fill::NonZero, + glyph_run.composite.blend, + 1.0, + Affine::IDENTITY, + &self.surface_clip, + ); + self.push_group_frame(None); + } let builder = self .scene .draw_glyphs(glyph_run.font) @@ -145,12 +151,26 @@ impl<'a> VelloSceneSink<'a> { y: glyph.y, }); builder.draw(glyph_run.style, glyphs); + + if blended { + if self.pop_group_frame().is_none() { + return; + } + self.scene.pop_layer(); + } } fn draw_blurred_rounded_rect(&mut self, draw: BlurredRoundedRect) { - if draw.composite.blend != peniko::BlendMode::default() { - self.set_error_once(Error::UnsupportedBlurredRoundedRectBlend); - return; + let blended = draw.composite.blend != BlendMode::default(); + if blended { + self.scene.push_layer( + Fill::NonZero, + draw.composite.blend, + 1.0, + draw.transform, + &draw.rect, + ); + self.push_group_frame(None); } self.scene.draw_blurred_rounded_rect( draw.transform, @@ -159,11 +179,35 @@ impl<'a> VelloSceneSink<'a> { draw.radius, draw.std_dev, ); + if blended { + if self.pop_group_frame().is_none() { + return; + } + self.scene.pop_layer(); + } } fn replay_masked_subscene(&mut self, scene: &Scene, transform: Affine) { replay_transformed(scene, self, transform); } + + fn normalize_alpha_mask(&mut self) { + self.scene.push_layer( + Fill::NonZero, + BlendMode::new(Mix::Normal, Compose::SrcIn), + 1.0, + Affine::IDENTITY, + &self.surface_clip, + ); + self.scene.fill( + Fill::NonZero, + Affine::IDENTITY, + &Brush::Solid(Color::WHITE), + None, + &self.surface_clip, + ); + self.scene.pop_layer(); + } } impl PaintSink for VelloSceneSink<'_> { @@ -213,18 +257,6 @@ impl PaintSink for VelloSceneSink<'_> { if self.error.is_some() { return; } - if !group.filters.is_empty() { - self.set_error_once(Error::UnsupportedFilter); - return; - } - if group - .mask - .as_ref() - .is_some_and(|mask| mask.mask.mode != MaskMode::Luminance) - { - self.set_error_once(Error::UnsupportedMask); - return; - } if let Some(clip) = group.clip { match clip { @@ -321,11 +353,6 @@ impl PaintSink for VelloSceneSink<'_> { return; }; if let Some(mask) = mask { - debug_assert_eq!( - mask.mode, - MaskMode::Luminance, - "only luminance masks should reach Vello group-mask replay" - ); self.scene.push_luminance_mask_layer( Fill::NonZero, 1.0, @@ -333,6 +360,9 @@ impl PaintSink for VelloSceneSink<'_> { &self.surface_clip, ); self.replay_masked_subscene(&mask.scene, mask.transform); + if mask.mode == MaskMode::Alpha { + self.normalize_alpha_mask(); + } self.scene.pop_layer(); } self.scene.pop_layer(); @@ -348,14 +378,14 @@ impl PaintSink for VelloSceneSink<'_> { }; let (blend, paint) = match (&paint, draw.composite.blend.compose) { - (Brush::Solid(c), peniko::Compose::Copy) if c.components[3] == 0.0 => ( - peniko::BlendMode::new(peniko::Mix::Normal, peniko::Compose::DestOut), - Brush::Solid(peniko::Color::from_rgba8(0, 0, 0, 255)), + (Brush::Solid(c), Compose::Copy) if c.components[3] == 0.0 => ( + BlendMode::new(Mix::Normal, Compose::DestOut), + Brush::Solid(Color::from_rgba8(0, 0, 0, 255)), ), _ => (draw.composite.blend, paint), }; - if blend != peniko::BlendMode::default() { + if blend != BlendMode::default() { match &draw.shape { GeometryRef::Rect(r) => { self.scene @@ -416,7 +446,7 @@ impl PaintSink for VelloSceneSink<'_> { } } - if blend != peniko::BlendMode::default() { + if blend != BlendMode::default() { if self.pop_group_frame().is_none() { return; } @@ -434,14 +464,14 @@ impl PaintSink for VelloSceneSink<'_> { }; let (blend, paint) = match (&paint, draw.composite.blend.compose) { - (Brush::Solid(c), peniko::Compose::Copy) if c.components[3] == 0.0 => ( - peniko::BlendMode::new(peniko::Mix::Normal, peniko::Compose::DestOut), - Brush::Solid(peniko::Color::from_rgba8(0, 0, 0, 255)), + (Brush::Solid(c), Compose::Copy) if c.components[3] == 0.0 => ( + BlendMode::new(Mix::Normal, Compose::DestOut), + Brush::Solid(Color::from_rgba8(0, 0, 0, 255)), ), _ => (draw.composite.blend, paint), }; - if blend != peniko::BlendMode::default() { + if blend != BlendMode::default() { match &draw.shape { GeometryRef::Rect(r) => { self.scene @@ -497,7 +527,7 @@ impl PaintSink for VelloSceneSink<'_> { } } - if blend != peniko::BlendMode::default() { + if blend != BlendMode::default() { if self.pop_group_frame().is_none() { return; } @@ -539,11 +569,12 @@ mod tests { } #[test] - fn vello_scene_sink_rejects_filters() { + fn vello_scene_sink_ignores_filters() { let mut scene = vello::Scene::new(); let mut sink = VelloSceneSink::new(&mut scene, Rect::new(0.0, 0.0, 32.0, 32.0)); 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] @@ -560,13 +591,15 @@ mod tests { } #[test] - fn vello_scene_sink_rejects_alpha_masks() { + fn vello_scene_sink_supports_alpha_masks() { let mut mask = Scene::new(); mask.fill(FillRef::new(Rect::new(0.0, 0.0, 16.0, 16.0), Color::WHITE)); let mut scene = vello::Scene::new(); let mut sink = VelloSceneSink::new(&mut scene, Rect::new(0.0, 0.0, 32.0, 32.0)); sink.push_group(GroupRef::new().with_mask(MaskRef::new(MaskMode::Alpha, &mask))); - assert!(matches!(sink.finish(), Err(Error::UnsupportedMask))); + sink.fill(FillRef::new(Rect::new(4.0, 4.0, 20.0, 20.0), Color::BLACK)); + sink.pop_group(); + assert!(matches!(sink.finish(), Ok(()))); } }