mirror of
https://github.com/typst/typst
synced 2025-08-14 15:17:57 +08:00
feat: add support for increased bit depths
This commit is contained in:
parent
c789893251
commit
b331d849b9
@ -25,6 +25,7 @@ struct Repr {
|
||||
dynamic: image::DynamicImage,
|
||||
icc: Option<Bytes>,
|
||||
dpi: Option<f64>,
|
||||
source_color_type: image::ExtendedColorType,
|
||||
}
|
||||
|
||||
impl RasterImage {
|
||||
@ -50,13 +51,18 @@ impl RasterImage {
|
||||
format: RasterFormat,
|
||||
icc: Smart<Bytes>,
|
||||
) -> StrResult<RasterImage> {
|
||||
let (dynamic, icc, dpi) = match format {
|
||||
let (dynamic, icc, dpi, source_color_type) = match format {
|
||||
RasterFormat::Exchange(format) => {
|
||||
fn decode<T: ImageDecoder>(
|
||||
decoder: ImageResult<T>,
|
||||
icc: Smart<Bytes>,
|
||||
) -> ImageResult<(image::DynamicImage, Option<Bytes>)> {
|
||||
) -> ImageResult<(
|
||||
image::DynamicImage,
|
||||
Option<Bytes>,
|
||||
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::<image::Rgb<u8>>(&data, format).into(),
|
||||
PixelEncoding::Rgba8 => to::<image::Rgba<u8>>(&data, format).into(),
|
||||
PixelEncoding::Luma8 => to::<image::Luma<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.
|
||||
@ -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 {
|
||||
|
@ -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<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
|
||||
_ => (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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user