use enough::Unstoppable;
use fast_ssim2::compute_ssimulacra2;
use imgref::ImgVec;
use jpegli::encoder::{EncoderConfig, PixelLayout, XybSubsampling};
use std::process::Command;
fn load_test_image(path: &str) -> (Vec<u8>, u32, u32) {
let file = std::fs::File::open(path).expect("open");
let decoder = png::Decoder::new(file);
let mut reader = decoder.read_info().expect("info");
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).expect("decode");
let pixels = buf[..info.buffer_size()].to_vec();
(pixels, info.width, info.height)
}
fn encode_rust_xyb(pixels: &[u8], width: u32, height: u32, quality: f32) -> Vec<u8> {
let config = EncoderConfig::xyb(quality, XybSubsampling::Full).progressive(true); let mut encoder = config
.encode_from_bytes(width, height, PixelLayout::Rgb8Srgb)
.expect("encoder creation failed");
encoder
.push_packed(pixels, Unstoppable)
.expect("push failed");
encoder.finish().expect("finish failed")
}
fn encode_cpp_xyb(src_path: &str, quality: u32) -> Vec<u8> {
let out_path = format!("/tmp/cpp_xyb_ssim2_{}.jpg", quality);
Command::new("cjpegli")
.args([src_path, &out_path, "-q", &quality.to_string(), "--xyb"])
.output()
.expect("cjpegli failed");
std::fs::read(&out_path).expect("read cpp output")
}
fn decode_jpeg_to_rgb(jpeg: &[u8], label: &str) -> (Vec<u8>, u32, u32) {
let tmp_jpg = format!("/tmp/xyb_decode_test_{}.jpg", label);
let tmp_png = format!("/tmp/xyb_decode_test_{}.png", label);
std::fs::write(&tmp_jpg, jpeg).expect("write temp jpg");
Command::new("djpegli")
.args([&tmp_jpg, &tmp_png])
.output()
.expect("djpegli decode failed");
let file = std::fs::File::open(&tmp_png).expect("open decoded png");
let decoder = png::Decoder::new(file);
let mut reader = decoder.read_info().expect("info");
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).expect("decode");
let pixels = buf[..info.buffer_size()].to_vec();
(pixels, info.width, info.height)
}
fn ssim2_from_rgb(img1: &[u8], img2: &[u8], width: usize, height: usize) -> f64 {
let v1 = ImgVec::new(img1.to_vec(), width * 3, height);
let v2 = ImgVec::new(img2.to_vec(), width * 3, height);
compute_ssimulacra2(v1.as_ref(), v2.as_ref()).unwrap_or(0.0)
}
fn compute_mean_diff(img1: &[u8], img2: &[u8]) -> (f64, f64, f64) {
assert_eq!(img1.len(), img2.len());
let mut sum_diff = 0.0f64;
let mut sum_abs_diff = 0.0f64;
let mut max_diff = 0.0f64;
for (a, b) in img1.iter().zip(img2.iter()) {
let diff = *a as f64 - *b as f64;
sum_diff += diff;
sum_abs_diff += diff.abs();
max_diff = max_diff.max(diff.abs());
}
let n = img1.len() as f64;
(sum_diff / n, sum_abs_diff / n, max_diff)
}
fn compute_channel_diff(img1: &[u8], img2: &[u8]) -> [(f64, f64, f64); 3] {
assert_eq!(img1.len(), img2.len());
let mut result = [(0.0f64, 0.0f64, 0.0f64); 3];
let npixels = img1.len() / 3;
for c in 0..3 {
let mut sum_diff = 0.0f64;
let mut sum_abs_diff = 0.0f64;
let mut max_diff = 0.0f64;
for i in 0..npixels {
let a = img1[i * 3 + c] as f64;
let b = img2[i * 3 + c] as f64;
let diff = a - b;
sum_diff += diff;
sum_abs_diff += diff.abs();
max_diff = max_diff.max(diff.abs());
}
result[c] = (
sum_diff / npixels as f64,
sum_abs_diff / npixels as f64,
max_diff,
);
}
result
}
fn main() {
let src_path = "/home/lilith/work/codec-eval/codec-corpus/kodak/1.png";
let (pixels, width, height) = load_test_image(src_path);
println!("XYB Rust vs C++ Direct Comparison (SSIMULACRA2)");
println!("Image: kodak/1.png ({}x{})", width, height);
println!("=========================================================");
println!();
println!(
"{:7} {:>10} {:>10} {:>8} {:>10} {:>10} {:>10}",
"Quality", "Rust bytes", "C++ bytes", "Size Δ%", "Mean Diff", "Abs Diff", "Max Diff"
);
println!(
"------------------------------------------------------------------------------------"
);
for q in [90] {
let rust_jpeg = encode_rust_xyb(&pixels, width, height, q as f32);
let cpp_jpeg = encode_cpp_xyb(src_path, q);
let (rust_rgb, rw, rh) = decode_jpeg_to_rgb(&rust_jpeg, &format!("rust_{}", q));
let (cpp_rgb, cw, ch) = decode_jpeg_to_rgb(&cpp_jpeg, &format!("cpp_{}", q));
assert_eq!((rw, rh), (cw, ch), "dimension mismatch");
let (mean_diff, mean_abs_diff, max_diff) = compute_mean_diff(&rust_rgb, &cpp_rgb);
let channel_diff = compute_channel_diff(&rust_rgb, &cpp_rgb);
let size_diff = (rust_jpeg.len() as f64 / cpp_jpeg.len() as f64 - 1.0) * 100.0;
println!(
"{:7} {:>10} {:>10} {:>+7.1}% {:>10.2} {:>10.2} {:>10.1}",
q,
rust_jpeg.len(),
cpp_jpeg.len(),
size_diff,
mean_diff,
mean_abs_diff,
max_diff
);
println!();
println!("Per-channel differences (Rust - C++):");
for (i, name) in ["R (X)", "G (Y)", "B (B)"].iter().enumerate() {
let (ch_mean, ch_abs, ch_max) = channel_diff[i];
println!(
" {}: mean={:+.2}, abs={:.2}, max={:.1}",
name, ch_mean, ch_abs, ch_max
);
}
}
println!();
println!("SSIM2 interpretation (Rust output vs C++ output):");
println!(" 90+ = Nearly identical outputs");
println!(" 80-90 = Minor differences");
println!(" 70-80 = Noticeable differences");
println!(" <70 = Significant differences");
println!();
println!("Quality vs Original (for reference):");
println!(
"{:7} {:>16} {:>16}",
"Quality", "Rust vs Orig", "C++ vs Orig"
);
println!("---------------------------------------------------------");
for q in [70, 80, 90] {
let rust_jpeg = encode_rust_xyb(&pixels, width, height, q as f32);
let cpp_jpeg = encode_cpp_xyb(src_path, q);
let (rust_rgb, _, _) = decode_jpeg_to_rgb(&rust_jpeg, &format!("rust_orig_{}", q));
let (cpp_rgb, _, _) = decode_jpeg_to_rgb(&cpp_jpeg, &format!("cpp_orig_{}", q));
let ssim2_rust_vs_orig =
ssim2_from_rgb(&pixels, &rust_rgb, width as usize, height as usize);
let ssim2_cpp_vs_orig = ssim2_from_rgb(&pixels, &cpp_rgb, width as usize, height as usize);
println!(
"{:7} {:>16.2} {:>16.2}",
q, ssim2_rust_vs_orig, ssim2_cpp_vs_orig
);
}
}