use crate::error::Result;
use crate::format::PixelFormat;
use crate::image::Image;
use crate::ssim::{SsimMode, SsimOptions, ssim};
#[derive(Debug, Clone, Copy, Default)]
pub struct DssimOptions {
pub mode: SsimMode,
}
pub fn dssim<F: PixelFormat>(
reference: &Image<F>,
distorted: &Image<F>,
opts: DssimOptions,
) -> Result<f64> {
let s = ssim(reference, distorted, SsimOptions { mode: opts.mode })?;
Ok((1.0 - s) / 2.0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error;
fn solid_srgb8(value: u8) -> Image<crate::format::Srgb8> {
Image::srgb8(11, 11, vec![value; 11 * 11 * 3]).unwrap()
}
#[test]
fn identical_images_are_zero() {
let img = solid_srgb8(100);
let score = dssim(&img, &img, DssimOptions::default()).unwrap();
assert_eq!(score, 0.0, "expected exactly 0.0, got {score}");
}
#[test]
fn equals_one_minus_ssim_over_two() {
let reference = solid_srgb8(128);
let mut data = vec![128u8; 11 * 11 * 3];
for (i, sample) in data.iter_mut().enumerate() {
if i % 5 == 0 {
*sample = 170;
}
}
let distorted = Image::srgb8(11, 11, data).unwrap();
let opts = DssimOptions::default();
let d = dssim(&reference, &distorted, opts).unwrap();
let s = ssim(&reference, &distorted, SsimOptions { mode: opts.mode }).unwrap();
assert_eq!(d.to_bits(), ((1.0 - s) / 2.0).to_bits());
}
#[test]
fn solid_offset_matches_closed_form() {
let reference = solid_srgb8(100);
let distorted = solid_srgb8(120);
let score = dssim(&reference, &distorted, DssimOptions::default()).unwrap();
let c1 = (0.01 * 255.0_f64).powi(2);
let ssim_expected = (2.0 * 100.0 * 120.0 + c1) / (100.0 * 100.0 + 120.0 * 120.0 + c1);
let expected = (1.0 - ssim_expected) / 2.0;
assert!(
(score - expected).abs() < 1e-9,
"got {score}, want {expected}"
);
}
#[test]
fn distortion_raises_the_score() {
let reference = solid_srgb8(128);
let mut data = vec![128u8; 11 * 11 * 3];
for (i, sample) in data.iter_mut().enumerate() {
if i % 7 == 0 {
*sample = 160;
}
}
let distorted = Image::srgb8(11, 11, data).unwrap();
let score = dssim(&reference, &distorted, DssimOptions::default()).unwrap();
assert!(score > 0.0, "distorted image scored {score}");
}
#[test]
fn image_below_window_is_rejected() {
let img = Image::srgb8(10, 10, vec![0; 10 * 10 * 3]).unwrap();
let err = dssim(&img, &img, DssimOptions::default()).unwrap_err();
assert!(matches!(err, Error::ImageTooSmall(10, 10, 11)));
}
#[test]
fn dimension_mismatch_is_an_error() {
let a = Image::srgb8(11, 11, vec![0; 11 * 11 * 3]).unwrap();
let b = Image::srgb8(12, 11, vec![0; 12 * 11 * 3]).unwrap();
let err = dssim(&a, &b, DssimOptions::default()).unwrap_err();
assert!(matches!(err, Error::DimensionMismatch { .. }));
}
}