use lcms2::{CacheFlag, Profile, ThreadContext, Transform};
use crate::RenderingIntent;
pub struct Lcms2;
struct PreparedTransform<C: lcms2::CacheFlag> {
transform: Transform<u8, u8, ThreadContext, C>,
from_channels: usize,
to_channels: usize,
}
thread_local! {
static LCMS2_CTX: lcms2::ThreadContext = lcms2::ThreadContext::new();
}
impl crate::ColorManagementSystem for Lcms2 {
fn prepare_transform(
&self,
from_icc: &[u8],
to_icc: &[u8],
intent: RenderingIntent,
) -> Result<Box<dyn crate::PreparedTransform>, Box<dyn std::error::Error + Send + Sync + 'static>>
{
use lcms2::ColorSpaceSignatureExt;
let prepared = LCMS2_CTX.with(|ctx| -> lcms2::LCMSResult<_> {
let from_profile = Profile::new_icc_context(ctx, from_icc)?;
let from_channels = from_profile.color_space().channels() as usize;
let to_profile = Profile::new_icc_context(ctx, to_icc)?;
let to_channels = to_profile.color_space().channels() as usize;
#[allow(clippy::unusual_byte_groupings)]
let format_base = 0b010_00000_000000_000_0000_100;
let from_pixel_format = lcms2::PixelFormat(format_base | ((from_channels as u32) << 3));
let to_pixel_format = lcms2::PixelFormat(format_base | ((to_channels as u32) << 3));
let transform = Transform::new_flags_context(
ctx,
&from_profile,
from_pixel_format,
&to_profile,
to_pixel_format,
match intent {
RenderingIntent::Perceptual => lcms2::Intent::Perceptual,
RenderingIntent::Relative => lcms2::Intent::RelativeColorimetric,
RenderingIntent::Saturation => lcms2::Intent::Saturation,
RenderingIntent::Absolute => lcms2::Intent::AbsoluteColorimetric,
},
lcms2::Flags::NO_CACHE,
)?;
Ok(PreparedTransform {
transform,
from_channels,
to_channels,
})
})?;
Ok(Box::new(prepared) as Box<_>)
}
}
impl<C> crate::PreparedTransform for PreparedTransform<C>
where
C: CacheFlag,
PreparedTransform<C>: Send + Sync,
{
fn num_input_channels(&self) -> usize {
self.from_channels
}
fn num_output_channels(&self) -> usize {
self.to_channels
}
fn transform(
&self,
channels: &mut [&mut [f32]],
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
let Self {
ref transform,
from_channels,
to_channels,
} = *self;
let mut buf_in = vec![0f32; 1024 * from_channels];
let mut buf_out = vec![0f32; 1024 * to_channels];
let len = channels.iter().map(|x| x.len()).min().unwrap();
for idx in (0..len).step_by(1024) {
let chunk_len = (len - idx).min(1024);
for k in 0..chunk_len {
for (channel_idx, ch) in channels[..from_channels].iter().enumerate() {
buf_in[k * from_channels + channel_idx] = ch[idx + k];
}
}
let transform_buf_in = bytemuck::cast_slice(&buf_in[..chunk_len * from_channels]);
let transform_buf_out =
bytemuck::cast_slice_mut(&mut buf_out[..chunk_len * to_channels]);
transform.transform_pixels(transform_buf_in, transform_buf_out);
for k in 0..chunk_len {
for (channel_idx, ch) in channels[..to_channels].iter_mut().enumerate() {
ch[idx + k] = buf_out[k * to_channels + channel_idx];
}
}
}
Ok(())
}
}