mirror of
https://github.com/typst/typst
synced 2025-08-13 06:37:57 +08:00
Refactor a bit more
This commit is contained in:
parent
9362c0e63e
commit
cd2a61c354
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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))
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user