use std::fs;
use std::io;
use std::path::PathBuf;
use std::u32;
use crc32fast::Hasher as Crc32;
use image::DynamicImage;
const BASE_PATH: [&str; 2] = [".", "tests"];
const IMAGE_DIR: &str = "images";
const OUTPUT_DIR: &str = "output";
const REFERENCE_DIR: &str = "reference";
fn process_images<F>(dir: &str, input_decoder: Option<&str>, func: F)
where
F: Fn(&PathBuf, PathBuf, &str),
{
let base: PathBuf = BASE_PATH.iter().collect();
let decoders = &[
"tga", "tiff", "png", "gif", "bmp", "ico", "jpg", "hdr", "pbm", "webp",
];
for decoder in decoders {
let mut path = base.clone();
path.push(dir);
path.push(decoder);
path.push("**");
path.push(
"*.".to_string()
+ match input_decoder {
Some(val) => val,
None => decoder,
},
);
let pattern = &*format!("{}", path.display());
for path in glob::glob(pattern).unwrap().filter_map(Result::ok) {
func(&base, path, decoder)
}
}
}
#[cfg(feature = "png")]
#[test]
fn render_images() {
process_images(IMAGE_DIR, None, |base, path, decoder| {
println!("render_images {}", path.display());
let img = match image::open(&path) {
Ok(img) => img,
Err(image::ImageError::Unsupported(e)) => {
println!("UNSUPPORTED {}: {}", path.display(), e);
return;
}
Err(err) => panic!("decoding of {:?} failed with: {}", path, err),
};
let mut crc = Crc32::new();
crc.update(img.as_bytes());
let (filename, testsuite) = {
let mut path: Vec<_> = path.components().collect();
(path.pop().unwrap(), path.pop().unwrap())
};
let mut out_path = base.clone();
out_path.push(OUTPUT_DIR);
out_path.push(decoder);
out_path.push(testsuite.as_os_str());
fs::create_dir_all(&out_path).unwrap();
out_path.push(format!(
"{}.{:x}.png",
filename.as_os_str().to_str().unwrap(),
crc.finalize(),
));
img.save(out_path).unwrap();
})
}
struct ReferenceTestCase {
orig_filename: String,
crc: u32,
kind: ReferenceTestKind,
}
enum ReferenceTestKind {
SingleImage,
AnimatedFrame {
frame: usize,
},
}
impl std::str::FromStr for ReferenceTestCase {
type Err = &'static str;
fn from_str(filename: &str) -> Result<Self, Self::Err> {
let mut filename_parts = filename.rsplitn(3, '.');
filename_parts.next().unwrap();
let meta_str = filename_parts.next().ok_or("missing metadata part")?;
let meta = meta_str.split('_').collect::<Vec<_>>();
let (crc, kind);
if meta.len() == 1 {
crc = parse_crc(meta[0]).ok_or("malformed CRC")?;
kind = ReferenceTestKind::SingleImage;
} else if meta.len() == 3 && meta[0] == "anim" {
crc = parse_crc(meta[2]).ok_or("malformed CRC")?;
let frame: usize = meta[1].parse().map_err(|_| "malformed frame number")?;
kind = ReferenceTestKind::AnimatedFrame {
frame: frame.checked_sub(1).ok_or("frame number must be 1-based")?,
};
} else {
return Err("unrecognized reference image metadata format");
}
let orig_filename = filename_parts
.next()
.ok_or("missing original file name")?
.to_owned();
Ok(Self {
orig_filename,
crc,
kind,
})
}
}
fn parse_crc(src: &str) -> Option<u32> {
u32::from_str_radix(src, 16).ok()
}
#[test]
fn check_references() {
process_images(REFERENCE_DIR, Some("png"), |base, path, decoder| {
println!("check_references {}", path.display());
let ref_img = match image::open(&path) {
Ok(img) => img,
Err(image::ImageError::Unsupported(_)) => return,
Err(err) => panic!("{}", err),
};
let (filename, testsuite) = {
let mut path: Vec<_> = path.components().collect();
(path.pop().unwrap(), path.pop().unwrap())
};
let filename_str = filename.as_os_str().to_str().unwrap();
let case: ReferenceTestCase = filename_str.parse().unwrap();
let mut img_path = base.clone();
img_path.push(IMAGE_DIR);
img_path.push(decoder);
img_path.push(testsuite.as_os_str());
img_path.push(case.orig_filename);
let mut test_img = None;
match case.kind {
ReferenceTestKind::AnimatedFrame { frame: frame_num } => {
let format = image::io::Reader::open(&img_path)
.unwrap()
.with_guessed_format()
.unwrap()
.format();
#[cfg(feature = "gif")]
if format == Some(image::ImageFormat::Gif) {
use image::AnimationDecoder;
let stream = io::BufReader::new(fs::File::open(&img_path).unwrap());
let decoder = match image::codecs::gif::GifDecoder::new(stream) {
Ok(decoder) => decoder,
Err(image::ImageError::Unsupported(_)) => return,
Err(err) => {
panic!("decoding of {:?} failed with: {}", img_path, err)
}
};
let mut frames = match decoder.into_frames().collect_frames() {
Ok(frames) => frames,
Err(image::ImageError::Unsupported(_)) => return,
Err(err) => {
panic!("collecting frames of {:?} failed with: {}", img_path, err)
}
};
let frame = frames.drain(frame_num..).next().unwrap();
test_img = Some(DynamicImage::from(frame.into_buffer()));
}
#[cfg(feature = "png")]
if format == Some(image::ImageFormat::Png) {
use image::AnimationDecoder;
let stream = io::BufReader::new(fs::File::open(&img_path).unwrap());
let decoder = match image::codecs::png::PngDecoder::new(stream) {
Ok(decoder) => decoder.apng(),
Err(image::ImageError::Unsupported(_)) => return,
Err(err) => {
panic!("decoding of {:?} failed with: {}", img_path, err)
}
};
let mut frames = match decoder.into_frames().collect_frames() {
Ok(frames) => frames,
Err(image::ImageError::Unsupported(_)) => return,
Err(err) => {
panic!("collecting frames of {:?} failed with: {}", img_path, err)
}
};
let frame = frames.drain(frame_num..).next().unwrap();
test_img = Some(DynamicImage::from(frame.into_buffer()));
}
if test_img.is_none() {
println!("Skipping - GIF codec is not enabled");
return;
}
}
ReferenceTestKind::SingleImage => {
match image::open(&img_path) {
Ok(img) => test_img = Some(img),
Err(image::ImageError::Unsupported(_)) => return,
Err(err) => panic!("decoding of {:?} failed with: {}", img_path, err),
};
}
}
let test_img = match test_img.as_ref() {
Some(img) => img,
None => return,
};
let test_crc_actual = {
let mut hasher = Crc32::new();
hasher.update(test_img.as_bytes());
hasher.finalize()
};
if test_crc_actual != case.crc {
panic!(
"The decoded image's hash does not match (expected = {:08x}, actual = {:08x}).",
case.crc, test_crc_actual
);
}
if ref_img.as_bytes() != test_img.as_bytes() {
panic!("Reference rendering does not match.");
}
})
}
#[cfg(feature = "hdr")]
#[test]
fn check_hdr_references() {
let mut ref_path: PathBuf = BASE_PATH.iter().collect();
ref_path.push(REFERENCE_DIR);
ref_path.push("hdr");
let mut path: PathBuf = BASE_PATH.iter().collect();
path.push(IMAGE_DIR);
path.push("hdr");
path.push("*");
path.push("*.hdr");
let pattern = &*format!("{}", path.display());
for path in glob::glob(pattern).unwrap().filter_map(Result::ok) {
use std::path::Component::Normal;
let mut ref_path = ref_path.clone();
for c in path
.components()
.rev()
.take(2)
.collect::<Vec<_>>()
.iter()
.rev()
{
match *c {
Normal(name) => ref_path.push(name),
_ => panic!(),
}
}
ref_path.set_extension("raw");
println!("{}", ref_path.display());
println!("{}", path.display());
let decoder =
image::codecs::hdr::HdrDecoder::new(io::BufReader::new(fs::File::open(&path).unwrap()))
.unwrap();
let decoded = decoder.read_image_hdr().unwrap();
let reference = image::codecs::hdr::read_raw_file(&ref_path).unwrap();
assert_eq!(decoded, reference);
}
}