diff options
Diffstat (limited to 'vendor/exr/tests/roundtrip.rs')
-rw-r--r-- | vendor/exr/tests/roundtrip.rs | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/vendor/exr/tests/roundtrip.rs b/vendor/exr/tests/roundtrip.rs new file mode 100644 index 0000000..3181ffe --- /dev/null +++ b/vendor/exr/tests/roundtrip.rs @@ -0,0 +1,376 @@ +extern crate exr; + +extern crate smallvec; + +use std::{panic}; +use std::io::{Cursor}; +use std::panic::catch_unwind; +use std::path::{PathBuf, Path}; +use std::ffi::OsStr; + +use exr::prelude::*; +use exr::error::{Error, UnitResult}; +use exr::prelude::pixel_vec::PixelVec; +use exr::image::validate_results::ValidateResult; +use rayon::prelude::IntoParallelIterator; +use rayon::iter::ParallelIterator; +use exr::block::samples::IntoNativeSample; + +#[test] +fn roundtrip_all_files_in_repository_x4(){ + check_all_files_in_repo(|path|{ + let file = std::fs::read(path).expect("cannot open file"); + + round_trip_simple(&file)?; + round_trip_full(&file)?; + round_trip_rgba_file(path, &file)?; + round_trip_parallel_file(&file)?; + + Ok(()) + }); +} + + +fn round_trip_full(file: &[u8]) -> Result<()> { + let read_image = read() + .no_deep_data().all_resolution_levels().all_channels().all_layers().all_attributes() + .non_parallel(); + + let image = read_image.clone().from_buffered(Cursor::new(file))?; + + let mut tmp_bytes = Vec::with_capacity(file.len()); + image.write().non_parallel().to_buffered(Cursor::new(&mut tmp_bytes))?; + + let image2 = read_image.from_buffered(Cursor::new(tmp_bytes))?; + + image.assert_equals_result(&image2); + Ok(()) +} + +fn round_trip_simple(file: &[u8]) -> Result<()> { + let read_image = read() + .no_deep_data().largest_resolution_level().all_channels().all_layers().all_attributes() + .non_parallel(); + + let image = read_image.clone().from_buffered(Cursor::new(file))?; + + let mut tmp_bytes = Vec::with_capacity(file.len()); + image.write().non_parallel().to_buffered(&mut Cursor::new(&mut tmp_bytes))?; + + let image2 = read_image.from_buffered(Cursor::new(&tmp_bytes))?; + + image.assert_equals_result(&image2); + Ok(()) +} + +fn round_trip_rgba_file(path: &Path, file: &[u8]) -> Result<()> { + // these files are known to be invalid, because they do not contain any rgb channels + let blacklist = [ + Path::new("tests/images/valid/openexr/LuminanceChroma/Garden.exr"), + Path::new("tests/images/valid/openexr/MultiView/Fog.exr"), + Path::new("tests/images/valid/openexr/TestImages/GrayRampsDiagonal.exr"), + Path::new("tests/images/valid/openexr/TestImages/GrayRampsHorizontal.exr"), + Path::new("tests/images/valid/openexr/TestImages/WideFloatRange.exr"), + Path::new("tests/images/valid/openexr/IlmfmlmflmTest/v1.7.test.tiled.exr") + ]; + + if blacklist.contains(&path) { return Ok(()) } + + let image_reader = read() + .no_deep_data() + .largest_resolution_level() // TODO all levels + .rgba_channels(PixelVec::<(f32,f32,f32,f32)>::constructor, PixelVec::set_pixel) + .first_valid_layer() + .all_attributes() + .non_parallel(); + + let image = image_reader.clone().from_buffered(Cursor::new(file))?; + + let mut tmp_bytes = Vec::with_capacity(file.len()); + + image.write().non_parallel() + .to_buffered(&mut Cursor::new(&mut tmp_bytes))?; + + let image2 = image_reader.from_buffered(Cursor::new(&tmp_bytes))?; + + image.assert_equals_result(&image2); + Ok(()) +} + +// TODO compare rgba vs rgb images for color content, and rgb vs rgb(a?) + + +fn round_trip_parallel_file(file: &[u8]) -> Result<()> { + let image = read() + .no_deep_data().all_resolution_levels().all_channels().all_layers().all_attributes() + .from_buffered(Cursor::new(file))?; + + let mut tmp_bytes = Vec::with_capacity(file.len()); + image.write().to_buffered(Cursor::new(&mut tmp_bytes))?; + + let image2 = read() + .no_deep_data().all_resolution_levels().all_channels().all_layers().all_attributes() + .pedantic() + .from_buffered(Cursor::new(tmp_bytes.as_slice()))?; + + image.assert_equals_result(&image2); + Ok(()) +} + +/// read all images in a directory. +/// does not check any content, just checks whether a read error or panic happened. +fn check_all_files_in_repo<T>( + operation: impl Sync + std::panic::RefUnwindSafe + Fn(&Path) -> exr::error::Result<T> +) { + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] + enum Result { Ok, Unsupported(String), Error(String) } + + let files: Vec<PathBuf> = all_exr_files_in_repo().collect(); + let mut results: Vec<(PathBuf, Result)> = files.into_par_iter() + .map(|file| { + let result = catch_unwind(||{ + let prev_hook = panic::take_hook(); + panic::set_hook(Box::new(|_| (/* do not println panics */))); + let result = operation(&file); + panic::set_hook(prev_hook); + + result + }); + + let result = match result { + Ok(Ok(_)) => Result::Ok, + Ok(Err(Error::NotSupported(message))) => Result::Unsupported(message.to_string()), + + Ok(Err(Error::Io(io))) => Result::Error(format!("IoError: {:?}", io)), + Ok(Err(Error::Invalid(message))) => Result::Error(format!("Invalid: {:?}", message)), + Ok(Err(Error::Aborted)) => panic!("a test produced `Error::Abort`"), + + Err(_) => Result::Error("Panic".to_owned()), + }; + + match &result { + Result::Error(_) => println!("✗ Error when processing {:?}", file), + _ => println!("✓ No error when processing {:?}", file) + }; + + (file, result) + }) + .collect(); + + results.sort_by(|(_, a), (_, b)| a.cmp(b)); + + println!("{:#?}", results.iter().map(|(path, result)| { + format!("{:?}: {}", result, path.to_str().unwrap()) + }).collect::<Vec<_>>()); + + assert!(results.len() > 80, "Not enough files were tested!"); + + if let Result::Error(_) = results.last().unwrap().1 { + panic!("A file triggered a panic"); + } +} + +fn all_exr_files_in_repo() -> impl Iterator<Item=PathBuf> { + walkdir::WalkDir::new("tests/images/valid").into_iter().map(std::result::Result::unwrap) + .filter(|entry| entry.path().extension() == Some(OsStr::new("exr"))) + .map(walkdir::DirEntry::into_path) +} + +#[test] +fn roundtrip_unusual_2() -> UnitResult { + let random_pixels: Vec<(f16, u32)> = vec![ + ( f16::from_f32(-5.0), 4), + ( f16::from_f32(4.0), 9), + ( f16::from_f32(2.0), 6), + ( f16::from_f32(21.0), 8), + ( f16::from_f32(64.0), 7), + ]; + + let size = Vec2(3, 2); + let pixels = (0..size.area()) + .zip(random_pixels.into_iter().cycle()) + .map(|(_index, color)| color).collect::<Vec<_>>(); + + let pixels = PixelVec { resolution: size, pixels }; + + let channels = SpecificChannels::build() + .with_channel("N") + .with_channel("Ploppalori Taranos") + .with_pixels(pixels.clone() + ); + + let image = Image::from_channels(size, channels); + + let mut tmp_bytes = Vec::new(); + image.write().non_parallel().to_buffered(&mut Cursor::new(&mut tmp_bytes))?; + + let image_reader = read() + .no_deep_data() + .largest_resolution_level() // TODO all levels + .specific_channels().required("N").required("Ploppalori Taranos") + .collect_pixels(PixelVec::<(f16,u32)>::constructor, PixelVec::set_pixel) + .first_valid_layer() + .all_attributes() + .non_parallel(); + + let image2 = image_reader.from_buffered(Cursor::new(&tmp_bytes))?; + + // custom compare function: considers nan equal to nan + assert_eq!(image.layer_data.size, size, "test is buggy"); + let pixels1 = &image.layer_data.channel_data.pixels; + let pixels2 = &image2.layer_data.channel_data.pixels; + + assert_eq!(pixels1.pixels, pixels2.pixels); + + Ok(()) +} + +// TODO test optional reader +// TODO dedup +#[test] +fn roundtrip_unusual_7() -> UnitResult { + + let random_pixels: Vec<(f16, u32, f32,f32,f32,f32,f32)> = vec![ + ( f16::from_f32(-5.0), 4, 1.0,2.0,3.0,4.0,5.0), + ( f16::from_f32(4.0), 8, 2.0,3.0,4.0,5.0,1.0), + ( f16::from_f32(2.0), 9, 3.0,4.0,5.0,1.0,2.0), + ( f16::from_f32(21.0), 6, 4.0,5.0,1.0,2.0,3.0), + ( f16::from_f32(64.0), 5, 5.0,1.0,2.0,3.0,4.0), + ]; + + let size = Vec2(3, 2); + let pixels = (0..size.area()) + .zip(random_pixels.into_iter().cycle()) + .map(|(_index, color)| color).collect::<Vec<_>>(); + + let pixels = PixelVec { resolution: size, pixels }; + + let channels = SpecificChannels::build() + .with_channel("N") + .with_channel("Ploppalori Taranos") + .with_channel("4") + .with_channel(".") + .with_channel("____") + .with_channel(" ") + .with_channel(" ") + .with_pixels(pixels.clone() + ); + + let image = Image::from_channels(size, channels); + + let mut tmp_bytes = Vec::new(); + image.write().non_parallel().to_buffered(&mut Cursor::new(&mut tmp_bytes))?; + + let image_reader = read() + .no_deep_data() + .largest_resolution_level() // TODO all levels + .specific_channels() + .required("N") + .required("Ploppalori Taranos") + .required("4") + .required(".") + .required("____") + .required(" ") + .required(" ") + .collect_pixels(PixelVec::<(f16, u32, f32,f32,f32,f32,f32)>::constructor, PixelVec::set_pixel) + .first_valid_layer() + .all_attributes() + .non_parallel(); + + let image2 = image_reader.from_buffered(Cursor::new(&tmp_bytes))?; + + // custom compare function: considers nan equal to nan + assert_eq!(image.layer_data.size, size, "test is buggy"); + let pixels1 = &image.layer_data.channel_data.pixels; + let pixels2 = &image2.layer_data.channel_data.pixels; + + assert_eq!(pixels1.pixels, pixels2.pixels); + Ok(()) +} + +#[test] +#[cfg(target_endian = "big")] // TODO big endian pxr24 +fn pxr24_expect_error_on_big_endian(){ + let image = exr::prelude::read_all_data_from_file( + "tests/images/valid/custom/compression_methods/f16/pxr24.exr" + ); + + match image { + Err(Error::NotSupported(_)) => {} + _ => panic!("pxr24 should report an error on big endian architecture") + } +} + +#[test] +#[cfg(target_endian = "little")] // TODO big endian pxr24 +fn roundtrip_pxr24() { + test_mixed_roundtrip_with_compression(Compression::PXR24) +} + +#[test] +fn roundtrip_rle() { + test_mixed_roundtrip_with_compression(Compression::RLE) +} + +#[test] +fn roundtrip_zip1() { + test_mixed_roundtrip_with_compression(Compression::ZIP1) +} + +#[test] +fn roundtrip_zip16() { + test_mixed_roundtrip_with_compression(Compression::ZIP16) +} + +#[test] +fn roundtrip_b44() { + test_mixed_roundtrip_with_compression(Compression::B44) +} + +#[test] +fn roundtrip_b44a() { + test_mixed_roundtrip_with_compression(Compression::B44A) +} + +#[test] +fn roundtrip_piz() { + test_mixed_roundtrip_with_compression(Compression::PIZ) +} + +#[test] +fn roundtrip_uncompressed() { + test_mixed_roundtrip_with_compression(Compression::Uncompressed) +} + +fn test_mixed_roundtrip_with_compression(compression: Compression) { + + let original_pixels: [(f16,f32,f32); 4] = [ + (0.0.to_f16(), -1.1, std::f32::consts::PI), + (9.1.to_f16(), -3.1, std::f32::consts::TAU), + (-10.0.to_f16(), -11.1, f32::EPSILON), + (half::f16::NAN, 10000.1, -1024.009), + ]; + + let mut file_bytes = Vec::new(); + let original_image = Image::from_encoded_channels( + (2,2), + Encoding { + compression, + .. Encoding::default() + }, + SpecificChannels::rgb( + PixelVec::new(Vec2(2,2), original_pixels.to_vec()) + ) + ); + + original_image.write().to_buffered(Cursor::new(&mut file_bytes)).unwrap(); + + let lossy_image = read().no_deep_data().largest_resolution_level() + .rgb_channels(PixelVec::<(f16,f32,f32)>::constructor, PixelVec::set_pixel) + .first_valid_layer().all_attributes().from_buffered(Cursor::new(&file_bytes)).unwrap(); + + // use automatic lossy detection by compression method + original_image.assert_equals_result(&original_image); + lossy_image.assert_equals_result(&lossy_image); + original_image.assert_equals_result(&lossy_image); +} |