use super::{DecodeContext, decode_context, fix_image_length, unpack_samples};
use crate::LumaData;
use crate::function::interpolate;
use crate::x_object::image::{ImageKind, ImageXObject};
use std::sync::LazyLock;
static BILEVEL_MASK_LUT: LazyLock<Box<[u64; 256]>> = LazyLock::new(|| {
let mut lut = Box::new([0; 256]);
for (byte, expanded) in lut.iter_mut().enumerate() {
let mut pixels = [0; 8];
for (bit, pixel) in pixels.iter_mut().enumerate() {
*pixel = if byte & (0x80 >> bit) != 0 { 255 } else { 0 };
}
*expanded = u64::from_ne_bytes(pixels);
}
lut
});
pub(crate) struct DecodedMask {
pub(crate) luma: LumaData,
}
pub(crate) fn decode_mask(
obj: &ImageXObject<'_>,
target_dimension: Option<(u32, u32)>,
) -> Option<DecodedMask> {
let ctx = decode_context(obj, target_dimension)?;
let width = ctx.width;
let scale_factors = ctx.scale_factors;
let invert = obj.kind == ImageKind::StencilMask;
let (data, height) = decode_mask_data(ctx, invert)?;
Some(DecodedMask {
luma: LumaData {
data,
width,
height,
interpolate: obj.interpolate,
scale_factors,
},
})
}
fn decode_mask_data(mut ctx: DecodeContext<'_>, invert: bool) -> Option<(Vec<u8>, u32)> {
let default_decode = ctx
.color_space
.default_decode_arr(ctx.bits_per_component as f32);
let inverted_default = ctx
.color_space
.inverted_default_decode_arr(ctx.bits_per_component as f32);
let bilevel_fast_path = ctx.bits_per_component == 1
&& ctx.color_space.num_components() == 1
&& (ctx.decode_arr.as_slice() == default_decode.as_slice()
|| ctx.decode_arr.as_slice() == inverted_default.as_slice());
let fast_path = ctx.bits_per_component == 8
&& (ctx.decode_arr.as_slice() == default_decode.as_slice()
|| ctx.decode_arr.as_slice() == inverted_default.as_slice());
let mut data = if bilevel_fast_path {
decode_bilevel_mask_data(
&ctx,
invert ^ (ctx.decode_arr.as_slice() == inverted_default.as_slice()),
)
} else if fast_path {
let mut decoded = ctx.decoded.data;
let should_invert = invert ^ (ctx.decode_arr.as_slice() == inverted_default.as_slice());
if should_invert {
for byte in decoded.to_mut() {
*byte = 255 - *byte;
}
}
decoded.into_owned()
} else {
let num_components = ctx.color_space.num_components() as usize;
let components = unpack_samples(
&ctx.decoded.data,
ctx.width,
ctx.height,
num_components,
ctx.bits_per_component,
)?;
let source_max = 2.0_f32.powi(ctx.bits_per_component as i32) - 1.0;
let mut decoded = Vec::with_capacity(components.len());
for pixel in components.chunks(num_components) {
for (component, (decode_min, decode_max)) in pixel.iter().zip(&ctx.decode_arr) {
let value =
interpolate(*component as f32, 0.0, source_max, *decode_min, *decode_max);
let value = if invert { 1.0 - value } else { value };
decoded.push((value * 255.0 + 0.5) as u8);
}
}
decoded
};
fix_image_length(
&mut data,
ctx.width,
&mut ctx.height,
0,
ctx.color_space.num_components() as usize,
)?;
Some((data, ctx.height))
}
fn decode_bilevel_mask_data(ctx: &DecodeContext<'_>, should_invert: bool) -> Vec<u8> {
let width = ctx.width as usize;
let height = ctx.height as usize;
let row_bytes = width.div_ceil(8);
let data = ctx.decoded.data.as_ref();
let xor_mask = if should_invert { u64::MAX } else { 0 };
let full_bytes = width / 8;
let tail_bits = width % 8;
let lut = &**BILEVEL_MASK_LUT;
let mut out = Vec::with_capacity(width * height);
let mut buffer = [[0; 8]; 32];
for row in data.chunks(row_bytes).take(height) {
for chunk in row[..row.len().min(full_bytes)].chunks(buffer.len()) {
for (expanded, &byte) in buffer.iter_mut().zip(chunk) {
*expanded = (lut[byte as usize] ^ xor_mask).to_ne_bytes();
}
out.extend_from_slice(buffer[..chunk.len()].as_flattened());
}
if tail_bits != 0 && row.len() > full_bytes {
let expanded = (lut[row[full_bytes] as usize] ^ xor_mask).to_ne_bytes();
out.extend_from_slice(&expanded[..tail_bits]);
}
}
out
}