Refactor a bit more

This commit is contained in:
Laurenz 2025-01-30 17:07:26 +01:00
parent 9362c0e63e
commit cd2a61c354
6 changed files with 48 additions and 54 deletions

View File

@ -9,7 +9,6 @@ pub use self::raster::{RasterFormat, RasterImage};
pub use self::svg::SvgImage; pub use self::svg::SvgImage;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::hash::Hash;
use std::sync::Arc; use std::sync::Arc;
use comemo::Tracked; use comemo::Tracked;

View File

@ -67,9 +67,9 @@ impl PixmapImage {
&self.0.source.data &self.0.source.data
} }
/// Transform the image data into a [`DynamicImage`]. /// Transform the image data to a [`DynamicImage`].
#[comemo::memoize] #[comemo::memoize]
pub fn to_image(&self) -> Arc<DynamicImage> { pub fn to_dynamic(&self) -> Arc<DynamicImage> {
// TODO: Optimize by returning a `View` if possible? // TODO: Optimize by returning a `View` if possible?
fn decode<P: Pixel<Subpixel = u8>>( fn decode<P: Pixel<Subpixel = u8>>(
source: &PixmapSource, source: &PixmapSource,

View File

@ -130,14 +130,14 @@ pub fn deferred_image(
Some(to_color_space(raster.dynamic().color())) Some(to_color_space(raster.dynamic().color()))
} }
ImageKind::Pixmap(pixmap) if pixmap.icc_profile().is_none() => { ImageKind::Pixmap(pixmap) if pixmap.icc_profile().is_none() => {
Some(to_color_space(pixmap.to_image().color())) Some(to_color_space(pixmap.to_dynamic().color()))
} }
_ => None, _ => None,
}; };
// PDF/A does not appear to allow interpolation[^1]. // PDF/A does not appear to allow interpolation.
// [^1]: https://github.com/typst/typst/issues/2942 // See https://github.com/typst/typst/issues/2942.
let interpolate = image.scaling() == Smart::Custom(ImageScaling::Smooth) && !pdfa; let interpolate = !pdfa && image.scaling() == Smart::Custom(ImageScaling::Smooth);
let deferred = Deferred::new(move || match image.kind() { let deferred = Deferred::new(move || match image.kind() {
ImageKind::Raster(raster) => { ImageKind::Raster(raster) => {
@ -159,7 +159,7 @@ pub fn deferred_image(
Ok(EncodedImage::Svg(chunk, id)) Ok(EncodedImage::Svg(chunk, id))
} }
ImageKind::Pixmap(pixmap) => Ok(encode_raster_image( ImageKind::Pixmap(pixmap) => Ok(encode_raster_image(
&pixmap.to_image(), &pixmap.to_dynamic(),
pixmap.icc_profile(), pixmap.icc_profile(),
EncodeFormat::Flate, EncodeFormat::Flate,
interpolate, interpolate,
@ -215,16 +215,6 @@ fn encode_raster_image(
} }
} }
/// Matches an [`image::ColorType`] to [`ColorSpace`].
fn to_color_space(color: image::ColorType) -> ColorSpace {
use image::ColorType::*;
match color {
L8 | La8 | L16 | La16 => ColorSpace::D65Gray,
Rgb8 | Rgba8 | Rgb16 | Rgba16 | Rgb32F | Rgba32F => ColorSpace::Srgb,
_ => unimplemented!(),
}
}
/// Encode an image's alpha channel if present. /// Encode an image's alpha channel if present.
#[typst_macros::time(name = "encode alpha")] #[typst_macros::time(name = "encode alpha")]
fn encode_alpha(image: &DynamicImage) -> (Vec<u8>, Filter) { fn encode_alpha(image: &DynamicImage) -> (Vec<u8>, Filter) {
@ -282,3 +272,13 @@ enum EncodeFormat {
DctDecode, DctDecode,
Flate, Flate,
} }
/// Matches an [`image::ColorType`] to [`ColorSpace`].
fn to_color_space(color: image::ColorType) -> ColorSpace {
use image::ColorType::*;
match color {
L8 | La8 | L16 | La16 => ColorSpace::D65Gray,
Rgb8 | Rgba8 | Rgb16 | Rgba16 | Rgb32F | Rgba32F => ColorSpace::Srgb,
_ => unimplemented!(),
}
}

View File

@ -59,34 +59,35 @@ pub fn render_image(
/// Prepare a texture for an image at a scaled size. /// Prepare a texture for an image at a scaled size.
#[comemo::memoize] #[comemo::memoize]
fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> { fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
let mut texture = sk::Pixmap::new(w, h)?;
match image.kind() { match image.kind() {
ImageKind::Raster(raster) => scale_image(raster.dynamic(), image.scaling(), w, h), ImageKind::Raster(raster) => {
ImageKind::Pixmap(raster) => { scale_image(&mut texture, raster.dynamic(), image.scaling())
scale_image(&raster.to_image(), image.scaling(), w, h) }
ImageKind::Pixmap(pixmap) => {
scale_image(&mut texture, &pixmap.to_dynamic(), image.scaling())
} }
// Safety: We do not keep any references to tree nodes beyond the scope
// of `with`.
ImageKind::Svg(svg) => { ImageKind::Svg(svg) => {
let mut pixmap = sk::Pixmap::new(w, h)?;
let tree = svg.tree(); let tree = svg.tree();
let ts = tiny_skia::Transform::from_scale( let ts = tiny_skia::Transform::from_scale(
w as f32 / tree.size().width(), w as f32 / tree.size().width(),
h as f32 / tree.size().height(), h as f32 / tree.size().height(),
); );
resvg::render(tree, ts, &mut pixmap.as_mut()); resvg::render(tree, ts, &mut texture.as_mut());
Some(Arc::new(pixmap))
} }
} }
Some(Arc::new(texture))
} }
/// Scale a rastered image to a given size and return texture. /// Scale a rastered image to a given size and write it into the `texture`.
fn scale_image( fn scale_image(
texture: &mut sk::Pixmap,
image: &image::DynamicImage, image: &image::DynamicImage,
scaling: Smart<ImageScaling>, scaling: Smart<ImageScaling>,
w: u32, ) {
h: u32, let w = texture.width();
) -> Option<Arc<sk::Pixmap>> { let h = texture.height();
let mut pixmap = sk::Pixmap::new(w, h)?;
let buf; let buf;
let resized = if (w, h) == (image.width(), image.height()) { let resized = if (w, h) == (image.width(), image.height()) {
// Small optimization to not allocate in case image is not resized. // Small optimization to not allocate in case image is not resized.
@ -94,18 +95,16 @@ fn scale_image(
} else { } else {
let upscale = w > image.width(); let upscale = w > image.width();
let filter = match scaling { let filter = match scaling {
Smart::Auto | Smart::Custom(ImageScaling::Smooth) if upscale => {
FilterType::CatmullRom
}
Smart::Custom(ImageScaling::Pixelated) => FilterType::Nearest, Smart::Custom(ImageScaling::Pixelated) => FilterType::Nearest,
_ if upscale => FilterType::CatmullRom,
_ => FilterType::Lanczos3, // downscale _ => FilterType::Lanczos3, // downscale
}; };
buf = image.resize_exact(w, h, filter); buf = image.resize_exact(w, h, filter);
&buf &buf
}; };
for ((_, _, src), dest) in resized.pixels().zip(pixmap.pixels_mut()) {
for ((_, _, src), dest) in resized.pixels().zip(texture.pixels_mut()) {
let Rgba([r, g, b, a]) = src; let Rgba([r, g, b, a]) = src;
*dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply(); *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
} }
Some(Arc::new(pixmap))
} }

View File

@ -1,8 +1,5 @@
use std::io::Cursor;
use base64::Engine; use base64::Engine;
use ecow::{eco_format, EcoString}; use ecow::{eco_format, EcoString};
use image::error::UnsupportedError;
use image::{codecs::png::PngEncoder, ImageEncoder}; use image::{codecs::png::PngEncoder, ImageEncoder};
use typst_library::foundations::Smart; use typst_library::foundations::Smart;
use typst_library::layout::{Abs, Axes}; use typst_library::layout::{Abs, Axes};
@ -24,8 +21,8 @@ impl SVGRenderer {
match image.scaling() { match image.scaling() {
Smart::Auto => {} Smart::Auto => {}
Smart::Custom(ImageScaling::Smooth) => { Smart::Custom(ImageScaling::Smooth) => {
// This is still experimental and not implemented in all major browsers[^1]. // This is still experimental and not implemented in all major browsers.
// [^1]: https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering#browser_compatibility // https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering#browser_compatibility
self.xml.write_attribute("style", "image-rendering: smooth") self.xml.write_attribute("style", "image-rendering: smooth")
} }
Smart::Custom(ImageScaling::Pixelated) => { Smart::Custom(ImageScaling::Pixelated) => {
@ -51,20 +48,19 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
}, },
ImageFormat::Pixmap(_) => "png", ImageFormat::Pixmap(_) => "png",
}; };
let data_owned;
let mut buf;
let data = match image.kind() { let data = match image.kind() {
ImageKind::Raster(raster) => raster.data(), ImageKind::Raster(raster) => raster.data(),
ImageKind::Svg(svg) => svg.data(), ImageKind::Svg(svg) => svg.data(),
ImageKind::Pixmap(pixmap) => { ImageKind::Pixmap(pixmap) => {
let mut data = Cursor::new(vec![]); buf = vec![];
let mut encoder = PngEncoder::new(&mut data); let mut encoder = PngEncoder::new(&mut buf);
if let Some(icc_profile) = pixmap.icc_profile() { if let Some(icc_profile) = pixmap.icc_profile() {
let _: Result<(), UnsupportedError> = encoder.set_icc_profile(icc_profile.to_vec()).ok();
encoder.set_icc_profile(icc_profile.to_vec());
} }
pixmap.to_image().write_with_encoder(encoder).unwrap(); pixmap.to_dynamic().write_with_encoder(encoder).unwrap();
data_owned = data.into_inner(); buf.as_slice()
&*data_owned
} }
}; };