From c915f69e87aceedaffff76e0044522238b6cde4b Mon Sep 17 00:00:00 2001 From: Laurenz Stampfl <47084093+LaurenzV@users.noreply.github.com> Date: Sun, 1 Dec 2024 10:19:31 +0100 Subject: [PATCH] more --- Cargo.lock | 14 ++- Cargo.toml | 9 +- .../src/visualize/image/raster.rs | 8 +- crates/typst-pdf/src/image.rs | 41 +++++---- crates/typst-pdf/src/krilla.rs | 91 +++++++++++++++++-- 5 files changed, 122 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5688e66de..7ccce83cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1307,19 +1307,22 @@ version = "0.3.0" dependencies = [ "base64", "bumpalo", + "comemo", "flate2", "float-cmp 0.10.0", "fontdb 0.22.0", "gif", "image-webp", + "imagesize", "miniz_oxide", "once_cell", "pdf-writer 0.12.0 (git+https://github.com/LaurenzV/pdf-writer?rev=f95a19c)", + "rayon", "resvg 0.44.0", "rustybuzz", "siphasher 1.0.1", "skrifa", - "subsetter", + "subsetter 0.2.0 (git+https://github.com/typst/subsetter?rev=172416a)", "tiny-skia", "tiny-skia-path", "usvg 0.44.0", @@ -2483,6 +2486,11 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74f98178f34057d4d4de93d68104007c6dea4dfac930204a69ab4622daefa648" +[[package]] +name = "subsetter" +version = "0.2.0" +source = "git+https://github.com/typst/subsetter?rev=172416a#172416a806246e6e9010d400d5690ca7a026e53d" + [[package]] name = "svg2pdf" version = "0.12.0" @@ -2497,7 +2505,7 @@ dependencies = [ "pdf-writer 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "resvg 0.43.0", "siphasher 1.0.1", - "subsetter", + "subsetter 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "tiny-skia", "ttf-parser", "usvg 0.43.0", @@ -3044,7 +3052,7 @@ dependencies = [ "miniz_oxide", "pdf-writer 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde", - "subsetter", + "subsetter 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "svg2pdf", "ttf-parser", "typst-assets", diff --git a/Cargo.toml b/Cargo.toml index e91641ac0..4426d4889 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,7 +68,7 @@ if_chain = "1" image = { version = "0.25.2", default-features = false, features = ["png", "jpeg", "gif"] } indexmap = { version = "2", features = ["serde"] } kamadak-exif = "0.5" -krilla = { path = "../krilla/crates/krilla" } +krilla = { path = "../krilla/crates/krilla", features = ["comemo", "rayon"] } kurbo = "0.11" libfuzzer-sys = "0.4" lipsum = "0.9" @@ -140,13 +140,6 @@ zip = { version = "2", default-features = false, features = ["deflate"] } [profile.dev.package."*"] opt-level = 2 -[profile.release] -lto = "thin" -codegen-units = 1 - -[profile.release.package."typst-cli"] -strip = true - [workspace.lints.clippy] blocks_in_conditions = "allow" comparison_chain = "allow" diff --git a/crates/typst-library/src/visualize/image/raster.rs b/crates/typst-library/src/visualize/image/raster.rs index 829826c75..b790f949c 100644 --- a/crates/typst-library/src/visualize/image/raster.rs +++ b/crates/typst-library/src/visualize/image/raster.rs @@ -20,7 +20,7 @@ pub struct RasterImage(Arc); struct Repr { data: Bytes, format: RasterFormat, - dynamic: image::DynamicImage, + dynamic: Arc, icc: Option>, dpi: Option, } @@ -59,7 +59,7 @@ impl RasterImage { // Extract pixel density. let dpi = determine_dpi(&data, exif.as_ref()); - Ok(Self(Arc::new(Repr { data, format, dynamic, icc, dpi }))) + Ok(Self(Arc::new(Repr { data, format, dynamic: Arc::new(dynamic), icc, dpi }))) } /// The raw image data. @@ -88,8 +88,8 @@ impl RasterImage { } /// Access the underlying dynamic image. - pub fn dynamic(&self) -> &image::DynamicImage { - &self.0.dynamic + pub fn dynamic(&self) -> Arc { + self.0.dynamic.clone() } /// Access the ICC profile, if any. diff --git a/crates/typst-pdf/src/image.rs b/crates/typst-pdf/src/image.rs index 85439164e..b3b1cd914 100644 --- a/crates/typst-pdf/src/image.rs +++ b/crates/typst-pdf/src/image.rs @@ -167,26 +167,27 @@ pub fn deferred_image( /// Skips the alpha channel as that's encoded separately. #[typst_macros::time(name = "encode raster image")] fn encode_raster_image(image: &RasterImage) -> (Vec, Filter, bool) { - let dynamic = image.dynamic(); - let channel_count = dynamic.color().channel_count(); - let has_color = channel_count > 2; - - if image.format() == RasterFormat::Jpg { - let mut data = Cursor::new(vec![]); - dynamic.write_to(&mut data, image::ImageFormat::Jpeg).unwrap(); - (data.into_inner(), Filter::DctDecode, has_color) - } else { - // TODO: Encode flate streams with PNG-predictor? - let data = match (dynamic, channel_count) { - (DynamicImage::ImageLuma8(luma), _) => deflate(luma.as_raw()), - (DynamicImage::ImageRgb8(rgb), _) => deflate(rgb.as_raw()), - // Grayscale image - (_, 1 | 2) => deflate(dynamic.to_luma8().as_raw()), - // Anything else - _ => deflate(dynamic.to_rgb8().as_raw()), - }; - (data, Filter::FlateDecode, has_color) - } + // let dynamic = image.dynamic(); + // let channel_count = dynamic.color().channel_count(); + // let has_color = channel_count > 2; + // + // if image.format() == RasterFormat::Jpg { + // let mut data = Cursor::new(vec![]); + // dynamic.write_to(&mut data, image::ImageFormat::Jpeg).unwrap(); + // (data.into_inner(), Filter::DctDecode, has_color) + // } else { + // // TODO: Encode flate streams with PNG-predictor? + // let data = match (dynamic, channel_count) { + // (DynamicImage::ImageLuma8(luma), _) => deflate(luma.as_raw()), + // (DynamicImage::ImageRgb8(rgb), _) => deflate(rgb.as_raw()), + // // Grayscale image + // (_, 1 | 2) => deflate(dynamic.to_luma8().as_raw()), + // // Anything else + // _ => deflate(dynamic.to_rgb8().as_raw()), + // }; + // (data, Filter::FlateDecode, has_color) + // } + unimplemented!() } /// Encode an image's alpha channel if present. diff --git a/crates/typst-pdf/src/krilla.rs b/crates/typst-pdf/src/krilla.rs index a5972262d..1cb7299bd 100644 --- a/crates/typst-pdf/src/krilla.rs +++ b/crates/typst-pdf/src/krilla.rs @@ -7,17 +7,20 @@ use krilla::path::{Fill, PathBuilder, Stroke}; use krilla::surface::Surface; use krilla::{PageSettings, SerializeSettings, SvgSettings}; use std::collections::HashMap; +use std::hash::{Hash, Hasher}; use std::ops::Range; -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use bytemuck::TransparentWrapper; -use image::GenericImageView; +use image::{DynamicImage, GenericImageView, Rgba}; +use krilla::image::{BitsPerComponent, CustomImage, ImageColorspace}; use krilla::validation::Validator; use krilla::version::PdfVersion; use svg2pdf::usvg::{NormalizedF32, Rect}; +use svg2pdf::usvg::filter::ColorChannel; use typst_library::layout::{Frame, FrameItem, GroupItem, Size}; use typst_library::model::Document; use typst_library::text::{Font, Glyph, TextItem}; -use typst_library::visualize::{ColorSpace, FillRule, FixedStroke, Geometry, Image, ImageKind, LineCap, LineJoin, Paint, Path, PathItem, Shape}; +use typst_library::visualize::{ColorSpace, FillRule, FixedStroke, Geometry, Image, ImageKind, LineCap, LineJoin, Paint, Path, PathItem, RasterFormat, RasterImage, Shape}; #[derive(TransparentWrapper)] #[repr(transparent)] @@ -116,7 +119,8 @@ pub fn handle_text(t: &TextItem, surface: &mut Surface, context: &mut ExportCont krilla::font::Font::new( // TODO: Don't do to_vec here! Arc::new(t.font.data().to_vec()), - t.font.index() + t.font.index(), + true ) // TODO: DOn't unwrap .unwrap() @@ -158,6 +162,73 @@ pub fn handle_text(t: &TextItem, surface: &mut Surface, context: &mut ExportCont } } +#[derive(Clone)] +struct PdfImage { + raster: RasterImage, + alpha_channel: OnceLock>>>, + actual_dynamic: OnceLock> +} + +impl PdfImage { + pub fn new(raster: RasterImage) -> Self { + Self { + raster, + alpha_channel: OnceLock::new(), + actual_dynamic: OnceLock::new(), + } + } +} + +impl Hash for PdfImage { + fn hash(&self, state: &mut H) { + self.raster.hash(state); + } +} + +impl CustomImage for PdfImage { + fn color_channel(&self) -> &[u8] { + self.actual_dynamic.get_or_init(|| { + let dynamic = self.raster.dynamic(); + let channel_count = dynamic.color().channel_count(); + + match (dynamic.as_ref(), channel_count) { + (DynamicImage::ImageLuma8(_), _) => dynamic.clone(), + (DynamicImage::ImageRgb8(_), _) => dynamic.clone(), + (_, 1 | 2) => Arc::new(DynamicImage::ImageLuma8(dynamic.to_luma8())), + _ => Arc::new(DynamicImage::ImageRgb8(dynamic.to_rgb8())), + } + }).as_bytes() + } + + fn alpha_channel(&self) -> Option<&[u8]> { + self.alpha_channel.get_or_init(|| + self.raster.dynamic().color().has_alpha() + .then(|| Arc::new(self.raster + .dynamic() + .pixels() + .map(|(_, _, Rgba([_, _, _, a]))| a) + .collect()) + ) + ).as_ref().map(|v| &***v) + } + + fn bits_per_component(&self) -> BitsPerComponent { + BitsPerComponent::Eight + } + + fn size(&self) -> (u32, u32) { + (self.raster.width(), self.raster.height()) + } + + fn color_space(&self) -> ImageColorspace { + if self.raster.dynamic().color().has_color() { + ImageColorspace::Rgb + } else { + ImageColorspace::Luma + } + } +} + #[typst_macros::time(name = "handle image")] pub fn handle_image( image: &Image, @@ -167,8 +238,7 @@ pub fn handle_image( ) { match image.kind() { ImageKind::Raster(raster) => { - let image = krilla::image::Image::from_png(raster.data()) - .unwrap(); + let image = convert_raster(raster.clone()); surface.draw_image( image, krilla::geom::Size::from_wh(size.x.to_f32(), size.y.to_f32()).unwrap(), @@ -184,6 +254,15 @@ pub fn handle_image( } } +#[comemo::memoize] +fn convert_raster(raster: RasterImage) -> krilla::image::Image { + match raster.format() { + // TODO: Remove to_vec + RasterFormat::Jpg => krilla::image::Image::from_jpeg(Arc::new(raster.data().to_vec())), + _ => krilla::image::Image::from_custom(PdfImage::new(raster)) + }.unwrap() +} + pub fn handle_shape(shape: &Shape, surface: &mut Surface) { let mut path_builder = PathBuilder::new();