#![cfg(feature = "iw-ssim")]
mod common;
use std::path::{Path, PathBuf};
use common::{Fixture, Gray8, Image, gray8};
use iqa::{IwssimOptions, SsimMode, iwssim};
const FIXTURE_DIR: &str = "tests/fixtures/iw_ssim";
const CASES: &[(&str, &str, &str)] = &[
("gradient_lo", "gradient_ref.pgm", "gradient_lo.pgm"),
("gradient_hi", "gradient_ref.pgm", "gradient_hi.pgm"),
("texture_lo", "texture_ref.pgm", "texture_lo.pgm"),
("texture_hi", "texture_ref.pgm", "texture_hi.pgm"),
];
const GOLDENS: &[(&str, f64)] = &[
("gradient_lo", 0.970210),
("gradient_hi", 0.754273),
("texture_lo", 0.997193),
("texture_hi", 0.981140),
];
#[test]
fn matches_reference_iwssim() {
for &(label, ref_pgm, dist_pgm) in CASES {
let reference = load(&fixture_path(ref_pgm));
let distorted = load(&fixture_path(dist_pgm));
let ours = iwssim(
&reference,
&distorted,
IwssimOptions {
mode: SsimMode::Luma709,
},
)
.unwrap_or_else(|e| panic!("{label}: iwssim errored: {e}"));
let golden = golden(label);
let tol = 1e-3 + 0.01 * golden;
let delta = (ours - golden).abs();
eprintln!("{label}: ours={ours:.6} reference={golden:.6} (Δ={delta:.6})");
assert!(
delta <= tol,
"{label}: ours={ours:.6} reference={golden:.6} (|Δ|={delta:.6} > tol={tol:.6})",
);
}
}
fn load(path: &Path) -> Image<Gray8> {
let decoded = image::open(path)
.unwrap_or_else(|e| {
panic!(
"open {}: {e}\n\
Run `cargo test --features iw-ssim --test iw_ssim_reference \
write_iw_ssim_fixtures -- --ignored` to (re)generate fixtures.",
path.display(),
)
})
.to_luma8();
let (width, height) = decoded.dimensions();
Image::gray8(width, height, decoded.into_raw()).expect("fixture dimensions match its buffer")
}
fn fixture_path(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join(FIXTURE_DIR)
.join(name)
}
fn golden(label: &str) -> f64 {
GOLDENS
.iter()
.find(|(l, _)| *l == label)
.map(|&(_, v)| v)
.unwrap_or_else(|| panic!("no golden recorded for case `{label}`"))
}
fn fixtures() -> Vec<(&'static str, Image<Gray8>)> {
let gradient = Gray8::base(192, 192);
let texture = gray8(192, 192, texture_sample);
vec![
("gradient_ref.pgm", Gray8::base(192, 192)),
("gradient_lo.pgm", Gray8::distort(&gradient, 10.0)),
("gradient_hi.pgm", Gray8::distort(&gradient, 40.0)),
("texture_ref.pgm", gray8(192, 192, texture_sample)),
("texture_lo.pgm", Gray8::distort(&texture, 10.0)),
("texture_hi.pgm", Gray8::distort(&texture, 30.0)),
]
}
fn texture_sample(x: u32, y: u32) -> u8 {
let (fx, fy) = (x as f64, y as f64);
let v = 128.0 + 70.0 * (fx * 0.42).sin() * (fy * 0.31).cos() + 25.0 * ((x ^ y) % 13) as f64;
v.round().clamp(8.0, 247.0) as u8
}
#[test]
#[ignore = "regenerates committed PGM fixtures; rerun scripts/gen-iwssim-goldens.sh after"]
fn write_iw_ssim_fixtures() {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join(FIXTURE_DIR);
std::fs::create_dir_all(&dir).expect("create fixture dir");
for (name, img) in fixtures() {
let mut bytes = format!("P5\n{} {}\n255\n", img.width(), img.height()).into_bytes();
bytes.extend_from_slice(img.samples());
std::fs::write(dir.join(name), bytes).unwrap_or_else(|e| panic!("write {name}: {e}"));
}
eprintln!("wrote {} fixtures to {}", fixtures().len(), dir.display());
}