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; fn exr_files() -> impl Iterator { 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) } /// read all images in a directory. /// does not check any content, just checks whether a read error or panic happened. fn check_files( ignore: Vec, operation: impl Sync + std::panic::RefUnwindSafe + Fn(&Path) -> exr::error::Result ) { #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] enum Result { Ok, Skipped, Unsupported(String), Error(String) } let files: Vec = exr_files().collect(); let mut results: Vec<(PathBuf, Result)> = files.into_par_iter() .map(|file| { if ignore.contains(&file) { return (file, Result::Skipped); } 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::>()); assert!(results.len() > 80, "Not enough files were tested!"); if let Result::Error(_) = results.last().unwrap().1 { panic!("A file triggered a panic"); } } #[test] fn round_trip_all_files_full() { println!("checking full feature set"); check_files(vec![], |path| { let read_image = read() .no_deep_data().all_resolution_levels().all_channels().all_layers().all_attributes() .non_parallel(); let image = read_image.clone().from_file(path)?; let mut tmp_bytes = Vec::new(); 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(()) }) } #[test] fn round_trip_all_files_simple() { println!("checking full feature set but not resolution levels"); check_files(vec![], |path| { let read_image = read() .no_deep_data().largest_resolution_level().all_channels().all_layers().all_attributes() .non_parallel(); let image = read_image.clone().from_file(path)?; let mut tmp_bytes = Vec::new(); 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(()) }) } #[test] fn round_trip_all_files_rgba() { // these files are known to be invalid, because they do not contain any rgb channels let blacklist = vec![ PathBuf::from("tests/images/valid/openexr/LuminanceChroma/Garden.exr"), PathBuf::from("tests/images/valid/openexr/MultiView/Fog.exr"), PathBuf::from("tests/images/valid/openexr/TestImages/GrayRampsDiagonal.exr"), PathBuf::from("tests/images/valid/openexr/TestImages/GrayRampsHorizontal.exr"), PathBuf::from("tests/images/valid/openexr/TestImages/WideFloatRange.exr"), PathBuf::from("tests/images/valid/openexr/IlmfmlmflmTest/v1.7.test.tiled.exr") ]; println!("checking rgba feature set"); check_files(blacklist, |path| { 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_file(path)?; let mut tmp_bytes = Vec::new(); 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?) #[test] fn round_trip_parallel_files() { check_files(vec![], |path| { let image = read() .no_deep_data().all_resolution_levels().all_channels().all_layers().all_attributes() .from_file(path)?; let mut tmp_bytes = Vec::new(); 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(()) }) } #[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::>(); 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::>(); 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); }