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,
|
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 {
|
||||||
|
@ -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.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user