mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Faster image rendering
This commit is contained in:
parent
c38d55614a
commit
2271d67f8f
@ -17,10 +17,10 @@ pub fn write_images(ctx: &mut PdfContext) {
|
|||||||
|
|
||||||
// Add the primary image.
|
// Add the primary image.
|
||||||
// TODO: Error if image could not be encoded.
|
// TODO: Error if image could not be encoded.
|
||||||
match image.decode().unwrap() {
|
match image.decode().unwrap().as_ref() {
|
||||||
DecodedImage::Raster(dynamic, format) => {
|
DecodedImage::Raster(dynamic, format) => {
|
||||||
// TODO: Error if image could not be encoded.
|
// 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);
|
let mut image = ctx.writer.image_xobject(image_ref, &data);
|
||||||
image.filter(filter);
|
image.filter(filter);
|
||||||
image.width(width as i32);
|
image.width(width as i32);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Rendering into raster images.
|
//! Rendering into raster images.
|
||||||
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
use image::{GenericImageView, Rgba};
|
use image::{GenericImageView, Rgba};
|
||||||
@ -316,64 +317,6 @@ fn render_shape(
|
|||||||
Some(())
|
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.
|
/// Convert a Typst path into a tiny-skia path.
|
||||||
fn convert_path(path: &geom::Path) -> Option<sk::Path> {
|
fn convert_path(path: &geom::Path) -> Option<sk::Path> {
|
||||||
let mut builder = sk::PathBuilder::new();
|
let mut builder = sk::PathBuilder::new();
|
||||||
@ -403,6 +346,70 @@ fn convert_path(path: &geom::Path) -> Option<sk::Path> {
|
|||||||
builder.finish()
|
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 {
|
impl From<Transform> for sk::Transform {
|
||||||
fn from(transform: Transform) -> Self {
|
fn from(transform: Transform) -> Self {
|
||||||
let Transform { sx, ky, kx, sy, tx, ty } = transform;
|
let Transform { sx, ky, kx, sy, tx, ty } = transform;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Image handling.
|
//! Image handling.
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::diag::{format_xml_like_error, StrResult};
|
use crate::diag::{format_xml_like_error, StrResult};
|
||||||
use crate::util::Buffer;
|
use crate::util::Buffer;
|
||||||
@ -50,8 +51,9 @@ impl Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Decode the image.
|
/// Decode the image.
|
||||||
pub fn decode(&self) -> StrResult<DecodedImage> {
|
#[comemo::memoize]
|
||||||
Ok(match self.format {
|
pub fn decode(&self) -> StrResult<Arc<DecodedImage>> {
|
||||||
|
Ok(Arc::new(match self.format {
|
||||||
ImageFormat::Vector(VectorFormat::Svg) => {
|
ImageFormat::Vector(VectorFormat::Svg) => {
|
||||||
let opts = usvg::Options::default();
|
let opts = usvg::Options::default();
|
||||||
let tree = usvg::Tree::from_data(&self.data, &opts.to_ref())
|
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)?;
|
let dynamic = reader.decode().map_err(format_image_error)?;
|
||||||
DecodedImage::Raster(dynamic, format)
|
DecodedImage::Raster(dynamic, format)
|
||||||
}
|
}
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user