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

@ -41,11 +41,11 @@ pub fn layout_image(
if has_foreign_object {
engine.sink.warn(warning!(
span,
"image contains foreign object";
hint: "SVG images with foreign objects might render incorrectly in typst";
hint: "see https://github.com/typst/typst/issues/1421 for more information"
));
span,
"image contains foreign object";
hint: "SVG images with foreign objects might render incorrectly in typst";
hint: "see https://github.com/typst/typst/issues/1421 for more information"
));
}
}

View File

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

View File

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

View File

@ -130,14 +130,14 @@ pub fn deferred_image(
Some(to_color_space(raster.dynamic().color()))
}
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,
};
// PDF/A does not appear to allow interpolation[^1].
// [^1]: https://github.com/typst/typst/issues/2942
let interpolate = image.scaling() == Smart::Custom(ImageScaling::Smooth) && !pdfa;
// PDF/A does not appear to allow interpolation.
// See https://github.com/typst/typst/issues/2942.
let interpolate = !pdfa && image.scaling() == Smart::Custom(ImageScaling::Smooth);
let deferred = Deferred::new(move || match image.kind() {
ImageKind::Raster(raster) => {
@ -159,7 +159,7 @@ pub fn deferred_image(
Ok(EncodedImage::Svg(chunk, id))
}
ImageKind::Pixmap(pixmap) => Ok(encode_raster_image(
&pixmap.to_image(),
&pixmap.to_dynamic(),
pixmap.icc_profile(),
EncodeFormat::Flate,
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.
#[typst_macros::time(name = "encode alpha")]
fn encode_alpha(image: &DynamicImage) -> (Vec<u8>, Filter) {
@ -282,3 +272,13 @@ enum EncodeFormat {
DctDecode,
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.
#[comemo::memoize]
fn build_texture(image: &Image, w: u32, h: u32) -> Option<Arc<sk::Pixmap>> {
let mut texture = sk::Pixmap::new(w, h)?;
match image.kind() {
ImageKind::Raster(raster) => scale_image(raster.dynamic(), image.scaling(), w, h),
ImageKind::Pixmap(raster) => {
scale_image(&raster.to_image(), image.scaling(), w, h)
ImageKind::Raster(raster) => {
scale_image(&mut texture, raster.dynamic(), image.scaling())
}
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) => {
let mut pixmap = sk::Pixmap::new(w, h)?;
let tree = svg.tree();
let ts = tiny_skia::Transform::from_scale(
w as f32 / tree.size().width(),
h as f32 / tree.size().height(),
);
resvg::render(tree, ts, &mut pixmap.as_mut());
Some(Arc::new(pixmap))
resvg::render(tree, ts, &mut texture.as_mut());
}
}
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(
texture: &mut sk::Pixmap,
image: &image::DynamicImage,
scaling: Smart<ImageScaling>,
w: u32,
h: u32,
) -> Option<Arc<sk::Pixmap>> {
let mut pixmap = sk::Pixmap::new(w, h)?;
) {
let w = texture.width();
let h = texture.height();
let buf;
let resized = if (w, h) == (image.width(), image.height()) {
// Small optimization to not allocate in case image is not resized.
@ -94,18 +95,16 @@ fn scale_image(
} else {
let upscale = w > image.width();
let filter = match scaling {
Smart::Auto | Smart::Custom(ImageScaling::Smooth) if upscale => {
FilterType::CatmullRom
}
Smart::Custom(ImageScaling::Pixelated) => FilterType::Nearest,
_ if upscale => FilterType::CatmullRom,
_ => FilterType::Lanczos3, // downscale
};
buf = image.resize_exact(w, h, filter);
&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;
*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 ecow::{eco_format, EcoString};
use image::error::UnsupportedError;
use image::{codecs::png::PngEncoder, ImageEncoder};
use typst_library::foundations::Smart;
use typst_library::layout::{Abs, Axes};
@ -24,8 +21,8 @@ impl SVGRenderer {
match image.scaling() {
Smart::Auto => {}
Smart::Custom(ImageScaling::Smooth) => {
// This is still experimental and not implemented in all major browsers[^1].
// [^1]: https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering#browser_compatibility
// This is still experimental and not implemented in all major browsers.
// https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering#browser_compatibility
self.xml.write_attribute("style", "image-rendering: smooth")
}
Smart::Custom(ImageScaling::Pixelated) => {
@ -51,20 +48,19 @@ pub fn convert_image_to_base64_url(image: &Image) -> EcoString {
},
ImageFormat::Pixmap(_) => "png",
};
let data_owned;
let mut buf;
let data = match image.kind() {
ImageKind::Raster(raster) => raster.data(),
ImageKind::Svg(svg) => svg.data(),
ImageKind::Pixmap(pixmap) => {
let mut data = Cursor::new(vec![]);
let mut encoder = PngEncoder::new(&mut data);
buf = vec![];
let mut encoder = PngEncoder::new(&mut buf);
if let Some(icc_profile) = pixmap.icc_profile() {
let _: Result<(), UnsupportedError> =
encoder.set_icc_profile(icc_profile.to_vec());
encoder.set_icc_profile(icc_profile.to_vec()).ok();
}
pixmap.to_image().write_with_encoder(encoder).unwrap();
data_owned = data.into_inner();
&*data_owned
pixmap.to_dynamic().write_with_encoder(encoder).unwrap();
buf.as_slice()
}
};