diff --git a/Cargo.toml b/Cargo.toml index d60f121..d555adc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,9 @@ exclude = [ members = ["qoi-bench"] [dev-dependencies] -png = "^0.17.2" +anyhow = "1.0" +png = "0.17" +walkdir = "2.3" [lib] name = "qoi_fast" diff --git a/tests/test_ref.rs b/tests/test_ref.rs new file mode 100644 index 0000000..c281684 --- /dev/null +++ b/tests/test_ref.rs @@ -0,0 +1,106 @@ +use std::fs::{self, File}; +use std::path::{Path, PathBuf}; + +use anyhow::{bail, Result}; +use walkdir::{DirEntry, WalkDir}; + +use qoi_fast::{qoi_decode_to_vec, qoi_encode_to_vec}; + +fn find_qoi_png_pairs(root: impl AsRef) -> Vec<(PathBuf, PathBuf)> { + let root = root.as_ref(); + + let get_ext = + |path: &Path| path.extension().unwrap_or_default().to_string_lossy().to_ascii_lowercase(); + let check_qoi_png_pair = |path: &Path| { + let (qoi, png) = (path.to_path_buf(), path.with_extension("png")); + if qoi.is_file() && get_ext(&qoi) == "qoi" && png.is_file() { + Some((qoi, png)) + } else { + None + } + }; + + let mut out = vec![]; + if let Some(pair) = check_qoi_png_pair(root) { + out.push(pair); + } else if root.is_dir() { + out.extend( + WalkDir::new(root) + .follow_links(true) + .into_iter() + .filter_map(Result::ok) + .map(DirEntry::into_path) + .filter_map(|p| check_qoi_png_pair(&p)), + ) + } + out +} + +struct Image { + pub width: u32, + pub height: u32, + pub channels: u8, + pub data: Vec, +} + +impl Image { + fn from_png(filename: &Path) -> Result { + let decoder = png::Decoder::new(File::open(filename)?); + let mut reader = decoder.read_info()?; + let mut buf = vec![0; reader.output_buffer_size()]; + let info = reader.next_frame(&mut buf)?; + let bytes = &buf[..info.buffer_size()]; + Ok(Self { + width: info.width, + height: info.height, + channels: info.color_type.samples() as u8, + data: bytes.to_vec(), + }) + } +} + +fn compare_slices(name: &str, desc: &str, result: &[u8], expected: &[u8]) -> Result<()> { + if result == expected { + Ok(()) + } else { + if let Some(i) = + (0..result.len().min(expected.len())).position(|i| result[i] != expected[i]) + { + bail!( + "{}: {} mismatch at byte {}: expected {:?}, got {:?}", + name, + desc, + i, + &expected[i..(i + 4).min(expected.len())], + &result[i..(i + 4).min(result.len())], + ); + } else { + bail!( + "{}: {} length mismatch: expected {}, got {}", + name, + desc, + expected.len(), + result.len() + ); + } + } +} + +#[test] +fn test_reference_images() -> Result<()> { + let pairs = find_qoi_png_pairs("assets"); + assert!(!pairs.is_empty()); + + for (qoi_path, png_path) in &pairs { + let png_name = png_path.file_name().unwrap_or_default().to_string_lossy(); + let img = Image::from_png(png_path)?; + println!("{} {} {} {}", png_name, img.width, img.height, img.channels); + let encoded = qoi_encode_to_vec(&img.data, img.width, img.height, img.channels, 0)?; + let expected = fs::read(qoi_path)?; + compare_slices(&png_name, "encoding", &encoded, &expected)?; + let (_header, decoded) = qoi_decode_to_vec(&expected, img.channels)?; + compare_slices(&png_name, "decoding", &decoded, &img.data)?; + } + + Ok(()) +}