use std::sync::Arc;
use stet_graphics::color::DeviceColor;
use stet_graphics::device::{ImageColorSpace, ImageParams, TintLookupTable};
pub struct ImageXObject {
pub sample_data: Vec<u8>,
pub smask_data: Option<Vec<u8>>,
pub width: u32,
pub height: u32,
pub pdf_color_space: PdfColorSpace,
pub bits_per_component: u32,
pub is_imagemask: bool,
pub mask_color: Option<DeviceColor>,
pub mask_spot_color: Option<stet_graphics::device::SpotColor>,
pub color_key_mask: Option<Vec<u8>>,
pub icc_profile: Option<IccProfileData>,
}
#[derive(Clone, Debug)]
pub enum PdfColorSpace {
DeviceGray,
DeviceRGB,
DeviceCMYK,
ICCBased {
n: u32,
},
Indexed {
base: Box<PdfColorSpace>,
hival: u32,
lookup: Vec<u8>,
},
Separation {
name: Vec<u8>,
alt: Box<PdfColorSpace>,
tint_table: Arc<TintLookupTable>,
},
DeviceN {
names: Vec<Vec<u8>>,
alt: Box<PdfColorSpace>,
tint_table: Arc<TintLookupTable>,
},
}
impl PdfColorSpace {
pub fn num_components(&self) -> usize {
match self {
PdfColorSpace::DeviceGray => 1,
PdfColorSpace::DeviceRGB => 3,
PdfColorSpace::DeviceCMYK => 4,
PdfColorSpace::ICCBased { n } => *n as usize,
PdfColorSpace::Indexed { .. } => 1,
PdfColorSpace::Separation { .. } => 1,
PdfColorSpace::DeviceN { tint_table, .. } => tint_table.num_inputs as usize,
}
}
}
#[derive(Clone)]
pub struct IccProfileData {
pub data: Vec<u8>,
pub n: u32,
}
pub fn convert_image(sample_data: &[u8], params: &ImageParams) -> ImageXObject {
match ¶ms.color_space {
ImageColorSpace::Mask {
color,
polarity,
spot_color,
} => convert_imagemask(sample_data, params, color, *polarity, spot_color.as_ref()),
ImageColorSpace::PreconvertedRGBA => convert_preconverted_rgba(sample_data, params),
ImageColorSpace::DeviceGray => ImageXObject {
sample_data: sample_data.to_vec(),
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::DeviceGray,
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile: None,
},
ImageColorSpace::DeviceRGB => ImageXObject {
sample_data: sample_data.to_vec(),
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::DeviceRGB,
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile: None,
},
ImageColorSpace::DeviceCMYK => ImageXObject {
sample_data: sample_data.to_vec(),
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::DeviceCMYK,
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile: None,
},
ImageColorSpace::ICCBased {
n, profile_data, ..
} => ImageXObject {
sample_data: sample_data.to_vec(),
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::ICCBased { n: *n },
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile: Some(IccProfileData {
data: (**profile_data).clone(),
n: *n,
}),
},
ImageColorSpace::Indexed {
base,
hival,
lookup,
} => {
let (pdf_base, icc_profile) = match base.as_ref() {
ImageColorSpace::DeviceGray => (PdfColorSpace::DeviceGray, None),
ImageColorSpace::DeviceCMYK => (PdfColorSpace::DeviceCMYK, None),
ImageColorSpace::ICCBased {
n, profile_data, ..
} => (
PdfColorSpace::ICCBased { n: *n },
Some(IccProfileData {
data: (**profile_data).clone(),
n: *n,
}),
),
ImageColorSpace::Separation {
name,
alt_space,
tint_table,
} => (
PdfColorSpace::Separation {
name: name.clone(),
alt: Box::new(image_cs_to_pdf_cs(alt_space)),
tint_table: tint_table.clone(),
},
None,
),
ImageColorSpace::DeviceN {
names,
alt_space,
tint_table,
} => (
PdfColorSpace::DeviceN {
names: names.clone(),
alt: Box::new(image_cs_to_pdf_cs(alt_space)),
tint_table: tint_table.clone(),
},
None,
),
_ => (PdfColorSpace::DeviceRGB, None),
};
ImageXObject {
sample_data: sample_data.to_vec(),
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::Indexed {
base: Box::new(pdf_base),
hival: *hival,
lookup: lookup.clone(),
},
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile,
}
}
ImageColorSpace::Separation {
name,
alt_space,
tint_table,
} => {
let pdf_alt = image_cs_to_pdf_cs(alt_space);
ImageXObject {
sample_data: sample_data.to_vec(),
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::Separation {
name: name.clone(),
alt: Box::new(pdf_alt),
tint_table: tint_table.clone(),
},
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile: None,
}
}
ImageColorSpace::DeviceN {
names,
alt_space,
tint_table,
} => {
let pdf_alt = image_cs_to_pdf_cs(alt_space);
ImageXObject {
sample_data: sample_data.to_vec(),
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::DeviceN {
names: names.clone(),
alt: Box::new(pdf_alt),
tint_table: tint_table.clone(),
},
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile: None,
}
}
ImageColorSpace::CIEBasedABC { params: cie_params } => {
let npixels = (params.width * params.height) as usize;
let mut rgb = Vec::with_capacity(npixels * 3);
for i in 0..npixels {
let si = i * 3;
let a = sample_data.get(si).copied().unwrap_or(0) as f64 / 255.0;
let b = sample_data.get(si + 1).copied().unwrap_or(0) as f64 / 255.0;
let c = sample_data.get(si + 2).copied().unwrap_or(0) as f64 / 255.0;
let color = DeviceColor::from_cie_abc(a, b, c, cie_params);
rgb.push((color.r * 255.0).round().clamp(0.0, 255.0) as u8);
rgb.push((color.g * 255.0).round().clamp(0.0, 255.0) as u8);
rgb.push((color.b * 255.0).round().clamp(0.0, 255.0) as u8);
}
ImageXObject {
sample_data: rgb,
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::DeviceRGB,
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile: None,
}
}
ImageColorSpace::CIEBasedA { params: cie_params } => {
let npixels = (params.width * params.height) as usize;
let mut rgb = Vec::with_capacity(npixels * 3);
for i in 0..npixels {
let val = sample_data.get(i).copied().unwrap_or(0) as f64 / 255.0;
let color = DeviceColor::from_cie_a(val, cie_params);
rgb.push((color.r * 255.0).round().clamp(0.0, 255.0) as u8);
rgb.push((color.g * 255.0).round().clamp(0.0, 255.0) as u8);
rgb.push((color.b * 255.0).round().clamp(0.0, 255.0) as u8);
}
ImageXObject {
sample_data: rgb,
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::DeviceRGB,
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile: None,
}
}
ImageColorSpace::Lab { range, .. } => {
let npixels = (params.width * params.height) as usize;
let mut rgb = Vec::with_capacity(npixels * 3);
let a_span = range[1] - range[0];
let b_span = range[3] - range[2];
for i in 0..npixels {
let si = i * 3;
let l = sample_data.get(si).copied().unwrap_or(0) as f64 / 255.0 * 100.0;
let a = sample_data.get(si + 1).copied().unwrap_or(0) as f64 / 255.0 * a_span
+ range[0];
let b = sample_data.get(si + 2).copied().unwrap_or(0) as f64 / 255.0 * b_span
+ range[2];
let color = DeviceColor::from_lab(l, a, b, range);
rgb.push((color.r * 255.0).round().clamp(0.0, 255.0) as u8);
rgb.push((color.g * 255.0).round().clamp(0.0, 255.0) as u8);
rgb.push((color.b * 255.0).round().clamp(0.0, 255.0) as u8);
}
ImageXObject {
sample_data: rgb,
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::DeviceRGB,
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile: None,
}
}
_ => ImageXObject {
sample_data: sample_data.to_vec(),
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::DeviceGray,
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: params.mask_color.clone(),
icc_profile: None,
},
}
}
fn convert_imagemask(
raw_bits: &[u8],
params: &ImageParams,
color: &DeviceColor,
polarity: bool,
spot_color: Option<&stet_graphics::device::SpotColor>,
) -> ImageXObject {
let mask_data = if polarity {
raw_bits.to_vec()
} else {
raw_bits.iter().map(|b| !b).collect()
};
ImageXObject {
sample_data: mask_data,
smask_data: None,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::DeviceGray,
bits_per_component: 1,
is_imagemask: true,
mask_color: Some(color.clone()),
mask_spot_color: spot_color.cloned(),
color_key_mask: None,
icc_profile: None,
}
}
fn image_cs_to_pdf_cs(cs: &ImageColorSpace) -> PdfColorSpace {
match cs {
ImageColorSpace::DeviceGray => PdfColorSpace::DeviceGray,
ImageColorSpace::DeviceRGB => PdfColorSpace::DeviceRGB,
ImageColorSpace::DeviceCMYK => PdfColorSpace::DeviceCMYK,
_ => PdfColorSpace::DeviceRGB, }
}
fn convert_preconverted_rgba(rgba: &[u8], params: &ImageParams) -> ImageXObject {
let npixels = (params.width * params.height) as usize;
let has_alpha = rgba.chunks_exact(4).any(|px| px[3] != 255);
let mut rgb = Vec::with_capacity(npixels * 3);
for px in rgba.chunks_exact(4) {
rgb.push(px[0]);
rgb.push(px[1]);
rgb.push(px[2]);
}
let smask_data = if has_alpha {
Some(rgba.chunks_exact(4).map(|px| px[3]).collect())
} else {
None
};
ImageXObject {
sample_data: rgb,
smask_data,
width: params.width,
height: params.height,
pdf_color_space: PdfColorSpace::DeviceRGB,
bits_per_component: 8,
is_imagemask: false,
mask_color: None,
mask_spot_color: None,
color_key_mask: None,
icc_profile: None,
}
}