codec_eval/metrics/
butteraugli.rs1use butteraugli::{ButteraugliParams, compute_butteraugli};
18
19use super::icc::ColorProfile;
20use crate::error::{Error, Result};
21
22pub fn calculate_butteraugli(
39 reference: &[u8],
40 test: &[u8],
41 width: usize,
42 height: usize,
43) -> Result<f64> {
44 if reference.len() != test.len() {
45 return Err(Error::DimensionMismatch {
46 expected: (width, height),
47 actual: (test.len() / 3 / height, height),
48 });
49 }
50
51 let expected_len = width * height * 3;
52 if reference.len() != expected_len {
53 return Err(Error::MetricCalculation {
54 metric: "Butteraugli".to_string(),
55 reason: format!(
56 "Invalid image size: expected {} bytes, got {}",
57 expected_len,
58 reference.len()
59 ),
60 });
61 }
62
63 let params = ButteraugliParams::default();
64 let result = compute_butteraugli(reference, test, width, height, ¶ms).map_err(|e| {
65 Error::MetricCalculation {
66 metric: "Butteraugli".to_string(),
67 reason: e.to_string(),
68 }
69 })?;
70
71 Ok(result.score)
72}
73
74pub fn calculate_butteraugli_with_intensity(
91 reference: &[u8],
92 test: &[u8],
93 width: usize,
94 height: usize,
95 intensity_target: f32,
96) -> Result<f64> {
97 if reference.len() != test.len() {
98 return Err(Error::DimensionMismatch {
99 expected: (width, height),
100 actual: (test.len() / 3 / height, height),
101 });
102 }
103
104 let expected_len = width * height * 3;
105 if reference.len() != expected_len {
106 return Err(Error::MetricCalculation {
107 metric: "Butteraugli".to_string(),
108 reason: format!(
109 "Invalid image size: expected {} bytes, got {}",
110 expected_len,
111 reference.len()
112 ),
113 });
114 }
115
116 let params = ButteraugliParams::default().with_intensity_target(intensity_target);
117 let result = compute_butteraugli(reference, test, width, height, ¶ms).map_err(|e| {
118 Error::MetricCalculation {
119 metric: "Butteraugli".to_string(),
120 reason: e.to_string(),
121 }
122 })?;
123
124 Ok(result.score)
125}
126
127pub fn calculate_butteraugli_icc(
140 reference: &[u8],
141 reference_profile: &ColorProfile,
142 test: &[u8],
143 test_profile: &ColorProfile,
144 width: usize,
145 height: usize,
146) -> Result<f64> {
147 let (ref_srgb, test_srgb) =
148 super::icc::prepare_for_comparison(reference, reference_profile, test, test_profile)?;
149
150 calculate_butteraugli(&ref_srgb, &test_srgb, width, height)
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_identical_images() {
159 let data: Vec<u8> = (0..100 * 100 * 3).map(|i| (i % 256) as u8).collect();
160 let score = calculate_butteraugli(&data, &data, 100, 100).unwrap();
161 assert!(
163 score < 0.01,
164 "Identical images should have score ~0, got {score}"
165 );
166 }
167
168 #[test]
169 fn test_different_images() {
170 let ref_data: Vec<u8> = vec![100u8; 100 * 100 * 3];
171 let test_data: Vec<u8> = vec![200u8; 100 * 100 * 3];
172 let score = calculate_butteraugli(&ref_data, &test_data, 100, 100).unwrap();
173 assert!(
175 score > 1.0,
176 "Very different images should have high score, got {score}"
177 );
178 }
179
180 #[test]
181 fn test_size_mismatch() {
182 let small: Vec<u8> = vec![128u8; 50 * 50 * 3];
183 let large: Vec<u8> = vec![128u8; 100 * 100 * 3];
184 let result = calculate_butteraugli(&small, &large, 100, 100);
185 assert!(result.is_err());
186 }
187
188 #[test]
189 fn test_custom_intensity() {
190 let data: Vec<u8> = (0..100 * 100 * 3).map(|i| (i % 256) as u8).collect();
191 let score = calculate_butteraugli_with_intensity(&data, &data, 100, 100, 250.0).unwrap();
192 assert!(
193 score < 0.01,
194 "Identical images should have score ~0 at any intensity"
195 );
196 }
197}