From b331d849b9c9d36eb614b18a6164183c89f22391 Mon Sep 17 00:00:00 2001 From: frozolotl Date: Sun, 29 Dec 2024 02:06:30 +0100 Subject: [PATCH] feat: add support for increased bit depths --- .../src/visualize/image/raster.rs | 28 +++++-- crates/typst-pdf/src/image.rs | 81 +++++++++++-------- 2 files changed, 66 insertions(+), 43 deletions(-) diff --git a/crates/typst-library/src/visualize/image/raster.rs b/crates/typst-library/src/visualize/image/raster.rs index 0883fe71d..c26904f74 100644 --- a/crates/typst-library/src/visualize/image/raster.rs +++ b/crates/typst-library/src/visualize/image/raster.rs @@ -25,6 +25,7 @@ struct Repr { dynamic: image::DynamicImage, icc: Option, dpi: Option, + source_color_type: image::ExtendedColorType, } impl RasterImage { @@ -50,13 +51,18 @@ impl RasterImage { format: RasterFormat, icc: Smart, ) -> StrResult { - let (dynamic, icc, dpi) = match format { + let (dynamic, icc, dpi, source_color_type) = match format { RasterFormat::Exchange(format) => { fn decode( decoder: ImageResult, icc: Smart, - ) -> ImageResult<(image::DynamicImage, Option)> { + ) -> ImageResult<( + image::DynamicImage, + Option, + image::ExtendedColorType, + )> { let mut decoder = decoder?; + let source_color_type = decoder.original_color_type(); let icc = icc.custom().or_else(|| { decoder .icc_profile() @@ -67,11 +73,11 @@ impl RasterImage { }); decoder.set_limits(Limits::default())?; let dynamic = image::DynamicImage::from_decoder(decoder)?; - Ok((dynamic, icc)) + Ok((dynamic, icc, source_color_type)) } let cursor = io::Cursor::new(&data); - let (mut dynamic, icc) = match format { + let (mut dynamic, icc, source_color_type) = match format { ExchangeFormat::Jpg => decode(JpegDecoder::new(cursor), icc), ExchangeFormat::Png => decode(PngDecoder::new(cursor), icc), ExchangeFormat::Gif => decode(GifDecoder::new(cursor), icc), @@ -90,7 +96,7 @@ impl RasterImage { // Extract pixel density. let dpi = determine_dpi(&data, exif.as_ref()); - (dynamic, icc, dpi) + (dynamic, icc, dpi, source_color_type) } RasterFormat::Pixel(format) => { @@ -125,18 +131,19 @@ impl RasterImage { .unwrap() } - let dynamic = match format.encoding { + let dynamic: image::DynamicImage = match format.encoding { PixelEncoding::Rgb8 => to::>(&data, format).into(), PixelEncoding::Rgba8 => to::>(&data, format).into(), PixelEncoding::Luma8 => to::>(&data, format).into(), PixelEncoding::Lumaa8 => to::>(&data, format).into(), }; + let source_color_type = dynamic.color().into(); - (dynamic, icc.custom(), None) + (dynamic, icc.custom(), None, source_color_type) } }; - Ok(Self(Arc::new(Repr { data, format, dynamic, icc, dpi }))) + Ok(Self(Arc::new(Repr { data, format, dynamic, icc, dpi, source_color_type }))) } /// The raw image data. @@ -175,6 +182,11 @@ impl RasterImage { pub fn icc(&self) -> Option<&Bytes> { self.0.icc.as_ref() } + + /// The output color type the image is encoded in. + pub fn source_color_type(&self) -> image::ExtendedColorType { + self.0.source_color_type + } } impl Hash for Repr { diff --git a/crates/typst-pdf/src/image.rs b/crates/typst-pdf/src/image.rs index 0b46c8e08..bbb282b69 100644 --- a/crates/typst-pdf/src/image.rs +++ b/crates/typst-pdf/src/image.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use ecow::eco_format; -use image::{DynamicImage, GenericImageView, LumaA, Rgba}; +use image::{DynamicImage, GenericImageView, LumaA, Pixel, Rgba}; use pdf_writer::{Chunk, Filter, Finish, Ref}; use typst_library::diag::{At, SourceResult, StrResult}; use typst_library::foundations::Smart; @@ -160,8 +160,8 @@ fn encode_raster_jpeg(image: &RasterImage, interpolate: bool) -> EncodedImage { let color_type = dynamic.color(); let color_space = to_color_space(color_type); - let bits_per_component = (raster.source_color_type().bits_per_pixel() - / u16::from(raster.source_color_type().channel_count())) + let bits_per_component = (image.source_color_type().bits_per_pixel() + / u16::from(image.source_color_type().channel_count())) as u8; let compressed_icc = image.icc().map(|bytes| deflate(bytes.as_ref())); @@ -185,20 +185,52 @@ fn encode_raster_jpeg(image: &RasterImage, interpolate: bool) -> EncodedImage { fn encode_raster_flate(image: &RasterImage, interpolate: bool) -> EncodedImage { let dynamic = image.dynamic(); let color_space = to_color_space(dynamic.color()); - let bits_per_component = bits_per_component(dynamic.color()); -fn encode_raster_flate(raster: &RasterImage, interpolate: bool) -> EncodedImage { - let image = raster.dynamic(); - let color_space = to_color_space(image.color()); - let bits_per_component = bits_per_component(image.color()); + // Encode image data in big-endian. The alpha channel is excluded. // TODO: Encode flate streams with PNG-predictor? - let (bits_per_component, data) = match (image, color_space) { - (DynamicImage::ImageRgb8(rgb), _) => (8, deflate(rgb.as_raw())), - // Grayscale image - (DynamicImage::ImageLuma8(luma), _) => (8, deflate(luma.as_raw())), - (_, ColorSpace::D65Gray) => (8, deflate(image.to_luma8().as_raw())), + let (bits_per_component, data) = match dynamic { + DynamicImage::ImageLuma8(buf) => (8, deflate(buf.as_raw())), + DynamicImage::ImageLumaA8(_) => (8, deflate(dynamic.to_luma8().as_raw())), + DynamicImage::ImageLuma16(buf) => { + let encoded: Vec = + buf.as_raw().iter().flat_map(|&c| c.to_be_bytes()).collect(); + (16, deflate(&encoded)) + } + DynamicImage::ImageLumaA16(buf) => { + let encoded: Vec = + buf.pixels().flat_map(|&LumaA([l, _])| l.to_be_bytes()).collect(); + (16, deflate(&encoded)) + } + DynamicImage::ImageRgb8(buf) => (8, deflate(buf.as_raw())), + DynamicImage::ImageRgba8(_) => (8, deflate(dynamic.to_rgb8().as_raw())), + DynamicImage::ImageRgb16(buf) => { + let encoded: Vec = + buf.as_raw().iter().flat_map(|&c| c.to_be_bytes()).collect(); + (16, deflate(&encoded)) + } + DynamicImage::ImageRgba16(buf) => { + let encoded: Vec = buf + .pixels() + .flat_map(|px| px.to_rgb().0) + .flat_map(|c| c.to_be_bytes()) + .collect(); + (16, deflate(&encoded)) + } + DynamicImage::ImageRgb32F(buf) => { + let encoded: Vec = + buf.as_raw().iter().flat_map(|&c| c.to_be_bytes()).collect(); + (32, deflate(&encoded)) + } + DynamicImage::ImageRgba32F(buf) => { + let encoded: Vec = buf + .pixels() + .flat_map(|px| px.to_rgb().0) + .flat_map(|c| c.to_be_bytes()) + .collect(); + (32, deflate(&encoded)) + } // Anything else - _ => (8, deflate(image.to_rgb8().as_raw())), + _ => (8, deflate(dynamic.to_rgb8().as_raw())), }; let compressed_icc = image.icc().map(|bytes| deflate(bytes.as_ref())); @@ -277,27 +309,6 @@ fn encode_svg( ) } -/// 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!(), - } -} - -/// How many bits does each component take up? -fn bits_per_component(color: image::ColorType) -> u8 { - use image::ColorType::*; - match color { - Rgb8 | Rgba8 | L8 | La8 => 8, - Rgb16 | Rgba16 | L16 | La16 => 16, - Rgb32F | Rgba32F => 32, - _ => unimplemented!(), - } -} - /// A pre-encoded image. pub enum EncodedImage { /// A pre-encoded rasterized image.