mirror of
https://github.com/typst/typst
synced 2025-08-13 14:47:54 +08:00
feat: use different bit depth in alpha vs rest
This commit is contained in:
parent
12a6d6cb4d
commit
c789893251
@ -67,17 +67,17 @@ pub fn write_images(
|
||||
|
||||
// Add a second gray-scale image containing the alpha values if
|
||||
// this image has an alpha channel.
|
||||
if let Some((alpha_data, alpha_filter)) = alpha {
|
||||
if let Some(alpha) = alpha {
|
||||
let mask_ref = chunk.alloc.bump();
|
||||
image.s_mask(mask_ref);
|
||||
image.finish();
|
||||
|
||||
let mut mask = chunk.image_xobject(mask_ref, alpha_data);
|
||||
mask.filter(*alpha_filter);
|
||||
let mut mask = chunk.image_xobject(mask_ref, &alpha.data);
|
||||
mask.filter(alpha.filter);
|
||||
mask.width(*width as i32);
|
||||
mask.height(*height as i32);
|
||||
mask.color_space().device_gray();
|
||||
mask.bits_per_component(i32::from(*bits_per_component));
|
||||
mask.bits_per_component(i32::from(alpha.bits_per_component));
|
||||
mask.interpolate(*interpolate);
|
||||
} else {
|
||||
image.finish();
|
||||
@ -159,7 +159,10 @@ 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 = bits_per_component(color_type);
|
||||
|
||||
let bits_per_component = (raster.source_color_type().bits_per_pixel()
|
||||
/ u16::from(raster.source_color_type().channel_count()))
|
||||
as u8;
|
||||
|
||||
let compressed_icc = image.icc().map(|bytes| deflate(bytes.as_ref()));
|
||||
let alpha = encode_alpha(dynamic);
|
||||
@ -183,15 +186,19 @@ 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());
|
||||
|
||||
// TODO: Encode flate streams with PNG-predictor?
|
||||
let data = match (dynamic, color_space) {
|
||||
(DynamicImage::ImageRgb8(rgb), _) => deflate(rgb.as_raw()),
|
||||
let (bits_per_component, data) = match (image, color_space) {
|
||||
(DynamicImage::ImageRgb8(rgb), _) => (8, deflate(rgb.as_raw())),
|
||||
// Grayscale image
|
||||
(DynamicImage::ImageLuma8(luma), _) => deflate(luma.as_raw()),
|
||||
(_, ColorSpace::D65Gray) => deflate(dynamic.to_luma8().as_raw()),
|
||||
(DynamicImage::ImageLuma8(luma), _) => (8, deflate(luma.as_raw())),
|
||||
(_, ColorSpace::D65Gray) => (8, deflate(image.to_luma8().as_raw())),
|
||||
// Anything else
|
||||
_ => deflate(dynamic.to_rgb8().as_raw()),
|
||||
_ => (8, deflate(image.to_rgb8().as_raw())),
|
||||
};
|
||||
|
||||
let compressed_icc = image.icc().map(|bytes| deflate(bytes.as_ref()));
|
||||
@ -210,13 +217,32 @@ fn encode_raster_flate(image: &RasterImage, interpolate: bool) -> EncodedImage {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode an image's alpha channel if present.
|
||||
#[typst_macros::time(name = "encode alpha")]
|
||||
fn encode_alpha(image: &DynamicImage) -> Option<(Vec<u8>, Filter)> {
|
||||
fn encode_alpha(image: &DynamicImage) -> Option<AlphaChannel> {
|
||||
if !image.color().has_alpha() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bits_per_component = match image.color() {
|
||||
image::ColorType::La8 => 8,
|
||||
image::ColorType::Rgba8 => 8,
|
||||
image::ColorType::La16 => 16,
|
||||
image::ColorType::Rgba16 => 16,
|
||||
image::ColorType::Rgba32F => 32,
|
||||
_ => 8,
|
||||
};
|
||||
|
||||
// Encode the alpha channel as big-endian.
|
||||
let alpha: Vec<u8> = match image {
|
||||
DynamicImage::ImageLumaA8(buf) => buf.pixels().map(|&LumaA([_, a])| a).collect(),
|
||||
@ -232,7 +258,11 @@ fn encode_alpha(image: &DynamicImage) -> Option<(Vec<u8>, Filter)> {
|
||||
// Everything else is encoded as RGBA8.
|
||||
_ => image.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect(),
|
||||
};
|
||||
Some((deflate(&alpha), Filter::FlateDecode))
|
||||
Some(AlphaChannel {
|
||||
data: deflate(&alpha),
|
||||
filter: Filter::FlateDecode,
|
||||
bits_per_component,
|
||||
})
|
||||
}
|
||||
|
||||
/// Encode an SVG into a chunk of PDF objects.
|
||||
@ -287,7 +317,7 @@ pub enum EncodedImage {
|
||||
/// The image's ICC profile, deflated, if any.
|
||||
compressed_icc: Option<Vec<u8>>,
|
||||
/// The alpha channel of the image, pre-deflated, if any.
|
||||
alpha: Option<(Vec<u8>, Filter)>,
|
||||
alpha: Option<AlphaChannel>,
|
||||
/// Whether image interpolation should be enabled.
|
||||
interpolate: bool,
|
||||
},
|
||||
@ -296,3 +326,13 @@ pub enum EncodedImage {
|
||||
/// The chunk is the SVG converted to PDF objects.
|
||||
Svg(Chunk, Ref),
|
||||
}
|
||||
|
||||
/// The alpha channel data.
|
||||
pub struct AlphaChannel {
|
||||
/// The raw alpha channel, encoded using the given filter.
|
||||
data: Vec<u8>,
|
||||
/// The filter used for the alpha channel.
|
||||
filter: Filter,
|
||||
/// The number of bits the alpha component is encoded in.
|
||||
bits_per_component: u8,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user