extern crate alloc;
use alloc::boxed::Box;
use moxcms::TransformOptions;
use crate::error::{ColorError, ColorResult};
use crate::profile::IccProfile;
use crate::rendering_intent::RenderingIntent;
pub struct ColorTransform {
executor: Box<moxcms::TransformF32BitExecutor>,
src_channels: usize,
dst_channels: usize,
}
impl ColorTransform {
pub fn builder() -> ColorTransformBuilder {
ColorTransformBuilder {
source: None,
destination: None,
intent: RenderingIntent::Perceptual,
}
}
pub fn transform(&self, src: &[f32], dst: &mut [f32]) -> ColorResult<()> {
self.executor
.transform(src, dst)
.map_err(|e| ColorError::TransformExecution(alloc::format!("{:?}", e)))
}
pub fn source_channels(&self) -> usize {
self.src_channels
}
pub fn destination_channels(&self) -> usize {
self.dst_channels
}
}
pub struct ColorTransformBuilder {
source: Option<IccProfile>,
destination: Option<IccProfile>,
intent: RenderingIntent,
}
impl ColorTransformBuilder {
pub fn source(mut self, profile: &IccProfile) -> Self {
self.source = Some(profile.clone());
self
}
pub fn destination(mut self, profile: &IccProfile) -> Self {
self.destination = Some(profile.clone());
self
}
pub fn intent(mut self, intent: RenderingIntent) -> Self {
self.intent = intent;
self
}
pub fn build(self) -> ColorResult<ColorTransform> {
let src = self
.source
.ok_or(ColorError::TransformNotBuilt)?;
let dst = self
.destination
.ok_or(ColorError::TransformNotBuilt)?;
let src_space = src.color_space()?;
let dst_space = dst.color_space()?;
let src_layout = src_space.to_moxcms_layout();
let dst_layout = dst_space.to_moxcms_layout();
let options = TransformOptions {
rendering_intent: self.intent.to_moxcms(),
..TransformOptions::default()
};
let executor = src
.inner
.create_transform_f32(src_layout, &dst.inner, dst_layout, options)
.map_err(|e| {
ColorError::TransformExecution(alloc::format!("{:?}", e))
})?;
Ok(ColorTransform {
executor,
src_channels: src_space.channel_count(),
dst_channels: dst_space.channel_count(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_without_source_fails() {
let result = ColorTransform::builder()
.destination(&IccProfile::new_srgb())
.build();
assert!(matches!(result, Err(ColorError::TransformNotBuilt)));
}
#[test]
fn build_without_destination_fails() {
let result = ColorTransform::builder()
.source(&IccProfile::new_srgb())
.build();
assert!(matches!(result, Err(ColorError::TransformNotBuilt)));
}
#[test]
fn srgb_to_adobe_rgb_transform() {
let src = IccProfile::new_srgb();
let dst = IccProfile::new_adobe_rgb();
let xform = ColorTransform::builder()
.source(&src)
.destination(&dst)
.build()
.unwrap();
let input = [1.0_f32, 0.0, 0.0];
let mut output = [0.0_f32; 3];
xform.transform(&input, &mut output).unwrap();
assert!(output[0] > 0.0);
}
}