use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::process::Command;
use tx2_iff::{Encoder, EncoderConfig, Decoder, DecoderConfig};
fn calculate_psnr(original: &image::RgbImage, decoded: &image::RgbImage) -> f64 {
let width = original.width();
let height = original.height();
if width != decoded.width() || height != decoded.height() {
return 0.0;
}
let mut mse = 0.0;
let pixel_count = (width * height * 3) as f64;
for y in 0..height {
for x in 0..width {
let orig = original.get_pixel(x, y);
let dec = decoded.get_pixel(x, y);
for c in 0..3 {
let diff = orig[c] as f64 - dec[c] as f64;
mse += diff * diff;
}
}
}
mse /= pixel_count;
if mse == 0.0 {
return f64::INFINITY; }
20.0 * (255.0_f64).log10() - 10.0 * mse.log10()
}
fn calculate_ssim_simple(original: &image::RgbImage, decoded: &image::RgbImage) -> f64 {
let width = original.width();
let height = original.height();
if width != decoded.width() || height != decoded.height() {
return 0.0;
}
let mut mean_orig = 0.0;
let mut mean_dec = 0.0;
let pixel_count = (width * height) as f64;
for y in 0..height {
for x in 0..width {
let orig = original.get_pixel(x, y);
let dec = decoded.get_pixel(x, y);
let lum_orig = 0.299 * orig[0] as f64 + 0.587 * orig[1] as f64 + 0.114 * orig[2] as f64;
let lum_dec = 0.299 * dec[0] as f64 + 0.587 * dec[1] as f64 + 0.114 * dec[2] as f64;
mean_orig += lum_orig;
mean_dec += lum_dec;
}
}
mean_orig /= pixel_count;
mean_dec /= pixel_count;
let mut var_orig = 0.0;
let mut var_dec = 0.0;
let mut covar = 0.0;
for y in 0..height {
for x in 0..width {
let orig = original.get_pixel(x, y);
let dec = decoded.get_pixel(x, y);
let lum_orig = 0.299 * orig[0] as f64 + 0.587 * orig[1] as f64 + 0.114 * orig[2] as f64;
let lum_dec = 0.299 * dec[0] as f64 + 0.587 * dec[1] as f64 + 0.114 * dec[2] as f64;
let diff_orig = lum_orig - mean_orig;
let diff_dec = lum_dec - mean_dec;
var_orig += diff_orig * diff_orig;
var_dec += diff_dec * diff_dec;
covar += diff_orig * diff_dec;
}
}
var_orig /= pixel_count;
var_dec /= pixel_count;
covar /= pixel_count;
let c1 = (0.01 * 255.0).powi(2);
let c2 = (0.03 * 255.0).powi(2);
let numerator = (2.0 * mean_orig * mean_dec + c1) * (2.0 * covar + c2);
let denominator = (mean_orig * mean_orig + mean_dec * mean_dec + c1) * (var_orig + var_dec + c2);
numerator / denominator
}
fn get_file_size(path: &str) -> Result<usize, Box<dyn std::error::Error>> {
let metadata = std::fs::metadata(path)?;
Ok(metadata.len() as usize)
}
fn benchmark_webp(input_path: &str, quality: u8) -> Result<(usize, f64, f64), Box<dyn std::error::Error>> {
let temp_webp = "/tmp/test.webp";
let temp_decoded = "/tmp/test_webp_decoded.png";
let status = Command::new("cwebp")
.args(&["-q", &quality.to_string(), input_path, "-o", temp_webp])
.output();
if status.is_err() {
return Err("cwebp not found. Install with: sudo apt install webp".into());
}
let webp_size = get_file_size(temp_webp)?;
Command::new("dwebp")
.args(&[temp_webp, "-o", temp_decoded])
.output()?;
let original = image::open(input_path)?.to_rgb8();
let decoded = image::open(temp_decoded)?.to_rgb8();
let psnr = calculate_psnr(&original, &decoded);
let ssim = calculate_ssim_simple(&original, &decoded);
std::fs::remove_file(temp_webp).ok();
std::fs::remove_file(temp_decoded).ok();
Ok((webp_size, psnr, ssim))
}
fn benchmark_iff(input_path: &str, config: EncoderConfig) -> Result<(usize, f64, f64), Box<dyn std::error::Error>> {
let temp_iff = "/tmp/test.iff";
let temp_decoded = "/tmp/test_iff_decoded.png";
println!(" Loading image...");
let image = image::open(input_path)?;
println!(" Encoding to IFF...");
let encoder = Encoder::new(config);
let iff_image = encoder.encode(&image)?;
println!(" Serializing...");
let iff_bytes = iff_image.to_bytes()?;
let iff_size = iff_bytes.len();
let mut file = File::create(temp_iff)?;
file.write_all(&iff_bytes)?;
drop(file);
println!(" Decoding IFF...");
let decoder_config = DecoderConfig::default();
let decoder = Decoder::new(decoder_config);
let decoded_image = decoder.decode(&iff_image)?;
decoded_image.save(temp_decoded)?;
println!(" Calculating quality metrics...");
let original = image.to_rgb8();
let decoded = image::open(temp_decoded)?.to_rgb8();
let psnr = calculate_psnr(&original, &decoded);
let ssim = calculate_ssim_simple(&original, &decoded);
std::fs::remove_file(temp_iff).ok();
std::fs::remove_file(temp_decoded).ok();
Ok((iff_size, psnr, ssim))
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: {} <test_image.png>", args[0]);
eprintln!();
eprintln!("This benchmark compares PPF-IFF against WebP and PNG.");
eprintln!("Requires: cwebp, dwebp (from webp package)");
std::process::exit(1);
}
let input_path = &args[1];
println!("=== PPF-IFF Compression Benchmark ===");
println!();
println!("Test image: {}", input_path);
let image = image::open(input_path)?;
let width = image.width();
let height = image.height();
let raw_size = (width * height * 3) as usize;
println!("Image size: {}x{}", width, height);
println!("Raw RGB size: {} bytes ({:.2} KB)", raw_size, raw_size as f64 / 1024.0);
println!();
println!("--- PNG (lossless baseline) ---");
let png_size = get_file_size(input_path)?;
println!("Size: {} bytes ({:.2} KB)", png_size, png_size as f64 / 1024.0);
println!("Compression: {:.2}x vs raw", raw_size as f64 / png_size as f64);
println!();
println!("--- WebP (quality 80) ---");
match benchmark_webp(input_path, 80) {
Ok((webp_size, psnr, ssim)) => {
println!("Size: {} bytes ({:.2} KB)", webp_size, webp_size as f64 / 1024.0);
println!("Compression: {:.2}x vs raw, {:.2}x vs PNG",
raw_size as f64 / webp_size as f64,
png_size as f64 / webp_size as f64);
println!("PSNR: {:.2} dB", psnr);
println!("SSIM: {:.4}", ssim);
}
Err(e) => {
println!("WebP benchmark failed: {}", e);
println!("Install with: sudo apt install webp");
}
}
println!();
let configs = vec![
("High Quality", EncoderConfig {
wavelet_levels: 5,
base_quantization: 5,
texture_min_size: 32,
texture_iterations: 100,
residual_threshold: 3.0,
enable_layer2: true,
enable_layer3: true,
}),
("Balanced", EncoderConfig {
wavelet_levels: 5,
base_quantization: 10,
texture_min_size: 32,
texture_iterations: 100,
residual_threshold: 5.0,
enable_layer2: true,
enable_layer3: true,
}),
("High Compression", EncoderConfig {
wavelet_levels: 6,
base_quantization: 20,
texture_min_size: 64,
texture_iterations: 50,
residual_threshold: 10.0,
enable_layer2: true,
enable_layer3: true,
}),
];
for (name, config) in configs {
println!("--- PPF-IFF ({}) ---", name);
match benchmark_iff(input_path, config) {
Ok((iff_size, psnr, ssim)) => {
println!("Size: {} bytes ({:.2} KB)", iff_size, iff_size as f64 / 1024.0);
println!("Compression: {:.2}x vs raw, {:.2}x vs PNG",
raw_size as f64 / iff_size as f64,
png_size as f64 / iff_size as f64);
println!("PSNR: {:.2} dB", psnr);
println!("SSIM: {:.4}", ssim);
}
Err(e) => {
println!("IFF benchmark failed: {}", e);
}
}
println!();
}
println!("=== Benchmark Complete ===");
Ok(())
}