1use dssim_core::Dssim;
10use imgref::ImgVec;
11use rgb::RGBA;
12
13use super::icc::ColorProfile;
14use crate::error::{Error, Result};
15use crate::viewing::ViewingCondition;
16
17pub fn calculate_dssim(
41 reference: &ImgVec<RGBA<f32>>,
42 test: &ImgVec<RGBA<f32>>,
43 _viewing: &ViewingCondition,
44) -> Result<f64> {
45 if reference.width() != test.width() || reference.height() != test.height() {
46 return Err(Error::DimensionMismatch {
47 expected: (reference.width(), reference.height()),
48 actual: (test.width(), test.height()),
49 });
50 }
51
52 let dssim = Dssim::new();
53
54 let ref_image = dssim
55 .create_image(reference)
56 .ok_or_else(|| Error::MetricCalculation {
57 metric: "DSSIM".to_string(),
58 reason: "Failed to create reference image".to_string(),
59 })?;
60
61 let test_image = dssim
62 .create_image(test)
63 .ok_or_else(|| Error::MetricCalculation {
64 metric: "DSSIM".to_string(),
65 reason: "Failed to create test image".to_string(),
66 })?;
67
68 let (dssim_val, _ssim_maps) = dssim.compare(&ref_image, test_image);
69
70 Ok(f64::from(dssim_val))
71}
72
73#[inline]
78fn srgb_to_linear(srgb: u8) -> f32 {
79 let s = f32::from(srgb) / 255.0;
80 if s <= 0.04045 {
81 s / 12.92
82 } else {
83 ((s + 0.055) / 1.055).powf(2.4)
84 }
85}
86
87#[must_use]
102pub fn rgb8_to_dssim_image(data: &[u8], width: usize, height: usize) -> ImgVec<RGBA<f32>> {
103 let pixels: Vec<RGBA<f32>> = data
104 .chunks_exact(3)
105 .map(|rgb| RGBA {
106 r: srgb_to_linear(rgb[0]),
107 g: srgb_to_linear(rgb[1]),
108 b: srgb_to_linear(rgb[2]),
109 a: 1.0,
110 })
111 .collect();
112
113 ImgVec::new(pixels, width, height)
114}
115
116#[must_use]
131pub fn rgba8_to_dssim_image(data: &[u8], width: usize, height: usize) -> ImgVec<RGBA<f32>> {
132 let pixels: Vec<RGBA<f32>> = data
133 .chunks_exact(4)
134 .map(|rgba| RGBA {
135 r: srgb_to_linear(rgba[0]),
136 g: srgb_to_linear(rgba[1]),
137 b: srgb_to_linear(rgba[2]),
138 a: f32::from(rgba[3]) / 255.0, })
140 .collect();
141
142 ImgVec::new(pixels, width, height)
143}
144
145pub fn calculate_dssim_icc(
159 reference: &[u8],
160 reference_profile: &ColorProfile,
161 test: &[u8],
162 test_profile: &ColorProfile,
163 width: usize,
164 height: usize,
165 viewing: &ViewingCondition,
166) -> Result<f64> {
167 let (ref_srgb, test_srgb) =
168 super::icc::prepare_for_comparison(reference, reference_profile, test, test_profile)?;
169
170 let ref_img = rgb8_to_dssim_image(&ref_srgb, width, height);
171 let test_img = rgb8_to_dssim_image(&test_srgb, width, height);
172
173 calculate_dssim(&ref_img, &test_img, viewing)
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_identical_images() {
182 let pixels: Vec<RGBA<f32>> = (0..100 * 100)
183 .map(|_| RGBA {
184 r: 0.5,
185 g: 0.5,
186 b: 0.5,
187 a: 1.0,
188 })
189 .collect();
190 let img = ImgVec::new(pixels, 100, 100);
191
192 let dssim = calculate_dssim(&img, &img, &ViewingCondition::desktop()).unwrap();
193 assert!(
194 dssim < 0.0001,
195 "Identical images should have near-zero DSSIM"
196 );
197 }
198
199 #[test]
200 fn test_different_images() {
201 let ref_pixels: Vec<RGBA<f32>> = (0..100 * 100)
202 .map(|_| RGBA {
203 r: 0.3,
204 g: 0.3,
205 b: 0.3,
206 a: 1.0,
207 })
208 .collect();
209 let test_pixels: Vec<RGBA<f32>> = (0..100 * 100)
210 .map(|_| RGBA {
211 r: 0.7,
212 g: 0.7,
213 b: 0.7,
214 a: 1.0,
215 })
216 .collect();
217
218 let ref_img = ImgVec::new(ref_pixels, 100, 100);
219 let test_img = ImgVec::new(test_pixels, 100, 100);
220
221 let dssim = calculate_dssim(&ref_img, &test_img, &ViewingCondition::desktop()).unwrap();
222 assert!(dssim > 0.0, "Different images should have non-zero DSSIM");
223 }
224
225 #[test]
226 fn test_dimension_mismatch() {
227 let small: Vec<RGBA<f32>> = (0..50 * 50)
228 .map(|_| RGBA {
229 r: 0.5,
230 g: 0.5,
231 b: 0.5,
232 a: 1.0,
233 })
234 .collect();
235 let large: Vec<RGBA<f32>> = (0..100 * 100)
236 .map(|_| RGBA {
237 r: 0.5,
238 g: 0.5,
239 b: 0.5,
240 a: 1.0,
241 })
242 .collect();
243
244 let small_img = ImgVec::new(small, 50, 50);
245 let large_img = ImgVec::new(large, 100, 100);
246
247 let result = calculate_dssim(&small_img, &large_img, &ViewingCondition::desktop());
248 assert!(matches!(result, Err(Error::DimensionMismatch { .. })));
249 }
250
251 #[test]
252 fn test_rgb8_conversion() {
253 let rgb_data = vec![255u8, 0, 0, 0, 255, 0]; let img = rgb8_to_dssim_image(&rgb_data, 2, 1);
255
256 assert_eq!(img.width(), 2);
257 assert_eq!(img.height(), 1);
258 let pixels: Vec<_> = img.pixels().collect();
259 assert!((pixels[0].r - 1.0).abs() < 0.001);
260 assert!((pixels[1].g - 1.0).abs() < 0.001);
261 }
262
263 #[test]
264 fn test_rgba8_conversion() {
265 let rgba_data = vec![255u8, 0, 0, 128, 0, 255, 0, 255]; let img = rgba8_to_dssim_image(&rgba_data, 2, 1);
267
268 assert_eq!(img.width(), 2);
269 assert_eq!(img.height(), 1);
270 let pixels: Vec<_> = img.pixels().collect();
271 assert!((pixels[0].a - 0.502).abs() < 0.01);
272 assert!((pixels[1].a - 1.0).abs() < 0.001);
273 }
274}