tx2-iff 0.1.0

PPF-IFF (Involuted Fractal Format) - Image codec using Physics-Prime Factorization, 360-prime quantization, and symplectic warping
Documentation
//! Comprehensive benchmark comparing PPF-IFF to other formats
//!
//! Usage: cargo run --example benchmark --features "encoder decoder" -- test_image.png

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; // Perfect reconstruction
    }

    20.0 * (255.0_f64).log10() - 10.0 * mse.log10()
}

fn calculate_ssim_simple(original: &image::RgbImage, decoded: &image::RgbImage) -> f64 {
    // Simplified SSIM calculation (luminance only)
    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;

    // Calculate means
    for y in 0..height {
        for x in 0..width {
            let orig = original.get_pixel(x, y);
            let dec = decoded.get_pixel(x, y);

            // Luminance
            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;

    // Calculate variances and covariance
    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;

    // SSIM constants
    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";

    // Encode to WebP
    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)?;

    // Decode WebP
    Command::new("dwebp")
        .args(&[temp_webp, "-o", temp_decoded])
        .output()?;

    // Calculate quality metrics
    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);

    // Cleanup
    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);

    // Cleanup
    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!();

    // Benchmark PNG (lossless baseline)
    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!();

    // Benchmark WebP
    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!();

    // Benchmark PPF-IFF with different configurations
    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(())
}