use crate::pixel_format::{ColorSpace, PixelFormat};
use bytemuck::{Pod, cast_slice, cast_slice_mut};
pub use moxcms::CmsError;
use moxcms::{ColorProfile, DataColorSpace, Layout, ProfileText, RenderingIntent, TransformExecutor, TransformOptions};
use std::marker::PhantomData;
use std::sync::Arc;
pub(crate) struct Profile(moxcms::ColorProfile);
impl Profile {
pub(crate) fn new_icc(data: &[u8]) -> Result<Self, CmsError> {
ColorProfile::new_from_slice(data).map(Profile)
}
pub(crate) fn new_srgb() -> Self {
Self(ColorProfile::new_srgb())
}
pub(crate) fn color_space(&self) -> ColorSpace {
match self.0.color_space {
DataColorSpace::Rgb => ColorSpace::Rgb,
DataColorSpace::Gray => ColorSpace::Gray,
DataColorSpace::Cmyk => ColorSpace::Cmyk,
_ => ColorSpace::Other,
}
}
pub(crate) fn description(&self) -> Option<String> {
let text = self.0.description.as_ref()?;
Some(match text {
ProfileText::PlainString(s) => s.clone(),
ProfileText::Localizable(locales) => locales.iter().find(|l| l.language == "en").or_else(|| locales.first())?.value.clone(),
ProfileText::Description(d) => {
if !d.ascii_string.is_empty() {
d.ascii_string.clone()
} else if !d.unicode_string.is_empty() {
d.unicode_string.clone()
} else {
d.mac_string.clone()
}
},
})
}
}
fn to_layout(format: PixelFormat) -> Layout {
match format {
PixelFormat::RGBA8 | PixelFormat::RGBA16 => Layout::Rgba,
#[cfg(any(feature = "jpeg", feature = "mozjpeg"))]
PixelFormat::CMYK8 => Layout::Rgba,
PixelFormat::GRAY8 | PixelFormat::GRAY16 => Layout::Gray,
PixelFormat::GRAYA8 | PixelFormat::GRAYA16 => Layout::GrayAlpha,
PixelFormat::RGB8 | PixelFormat::RGB16 => Layout::Rgb,
}
}
fn is_8bit(format: PixelFormat) -> bool {
match format {
PixelFormat::RGB8 | PixelFormat::RGBA8 | PixelFormat::GRAY8 | PixelFormat::GRAYA8 => true,
#[cfg(any(feature = "jpeg", feature = "mozjpeg"))]
PixelFormat::CMYK8 => true,
_ => false,
}
}
pub(crate) struct Transform<In: Pod, Out: Pod> {
executor: Arc<dyn TransformExecutor<u16> + Send + Sync>,
src_8bit: bool,
_marker: PhantomData<fn(In, Out)>,
}
impl<In: Pod, Out: Pod> Transform<In, Out> {
pub(crate) fn new(src: &Profile, src_format: PixelFormat, dst: &Profile, dst_format: PixelFormat) -> Result<Self, CmsError> {
let options = TransformOptions {
rendering_intent: RenderingIntent::RelativeColorimetric,
..Default::default()
};
let executor = src.0.create_transform_16bit(to_layout(src_format), &dst.0, to_layout(dst_format), options)?;
Ok(Self {
executor,
src_8bit: is_8bit(src_format),
_marker: PhantomData,
})
}
pub(crate) fn transform_pixels(&self, src: &[In], dst: &mut [Out]) -> Result<(), CmsError> {
let dst: &mut [u16] = cast_slice_mut(dst);
if self.src_8bit {
let src: &[u8] = cast_slice(src);
let src: Vec<u16> = src
.iter()
.map(|&v| {
u16::from(v) * 257
})
.collect();
self.executor.transform(&src, dst)?;
} else {
let src: &[u16] = cast_slice(src);
self.executor.transform(src, dst)?;
}
Ok(())
}
}