use std::error::Error;
use std::ffi::OsString;
use std::path::Path;
use iqa::{Image, PsnrMode, PsnrOptions, Srgb8};
fn load(path: &Path) -> Result<Image<Srgb8>, Box<dyn Error>> {
let decoded = image::open(path)?.to_rgb8();
let (width, height) = decoded.dimensions();
Ok(Image::srgb8(width, height, decoded.into_raw())?)
}
fn main() -> Result<(), Box<dyn Error>> {
let mut args = std::env::args_os().skip(1);
let (Some(reference_path), Some(distorted_path)) = (args.next(), args.next()) else {
eprintln!("usage: compare <reference-image> <distorted-image>");
eprintln!(" both may be any PNG or JPEG file");
std::process::exit(2);
};
let reference = load(Path::new(&reference_path))?;
let distorted = load(Path::new(&distorted_path))?;
describe("reference", &reference_path, &reference);
describe("distorted", &distorted_path, &distorted);
println!();
report(
"PSNR (RGB-averaged)",
iqa::psnr(&reference, &distorted, PsnrOptions::default()),
"dB (higher is better)",
);
report(
"PSNR (Rec.709 luma)",
iqa::psnr(
&reference,
&distorted,
PsnrOptions {
mode: PsnrMode::Luma709,
},
),
"dB (higher is better)",
);
report(
"SSIM (RGB-averaged)",
iqa::ssim(&reference, &distorted, iqa::SsimOptions::default()),
" (1.0 = identical)",
);
report(
"DSSIM (RGB-averaged)",
iqa::dssim(&reference, &distorted, iqa::DssimOptions::default()),
" (0 = identical, lower is better)",
);
report(
"MS-SSIM (RGB-averaged)",
iqa::msssim(&reference, &distorted, iqa::MsssimOptions::default()),
" (1.0 = identical)",
);
report(
"IW-SSIM (RGB-averaged)",
iqa::iwssim(&reference, &distorted, iqa::IwssimOptions::default()),
" (1.0 = identical)",
);
report(
"PSNR-HVS-M (RGB-averaged)",
iqa::psnr_hvs_m(&reference, &distorted, iqa::PsnrHvsOptions::default()),
"dB (higher is better)",
);
report(
"CIEDE2000 (ΔE₀₀)",
iqa::ciede2000(&reference, &distorted, iqa::Ciede2000Options::default()),
" (0 = identical, lower is better)",
);
report(
"SSIMULACRA2",
iqa::ssimulacra2(&reference, &distorted),
" (100 = identical)",
);
report(
"Butteraugli",
iqa::butteraugli(&reference, &distorted, iqa::ButteraugliOptions::default()),
" (0 = identical, lower is better)",
);
Ok(())
}
fn describe(role: &str, path: &OsString, image: &Image<Srgb8>) {
println!(
"{role}: {} ({}x{})",
Path::new(path).display(),
image.width(),
image.height(),
);
}
fn report(name: &str, result: iqa::Result<f64>, unit: &str) {
match result {
Ok(score) => println!(" {name:<25} {score:>9.3} {unit}"),
Err(error) => println!(" {name:<25} error: {error}"),
}
}