use crate::error::{CalibrationError, CalibrationResult};
use crate::icc::IccProfile;
use crate::Rgb;
use rayon::prelude::*;
pub struct IccProfileApplicator {
profile: IccProfile,
}
impl IccProfileApplicator {
#[must_use]
pub fn new(profile: IccProfile) -> Self {
Self { profile }
}
#[must_use]
pub fn apply_to_color(&self, rgb: &Rgb) -> Rgb {
self.profile.rgb_to_xyz(rgb)
}
pub fn apply_to_image(
&self,
image_data: &[u8],
_width: usize,
_height: usize,
) -> CalibrationResult<Vec<u8>> {
if image_data.len() % 3 != 0 {
return Err(CalibrationError::InvalidImageDimensions(
"Image data length must be a multiple of 3 for RGB".to_string(),
));
}
let mut output = vec![0u8; image_data.len()];
output
.par_chunks_exact_mut(3)
.enumerate()
.for_each(|(idx, out_chunk)| {
let in_off = idx * 3;
let r = f64::from(image_data[in_off]) / 255.0;
let g = f64::from(image_data[in_off + 1]) / 255.0;
let b = f64::from(image_data[in_off + 2]) / 255.0;
let transformed = self.apply_to_color(&[r, g, b]);
out_chunk[0] = (transformed[0] * 255.0).clamp(0.0, 255.0) as u8;
out_chunk[1] = (transformed[1] * 255.0).clamp(0.0, 255.0) as u8;
out_chunk[2] = (transformed[2] * 255.0).clamp(0.0, 255.0) as u8;
});
Ok(output)
}
#[must_use]
pub fn convert_between_profiles(
source_profile: &IccProfile,
target_profile: &IccProfile,
rgb: &Rgb,
) -> Rgb {
let xyz = source_profile.rgb_to_xyz(rgb);
target_profile.xyz_to_rgb(&xyz)
}
#[must_use]
pub fn profile(&self) -> &IccProfile {
&self.profile
}
pub fn verify(&self) -> CalibrationResult<()> {
self.profile.validate()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Illuminant;
#[test]
fn test_icc_profile_applicator_new() {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let profile = IccProfile::new("Test Profile".to_string(), identity, Illuminant::D65);
let applicator = IccProfileApplicator::new(profile);
assert_eq!(applicator.profile().description, "Test Profile");
}
#[test]
fn test_icc_profile_applicator_apply_to_color() {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let profile = IccProfile::new("Test Profile".to_string(), identity, Illuminant::D65);
let applicator = IccProfileApplicator::new(profile);
let rgb = [0.5, 0.6, 0.7];
let result = applicator.apply_to_color(&rgb);
assert!((result[0] - 0.5).abs() < 1e-10);
assert!((result[1] - 0.6).abs() < 1e-10);
assert!((result[2] - 0.7).abs() < 1e-10);
}
#[test]
fn test_icc_profile_applicator_apply_to_image() {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let profile = IccProfile::new("Test Profile".to_string(), identity, Illuminant::D65);
let applicator = IccProfileApplicator::new(profile);
let image = vec![128, 128, 128, 255, 0, 0];
let result = applicator.apply_to_image(&image, 2, 1);
assert!(result.is_ok());
let output = result.expect("expected successful result");
assert_eq!(output.len(), image.len());
}
#[test]
fn test_icc_profile_applicator_invalid_image() {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let profile = IccProfile::new("Test Profile".to_string(), identity, Illuminant::D65);
let applicator = IccProfileApplicator::new(profile);
let image = vec![128, 128]; let result = applicator.apply_to_image(&image, 1, 1);
assert!(result.is_err());
}
#[test]
fn test_convert_between_profiles() {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let profile1 = IccProfile::new("Profile 1".to_string(), identity, Illuminant::D65);
let profile2 = IccProfile::new("Profile 2".to_string(), identity, Illuminant::D65);
let rgb = [0.5, 0.6, 0.7];
let result = IccProfileApplicator::convert_between_profiles(&profile1, &profile2, &rgb);
assert!((result[0] - 0.5).abs() < 1e-10);
assert!((result[1] - 0.6).abs() < 1e-10);
assert!((result[2] - 0.7).abs() < 1e-10);
}
#[test]
fn test_icc_profile_applicator_verify() {
let identity = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let profile = IccProfile::new("Test Profile".to_string(), identity, Illuminant::D65);
let applicator = IccProfileApplicator::new(profile);
assert!(applicator.verify().is_ok());
}
}