From 2271d67f8f5fe65882e74622ad01c075102725b1 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 12 Dec 2022 10:35:42 +0100 Subject: [PATCH] Faster image rendering --- src/export/pdf/image.rs | 4 +- src/export/render.rs | 123 +++++++++++++++++++++------------------- src/image.rs | 8 ++- 3 files changed, 72 insertions(+), 63 deletions(-) diff --git a/src/export/pdf/image.rs b/src/export/pdf/image.rs index e507a6130..78731734d 100644 --- a/src/export/pdf/image.rs +++ b/src/export/pdf/image.rs @@ -17,10 +17,10 @@ pub fn write_images(ctx: &mut PdfContext) { // Add the primary image. // TODO: Error if image could not be encoded. - match image.decode().unwrap() { + match image.decode().unwrap().as_ref() { DecodedImage::Raster(dynamic, format) => { // TODO: Error if image could not be encoded. - let (data, filter, has_color) = encode_image(format, &dynamic).unwrap(); + let (data, filter, has_color) = encode_image(*format, &dynamic).unwrap(); let mut image = ctx.writer.image_xobject(image_ref, &data); image.filter(filter); image.width(width as i32); diff --git a/src/export/render.rs b/src/export/render.rs index 68625e230..86648e969 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -1,6 +1,7 @@ //! Rendering into raster images. use std::io::Read; +use std::sync::Arc; use image::imageops::FilterType; use image::{GenericImageView, Rgba}; @@ -316,64 +317,6 @@ fn render_shape( Some(()) } -/// Render a raster or SVG image into the canvas. -fn render_image( - canvas: &mut sk::Pixmap, - ts: sk::Transform, - mask: Option<&sk::ClipMask>, - image: &Image, - size: Size, -) -> Option<()> { - let view_width = size.x.to_f32(); - let view_height = size.y.to_f32(); - - let aspect = (image.width() as f32) / (image.height() as f32); - let scale = ts.sx.max(ts.sy); - let w = (scale * view_width.max(aspect * view_height)).ceil() as u32; - let h = ((w as f32) / aspect).ceil() as u32; - - let mut pixmap = sk::Pixmap::new(w, h)?; - match image.decode().unwrap() { - DecodedImage::Raster(dynamic, _) => { - let downscale = w < image.width(); - let filter = - if downscale { FilterType::Lanczos3 } else { FilterType::CatmullRom }; - let buf = dynamic.resize(w, h, filter); - for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) { - let Rgba([r, g, b, a]) = src; - *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply(); - } - } - DecodedImage::Svg(tree) => { - resvg::render( - &tree, - FitTo::Size(w, h), - sk::Transform::identity(), - pixmap.as_mut(), - )?; - } - } - - let scale_x = view_width / pixmap.width() as f32; - let scale_y = view_height / pixmap.height() as f32; - - let paint = sk::Paint { - shader: sk::Pattern::new( - pixmap.as_ref(), - sk::SpreadMode::Pad, - sk::FilterQuality::Nearest, - 1.0, - sk::Transform::from_scale(scale_x, scale_y), - ), - ..Default::default() - }; - - let rect = sk::Rect::from_xywh(0.0, 0.0, view_width, view_height)?; - canvas.fill_rect(rect, &paint, ts, mask); - - Some(()) -} - /// Convert a Typst path into a tiny-skia path. fn convert_path(path: &geom::Path) -> Option { let mut builder = sk::PathBuilder::new(); @@ -403,6 +346,70 @@ fn convert_path(path: &geom::Path) -> Option { builder.finish() } +/// Render a raster or SVG image into the canvas. +fn render_image( + canvas: &mut sk::Pixmap, + ts: sk::Transform, + mask: Option<&sk::ClipMask>, + image: &Image, + size: Size, +) -> Option<()> { + let view_width = size.x.to_f32(); + let view_height = size.y.to_f32(); + + let aspect = (image.width() as f32) / (image.height() as f32); + let scale = ts.sx.max(ts.sy); + let w = (scale * view_width.max(aspect * view_height)).ceil() as u32; + let h = ((w as f32) / aspect).ceil() as u32; + + let pixmap = scaled_texture(image, w, h)?; + let scale_x = view_width / pixmap.width() as f32; + let scale_y = view_height / pixmap.height() as f32; + + let paint = sk::Paint { + shader: sk::Pattern::new( + (*pixmap).as_ref(), + sk::SpreadMode::Pad, + sk::FilterQuality::Nearest, + 1.0, + sk::Transform::from_scale(scale_x, scale_y), + ), + ..Default::default() + }; + + let rect = sk::Rect::from_xywh(0.0, 0.0, view_width, view_height)?; + canvas.fill_rect(rect, &paint, ts, mask); + + Some(()) +} + +/// Prepare a texture for an image at a scaled size. +#[comemo::memoize] +fn scaled_texture(image: &Image, w: u32, h: u32) -> Option> { + let mut pixmap = sk::Pixmap::new(w, h)?; + match image.decode().unwrap().as_ref() { + DecodedImage::Raster(dynamic, _) => { + let downscale = w < image.width(); + let filter = + if downscale { FilterType::Lanczos3 } else { FilterType::CatmullRom }; + let buf = dynamic.resize(w, h, filter); + for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) { + let Rgba([r, g, b, a]) = src; + *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply(); + } + } + DecodedImage::Svg(tree) => { + resvg::render( + &tree, + FitTo::Size(w, h), + sk::Transform::identity(), + pixmap.as_mut(), + )?; + } + } + Some(Arc::new(pixmap)) +} + impl From for sk::Transform { fn from(transform: Transform) -> Self { let Transform { sx, ky, kx, sy, tx, ty } = transform; diff --git a/src/image.rs b/src/image.rs index 235b9ef65..038641a42 100644 --- a/src/image.rs +++ b/src/image.rs @@ -1,6 +1,7 @@ //! Image handling. use std::io; +use std::sync::Arc; use crate::diag::{format_xml_like_error, StrResult}; use crate::util::Buffer; @@ -50,8 +51,9 @@ impl Image { } /// Decode the image. - pub fn decode(&self) -> StrResult { - Ok(match self.format { + #[comemo::memoize] + pub fn decode(&self) -> StrResult> { + Ok(Arc::new(match self.format { ImageFormat::Vector(VectorFormat::Svg) => { let opts = usvg::Options::default(); let tree = usvg::Tree::from_data(&self.data, &opts.to_ref()) @@ -64,7 +66,7 @@ impl Image { let dynamic = reader.decode().map_err(format_image_error)?; DecodedImage::Raster(dynamic, format) } - }) + })) } }