From 1b6a04ca5504955c571d1c97504fb45ea0befee4 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Mon, 8 Jan 2024 01:21:28 +0400 Subject: Initial vendor packages Signed-off-by: Valentin Popov --- vendor/exr/tests/across_compression.rs | 263 +++++++++++++++++++++++ vendor/exr/tests/dev.rs | 86 ++++++++ vendor/exr/tests/fuzz.rs | 176 +++++++++++++++ vendor/exr/tests/roundtrip.rs | 376 +++++++++++++++++++++++++++++++++ 4 files changed, 901 insertions(+) create mode 100644 vendor/exr/tests/across_compression.rs create mode 100644 vendor/exr/tests/dev.rs create mode 100644 vendor/exr/tests/fuzz.rs create mode 100644 vendor/exr/tests/roundtrip.rs (limited to 'vendor/exr/tests') diff --git a/vendor/exr/tests/across_compression.rs b/vendor/exr/tests/across_compression.rs new file mode 100644 index 0000000..b1776e9 --- /dev/null +++ b/vendor/exr/tests/across_compression.rs @@ -0,0 +1,263 @@ +use std::path::Path; +use exr::prelude::*; +use exr::image::validate_results::ValidateResult; + +fn dir() -> &'static Path { Path::new("tests/images/valid/custom/compression_methods") } + +fn expect_eq_other(sub_dir: &str, image_name: &str, expected: &str) { + let path = dir().join(sub_dir).join(image_name); + + match read_first_flat_layer_from_file(path) { + Err(Error::NotSupported(message)) => println!("skipping ({})", message), + Err(error) => panic!("unexpected error: {}", error), + Ok(mut decompressed) => { + let decompressed_path = dir().join(sub_dir).join(expected); + let mut expected_decompressed = read_first_flat_layer_from_file(decompressed_path) + .expect("uncompressed image could not be loaded"); + + // HACK: make metadata match artificially, to avoid failing the check due to meta data mismatch + // (the name of the compression methods should not be equal, as we test between compression methods) + expected_decompressed.layer_data.encoding.compression = Compression::Uncompressed; + decompressed.layer_data.encoding.compression = Compression::Uncompressed; + + debug_assert_eq!(expected_decompressed.layer_data.attributes, decompressed.layer_data.attributes, "attributes should not be affected by compression"); + debug_assert_eq!(expected_decompressed.layer_data.size, decompressed.layer_data.size, "size should not be affected by compression"); + + // Note: Unimplemented methods may still work, if each compressed tile would be larger than uncompressed. + expected_decompressed.assert_equals_result(&decompressed); + } + } +} + +// comparing to a different format, png, +// is the only real way to check that +// little endian data is unpacked correctly on big endian systems +// it does not attempt to compare NaN +fn expect_eq_png(image_name: &str) { + type Rgb16Image = ::image::ImageBuffer<::image::Rgb, Vec>; + + let exr_path = dir().join("u16").join(image_name); + let png_from_exr = read_first_rgba_layer_from_file( + exr_path, + + |resolution, _channels: &RgbaChannels| -> Rgb16Image { + ::image::ImageBuffer::new( + resolution.width() as u32, + resolution.height() as u32 + ) + }, + + // set each pixel in the png buffer from the exr file + |png_pixels: &mut Rgb16Image, position: Vec2, (r,g,b,_): (f32,f32,f32,f32)| { + png_pixels.put_pixel( + position.x() as u32, position.y() as u32, + ::image::Rgb([to_u16(r), to_u16(g), to_u16(b)]) + ); + } + ); + + fn to_u16(num: f32) -> u16 { (num.powf(1.0/2.14).clamp(0.0, 1.0) * u16::MAX as f32).round() as u16 } + + match png_from_exr { + Err(Error::NotSupported(message)) => println!("skipping ({})", message), + Err(error) => panic!("unexpected error: {}", error), + Ok(decompressed) => { + let truth_path = dir().join("u16").join("ground_truth.png"); + let truth_dyn_img = image::open(truth_path).unwrap(); + + let ground_truth_png = truth_dyn_img.to_rgb16(); + let exr_as_png_px = decompressed.layer_data.channel_data.pixels; + debug_assert_eq!(ground_truth_png.dimensions(), exr_as_png_px.dimensions(), "size should not be affected by compression"); + + let expected_px = ground_truth_png.pixels() + .flat_map(|px| px.0.iter().copied()); + + let actual_px = exr_as_png_px.pixels() + .flat_map(|px| px.0.iter().copied()); + + let max_diff = u16::MAX/10; + for (exp, val) in expected_px.zip(actual_px) { + assert!( + exp.abs_diff(val) < max_diff, + "values not similar enough: found {}, expected {}", val, exp + ); + } + } + } +} + +fn expect_eq_uncompressed(sub_dir: &str, image_name: &str) { + expect_eq_other(sub_dir, image_name, "uncompressed.exr") +} + + +#[test] +fn compare_compression_contents_zip_f32() { + expect_eq_uncompressed("f32", "zip.exr"); +} + +#[test] +fn compare_compression_contents_zip_f16() { + expect_eq_uncompressed("f16", "zip.exr"); +} + +#[test] +fn compare_compression_contents_zips_f32() { + expect_eq_uncompressed("f32", "zips.exr"); +} + +#[test] +fn compare_compression_contents_zips_f16() { + expect_eq_uncompressed("f16", "zips.exr"); +} + +#[test] +fn compare_compression_contents_b44_f32() { + expect_eq_uncompressed("f32", "b44.exr"); // f32s are not compressed in b44 and can be compared exactly +} + +#[test] +fn compare_compression_contents_b44_f16() { + expect_eq_other("f16", "b44.exr", "decompressed_b44.exr"); +} + +#[test] +fn compare_compression_contents_b44a_f32() { + expect_eq_uncompressed("f32", "b44a.exr"); // f32s are not compressed in b44 and can be compared exactly +} + +#[test] +fn compare_compression_contents_b44a_f16() { + expect_eq_other("f16", "b44a.exr", "decompressed_b44a.exr"); +} + +#[test] +fn compare_compression_contents_dwaa_f32() { + expect_eq_other("f32", "dwaa.exr", "decompressed_dwaa.exr"); +} + +#[test] +fn compare_compression_contents_dwaa_f16() { + expect_eq_other("f16", "dwaa.exr", "decompressed_dwaa.exr"); +} + +#[test] +fn compare_compression_contents_dwab_f32() { + expect_eq_other("f32", "dwab.exr", "decompressed_dwab.exr"); +} + +#[test] +fn compare_compression_contents_dwab_f16() { + expect_eq_other("f16", "dwab.exr", "decompressed_dwab.exr"); +} + +#[test] +fn compare_compression_contents_piz_f32() { + expect_eq_uncompressed("f32", "piz.exr"); +} + +#[test] +fn compare_compression_contents_piz_f16() { + expect_eq_uncompressed("f16", "piz.exr"); +} + +#[test] +fn compare_compression_contents_rle_f32() { + expect_eq_uncompressed("f32", "rle.exr"); +} + +#[test] +fn compare_compression_contents_rle_f16() { + expect_eq_uncompressed("f16", "rle.exr"); +} + + +#[test] +#[cfg(target_endian = "little")] // TODO make it work on big endian +fn compare_compression_contents_pxr24_f16() { + expect_eq_other("f16", "pxr24.exr", "decompressed_pxr24.exr"); +} + +#[test] +#[cfg(target_endian = "little")] // TODO make it work on big endian +fn compare_compression_contents_pxr24_f32() { + expect_eq_other("f32", "pxr24.exr", "decompressed_pxr24.exr"); +} + +#[test] +fn compare_png_to_uncompressed_f16() { + expect_eq_png("f16_uncompressed.exr"); +} + +#[test] +fn compare_png_to_piz_f16() { + expect_eq_png("f16_piz.exr"); +} + +#[test] +fn compare_png_to_rle_f16() { + expect_eq_png("f16_rle.exr"); +} + +#[test] +fn compare_png_to_zip_f16() { + expect_eq_png("f16_zip.exr"); +} + +#[test] +fn compare_png_to_zips_f16() { + expect_eq_png("f16_zips.exr"); +} + +#[test] +fn compare_png_to_dwaa_f16() { + expect_eq_png("f16_dwaa.exr"); +} + +#[test] +fn compare_png_to_b44_f16() { + expect_eq_png("f16_b44.exr"); +} + +#[test] +fn compare_png_to_b44a_f16() { + expect_eq_png("f16_b44a.exr"); +} + +#[test] +#[cfg(target_endian = "little")] // TODO make it work on big endian +fn compare_png_to_pxr24_f16() { + expect_eq_png("f16_pxr24.exr"); +} + + +#[test] +fn compare_png_to_uncompressed_f32() { + expect_eq_png("f32_uncompressed.exr"); +} + +#[test] +fn compare_png_to_piz_f32() { + expect_eq_png("f32_piz.exr"); +} + +#[test] +fn compare_png_to_rle_f32() { + expect_eq_png("f32_rle.exr"); +} + +#[test] +fn compare_png_to_zip_f32() { + expect_eq_png("f32_zip.exr"); +} + +#[test] +fn compare_png_to_dwaa_f32() { + expect_eq_png("f32_dwaa.exr"); +} + +#[test] +#[cfg(target_endian = "little")] // TODO make it work on big endian +fn compare_png_to_pxr24_f32() { + expect_eq_png("f32_pxr24.exr"); +} diff --git a/vendor/exr/tests/dev.rs b/vendor/exr/tests/dev.rs new file mode 100644 index 0000000..15dd7ab --- /dev/null +++ b/vendor/exr/tests/dev.rs @@ -0,0 +1,86 @@ +//! Contains some "test" functions that were be used for developing. + +extern crate exr; +extern crate smallvec; + +use exr::prelude::*; + +use std::path::{PathBuf}; +use std::ffi::OsStr; +use std::io::{Cursor}; +use exr::meta::header::Header; +use exr::image::validate_results::ValidateResult; +use rayon::prelude::IntoParallelIterator; +use rayon::iter::ParallelIterator; + +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) +} + +#[test] +#[ignore] +fn print_meta_of_all_files() { + let files: Vec = exr_files().collect(); + + files.into_par_iter().for_each(|path| { + let meta = MetaData::read_from_file(&path, false); + println!("{:?}: \t\t\t {:?}", path.file_name().unwrap(), meta.unwrap()); + }); +} + +#[test] +#[ignore] +fn search_previews_of_all_files() { + let files: Vec = exr_files().collect(); + + files.into_par_iter().for_each(|path| { + let meta = MetaData::read_from_file(&path, false).unwrap(); + let has_preview = meta.headers.iter().any(|header: &Header| + header.own_attributes.preview.is_some() || header.own_attributes.other.values() + .any(|value| match value { AttributeValue::Preview(_) => true, _ => false }) + ); + + if has_preview { + println!("Found preview attribute in {:?}", path.file_name().unwrap()); + } + }); +} + +// use this command for big endian testing: +// cross test --target mips-unknown-linux-gnu --verbose --test dev test_roundtrip -- --ignored +#[test] +#[ignore] +pub fn test_roundtrip() { + // works + //let path = "tests/images/fuzzed/b44_overly_restrictive_assert.exr"; + let path = "tests/images/valid/custom/compression_methods/f32/pxr24.exr"; + + // worksn't + // let path = "tests/images/valid/openexr/Chromaticities/Rec709_YC.exr"; // subsampling + // let path = "tests/images/valid/openexr/LuminanceChroma/Flowers.exr"; // subsampling + + // let path = "tests/images/valid/openexr/IlmfmlmflmTest/test_native1.exr"; + // let path = "tests/images/valid/openexr/IlmfmlmflmTest/test_native2.exr"; // contains NaN + + // deep data? + // let path = "tests/images/valid/openexr/v2/Stereo/Balls.exr"; + // let path = "tests/images/valid/openexr/v2/Stereo/Ground.exr"; + + println!("{:?}", exr::meta::MetaData::read_from_file(path, true)); + + 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).unwrap(); + + let mut tmp_bytes = Vec::new(); + image.write().to_buffered(Cursor::new(&mut tmp_bytes)).unwrap(); + image.write().to_file("debug_pxr24.exr").unwrap(); + + let image2 = read_image.from_buffered(Cursor::new(tmp_bytes)).unwrap(); + + image.assert_equals_result(&image2); +} diff --git a/vendor/exr/tests/fuzz.rs b/vendor/exr/tests/fuzz.rs new file mode 100644 index 0000000..9bdc79d --- /dev/null +++ b/vendor/exr/tests/fuzz.rs @@ -0,0 +1,176 @@ +//! Fuzzy testing. +//! Tries to discover panics with random bytes. +//! This test is expensive and therefore marked with `#[ignore]`. To run this test, use `cargo test -- --ignored`. + +use std::panic::{catch_unwind}; +use rand::rngs::{StdRng}; +use rand::{Rng}; + +extern crate exr; +use exr::prelude::*; +use std::path::PathBuf; +use std::ffi::OsStr; +use std::fs::File; +use std::io::{Write, Cursor}; +use exr::image::read::read_first_rgba_layer_from_file; +use exr::image::pixel_vec::PixelVec; + +fn exr_files(path: &'static str, filter: bool) -> impl Iterator { + walkdir::WalkDir::new(path).into_iter().map(std::result::Result::unwrap) + .filter(|entry| entry.path().is_file()) + + .filter(move |entry| !filter || entry.path().extension() == Some(OsStr::new("exr"))) + .map(walkdir::DirEntry::into_path) +} + + +/// Just don't panic. +#[test] +pub fn fuzzed(){ + for ref file in exr_files("tests/images/fuzzed", false) { + let _ = read().no_deep_data().largest_resolution_level().all_channels() + .first_valid_layer().all_attributes().pedantic().from_file(file); + + let _ = read().no_deep_data().all_resolution_levels().all_channels() + .all_layers().all_attributes().pedantic().from_file(file); + } +} + +/// Require an error but no panic. +#[test] +pub fn damaged(){ + let mut passed = true; + + for ref file in exr_files("tests/images/invalid", false) { + let result = catch_unwind(move || { + let _meta_data = MetaData::read_from_file(file, false)?; + + { + let _minimal = read().no_deep_data() + .largest_resolution_level() + .rgba_channels( + |_size, _channels| (), + |_: &mut (), _position: Vec2, _pixel: (Sample, Sample, Sample, Sample)| {} + ) + .first_valid_layer().all_attributes() + .from_file(&file)?; + } + + { + let _minimal = read().no_deep_data() + .largest_resolution_level() // TODO all levels + .rgba_channels( + |_size, _channels| (), + |_: &mut (), _position: Vec2, _pixel: (Sample, Sample, Sample, Sample)| {} + ) + .all_layers().all_attributes() + .pedantic() + .from_file(&file)?; + } + + { + let _rgba = read_first_rgba_layer_from_file( + file, + PixelVec::<(Sample, Sample, Sample, Sample)>::constructor, + PixelVec::set_pixel + )?; + } + + { + let _full = read_all_data_from_file(file)?; + } + + Ok(()) + }); + + // this should not panic, only err: + passed = passed && match result { + Ok(Err(Error::Invalid(message))) => { + println!("✓ Recognized as invalid ({}): {:?}", message, file); + true + }, + + Ok(Err(Error::NotSupported(message))) => { + println!("- Unsupported ({}): {:?}", message, file); + true + }, + + Ok(Err(Error::Io(error))) => { + println!("✗ Unexpected IO Error: {:?}, {:?}", file, error); + false + }, + + Err(_) => { + println!("✗ Not recognized as invalid: {:?}", file); + false + }, + + Ok(Ok(_)) => { + let meta_data = MetaData::read_from_file(file, true); + + if let Err(error) = meta_data { + println!("✓ Recognized as invalid when pedantic ({}): {:?}", error, file); + true + } + else { + println!("✗ Oh no, there is nothing wrong with: {:#?}", file); + false + } + }, + + _ => unreachable!(), + }; + } + + assert!(passed, "A damaged file was not handled correctly"); +} + +#[test] +#[ignore] +pub fn fuzz(){ + println!("started fuzzing"); + let files: Vec = exr_files("tests/images", true).collect(); + + let seed = [92,1,0,130,211,8,21,70,74,4,9,5,0,23,0,3,20,25,6,5,229,30,0,34,218,0,40,7,5,2,7,0,]; + let mut random: StdRng = rand::SeedableRng::from_seed(seed); + + let mut records = File::create("tests/images/fuzzed/list.txt").unwrap(); + records.write_all(format!("seed = {:?}", seed).as_bytes()).unwrap(); + + let start_index = 0; // default is 0. increase this integer for debugging a specific fuzz case + for fuzz_index in 0 .. 1024_u64 * 2048 * 4 { + + let file_1_name = &files[random.gen_range(0 .. files.len())]; + let mutation_point = random.gen::().powi(3); + let mutation = random.gen::(); + + if fuzz_index >= start_index { + let mut file = std::fs::read(file_1_name).unwrap(); + let index = ((mutation_point * file.len() as f32) as usize + 4) % file.len(); + file[index] = mutation; + + let file = file.as_slice(); + let result = catch_unwind(move || { + let read_all_data = read().no_deep_data() + .all_resolution_levels().all_channels().all_layers().all_attributes(); + + match read_all_data.from_buffered(Cursor::new(file)) { + Err(Error::Invalid(error)) => println!("✓ No Panic. [{}]: Invalid: {}.", fuzz_index, error), + Err(Error::NotSupported(error)) => println!("- No Panic. [{}]: Unsupported: {}.", fuzz_index, error), + _ => {}, + } + }); + + if let Err(_) = result { + records.write_all(fuzz_index.to_string().as_bytes()).unwrap(); + records.flush().unwrap(); + + let seed = seed.iter().map(|num| num.to_string()).collect::>().join("-"); + let mut saved = File::create(format!("tests/images/fuzzed/fuzz_{}_{}.exr", fuzz_index, seed)).unwrap(); + saved.write_all(file).unwrap(); + + println!("✗ PANIC! [{}]", fuzz_index); + } + } + } +} 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( + operation: impl Sync + std::panic::RefUnwindSafe + Fn(&Path) -> exr::error::Result +) { + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] + enum Result { Ok, Unsupported(String), Error(String) } + + let files: Vec = 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::>()); + + 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 { + 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::>(); + + 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); +} -- cgit v1.2.3