codec_eval/metrics/
butteraugli.rs1use butteraugli::{ButteraugliParams, Img, RGB8, butteraugli as butteraugli_compare};
18
19use super::icc::ColorProfile;
20use crate::error::{Error, Result};
21
22fn rgb_bytes_to_pixels(rgb: &[u8]) -> Vec<RGB8> {
24 rgb.chunks_exact(3)
25 .map(|c| RGB8::new(c[0], c[1], c[2]))
26 .collect()
27}
28
29pub fn calculate_butteraugli(
46 reference: &[u8],
47 test: &[u8],
48 width: usize,
49 height: usize,
50) -> Result<f64> {
51 if reference.len() != test.len() {
52 return Err(Error::DimensionMismatch {
53 expected: (width, height),
54 actual: (test.len() / 3 / height, height),
55 });
56 }
57
58 let expected_len = width * height * 3;
59 if reference.len() != expected_len {
60 return Err(Error::MetricCalculation {
61 metric: "Butteraugli".to_string(),
62 reason: format!(
63 "Invalid image size: expected {} bytes, got {}",
64 expected_len,
65 reference.len()
66 ),
67 });
68 }
69
70 let img1 = Img::new(rgb_bytes_to_pixels(reference), width, height);
71 let img2 = Img::new(rgb_bytes_to_pixels(test), width, height);
72 let params = ButteraugliParams::default();
73 let result = butteraugli_compare(img1.as_ref(), img2.as_ref(), ¶ms).map_err(|e| {
74 Error::MetricCalculation {
75 metric: "Butteraugli".to_string(),
76 reason: e.to_string(),
77 }
78 })?;
79
80 Ok(result.score)
81}
82
83pub fn calculate_butteraugli_with_intensity(
100 reference: &[u8],
101 test: &[u8],
102 width: usize,
103 height: usize,
104 intensity_target: f32,
105) -> Result<f64> {
106 if reference.len() != test.len() {
107 return Err(Error::DimensionMismatch {
108 expected: (width, height),
109 actual: (test.len() / 3 / height, height),
110 });
111 }
112
113 let expected_len = width * height * 3;
114 if reference.len() != expected_len {
115 return Err(Error::MetricCalculation {
116 metric: "Butteraugli".to_string(),
117 reason: format!(
118 "Invalid image size: expected {} bytes, got {}",
119 expected_len,
120 reference.len()
121 ),
122 });
123 }
124
125 let img1 = Img::new(rgb_bytes_to_pixels(reference), width, height);
126 let img2 = Img::new(rgb_bytes_to_pixels(test), width, height);
127 let params = ButteraugliParams::default().with_intensity_target(intensity_target);
128 let result = butteraugli_compare(img1.as_ref(), img2.as_ref(), ¶ms).map_err(|e| {
129 Error::MetricCalculation {
130 metric: "Butteraugli".to_string(),
131 reason: e.to_string(),
132 }
133 })?;
134
135 Ok(result.score)
136}
137
138pub fn calculate_butteraugli_icc(
151 reference: &[u8],
152 reference_profile: &ColorProfile,
153 test: &[u8],
154 test_profile: &ColorProfile,
155 width: usize,
156 height: usize,
157) -> Result<f64> {
158 let (ref_srgb, test_srgb) =
159 super::icc::prepare_for_comparison(reference, reference_profile, test, test_profile)?;
160
161 calculate_butteraugli(&ref_srgb, &test_srgb, width, height)
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_identical_images() {
170 let data: Vec<u8> = (0..100 * 100 * 3).map(|i| (i % 256) as u8).collect();
171 let score = calculate_butteraugli(&data, &data, 100, 100).unwrap();
172 assert!(
174 score < 0.01,
175 "Identical images should have score ~0, got {score}"
176 );
177 }
178
179 #[test]
180 fn test_different_images() {
181 let ref_data: Vec<u8> = vec![100u8; 100 * 100 * 3];
182 let test_data: Vec<u8> = vec![200u8; 100 * 100 * 3];
183 let score = calculate_butteraugli(&ref_data, &test_data, 100, 100).unwrap();
184 assert!(
186 score > 1.0,
187 "Very different images should have high score, got {score}"
188 );
189 }
190
191 #[test]
192 fn test_size_mismatch() {
193 let small: Vec<u8> = vec![128u8; 50 * 50 * 3];
194 let large: Vec<u8> = vec![128u8; 100 * 100 * 3];
195 let result = calculate_butteraugli(&small, &large, 100, 100);
196 assert!(result.is_err());
197 }
198
199 #[test]
200 fn test_custom_intensity() {
201 let data: Vec<u8> = (0..100 * 100 * 3).map(|i| (i % 256) as u8).collect();
202 let score = calculate_butteraugli_with_intensity(&data, &data, 100, 100, 250.0).unwrap();
203 assert!(
204 score < 0.01,
205 "Identical images should have score ~0 at any intensity"
206 );
207 }
208}