From bad9f2469dd302ce3131857d8defdeecc41a158e Mon Sep 17 00:00:00 2001 From: Anthony Firn Date: Fri, 3 Apr 2026 07:22:18 -0700 Subject: [PATCH] Oversample small SVG icons before rasterization --- tiny_skia/src/vector.rs | 29 +++++++++++++++++++++++--- wgpu/src/image/vector.rs | 45 ++++++++++++++++++++++++++++++++-------- 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/tiny_skia/src/vector.rs b/tiny_skia/src/vector.rs index 59cd199307..7cb371f2bb 100644 --- a/tiny_skia/src/vector.rs +++ b/tiny_skia/src/vector.rs @@ -11,6 +11,22 @@ use std::fs; use std::panic; use std::sync::Arc; +const SMALL_SVG_OVERSAMPLE_LIMIT: u32 = 128; +const SMALL_SVG_OVERSAMPLE_FACTOR: u32 = 2; + +fn oversample_size(size: Size) -> Size { + let factor = if size.width.max(size.height) <= SMALL_SVG_OVERSAMPLE_LIMIT { + SMALL_SVG_OVERSAMPLE_FACTOR + } else { + 1 + }; + + Size::new( + size.width.saturating_mul(factor), + size.height.saturating_mul(factor), + ) +} + #[derive(Debug)] pub struct Pipeline { cache: RefCell, @@ -40,16 +56,23 @@ impl Pipeline { transform: Transform, clip_mask: Option<&tiny_skia::Mask>, ) { + let requested_size = + oversample_size(Size::new(bounds.width as u32, bounds.height as u32)); + if let Some(image) = self.cache.borrow_mut().draw( handle, color, // XX Do not apply transform scaling for rotation to the size - Size::new((bounds.width) as u32, (bounds.height) as u32), + requested_size, ) { + let width_scale = bounds.width / image.width() as f32; + let height_scale = bounds.height / image.height() as f32; + let transform = transform.pre_scale(width_scale, height_scale); + // XX Do not apply transform scaling for rotation to the position pixels.draw_pixmap( - (bounds.x) as i32, - (bounds.y) as i32, + (bounds.x / width_scale) as i32, + (bounds.y / height_scale) as i32, image, &tiny_skia::PixmapPaint { opacity, diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 0704a966ed..907598ff6a 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -43,6 +43,21 @@ pub struct Cache { } type ColorFilter = Option<[u8; 4]>; +const SMALL_SVG_OVERSAMPLE_LIMIT: u32 = 128; +const SMALL_SVG_OVERSAMPLE_FACTOR: u32 = 2; + +fn oversample_dimensions(width: u32, height: u32) -> (u32, u32) { + let factor = if width.max(height) <= SMALL_SVG_OVERSAMPLE_LIMIT { + SMALL_SVG_OVERSAMPLE_FACTOR + } else { + 1 + }; + + ( + width.saturating_mul(factor), + height.saturating_mul(factor), + ) +} impl Cache { /// Load svg @@ -108,9 +123,11 @@ impl Cache { (scale * size.width).ceil() as u32, (scale * size.height).ceil() as u32, ); + let (raster_width, raster_height) = + oversample_dimensions(width, height); let color = color.map(Color::into_rgba8); - let key = (id, width, height, color); + let key = (id, raster_width, raster_height, color); // TODO: Optimize! // We currently rerasterize the SVG when its size changes. This is slow @@ -125,7 +142,7 @@ impl Cache { match self.load(handle) { Svg::Loaded(tree) => { - if width == 0 || height == 0 { + if raster_width == 0 || raster_height == 0 { return None; } @@ -133,14 +150,15 @@ impl Cache { // We currently rerasterize the SVG when its size changes. This is slow // as heck. A GPU rasterizer like `pathfinder` may perform better. // It would be cool to be able to smooth resize the `svg` example. - let mut img = tiny_skia::Pixmap::new(width, height)?; + let mut img = + tiny_skia::Pixmap::new(raster_width, raster_height)?; let tree_size = tree.size().to_int_size(); - let target_size = if width > height { - tree_size.scale_to_width(width) + let target_size = if raster_width > raster_height { + tree_size.scale_to_width(raster_width) } else { - tree_size.scale_to_height(height) + tree_size.scale_to_height(raster_height) }; let transform = if let Some(target_size) = target_size { @@ -181,9 +199,18 @@ impl Cache { } let allocation = atlas - .upload(device, encoder, belt, width, height, &rgba)?; - - log::debug!("allocating {id} {width}x{height}"); + .upload( + device, + encoder, + belt, + raster_width, + raster_height, + &rgba, + )?; + + log::debug!( + "allocating {id} {raster_width}x{raster_height}" + ); let _ = self.svg_hits.insert(id); let _ = self.rasterized_hits.insert(key);