feat: add support for increased bit depths

This commit is contained in:
frozolotl 2024-12-29 02:06:30 +01:00
parent c789893251
commit b331d849b9
2 changed files with 66 additions and 43 deletions

View File

@ -25,6 +25,7 @@ struct Repr {
dynamic: image::DynamicImage, dynamic: image::DynamicImage,
icc: Option<Bytes>, icc: Option<Bytes>,
dpi: Option<f64>, dpi: Option<f64>,
source_color_type: image::ExtendedColorType,
} }
impl RasterImage { impl RasterImage {
@ -50,13 +51,18 @@ impl RasterImage {
format: RasterFormat, format: RasterFormat,
icc: Smart<Bytes>, icc: Smart<Bytes>,
) -> StrResult<RasterImage> { ) -> StrResult<RasterImage> {
let (dynamic, icc, dpi) = match format { let (dynamic, icc, dpi, source_color_type) = match format {
RasterFormat::Exchange(format) => { RasterFormat::Exchange(format) => {
fn decode<T: ImageDecoder>( fn decode<T: ImageDecoder>(
decoder: ImageResult<T>, decoder: ImageResult<T>,
icc: Smart<Bytes>, icc: Smart<Bytes>,
) -> ImageResult<(image::DynamicImage, Option<Bytes>)> { ) -> ImageResult<(
image::DynamicImage,
Option<Bytes>,
image::ExtendedColorType,
)> {
let mut decoder = decoder?; let mut decoder = decoder?;
let source_color_type = decoder.original_color_type();
let icc = icc.custom().or_else(|| { let icc = icc.custom().or_else(|| {
decoder decoder
.icc_profile() .icc_profile()
@ -67,11 +73,11 @@ impl RasterImage {
}); });
decoder.set_limits(Limits::default())?; decoder.set_limits(Limits::default())?;
let dynamic = image::DynamicImage::from_decoder(decoder)?; let dynamic = image::DynamicImage::from_decoder(decoder)?;
Ok((dynamic, icc)) Ok((dynamic, icc, source_color_type))
} }
let cursor = io::Cursor::new(&data); 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::Jpg => decode(JpegDecoder::new(cursor), icc),
ExchangeFormat::Png => decode(PngDecoder::new(cursor), icc), ExchangeFormat::Png => decode(PngDecoder::new(cursor), icc),
ExchangeFormat::Gif => decode(GifDecoder::new(cursor), icc), ExchangeFormat::Gif => decode(GifDecoder::new(cursor), icc),
@ -90,7 +96,7 @@ impl RasterImage {
// Extract pixel density. // Extract pixel density.
let dpi = determine_dpi(&data, exif.as_ref()); let dpi = determine_dpi(&data, exif.as_ref());
(dynamic, icc, dpi) (dynamic, icc, dpi, source_color_type)
} }
RasterFormat::Pixel(format) => { RasterFormat::Pixel(format) => {
@ -125,18 +131,19 @@ impl RasterImage {
.unwrap() .unwrap()
} }
let dynamic = match format.encoding { let dynamic: image::DynamicImage = match format.encoding {
PixelEncoding::Rgb8 => to::<image::Rgb<u8>>(&data, format).into(), PixelEncoding::Rgb8 => to::<image::Rgb<u8>>(&data, format).into(),
PixelEncoding::Rgba8 => to::<image::Rgba<u8>>(&data, format).into(), PixelEncoding::Rgba8 => to::<image::Rgba<u8>>(&data, format).into(),
PixelEncoding::Luma8 => to::<image::Luma<u8>>(&data, format).into(), PixelEncoding::Luma8 => to::<image::Luma<u8>>(&data, format).into(),
PixelEncoding::Lumaa8 => to::<image::LumaA<u8>>(&data, format).into(), PixelEncoding::Lumaa8 => to::<image::LumaA<u8>>(&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. /// The raw image data.
@ -175,6 +182,11 @@ impl RasterImage {
pub fn icc(&self) -> Option<&Bytes> { pub fn icc(&self) -> Option<&Bytes> {
self.0.icc.as_ref() 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 { impl Hash for Repr {

View File

@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use ecow::eco_format; 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 pdf_writer::{Chunk, Filter, Finish, Ref};
use typst_library::diag::{At, SourceResult, StrResult}; use typst_library::diag::{At, SourceResult, StrResult};
use typst_library::foundations::Smart; 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_type = dynamic.color();
let color_space = to_color_space(color_type); let color_space = to_color_space(color_type);
let bits_per_component = (raster.source_color_type().bits_per_pixel() let bits_per_component = (image.source_color_type().bits_per_pixel()
/ u16::from(raster.source_color_type().channel_count())) / u16::from(image.source_color_type().channel_count()))
as u8; as u8;
let compressed_icc = image.icc().map(|bytes| deflate(bytes.as_ref())); 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 { fn encode_raster_flate(image: &RasterImage, interpolate: bool) -> EncodedImage {
let dynamic = image.dynamic(); let dynamic = image.dynamic();
let color_space = to_color_space(dynamic.color()); 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? // TODO: Encode flate streams with PNG-predictor?
let (bits_per_component, data) = match (image, color_space) { let (bits_per_component, data) = match dynamic {
(DynamicImage::ImageRgb8(rgb), _) => (8, deflate(rgb.as_raw())), DynamicImage::ImageLuma8(buf) => (8, deflate(buf.as_raw())),
// Grayscale image DynamicImage::ImageLumaA8(_) => (8, deflate(dynamic.to_luma8().as_raw())),
(DynamicImage::ImageLuma8(luma), _) => (8, deflate(luma.as_raw())), DynamicImage::ImageLuma16(buf) => {
(_, ColorSpace::D65Gray) => (8, deflate(image.to_luma8().as_raw())), let encoded: Vec<u8> =
buf.as_raw().iter().flat_map(|&c| c.to_be_bytes()).collect();
(16, deflate(&encoded))
}
DynamicImage::ImageLumaA16(buf) => {
let encoded: Vec<u8> =
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<u8> =
buf.as_raw().iter().flat_map(|&c| c.to_be_bytes()).collect();
(16, deflate(&encoded))
}
DynamicImage::ImageRgba16(buf) => {
let encoded: Vec<u8> = 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<u8> =
buf.as_raw().iter().flat_map(|&c| c.to_be_bytes()).collect();
(32, deflate(&encoded))
}
DynamicImage::ImageRgba32F(buf) => {
let encoded: Vec<u8> = buf
.pixels()
.flat_map(|px| px.to_rgb().0)
.flat_map(|c| c.to_be_bytes())
.collect();
(32, deflate(&encoded))
}
// Anything else // 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())); 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. /// A pre-encoded image.
pub enum EncodedImage { pub enum EncodedImage {
/// A pre-encoded rasterized image. /// A pre-encoded rasterized image.