codec_eval/metrics/
icc.rs1use crate::error::{Error, Result};
30
31#[derive(Debug, Clone, Default)]
33pub enum ColorProfile {
34 #[default]
36 Srgb,
37 Icc(Vec<u8>),
39}
40
41impl ColorProfile {
42 #[must_use]
44 pub fn is_srgb(&self) -> bool {
45 matches!(self, Self::Srgb)
46 }
47
48 #[must_use]
50 pub fn from_icc_bytes(icc: Option<&[u8]>) -> Self {
51 match icc {
52 Some(data) if !data.is_empty() => Self::Icc(data.to_vec()),
53 _ => Self::Srgb,
54 }
55 }
56}
57
58#[cfg(feature = "icc")]
69pub fn transform_to_srgb(rgb: &[u8], profile: &ColorProfile) -> Result<Vec<u8>> {
70 use moxcms::{ColorProfile as MoxProfile, Layout, TransformOptions};
71
72 match profile {
73 ColorProfile::Srgb => Ok(rgb.to_vec()),
74 ColorProfile::Icc(icc_data) => {
75 let input_profile =
76 MoxProfile::new_from_slice(icc_data).map_err(|e| Error::MetricCalculation {
77 metric: "ICC".to_string(),
78 reason: format!("Failed to parse ICC profile: {e}"),
79 })?;
80
81 let srgb = MoxProfile::new_srgb();
82
83 let transform = input_profile
86 .create_transform_8bit(Layout::Rgb, &srgb, Layout::Rgb, TransformOptions::default())
87 .map_err(|e| Error::MetricCalculation {
88 metric: "ICC".to_string(),
89 reason: format!("Failed to create ICC transform: {e}"),
90 })?;
91
92 let mut output = vec![0u8; rgb.len()];
93 transform
94 .transform(rgb, &mut output)
95 .map_err(|e| Error::MetricCalculation {
96 metric: "ICC".to_string(),
97 reason: format!("Failed to apply ICC transform: {e}"),
98 })?;
99
100 Ok(output)
101 }
102 }
103}
104
105#[cfg(not(feature = "icc"))]
107pub fn transform_to_srgb(rgb: &[u8], profile: &ColorProfile) -> Result<Vec<u8>> {
108 match profile {
109 ColorProfile::Srgb => Ok(rgb.to_vec()),
110 ColorProfile::Icc(_) => Err(Error::MetricCalculation {
111 metric: "ICC".to_string(),
112 reason: "ICC profile support requires the 'icc' feature".to_string(),
113 }),
114 }
115}
116
117pub fn prepare_for_comparison(
122 reference: &[u8],
123 reference_profile: &ColorProfile,
124 test: &[u8],
125 test_profile: &ColorProfile,
126) -> Result<(Vec<u8>, Vec<u8>)> {
127 let ref_srgb = transform_to_srgb(reference, reference_profile)?;
128 let test_srgb = transform_to_srgb(test, test_profile)?;
129 Ok((ref_srgb, test_srgb))
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_srgb_passthrough() {
138 let rgb = vec![100u8, 150, 200, 50, 100, 150];
139 let result = transform_to_srgb(&rgb, &ColorProfile::Srgb).unwrap();
140 assert_eq!(result, rgb);
141 }
142
143 #[test]
144 fn test_color_profile_default() {
145 assert!(ColorProfile::default().is_srgb());
146 }
147
148 #[test]
149 fn test_from_icc_bytes_none() {
150 let profile = ColorProfile::from_icc_bytes(None);
151 assert!(profile.is_srgb());
152 }
153
154 #[test]
155 fn test_from_icc_bytes_empty() {
156 let profile = ColorProfile::from_icc_bytes(Some(&[]));
157 assert!(profile.is_srgb());
158 }
159
160 #[test]
161 fn test_from_icc_bytes_data() {
162 let profile = ColorProfile::from_icc_bytes(Some(&[1, 2, 3, 4]));
163 assert!(!profile.is_srgb());
164 }
165}