diff --git a/imaging_skia/src/lib.rs b/imaging_skia/src/lib.rs index da09216..b916248 100644 --- a/imaging_skia/src/lib.rs +++ b/imaging_skia/src/lib.rs @@ -122,11 +122,8 @@ use imaging::{ use kurbo::{Affine, Shape as _}; use peniko::color::{ColorSpaceTag, HueDirection}; use peniko::{BrushRef, ImageAlphaType, ImageFormat, ImageQuality, InterpolationAlphaSpace}; -use skia_safe as sk; -use std::{cell::RefCell, rc::Rc}; - -use sinks::MaskCache; pub use sinks::{SkCanvasSink, SkPictureRecorderSink}; +use skia_safe as sk; /// Errors that can occur when rendering via Skia. #[derive(Debug)] @@ -156,7 +153,6 @@ pub struct SkiaRenderer { width: i32, height: i32, tolerance: f64, - mask_cache: Rc>, } impl SkiaRenderer { @@ -181,26 +177,16 @@ impl SkiaRenderer { width, height, tolerance: 0.1, - mask_cache: Rc::new(RefCell::new(MaskCache::default())), } } /// Set the tolerance used when converting shapes to paths. pub fn set_tolerance(&mut self, tolerance: f64) { - if self.tolerance != tolerance { - self.mask_cache.borrow_mut().clear(); - } self.tolerance = tolerance; } - /// Drop any realized mask artifacts cached by the renderer. - /// - /// The cache is renderer-scoped so unchanged masked subscenes can be reused across renders. - /// Call this if you need to release memory aggressively or after changing assumptions that - /// affect mask realization outside the recorded scene itself. - pub fn clear_cached_masks(&mut self) { - self.mask_cache.borrow_mut().clear(); - } + /// No-op retained for API compatibility after native Skia masking removed realized mask caches. + pub fn clear_cached_masks(&mut self) {} fn reset(&mut self) { let canvas = self.surface.canvas(); @@ -213,8 +199,7 @@ impl SkiaRenderer { pub fn render_scene_rgba8(&mut self, scene: &Scene) -> Result, Error> { scene.validate().map_err(Error::InvalidScene)?; self.reset(); - let mut sink = - SkCanvasSink::new_with_mask_cache(self.surface.canvas(), Rc::clone(&self.mask_cache)); + let mut sink = SkCanvasSink::new(self.surface.canvas()); sink.set_tolerance(self.tolerance); replay(scene, &mut sink); sink.finish()?; @@ -735,36 +720,30 @@ mod tests { let mut renderer = SkiaRenderer::new(64, 64); renderer.render_scene_rgba8(&scene).unwrap(); - assert_eq!(renderer.mask_cache.borrow().len(), 1); renderer.render_scene_rgba8(&scene).unwrap(); - assert_eq!(renderer.mask_cache.borrow().len(), 1); } #[test] - fn clear_cached_masks_drops_realized_masks() { + fn clear_cached_masks_is_a_no_op_without_realized_masks() { let scene = masked_scene(MaskMode::Luminance); let mut renderer = SkiaRenderer::new(64, 64); renderer.render_scene_rgba8(&scene).unwrap(); - assert_eq!(renderer.mask_cache.borrow().len(), 1); renderer.clear_cached_masks(); - assert_eq!(renderer.mask_cache.borrow().len(), 0); renderer.render_scene_rgba8(&scene).unwrap(); - assert_eq!(renderer.mask_cache.borrow().len(), 1); } #[test] - fn changing_tolerance_clears_cached_masks() { + fn changing_tolerance_keeps_masked_rendering_working() { let scene = masked_scene(MaskMode::Alpha); let mut renderer = SkiaRenderer::new(64, 64); renderer.render_scene_rgba8(&scene).unwrap(); - assert_eq!(renderer.mask_cache.borrow().len(), 1); renderer.set_tolerance(0.25); - assert_eq!(renderer.mask_cache.borrow().len(), 0); + renderer.render_scene_rgba8(&scene).unwrap(); } } diff --git a/imaging_skia/src/sinks.rs b/imaging_skia/src/sinks.rs index d3b4338..b6abf4c 100644 --- a/imaging_skia/src/sinks.rs +++ b/imaging_skia/src/sinks.rs @@ -13,79 +13,6 @@ use imaging::{ }; use kurbo::{Affine, Rect, Shape as _}; use skia_safe as sk; -use std::{cell::RefCell, collections::VecDeque, rc::Rc}; - -#[derive(Clone, Debug)] -struct CachedMask { - scene: record::Scene, - mode: MaskMode, - transform: Affine, - width: i32, - height: i32, - tolerance: f64, - bytes: Vec, -} - -#[derive(Debug, Default)] -pub(crate) struct MaskCache { - entries: VecDeque, -} - -impl MaskCache { - pub(crate) fn clear(&mut self) { - self.entries.clear(); - } - - fn get( - &self, - scene: &record::Scene, - mode: MaskMode, - transform: Affine, - width: i32, - height: i32, - tolerance: f64, - ) -> Option> { - self.entries - .iter() - .find(|entry| { - entry.scene == *scene - && entry.mode == mode - && entry.transform == transform - && entry.width == width - && entry.height == height - && entry.tolerance == tolerance - }) - .map(|entry| entry.bytes.clone()) - } - - fn insert( - &mut self, - scene: &record::Scene, - mode: MaskMode, - transform: Affine, - width: i32, - height: i32, - tolerance: f64, - bytes: &[u8], - ) { - // If more backends end up wanting realized-mask caches, add a portable scene/cache key at - // the imaging layer instead of retaining full scenes in backend-local caches. - self.entries.push_back(CachedMask { - scene: scene.clone(), - mode, - transform, - width, - height, - tolerance, - bytes: bytes.to_vec(), - }); - } - - #[cfg(test)] - pub(crate) fn len(&self) -> usize { - self.entries.len() - } -} #[derive(Debug)] struct StreamState { @@ -321,188 +248,72 @@ fn draw_blurred_rounded_rect( canvas.draw_rrect(rrect, &paint); } -fn canvas_dimensions(canvas: &sk::Canvas) -> Option<(i32, i32)> { - let dims = canvas.image_info().dimensions(); - (dims.width > 0 && dims.height > 0).then_some((dims.width, dims.height)) -} - -fn raster_surface_for_canvas(canvas: &sk::Canvas) -> Option { - let (width, height) = canvas_dimensions(canvas)?; - sk::surfaces::raster_n32_premul((width, height)) -} - -fn read_surface_rgba_premul(surface: &mut sk::Surface) -> Option> { - let image = surface.image_snapshot(); - let dims = image.dimensions(); - let row_bytes = (dims.width as usize) * 4; - let info = sk::ImageInfo::new( - (dims.width, dims.height), - sk::ColorType::RGBA8888, - sk::AlphaType::Premul, - None, - ); - let mut bytes = vec![0_u8; row_bytes * (dims.height as usize)]; - image - .read_pixels( - &info, - bytes.as_mut_slice(), - row_bytes, - (0, 0), - sk::image::CachingHint::Disallow, - ) - .then_some(bytes) -} - -fn mask_value(mask: &[u8], mode: MaskMode) -> u8 { - match mode { - MaskMode::Alpha => mask[3], - MaskMode::Luminance => { - let value = (54_u32 * u32::from(mask[0]) - + 183_u32 * u32::from(mask[1]) - + 19_u32 * u32::from(mask[2]) - + 128) - >> 8; - u8::try_from(value).expect("luminance mask value stays within u8 range") +fn draw_masked_group(canvas: &sk::Canvas, state: &mut StreamState, masked: MaskedGroupFrame) { + let mut group_paint = sk::Paint::default(); + group_paint.set_anti_alias(true); + group_paint.set_blend_mode(map_blend_mode(&masked.composite.blend)); + group_paint.set_alpha_f(masked.composite.alpha); + if !masked.filters.is_empty() { + if let Some(filter) = build_filter_chain(&masked.filters) { + group_paint.set_image_filter(filter); + } else { + state.set_error_once(Error::UnsupportedFilter); + return; } } -} -fn apply_mask_to_premul_rgba(content: &mut [u8], mask: &[u8], mode: MaskMode) { - for (content_px, mask_px) in content.chunks_exact_mut(4).zip(mask.chunks_exact(4)) { - let mask = u32::from(mask_value(mask_px, mode)); - for channel in content_px.iter_mut() { - let value = (u32::from(*channel) * mask + 127) / 255; - *channel = u8::try_from(value).expect("masked premul channel stays within u8 range"); - } + let clip_path = masked + .clip + .as_ref() + .and_then(|clip| clip_path(canvas, state, clip.as_ref())); + if let Some(path) = clip_path.as_ref() { + canvas.save(); + canvas.clip_path(path, None, true); } -} -fn draw_masked_group( - canvas: &sk::Canvas, - state: &mut StreamState, - masked: MaskedGroupFrame, - mask_cache: Option<&Rc>>, -) { - let Some((width, height)) = canvas_dimensions(canvas) else { - state.set_error_once(Error::Internal( - "masked layer requires raster canvas dimensions", - )); - return; - }; - - let mask_bytes = if let Some(cache) = mask_cache - && let Some(bytes) = cache.borrow().get( - &masked.mask, - masked.mode, - masked.transform, - width, - height, - state.tolerance, - ) { - bytes - } else { - let Some(mut mask_surface) = raster_surface_for_canvas(canvas) else { - state.set_error_once(Error::Internal( - "masked layer requires raster canvas dimensions", - )); - return; - }; - { - let mut sink = match mask_cache { - Some(cache) => { - SkCanvasSink::new_with_mask_cache(mask_surface.canvas(), cache.clone()) - } - None => SkCanvasSink::new(mask_surface.canvas()), - }; - sink.set_tolerance(state.tolerance); - replay_transformed(&masked.mask, &mut sink, masked.transform); - if let Err(err) = sink.finish() { - state.set_error_once(err); - return; - } - } - let Some(bytes) = read_surface_rgba_premul(&mut mask_surface) else { - state.set_error_once(Error::Internal("read masked layer surface")); - return; - }; - if let Some(cache) = mask_cache { - cache.borrow_mut().insert( - &masked.mask, - masked.mode, - masked.transform, - width, - height, - state.tolerance, - &bytes, - ); - } - bytes - }; - - let Some(mut content_surface) = raster_surface_for_canvas(canvas) else { - state.set_error_once(Error::Internal( - "masked layer requires raster canvas dimensions", - )); - return; - }; + set_matrix(canvas, Affine::IDENTITY); + canvas.save_layer(&sk::canvas::SaveLayerRec::default().paint(&group_paint)); { - let mut sink = match mask_cache { - Some(cache) => { - SkCanvasSink::new_with_mask_cache(content_surface.canvas(), cache.clone()) - } - None => SkCanvasSink::new(content_surface.canvas()), - }; + let mut sink = SkCanvasSink::new(canvas); sink.set_tolerance(state.tolerance); replay(&masked.content, &mut sink); if let Err(err) = sink.finish() { state.set_error_once(err); + canvas.restore(); + if clip_path.is_some() { + canvas.restore(); + } return; } } - let Some(mut content_bytes) = read_surface_rgba_premul(&mut content_surface) else { - state.set_error_once(Error::Internal("read masked content surface")); - return; - }; - apply_mask_to_premul_rgba(&mut content_bytes, &mask_bytes, masked.mode); - let info = sk::ImageInfo::new( - (width, height), - sk::ColorType::RGBA8888, - sk::AlphaType::Premul, - None, - ); - let row_bytes = (width as usize) * 4; - let Some(image) = - sk::images::raster_from_data(&info, sk::Data::new_copy(&content_bytes), row_bytes) - else { - state.set_error_once(Error::Internal("create masked layer image")); - return; - }; - let mut paint = sk::Paint::default(); - paint.set_anti_alias(true); - paint.set_blend_mode(map_blend_mode(&masked.composite.blend)); - paint.set_alpha_f(masked.composite.alpha); - if !masked.filters.is_empty() { - if let Some(filter) = build_filter_chain(&masked.filters) { - paint.set_image_filter(filter); - } else { - state.set_error_once(Error::UnsupportedFilter); - return; - } + let mut mask_paint = sk::Paint::default(); + mask_paint.set_anti_alias(true); + mask_paint.set_blend_mode(sk::BlendMode::DstIn); + if masked.mode == MaskMode::Luminance { + mask_paint.set_color_filter(sk::ColorFilter::luma()); } + canvas.save_layer(&sk::canvas::SaveLayerRec::default().paint(&mask_paint)); + set_matrix(canvas, Affine::IDENTITY); - let clip_path = masked - .clip - .as_ref() - .and_then(|clip| clip_path(canvas, state, clip.as_ref())); - if let Some(path) = clip_path.as_ref() { - canvas.save(); - canvas.clip_path(path, None, true); + { + let mut sink = SkCanvasSink::new(canvas); + sink.set_tolerance(state.tolerance); + replay_transformed(&masked.mask, &mut sink, masked.transform); + if let Err(err) = sink.finish() { + state.set_error_once(err); + canvas.restore(); + canvas.restore(); + if clip_path.is_some() { + canvas.restore(); + } + return; + } } - set_matrix(canvas, Affine::IDENTITY); - canvas.draw_image(&image, (0, 0), Some(&paint)); + canvas.restore(); + canvas.restore(); if clip_path.is_some() { canvas.restore(); @@ -569,11 +380,7 @@ fn paint_sink_push_group(canvas: &sk::Canvas, state: &mut StreamState, group: Gr state.group_stack.push(GroupFrame::Direct { restores }); } -fn paint_sink_pop_group( - canvas: &sk::Canvas, - state: &mut StreamState, - mask_cache: Option<&Rc>>, -) { +fn paint_sink_pop_group(canvas: &sk::Canvas, state: &mut StreamState) { if state.error.is_some() { return; } @@ -594,7 +401,7 @@ fn paint_sink_pop_group( state.group_stack.push(GroupFrame::Masked(frame)); return; } - draw_masked_group(canvas, state, *frame, mask_cache); + draw_masked_group(canvas, state, *frame); } } } @@ -699,7 +506,6 @@ fn paint_sink_stroke(canvas: &sk::Canvas, state: &mut StreamState, draw: StrokeR /// Borrowed adapter that streams `imaging` commands into an existing [`skia_safe::Canvas`]. pub struct SkCanvasSink<'a> { canvas: &'a sk::Canvas, - mask_cache: Option>>, state: StreamState, } @@ -719,18 +525,6 @@ impl<'a> SkCanvasSink<'a> { pub fn new(canvas: &'a sk::Canvas) -> Self { Self { canvas, - mask_cache: None, - state: StreamState::new(), - } - } - - pub(crate) fn new_with_mask_cache( - canvas: &'a sk::Canvas, - mask_cache: Rc>, - ) -> Self { - Self { - canvas, - mask_cache: Some(mask_cache), state: StreamState::new(), } } @@ -760,7 +554,7 @@ impl PaintSink for SkCanvasSink<'_> { } fn pop_group(&mut self) { - paint_sink_pop_group(self.canvas, &mut self.state, self.mask_cache.as_ref()); + paint_sink_pop_group(self.canvas, &mut self.state); } fn fill(&mut self, draw: FillRef<'_>) { @@ -889,7 +683,7 @@ impl PaintSink for SkPictureRecorderSink { state.set_error_once(Error::Internal("picture recorder not recording")); return; }; - paint_sink_pop_group(canvas, state, None); + paint_sink_pop_group(canvas, state); } fn fill(&mut self, draw: FillRef<'_>) {