use enough::Unstoppable;
use std::fs;
use std::path::{Path, PathBuf};
use zenavif::{DecoderConfig, decode_with};
use zenpixels::{PixelBuffer, PixelDescriptor};
fn generate_reference(
avif_path: &Path,
output_dir: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let data = fs::read(avif_path)?;
let config = DecoderConfig::new().threads(1);
let image = decode_with(&data, &config, &Unstoppable)?;
let output_path = output_dir.join(format!(
"{}.png",
avif_path.file_stem().unwrap().to_str().unwrap()
));
let desc = image.descriptor();
if desc.layout_compatible(PixelDescriptor::RGB8) {
let img = image.try_as_imgref::<rgb::Rgb<u8>>().unwrap();
let width = img.width() as u32;
let height = img.height() as u32;
let mut buffer = image::RgbImage::new(width, height);
for y in 0..height {
for x in 0..width {
let pixel = img[(x as usize, y as usize)];
buffer.put_pixel(x, y, image::Rgb([pixel.r, pixel.g, pixel.b]));
}
}
buffer.save(&output_path)?;
} else if desc.layout_compatible(PixelDescriptor::RGBA8) {
let img = image.try_as_imgref::<rgb::Rgba<u8>>().unwrap();
let width = img.width() as u32;
let height = img.height() as u32;
let mut buffer = image::RgbaImage::new(width, height);
for y in 0..height {
for x in 0..width {
let pixel = img[(x as usize, y as usize)];
buffer.put_pixel(x, y, image::Rgba([pixel.r, pixel.g, pixel.b, pixel.a]));
}
}
buffer.save(&output_path)?;
} else if desc.layout_compatible(PixelDescriptor::RGB16) {
let img = image.try_as_imgref::<rgb::Rgb<u16>>().unwrap();
let width = img.width() as u32;
let height = img.height() as u32;
let mut buffer = image::RgbImage::new(width, height);
for y in 0..height {
for x in 0..width {
let pixel = img[(x as usize, y as usize)];
buffer.put_pixel(
x,
y,
image::Rgb([
(pixel.r >> 8) as u8,
(pixel.g >> 8) as u8,
(pixel.b >> 8) as u8,
]),
);
}
}
buffer.save(&output_path)?;
} else {
eprintln!("Unsupported format for reference generation");
return Ok(());
}
println!("Generated reference: {:?}", output_path);
Ok(())
}
fn compare_against_reference(
image: &PixelBuffer,
reference_path: &Path,
max_diff: u8,
) -> Result<bool, Box<dyn std::error::Error>> {
let reference = image::open(reference_path)?;
let desc = image.descriptor();
if desc.layout_compatible(PixelDescriptor::RGB8) {
let img = image.try_as_imgref::<rgb::Rgb<u8>>().unwrap();
let ref_rgb = reference.to_rgb8();
if img.width() != ref_rgb.width() as usize || img.height() != ref_rgb.height() as usize {
eprintln!(
"Dimension mismatch: {}x{} vs {}x{}",
img.width(),
img.height(),
ref_rgb.width(),
ref_rgb.height()
);
return Ok(false);
}
let mut max_error = 0u8;
let mut error_count = 0;
for y in 0..img.height() {
for x in 0..img.width() {
let our_pixel = img[(x, y)];
let ref_pixel = ref_rgb.get_pixel(x as u32, y as u32);
let diff_r = (our_pixel.r as i16 - ref_pixel[0] as i16).unsigned_abs() as u8;
let diff_g = (our_pixel.g as i16 - ref_pixel[1] as i16).unsigned_abs() as u8;
let diff_b = (our_pixel.b as i16 - ref_pixel[2] as i16).unsigned_abs() as u8;
let max_channel_diff = diff_r.max(diff_g).max(diff_b);
if max_channel_diff > max_diff {
max_error = max_error.max(max_channel_diff);
error_count += 1;
}
}
}
if error_count > 0 {
let total_pixels = img.width() * img.height();
let error_percent = (error_count as f64 / total_pixels as f64) * 100.0;
eprintln!(
"Pixel errors: {} ({:.2}%), max error: {}",
error_count, error_percent, max_error
);
return Ok(false);
}
Ok(true)
} else {
eprintln!("Format comparison not yet implemented");
Ok(true) }
}
#[test]
#[ignore]
fn generate_references() {
let test_files = vec![
"tests/vectors/libavif/sofa_grid1x5_420.avif",
"tests/vectors/libavif/colors-profile2-420-8-094.avif",
"tests/vectors/libavif/colors_hdr_srgb.avif",
];
let output_dir = Path::new("tests/references");
fs::create_dir_all(output_dir).unwrap();
for file in test_files {
let path = Path::new(file);
if path.exists() {
match generate_reference(path, output_dir) {
Ok(()) => println!("✓ {}", path.display()),
Err(e) => eprintln!("✗ {}: {}", path.display(), e),
}
}
}
}
#[test]
#[ignore]
fn verify_pixel_accuracy() {
let test_cases = vec![
(
"tests/vectors/libavif/sofa_grid1x5_420.avif",
"tests/references/sofa_grid1x5_420.png",
1,
),
(
"tests/vectors/libavif/colors-profile2-420-8-094.avif",
"tests/references/colors-profile2-420-8-094.png",
1,
),
];
let config = DecoderConfig::new().threads(1);
let mut passed = 0;
let mut failed = 0;
for (avif_file, ref_file, max_diff) in test_cases {
let avif_path = Path::new(avif_file);
let ref_path = Path::new(ref_file);
if !avif_path.exists() || !ref_path.exists() {
eprintln!("⊘ {} (reference not found)", avif_file);
continue;
}
eprint!(" {:50} ", avif_path.file_name().unwrap().to_str().unwrap());
match fs::read(avif_path) {
Ok(data) => match decode_with(&data, &config, &Unstoppable) {
Ok(image) => match compare_against_reference(&image, ref_path, max_diff) {
Ok(true) => {
eprintln!("✓ Pixels match");
passed += 1;
}
Ok(false) => {
eprintln!("✗ Pixel mismatch");
failed += 1;
}
Err(e) => {
eprintln!("✗ Comparison error: {}", e);
failed += 1;
}
},
Err(e) => {
eprintln!("✗ Decode error: {}", e);
failed += 1;
}
},
Err(e) => {
eprintln!("✗ Read error: {}", e);
failed += 1;
}
}
}
eprintln!("\n📊 Pixel Verification Results:");
eprintln!(" Passed: {}", passed);
eprintln!(" Failed: {}", failed);
assert_eq!(failed, 0, "Pixel verification failed for {} files", failed);
}
fn find_test_vectors() -> Vec<PathBuf> {
let mut vectors = Vec::new();
let vectors_dir = Path::new("tests/vectors");
if !vectors_dir.exists() {
return vectors;
}
if let Ok(entries) = fs::read_dir(vectors_dir) {
for entry in entries.flatten() {
if let Ok(file_type) = entry.file_type() {
if file_type.is_dir() {
if let Ok(sub_entries) = fs::read_dir(entry.path()) {
for sub_entry in sub_entries.flatten() {
let path = sub_entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("avif") {
vectors.push(path);
}
}
}
} else if entry.path().extension().and_then(|s| s.to_str()) == Some("avif") {
vectors.push(entry.path());
}
}
}
}
vectors.sort();
vectors
}
#[test]
#[ignore]
fn verify_against_libavif() {
let reference_dir = Path::new("tests/zenavif-references");
if !reference_dir.exists() {
eprintln!("\n⚠️ No libavif references found!");
eprintln!("Run: just generate-references");
eprintln!("Or: just docker-build && just generate-references");
panic!("libavif references required for pixel verification");
}
let vectors = find_test_vectors();
if vectors.is_empty() {
eprintln!("\n⚠️ No test vectors found!");
eprintln!("Run: just download-vectors");
panic!("test vectors required for pixel verification");
}
let config = DecoderConfig::new().threads(1);
let mut passed = 0;
let mut failed = 0;
let mut skipped = 0;
eprintln!("\n📊 Verifying pixel accuracy against libavif...\n");
for avif_path in vectors {
let basename = avif_path.file_stem().unwrap().to_str().unwrap();
let ref_path = reference_dir.join(format!("{}.png", basename));
if !ref_path.exists() {
eprintln!(" {:50} ⊘ No libavif reference", basename);
skipped += 1;
continue;
}
eprint!(" {:50} ", basename);
let data = match fs::read(&avif_path) {
Ok(data) => data,
Err(e) => {
eprintln!("✗ Read error: {}", e);
failed += 1;
continue;
}
};
match decode_with(&data, &config, &Unstoppable) {
Ok(image) => match compare_against_reference(&image, &ref_path, 1) {
Ok(true) => {
eprintln!("✓ Matches libavif");
passed += 1;
}
Ok(false) => {
eprintln!("✗ Pixel mismatch");
failed += 1;
}
Err(e) => {
eprintln!("✗ Compare error: {}", e);
failed += 1;
}
},
Err(e) => {
eprintln!("✗ Decode failed: {}", e);
failed += 1;
}
}
}
eprintln!("\n📊 Pixel Accuracy vs libavif:");
eprintln!(" Matches: {}", passed);
eprintln!(" Mismatch: {}", failed);
eprintln!(" Skipped: {}", skipped);
if failed > 0 {
eprintln!("\n❌ {} files have pixel mismatches with libavif", failed);
eprintln!("This indicates potential bugs in:");
eprintln!(" - YUV to RGB conversion");
eprintln!(" - Color space handling");
eprintln!(" - Alpha channel processing");
eprintln!(" - Chroma upsampling");
}
assert_eq!(failed, 0, "Pixel verification failed for {} files", failed);
}