use crate::Error;
use lopdf::ObjectId;
use png::{BitDepth, ColorType};
use std::io::Read;
#[derive(Debug, Clone)]
pub struct ImageXObject {
pub width: u32,
pub height: u32,
pub color_space: ColorType,
pub bits_per_component: BitDepth,
pub interpolate: bool,
pub image_data: Vec<u8>,
pub s_mask: Option<ObjectId>,
}
impl<'a> ImageXObject {
pub fn try_from<R: Read>(
image_decoder: png::Decoder<R>,
) -> Result<(Self, Option<Self>), Error> {
let image_reader = image_decoder.read_info();
let mut reader = match image_reader {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).unwrap();
let image_data = Vec::from(&buf[..info.buffer_size()]);
let mut color_type = info.color_type;
let (image_color_data, alpha_data) = match info.color_type {
ColorType::Rgba => {
color_type = ColorType::Rgb;
(
Self::rgba_to_rgb(&image_data),
Some(Self::rgba_to_a(&image_data)),
)
}
ColorType::GrayscaleAlpha => {
color_type = ColorType::Grayscale;
(
Self::grayscale_alpha_to_grayscale(&image_data),
Some(Self::grayscale_alpha_to_grayscale(&image_data)),
)
}
_ => (image_data, None),
};
Ok((
Self {
width: info.width,
height: info.height,
color_space: color_type,
bits_per_component: info.bit_depth,
image_data: image_color_data,
interpolate: false,
s_mask: None, },
alpha_data.map(|alpha_data| Self {
width: info.width,
height: info.height,
color_space: ColorType::Grayscale,
bits_per_component: info.bit_depth,
image_data: alpha_data,
interpolate: false,
s_mask: None,
}),
))
}
fn rgba_to_rgb(data: &[u8]) -> Vec<u8> {
let mut temp_counter = 0;
let mut temp = [0u8; 3];
let mut output = Vec::with_capacity(data.len());
for byte in data {
match temp_counter {
0..=2 => {
temp[temp_counter] = *byte;
temp_counter += 1;
}
_ => {
output.extend_from_slice(&temp);
temp_counter = 0;
}
}
}
output
}
fn grayscale_alpha_to_grayscale(data: &[u8]) -> Vec<u8> {
let mut temp_counter = 0;
let mut temp = 0u8;
let mut output = Vec::with_capacity(data.len() / 2);
for byte in data {
match temp_counter {
0 => {
temp = *byte;
temp_counter += 1;
}
_ => {
output.push(temp);
temp_counter = 0;
}
}
}
output
}
fn rgba_to_a(data: &[u8]) -> Vec<u8> {
let mut temp_counter = 0;
let mut output = Vec::with_capacity(data.len() / 4);
for byte in data {
match temp_counter {
0..=2 => {
temp_counter += 1;
}
_ => {
output.extend_from_slice(&[*byte]);
temp_counter = 0;
}
}
}
output
}
}
impl From<ImageXObject> for lopdf::Stream {
fn from(image: ImageXObject) -> Self {
use lopdf::Object::*;
let cs: &'static str = match image.color_space {
ColorType::Rgb => "DeviceRGB",
ColorType::Grayscale => "DeviceGray",
ColorType::Indexed => "Indexed",
ColorType::Rgba | ColorType::GrayscaleAlpha => "DeviceN",
};
let identity_matrix: Vec<f64> = vec![1.0, 0.0, 0.0, 1.0, 0.0, 0.0];
let bbox: lopdf::Object = Array(identity_matrix.into_iter().map(Real).collect());
let mut dict = lopdf::Dictionary::from_iter(vec![
("Type", Name("XObject".as_bytes().to_vec())),
("Subtype", Name("Image".as_bytes().to_vec())),
("Width", Integer(image.width as i64)),
("Height", Integer(image.height as i64)),
("Interpolate", image.interpolate.into()),
("BitsPerComponent", Integer(image.bits_per_component as i64)),
("ColorSpace", Name(cs.as_bytes().to_vec())),
("BBox", bbox),
]);
if let Some(s_mask) = image.s_mask {
dict.set("SMask", Reference(s_mask));
}
lopdf::Stream::new(dict, image.image_data)
}
}
impl From<ImageXObject> for lopdf::Object {
fn from(image: ImageXObject) -> Self {
lopdf::Object::Stream(image.into())
}
}