Faster image rendering

This commit is contained in:
Laurenz 2022-12-12 10:35:42 +01:00
parent c38d55614a
commit 2271d67f8f
3 changed files with 72 additions and 63 deletions

View File

@ -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);

View File

@ -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<sk::Path> {
let mut builder = sk::PathBuilder::new();
@ -403,6 +346,70 @@ fn convert_path(path: &geom::Path) -> Option<sk::Path> {
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<Arc<sk::Pixmap>> {
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<Transform> for sk::Transform {
fn from(transform: Transform) -> Self {
let Transform { sx, ky, kx, sy, tx, ty } = transform;

View File

@ -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<DecodedImage> {
Ok(match self.format {
#[comemo::memoize]
pub fn decode(&self) -> StrResult<Arc<DecodedImage>> {
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)
}
})
}))
}
}