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/src/image/mod.rs | 1326 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1326 insertions(+) create mode 100644 vendor/exr/src/image/mod.rs (limited to 'vendor/exr/src/image/mod.rs') diff --git a/vendor/exr/src/image/mod.rs b/vendor/exr/src/image/mod.rs new file mode 100644 index 0000000..db75050 --- /dev/null +++ b/vendor/exr/src/image/mod.rs @@ -0,0 +1,1326 @@ + +//! Data structures that represent a complete exr image. +//! Contains generic structs that must be nested to obtain a complete image type. +//! +//! +//! For example, an rgba image containing multiple layers +//! can be represented using `Image>>`. +//! An image containing a single layer with arbitrary channels and no deep data +//! can be represented using `Image>>`. +//! +//! +//! These and other predefined types are included in this module as +//! 1. `PixelImage`: A single layer, fixed set of arbitrary channels. +//! 1. `PixelLayersImage`: Multiple layers, fixed set of arbitrary channels. +//! 1. `RgbaImage`: A single layer, fixed set of channels: rgb, optional a. +//! 1. `RgbaLayersImage`: Multiple layers, fixed set of channels: rgb, optional a. +//! 1. `FlatImage`: Multiple layers, any channels, no deep data. +//! 1. `AnyImage`: All supported data (multiple layers, arbitrary channels, no deep data yet) +//! +//! You can also use your own types inside an image, +//! for example if you want to use a custom sample storage. +//! +//! This is the high-level interface for the pixels of an image. +//! See `exr::blocks` module for a low-level interface. + +pub mod read; +pub mod write; +pub mod crop; +pub mod pixel_vec; +pub mod recursive; +// pub mod channel_groups; + + +use crate::meta::header::{ImageAttributes, LayerAttributes}; +use crate::meta::attribute::{Text, LineOrder}; +use half::f16; +use crate::math::{Vec2, RoundingMode}; +use crate::compression::Compression; +use smallvec::{SmallVec}; +use crate::error::Error; + +/// Don't do anything +pub(crate) fn ignore_progress(_progress: f64){} + +/// This image type contains all supported exr features and can represent almost any image. +/// It currently does not support deep data yet. +pub type AnyImage = Image>>>; + +/// This image type contains the most common exr features and can represent almost any plain image. +/// Does not contain resolution levels. Does not support deep data. +pub type FlatImage = Image>>; + +/// This image type contains multiple layers, with each layer containing a user-defined type of pixels. +pub type PixelLayersImage = Image>>; + +/// This image type contains a single layer containing a user-defined type of pixels. +pub type PixelImage = Image>>; + +/// This image type contains multiple layers, with each layer containing a user-defined type of rgba pixels. +pub type RgbaLayersImage = PixelLayersImage; + +/// This image type contains a single layer containing a user-defined type of rgba pixels. +pub type RgbaImage = PixelImage; + +/// Contains information about the channels in an rgba image, in the order `(red, green, blue, alpha)`. +/// The alpha channel is not required. May be `None` if the image did not contain an alpha channel. +pub type RgbaChannels = (ChannelDescription, ChannelDescription, ChannelDescription, Option); + +/// Contains information about the channels in an rgb image, in the order `(red, green, blue)`. +pub type RgbChannels = (ChannelDescription, ChannelDescription, ChannelDescription); + +/// The complete exr image. +/// `Layers` can be either a single `Layer` or `Layers`. +#[derive(Debug, Clone, PartialEq)] +pub struct Image { + + /// Attributes that apply to the whole image file. + /// These attributes appear in each layer of the file. + /// Excludes technical meta data. + /// Each layer in this image also has its own attributes. + pub attributes: ImageAttributes, + + /// The layers contained in the image file. + /// Can be either a single `Layer` or a list of layers. + pub layer_data: Layers, +} + +/// A list of layers. `Channels` can be `SpecificChannels` or `AnyChannels`. +pub type Layers = SmallVec<[Layer; 2]>; + +/// A single Layer, including fancy attributes and compression settings. +/// `Channels` can be either `SpecificChannels` or `AnyChannels` +#[derive(Debug, Clone, PartialEq)] +pub struct Layer { + + /// The actual pixel data. Either `SpecificChannels` or `AnyChannels` + pub channel_data: Channels, + + /// Attributes that apply to this layer. + /// May still contain attributes that should be considered global for an image file. + /// Excludes technical meta data: Does not contain data window size, line order, tiling, or compression attributes. + /// The image also has attributes, which do not differ per layer. + pub attributes: LayerAttributes, + + /// The pixel resolution of this layer. + /// See `layer.attributes` for more attributes, like for example layer position. + pub size: Vec2, + + /// How the pixels are split up and compressed. + pub encoding: Encoding +} + +/// How the pixels are split up and compressed. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Encoding { + + /// How the pixel data of all channels in this layer is compressed. May be `Compression::Uncompressed`. + /// See `layer.attributes` for more attributes. + pub compression: Compression, + + /// Describes how the pixels of this layer are divided into smaller blocks. + /// Either splits the image into its scan lines or splits the image into tiles of the specified size. + /// A single block can be loaded without processing all bytes of a file. + pub blocks: Blocks, + + /// In what order the tiles of this header occur in the file. + /// Does not change any actual image orientation. + /// See `layer.attributes` for more attributes. + pub line_order: LineOrder, +} + +/// How the image pixels are split up into separate blocks. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Blocks { + + /// The image is divided into scan line blocks. + /// The number of scan lines in a block depends on the compression method. + ScanLines, + + /// The image is divided into tile blocks. + /// Also specifies the size of each tile in the image + /// and whether this image contains multiple resolution levels. + /// + /// The inner `Vec2` describes the size of each tile. + /// Stays the same number of pixels across all levels. + Tiles (Vec2) +} + + +/// A grid of pixels. The pixels are written to your custom pixel storage. +/// `PixelStorage` can be anything, from a flat `Vec` to `Vec>`, as desired. +/// In order to write this image to a file, your `PixelStorage` must implement [`GetPixel`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SpecificChannels { + + /// A description of the channels in the file, as opposed to the channels in memory. + /// Should always be a tuple containing `ChannelDescription`s, one description for each channel. + pub channels: ChannelsDescription, // TODO this is awkward. can this be not a type parameter please? maybe vec> ?? + + /// Your custom pixel storage + // TODO should also support `Levels`, where levels are desired! + pub pixels: Pixels, // TODO rename to "pixels"? +} + + +/// A dynamic list of arbitrary channels. +/// `Samples` can currently only be `FlatSamples` or `Levels`. +#[derive(Debug, Clone, PartialEq)] +pub struct AnyChannels { + + /// This list must be sorted alphabetically, by channel name. + /// Use `AnyChannels::sorted` for automatic sorting. + pub list: SmallVec<[AnyChannel; 4]> +} + +/// A single arbitrary channel. +/// `Samples` can currently only be `FlatSamples` or `Levels` +#[derive(Debug, Clone, PartialEq)] +pub struct AnyChannel { + + /// One of "R", "G", or "B" most of the time. + pub name: Text, + + /// The actual pixel data. + /// Can be `FlatSamples` or `Levels`. + pub sample_data: Samples, + + /// This attribute only tells lossy compression methods + /// whether this value should be quantized exponentially or linearly. + /// + /// Should be `false` for red, green, blue and luma channels, as they are not perceived linearly. + /// Should be `true` for hue, chroma, saturation, and alpha channels. + pub quantize_linearly: bool, + + /// How many of the samples are skipped compared to the other channels in this layer. + /// + /// Can be used for chroma subsampling for manual lossy data compression. + /// Values other than 1 are allowed only in flat, scan-line based images. + /// If an image is deep or tiled, the sampling rates for all of its channels must be 1. + pub sampling: Vec2, +} + +/// One or multiple resolution levels of the same image. +/// `Samples` can be `FlatSamples`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Levels { + + /// A single image without smaller versions of itself. + /// If you only want to handle exclusively this case, use `Samples` directly, and not `Levels`. + Singular(Samples), + + /// Contains uniformly scaled smaller versions of the original. + Mip + { + /// Whether to round up or down when calculating Mip/Rip levels. + rounding_mode: RoundingMode, + + /// The smaller versions of the original. + level_data: LevelMaps + }, + + /// Contains any possible combination of smaller versions of the original. + Rip + { + /// Whether to round up or down when calculating Mip/Rip levels. + rounding_mode: RoundingMode, + + /// The smaller versions of the original. + level_data: RipMaps + }, +} + +/// A list of resolution levels. `Samples` can currently only be `FlatSamples`. +// or `DeepAndFlatSamples` (not yet implemented). +pub type LevelMaps = Vec; + +/// In addition to the full resolution image, +/// this layer also contains smaller versions, +/// and each smaller version has further versions with varying aspect ratios. +/// `Samples` can currently only be `FlatSamples`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RipMaps { + + /// A flattened list containing the individual levels + pub map_data: LevelMaps, + + /// The number of levels that were generated along the x-axis and y-axis. + pub level_count: Vec2, +} + + +// TODO deep data +/*#[derive(Clone, PartialEq)] +pub enum DeepAndFlatSamples { + Deep(DeepSamples), + Flat(FlatSamples) +}*/ + +/// A vector of non-deep values (one value per pixel per channel). +/// Stores row after row in a single vector. +/// The precision of all values is either `f16`, `f32` or `u32`. +/// +/// Since this is close to the pixel layout in the byte file, +/// this will most likely be the fastest storage. +/// Using a different storage, for example `SpecificChannels`, +/// will probably be slower. +#[derive(Clone, PartialEq)] // debug is implemented manually +pub enum FlatSamples { + + /// A vector of non-deep `f16` values. + F16(Vec), + + /// A vector of non-deep `f32` values. + F32(Vec), + + /// A vector of non-deep `u32` values. + U32(Vec), +} + + +/*#[derive(Clone, PartialEq)] +pub enum DeepSamples { + F16(Vec>), + F32(Vec>), + U32(Vec>), +}*/ + +use crate::block::samples::*; +use crate::meta::attribute::*; +use crate::error::Result; +use crate::block::samples::Sample; +use crate::image::write::channels::*; +use crate::image::write::layers::WritableLayers; +use crate::image::write::samples::{WritableSamples}; +use crate::meta::{mip_map_levels, rip_map_levels}; +use crate::io::Data; +use crate::image::recursive::{NoneMore, Recursive, IntoRecursive}; +use std::marker::PhantomData; +use std::ops::Not; +use crate::image::validate_results::{ValidationOptions}; + + +impl Layer { + /// Sometimes called "data window" + pub fn absolute_bounds(&self) -> IntegerBounds { + IntegerBounds::new(self.attributes.layer_position, self.size) + } +} + + +impl SpecificChannels { + /// Create some pixels with channel information. + /// The `Channels` must be a tuple containing either `ChannelDescription` or `Option`. + /// The length of the tuple dictates the number of channels in the sample storage. + pub fn new(channels: Channels, source_samples: SampleStorage) -> Self + where + SampleStorage: GetPixel, + SampleStorage::Pixel: IntoRecursive, + Channels: Sync + Clone + IntoRecursive, + ::Recursive: WritableChannelsDescription<::Recursive>, + { + SpecificChannels { channels, pixels: source_samples } + } +} + +/// Convert this type into one of the known sample types. +/// Also specify the preferred native type, which dictates the default sample type in the image. +pub trait IntoSample: IntoNativeSample { + + /// The native sample types that this type should be converted to. + const PREFERRED_SAMPLE_TYPE: SampleType; +} + +impl IntoSample for f16 { const PREFERRED_SAMPLE_TYPE: SampleType = SampleType::F16; } +impl IntoSample for f32 { const PREFERRED_SAMPLE_TYPE: SampleType = SampleType::F32; } +impl IntoSample for u32 { const PREFERRED_SAMPLE_TYPE: SampleType = SampleType::U32; } + +/// Used to construct a `SpecificChannels`. +/// Call `with_named_channel` as many times as desired, +/// and then call `with_pixels` to define the colors. +#[derive(Debug)] +pub struct SpecificChannelsBuilder { + channels: RecursiveChannels, + px: PhantomData +} + +/// This check can be executed at compile time +/// if the channel names are `&'static str` and the compiler is smart enough. +pub trait CheckDuplicates { + + /// Check for duplicate channel names. + fn already_contains(&self, name: &Text) -> bool; +} + +impl CheckDuplicates for NoneMore { + fn already_contains(&self, _: &Text) -> bool { false } +} + +impl CheckDuplicates for Recursive { + fn already_contains(&self, name: &Text) -> bool { + &self.value.name == name || self.inner.already_contains(name) + } +} + +impl SpecificChannels<(),()> +{ + /// Start building some specific channels. On the result of this function, + /// call `with_named_channel` as many times as desired, + /// and then call `with_pixels` to define the colors. + pub fn build() -> SpecificChannelsBuilder { + SpecificChannelsBuilder { channels: NoneMore, px: Default::default() } + } +} + +impl SpecificChannelsBuilder +{ + /// Add another channel to this image. Does not add the actual pixels, + /// but instead only declares the presence of the channel. + /// Panics if the name contains unsupported characters. + /// Panics if a channel with the same name already exists. + /// Use `Text::new_or_none()` to manually handle these cases. + /// Use `with_channel_details` instead if you want to specify more options than just the name of the channel. + /// The generic parameter can usually be inferred from the closure in `with_pixels`. + pub fn with_channel(self, name: impl Into) + -> SpecificChannelsBuilder, Recursive> + { + self.with_channel_details::(ChannelDescription::named(name, Sample::PREFERRED_SAMPLE_TYPE)) + } + + /// Add another channel to this image. Does not add the actual pixels, + /// but instead only declares the presence of the channel. + /// Use `with_channel` instead if you only want to specify the name of the channel. + /// Panics if a channel with the same name already exists. + /// The generic parameter can usually be inferred from the closure in `with_pixels`. + pub fn with_channel_details>(self, channel: ChannelDescription) + -> SpecificChannelsBuilder, Recursive> + { + // duplicate channel names are checked later, but also check now to make sure there are no problems with the `SpecificChannelsWriter` + assert!(self.channels.already_contains(&channel.name).not(), "channel name `{}` is duplicate", channel.name); + + SpecificChannelsBuilder { + channels: Recursive::new(self.channels, channel), + px: PhantomData::default() + } + } + + /// Specify the actual pixel contents of the image. + /// You can pass a closure that returns a color for each pixel (`Fn(Vec2) -> Pixel`), + /// or you can pass your own image if it implements `GetPixel`. + /// The pixel type must be a tuple with the correct number of entries, depending on the number of channels. + /// The tuple entries can be either `f16`, `f32`, `u32` or `Sample`. + /// Use `with_pixel_fn` instead of this function, to get extra type safety for your pixel closure. + pub fn with_pixels(self, get_pixel: Pixels) -> SpecificChannels + where Pixels: GetPixel, ::Pixel: IntoRecursive, + { + SpecificChannels { + channels: self.channels, + pixels: get_pixel + } + } + + /// Specify the contents of the image. + /// The pixel type must be a tuple with the correct number of entries, depending on the number of channels. + /// The tuple entries can be either `f16`, `f32`, `u32` or `Sample`. + /// Use `with_pixels` instead of this function, if you want to pass an object that is not a closure. + /// + /// Usually, the compiler can infer the type of the pixel (for example, `f16,f32,f32`) from the closure. + /// If that's not possible, you can specify the type of the channels + /// when declaring the channel (for example, `with_named_channel::("R")`). + pub fn with_pixel_fn(self, get_pixel: Pixels) -> SpecificChannels + where Pixels: Sync + Fn(Vec2) -> Pixel, Pixel: IntoRecursive, + { + SpecificChannels { + channels: self.channels, + pixels: get_pixel + } + } +} + +impl SpecificChannels< + SampleStorage, (ChannelDescription, ChannelDescription, ChannelDescription, ChannelDescription) +> +{ + + /// Create an image with red, green, blue, and alpha channels. + /// You can pass a closure that returns a color for each pixel (`Fn(Vec2) -> (R,G,B,A)`), + /// or you can pass your own image if it implements `GetPixel`. + /// Each of `R`, `G`, `B` and `A` can be either `f16`, `f32`, `u32`, or `Sample`. + pub fn rgba(source_samples: SampleStorage) -> Self + where R: IntoSample, G: IntoSample, + B: IntoSample, A: IntoSample, + SampleStorage: GetPixel + { + SpecificChannels { + channels: ( + ChannelDescription::named("R", R::PREFERRED_SAMPLE_TYPE), + ChannelDescription::named("G", G::PREFERRED_SAMPLE_TYPE), + ChannelDescription::named("B", B::PREFERRED_SAMPLE_TYPE), + ChannelDescription::named("A", A::PREFERRED_SAMPLE_TYPE), + ), + pixels: source_samples + } + } +} + +impl SpecificChannels< + SampleStorage, (ChannelDescription, ChannelDescription, ChannelDescription) +> +{ + + /// Create an image with red, green, and blue channels. + /// You can pass a closure that returns a color for each pixel (`Fn(Vec2) -> (R,G,B)`), + /// or you can pass your own image if it implements `GetPixel`. + /// Each of `R`, `G` and `B` can be either `f16`, `f32`, `u32`, or `Sample`. + pub fn rgb(source_samples: SampleStorage) -> Self + where R: IntoSample, G: IntoSample, B: IntoSample, + SampleStorage: GetPixel + { + SpecificChannels { + channels: ( + ChannelDescription::named("R", R::PREFERRED_SAMPLE_TYPE), + ChannelDescription::named("G", G::PREFERRED_SAMPLE_TYPE), + ChannelDescription::named("B", B::PREFERRED_SAMPLE_TYPE), + ), + pixels: source_samples + } + } +} + + +/// A list of samples representing a single pixel. +/// Does not heap allocate for images with 8 or fewer channels. +pub type FlatSamplesPixel = SmallVec<[Sample; 8]>; + +// TODO also deep samples? +impl Layer> { + + /// Use `samples_at` if you can borrow from this layer + pub fn sample_vec_at(&self, position: Vec2) -> FlatSamplesPixel { + self.samples_at(position).collect() + } + + /// Lookup all channels of a single pixel in the image + pub fn samples_at(&self, position: Vec2) -> FlatSampleIterator<'_> { + FlatSampleIterator { + layer: self, + channel_index: 0, + position + } + } +} + +/// Iterate over all channels of a single pixel in the image +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct FlatSampleIterator<'s> { + layer: &'s Layer>, + channel_index: usize, + position: Vec2, +} + +impl Iterator for FlatSampleIterator<'_> { + type Item = Sample; + + fn next(&mut self) -> Option { + if self.channel_index < self.layer.channel_data.list.len() { + let channel = &self.layer.channel_data.list[self.channel_index]; + let sample = channel.sample_data.value_by_flat_index(self.position.flat_index_for_size(self.layer.size)); + self.channel_index += 1; + Some(sample) + } + else { None } + } + + fn nth(&mut self, pos: usize) -> Option { + self.channel_index += pos; + self.next() + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = self.layer.channel_data.list.len().saturating_sub(self.channel_index); + (remaining, Some(remaining)) + } +} + +impl ExactSizeIterator for FlatSampleIterator<'_> {} + +impl AnyChannels{ + + /// A new list of arbitrary channels. Sorts the list to make it alphabetically stable. + pub fn sort(mut list: SmallVec<[AnyChannel; 4]>) -> Self { + list.sort_unstable_by_key(|channel| channel.name.clone()); // TODO no clone? + Self { list } + } +} + +// FIXME check content size of layer somewhere??? before writing? +impl Levels { + + /// Get a resolution level by index, sorted by size, decreasing. + pub fn get_level(&self, level: Vec2) -> Result<&LevelSamples> { + match self { + Levels::Singular(block) => { + debug_assert_eq!(level, Vec2(0,0), "singular image cannot write leveled blocks bug"); + Ok(block) + }, + + Levels::Mip { level_data, .. } => { + debug_assert_eq!(level.x(), level.y(), "mip map levels must be equal on x and y bug"); + level_data.get(level.x()).ok_or(Error::invalid("block mip level index")) + }, + + Levels::Rip { level_data, .. } => { + level_data.get_by_level(level).ok_or(Error::invalid("block rip level index")) + } + } + } + + /// Get a resolution level by index, sorted by size, decreasing. + // TODO storage order for RIP maps? + pub fn get_level_mut(&mut self, level: Vec2) -> Result<&mut LevelSamples> { + match self { + Levels::Singular(ref mut block) => { + debug_assert_eq!(level, Vec2(0,0), "singular image cannot write leveled blocks bug"); + Ok(block) + }, + + Levels::Mip { level_data, .. } => { + debug_assert_eq!(level.x(), level.y(), "mip map levels must be equal on x and y bug"); + level_data.get_mut(level.x()).ok_or(Error::invalid("block mip level index")) + }, + + Levels::Rip { level_data, .. } => { + level_data.get_by_level_mut(level).ok_or(Error::invalid("block rip level index")) + } + } + } + + /// Get a slice of all resolution levels, sorted by size, decreasing. + pub fn levels_as_slice(&self) -> &[LevelSamples] { + match self { + Levels::Singular(data) => std::slice::from_ref(data), + Levels::Mip { level_data, .. } => level_data, + Levels::Rip { level_data, .. } => &level_data.map_data, + } + } + + /// Get a mutable slice of all resolution levels, sorted by size, decreasing. + pub fn levels_as_slice_mut(&mut self) -> &mut [LevelSamples] { + match self { + Levels::Singular(data) => std::slice::from_mut(data), + Levels::Mip { level_data, .. } => level_data, + Levels::Rip { level_data, .. } => &mut level_data.map_data, + } + } + + // TODO simplify working with levels in general! like level_size_by_index and such + + /*pub fn levels_with_size(&self, rounding: RoundingMode, max_resolution: Vec2) -> Vec<(Vec2, &S)> { + match self { + Levels::Singular(ref data) => vec![ (max_resolution, data) ], + Levels::Mip(ref maps) => mip_map_levels(rounding, max_resolution).map(|(_index, size)| size).zip(maps).collect(), + Levels::Rip(ref rip_maps) => rip_map_levels(rounding, max_resolution).map(|(_index, size)| size).zip(&rip_maps.map_data).collect(), + } + }*/ + + /// Whether this stores multiple resolution levels. + pub fn level_mode(&self) -> LevelMode { + match self { + Levels::Singular(_) => LevelMode::Singular, + Levels::Mip { .. } => LevelMode::MipMap, + Levels::Rip { .. } => LevelMode::RipMap, + } + } +} + +impl RipMaps { + + /// Flatten the 2D level index to a one dimensional index. + pub fn get_level_index(&self, level: Vec2) -> usize { + level.flat_index_for_size(self.level_count) + } + + /// Return a level by level index. Level `0` has the largest resolution. + pub fn get_by_level(&self, level: Vec2) -> Option<&Samples> { + self.map_data.get(self.get_level_index(level)) + } + + /// Return a mutable level reference by level index. Level `0` has the largest resolution. + pub fn get_by_level_mut(&mut self, level: Vec2) -> Option<&mut Samples> { + let index = self.get_level_index(level); + self.map_data.get_mut(index) + } +} + +impl FlatSamples { + + /// The number of samples in the image. Should be the width times the height. + /// Might vary when subsampling is used. + pub fn len(&self) -> usize { + match self { + FlatSamples::F16(vec) => vec.len(), + FlatSamples::F32(vec) => vec.len(), + FlatSamples::U32(vec) => vec.len(), + } + } + + /// Views all samples in this storage as f32. + /// Matches the underlying sample type again for every sample, + /// match yourself if performance is critical! Does not allocate. + pub fn values_as_f32<'s>(&'s self) -> impl 's + Iterator { + self.values().map(|sample| sample.to_f32()) + } + + /// All samples in this storage as iterator. + /// Matches the underlying sample type again for every sample, + /// match yourself if performance is critical! Does not allocate. + pub fn values<'s>(&'s self) -> impl 's + Iterator { + (0..self.len()).map(move |index| self.value_by_flat_index(index)) + } + + /// Lookup a single value, by flat index. + /// The flat index can be obtained using `Vec2::flatten_for_width` + /// which computes the index in a flattened array of pixel rows. + pub fn value_by_flat_index(&self, index: usize) -> Sample { + match self { + FlatSamples::F16(vec) => Sample::F16(vec[index]), + FlatSamples::F32(vec) => Sample::F32(vec[index]), + FlatSamples::U32(vec) => Sample::U32(vec[index]), + } + } +} + + +impl<'s, ChannelData:'s> Layer { + + /// Create a layer with the specified size, attributes, encoding and channels. + /// The channels can be either `SpecificChannels` or `AnyChannels`. + pub fn new( + dimensions: impl Into>, + attributes: LayerAttributes, + encoding: Encoding, + channels: ChannelData + ) -> Self + where ChannelData: WritableChannels<'s> + { + Layer { channel_data: channels, attributes, size: dimensions.into(), encoding } + } + + // TODO test pls wtf + /// Panics for images with Scanline encoding. + pub fn levels_with_resolution<'l, L>(&self, levels: &'l Levels) -> Box)>> { + match levels { + Levels::Singular(level) => Box::new(std::iter::once((level, self.size))), + + Levels::Mip { rounding_mode, level_data } => Box::new(level_data.iter().zip( + mip_map_levels(*rounding_mode, self.size) + .map(|(_index, size)| size) + )), + + Levels::Rip { rounding_mode, level_data } => Box::new(level_data.map_data.iter().zip( + rip_map_levels(*rounding_mode, self.size) + .map(|(_index, size)| size) + )), + } + } +} + +impl Encoding { + + /// No compression. Massive space requirements. + /// Fast, because it minimizes data shuffling and reallocation. + pub const UNCOMPRESSED: Encoding = Encoding { + compression: Compression::Uncompressed, + blocks: Blocks::ScanLines, // longest lines, faster memcpy + line_order: LineOrder::Increasing // presumably fastest? + }; + + /// Run-length encoding with tiles of 64x64 pixels. This is the recommended default encoding. + /// Almost as fast as uncompressed data, but optimizes single-colored areas such as mattes and masks. + pub const FAST_LOSSLESS: Encoding = Encoding { + compression: Compression::RLE, + blocks: Blocks::Tiles(Vec2(64, 64)), // optimize for RLE compression + line_order: LineOrder::Unspecified + }; + + /// ZIP compression with blocks of 16 lines. Slow, but produces small files without visible artefacts. + pub const SMALL_LOSSLESS: Encoding = Encoding { + compression: Compression::ZIP16, + blocks: Blocks::ScanLines, // largest possible, but also with high probability of parallel workers + line_order: LineOrder::Increasing + }; + + /// PIZ compression with tiles of 256x256 pixels. Small images, not too slow. + pub const SMALL_FAST_LOSSLESS: Encoding = Encoding { + compression: Compression::PIZ, + blocks: Blocks::Tiles(Vec2(256, 256)), + line_order: LineOrder::Unspecified + }; +} + +impl Default for Encoding { + fn default() -> Self { Encoding::FAST_LOSSLESS } +} + +impl<'s, LayerData: 's> Image where LayerData: WritableLayers<'s> { + /// Create an image with one or multiple layers. The layer can be a `Layer`, or `Layers` small vector, or `Vec` or `&[Layer]`. + pub fn new(image_attributes: ImageAttributes, layer_data: LayerData) -> Self { + Image { attributes: image_attributes, layer_data } + } +} + +// explorable constructor alias +impl<'s, Channels: 's> Image> where Channels: WritableChannels<'s> { + /// Create an image with multiple layers. The layer can be a `Vec` or `Layers` (a small vector). + pub fn from_layers(image_attributes: ImageAttributes, layer_data: impl Into>) -> Self { + Self::new(image_attributes, layer_data.into()) + } +} + + +impl<'s, ChannelData:'s> Image> where ChannelData: WritableChannels<'s> { + + /// Uses the display position and size to the channel position and size of the layer. + pub fn from_layer(layer: Layer) -> Self { + let bounds = IntegerBounds::new(layer.attributes.layer_position, layer.size); + Self::new(ImageAttributes::new(bounds), layer) + } + + /// Uses empty attributes. + pub fn from_encoded_channels(size: impl Into>, encoding: Encoding, channels: ChannelData) -> Self { + // layer name is not required for single-layer images + Self::from_layer(Layer::new(size, LayerAttributes::default(), encoding, channels)) + } + + /// Uses empty attributes and fast compression. + pub fn from_channels(size: impl Into>, channels: ChannelData) -> Self { + Self::from_encoded_channels(size, Encoding::default(), channels) + } +} + + +impl Image { + + /// Create an empty image, to be filled with layers later on. Add at least one layer to obtain a valid image. + /// Call `with_layer(another_layer)` for each layer you want to add to this image. + pub fn empty(attributes: ImageAttributes) -> Self { Self { attributes, layer_data: NoneMore } } +} + +impl<'s, InnerLayers: 's> Image where + InnerLayers: WritableLayers<'s>, +{ + /// Add another layer to this image. The layer type does + /// not have to equal the existing layers in this image. + pub fn with_layer(self, layer: Layer) + -> Image>> + where NewChannels: 's + WritableChannels<'s> + { + Image { + attributes: self.attributes, + layer_data: Recursive::new(self.layer_data, layer) + } + } +} + + +impl<'s, SampleData: 's> AnyChannel { + + /// Create a new channel without subsampling. + /// + /// Automatically flags this channel for specialized compression + /// if the name is "R", "G", "B", "Y", or "L", + /// as they typically encode values that are perceived non-linearly. + /// Construct the value yourself using `AnyChannel { .. }`, if you want to control this flag. + pub fn new(name: impl Into, sample_data: SampleData) -> Self where SampleData: WritableSamples<'s> { + let name: Text = name.into(); + + AnyChannel { + quantize_linearly: ChannelDescription::guess_quantization_linearity(&name), + name, sample_data, + sampling: Vec2(1, 1), + } + } + + /*/// This is the same as `AnyChannel::new()`, but additionally ensures that the closure type is correct. + pub fn from_closure(name: Text, sample_data: S) -> Self + where S: Sync + Fn(Vec2) -> V, V: InferSampleType + Data + { + Self::new(name, sample_data) + }*/ +} + +impl std::fmt::Debug for FlatSamples { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.len() <= 6 { + match self { + FlatSamples::F16(vec) => vec.fmt(formatter), + FlatSamples::F32(vec) => vec.fmt(formatter), + FlatSamples::U32(vec) => vec.fmt(formatter), + } + } + else { + match self { + FlatSamples::F16(vec) => write!(formatter, "[f16; {}]", vec.len()), + FlatSamples::F32(vec) => write!(formatter, "[f32; {}]", vec.len()), + FlatSamples::U32(vec) => write!(formatter, "[u32; {}]", vec.len()), + } + } + } +} + + + +/// Compare the result of a round trip test with the original method. +/// Supports lossy compression methods. +// #[cfg(test)] TODO do not ship this code +pub mod validate_results { + use crate::prelude::*; + use smallvec::Array; + use crate::prelude::recursive::*; + use crate::image::write::samples::WritableSamples; + use std::ops::Not; + use crate::block::samples::IntoNativeSample; + + + /// Compare two objects, but with a few special quirks. + /// Intended mainly for unit testing. + pub trait ValidateResult { + + /// Compare self with the other. Panics if not equal. + /// + /// Exceptional behaviour: + /// This does not work the other way around! This method is not symmetrical! + /// Returns whether the result is correct for this image. + /// For lossy compression methods, uses approximate equality. + /// Intended for unit testing. + /// + /// Warning: If you use `SpecificChannels`, the comparison might be inaccurate + /// for images with mixed compression methods. This is to be used with `AnyChannels` mainly. + fn assert_equals_result(&self, result: &Self) { + self.validate_result(result, ValidationOptions::default(), || String::new()).unwrap(); + } + + /// Compare self with the other. + /// Exceptional behaviour: + /// - Any two NaN values are considered equal, regardless of bit representation. + /// - If a `lossy` is specified, any two values that differ only by a small amount will be considered equal. + /// - If `nan_to_zero` is true, and __self is NaN/Infinite and the other value is zero, they are considered equal__ + /// (because some compression methods replace nan with zero) + /// + /// This does not work the other way around! This method is not symmetrical! + fn validate_result( + &self, lossy_result: &Self, + options: ValidationOptions, + // this is a lazy string, because constructing a string is only necessary in the case of an error, + // but eats up memory and allocation time every time. this was measured. + context: impl Fn() -> String + ) -> ValidationResult; + } + + /// Whether to do accurate or approximate comparison. + #[derive(Default, Debug, Eq, PartialEq, Hash, Copy, Clone)] + pub struct ValidationOptions { + allow_lossy: bool, + nan_converted_to_zero: bool, + } + + /// If invalid, contains the error message. + pub type ValidationResult = std::result::Result<(), String>; + + + impl ValidateResult for Image where C: ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + if self.attributes != other.attributes { Err(location() + "| image > attributes") } + else { self.layer_data.validate_result(&other.layer_data, options, || location() + "| image > layer data") } + } + } + + impl ValidateResult for Layer> + where AnyChannel: ValidateResult, S: for<'a> WritableSamples<'a> + { + fn validate_result(&self, other: &Self, _overridden: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + let location = || format!("{} (layer `{:?}`)", location(), self.attributes.layer_name); + if self.attributes != other.attributes { Err(location() + " > attributes") } + else if self.encoding != other.encoding { Err(location() + " > encoding") } + else if self.size != other.size { Err(location() + " > size") } + else if self.channel_data.list.len() != other.channel_data.list.len() { Err(location() + " > channel count") } + else { + for (own_chan, other_chan) in self.channel_data.list.iter().zip(other.channel_data.list.iter()) { + own_chan.validate_result( + other_chan, + + ValidationOptions { + // no tolerance for lossless channels + allow_lossy: other.encoding.compression + .is_lossless_for(other_chan.sample_data.sample_type()).not(), + + // consider nan and zero equal if the compression method does not support nan + nan_converted_to_zero: other.encoding.compression.supports_nan().not() + }, + + || format!("{} > channel `{}`", location(), own_chan.name) + )?; + } + Ok(()) + } + } + } + + impl ValidateResult for Layer> + where SpecificChannels: ValidateResult + { + /// This does an approximate comparison for all channels, + /// even if some channels can be compressed without loss. + fn validate_result(&self, other: &Self, _overridden: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + let location = || format!("{} (layer `{:?}`)", location(), self.attributes.layer_name); + + // TODO dedup with above + if self.attributes != other.attributes { Err(location() + " > attributes") } + else if self.encoding != other.encoding { Err(location() + " > encoding") } + else if self.size != other.size { Err(location() + " > size") } + else { + let options = ValidationOptions { + // no tolerance for lossless channels + // pxr only looses data for f32 values, B44 only for f16, not other any other types + allow_lossy: other.encoding.compression.may_loose_data(),// TODO check specific channels sample types + + // consider nan and zero equal if the compression method does not support nan + nan_converted_to_zero: other.encoding.compression.supports_nan().not() + }; + + self.channel_data.validate_result(&other.channel_data, options, || location() + " > channel_data")?; + Ok(()) + } + } + } + + impl ValidateResult for AnyChannels where S: ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + self.list.validate_result(&other.list, options, location) + } + } + + impl ValidateResult for AnyChannel where S: ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + if self.name != other.name { Err(location() + " > name") } + else if self.quantize_linearly != other.quantize_linearly { Err(location() + " > quantize_linearly") } + else if self.sampling != other.sampling { Err(location() + " > sampling") } + else { + self.sample_data.validate_result(&other.sample_data, options, || location() + " > sample_data") + } + } + } + + impl ValidateResult for SpecificChannels where Pxs: ValidateResult, Chans: Eq { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + if self.channels != other.channels { Err(location() + " > specific channels") } + else { self.pixels.validate_result(&other.pixels, options, || location() + " > specific pixels") } + } + } + + impl ValidateResult for Levels where S: ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + self.levels_as_slice().validate_result(&other.levels_as_slice(), options, || location() + " > levels") + } + } + + impl ValidateResult for FlatSamples { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + use FlatSamples::*; + match (self, other) { + (F16(values), F16(other_values)) => values.as_slice().validate_result(&other_values.as_slice(), options, ||location() + " > f16 samples"), + (F32(values), F32(other_values)) => values.as_slice().validate_result(&other_values.as_slice(), options, ||location() + " > f32 samples"), + (U32(values), U32(other_values)) => values.as_slice().validate_result(&other_values.as_slice(), options, ||location() + " > u32 samples"), + (own, other) => Err(format!("{}: samples type mismatch. expected {:?}, found {:?}", location(), own.sample_type(), other.sample_type())) + } + } + } + + impl ValidateResult for &[T] where T: ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + if self.len() != other.len() { Err(location() + " count") } + else { + for (index, (slf, other)) in self.iter().zip(other.iter()).enumerate() { + slf.validate_result(other, options, ||format!("{} element [{}] of {}", location(), index, self.len()))?; + } + Ok(()) + } + } + } + + impl ValidateResult for SmallVec where A::Item: ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + self.as_slice().validate_result(&other.as_slice(), options, location) + } + } + + impl ValidateResult for Vec where A: ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + self.as_slice().validate_result(&other.as_slice(), options, location) + } + } + + impl ValidateResult for (A, B, C, D) where A: Clone+ ValidateResult, B: Clone+ ValidateResult, C: Clone+ ValidateResult, D: Clone+ ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + self.clone().into_recursive().validate_result(&other.clone().into_recursive(), options, location) + } + } + + impl ValidateResult for (A, B, C) where A: Clone+ ValidateResult, B: Clone+ ValidateResult, C: Clone+ ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + self.clone().into_recursive().validate_result(&other.clone().into_recursive(), options, location) + } + } + + // // (low priority because it is only used in the tests) + /*TODO + impl SimilarToLossy for Tuple where + Tuple: Clone + IntoRecursive, + ::Recursive: SimilarToLossy, + { + fn similar_to_lossy(&self, other: &Self, max_difference: f32) -> bool { + self.clone().into_recursive().similar_to_lossy(&other.clone().into_recursive(), max_difference) + } // TODO no clone? + }*/ + + + // implement for recursive types + impl ValidateResult for NoneMore { + fn validate_result(&self, _: &Self, _: ValidationOptions, _: impl Fn()->String) -> ValidationResult { Ok(()) } + } + + impl ValidateResult for Recursive where Inner: ValidateResult, T: ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + self.value.validate_result(&other.value, options, &location).and_then(|()| + self.inner.validate_result(&other.inner, options, &location) + ) + } + } + + impl ValidateResult for Option where S: ValidateResult { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + match (self, other) { + (None, None) => Ok(()), + (Some(value), Some(other)) => value.validate_result(other, options, location), + _ => Err(location() + ": option mismatch") + } + } + } + + impl ValidateResult for f32 { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + if self == other || (self.is_nan() && other.is_nan()) || (options.nan_converted_to_zero && !self.is_normal() && *other == 0.0) { + return Ok(()); + } + + if options.allow_lossy { + let epsilon = 0.06; + let max_difference = 0.1; + + let adaptive_threshold = epsilon * (self.abs() + other.abs()); + let tolerance = adaptive_threshold.max(max_difference); + let difference = (self - other).abs(); + + return if difference <= tolerance { Ok(()) } + else { Err(format!("{}: expected ~{}, found {} (adaptive tolerance {})", location(), self, other, tolerance)) }; + } + + Err(format!("{}: expected exactly {}, found {}", location(), self, other)) + } + } + + impl ValidateResult for f16 { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + if self.to_bits() == other.to_bits() { Ok(()) } else { + self.to_f32().validate_result(&other.to_f32(), options, location) + } + } + } + + impl ValidateResult for u32 { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + if self == other { Ok(()) } else { // todo to float conversion resulting in nan/infinity? + self.to_f32().validate_result(&other.to_f32(), options, location) + } + } + } + + impl ValidateResult for Sample { + fn validate_result(&self, other: &Self, options: ValidationOptions, location: impl Fn()->String) -> ValidationResult { + use Sample::*; + match (self, other) { + (F16(a), F16(b)) => a.validate_result(b, options, ||location() + " (f16)"), + (F32(a), F32(b)) => a.validate_result(b, options, ||location() + " (f32)"), + (U32(a), U32(b)) => a.validate_result(b, options, ||location() + " (u32)"), + (_,_) => Err(location() + ": sample type mismatch") + } + } + } + + + #[cfg(test)] + mod test_value_result { + use std::f32::consts::*; + use std::io::Cursor; + use crate::image::pixel_vec::PixelVec; + use crate::image::validate_results::{ValidateResult, ValidationOptions}; + use crate::meta::attribute::LineOrder::Increasing; + use crate::image::{FlatSamples}; + + fn expect_valid(original: &T, result: &T, allow_lossy: bool, nan_converted_to_zero: bool) where T: ValidateResult { + original.validate_result( + result, + ValidationOptions { allow_lossy, nan_converted_to_zero }, + || String::new() + ).unwrap(); + } + + fn expect_invalid(original: &T, result: &T, allow_lossy: bool, nan_converted_to_zero: bool) where T: ValidateResult { + assert!(original.validate_result( + result, + ValidationOptions { allow_lossy, nan_converted_to_zero }, + || String::new() + ).is_err()); + } + + #[test] + fn test_f32(){ + let original:&[f32] = &[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, -20.4, f32::NAN]; + let lossy:&[f32] = &[0.0, 0.2, 0.2, 0.3, 0.4, 0.5, -20.5, f32::NAN]; + + expect_valid(&original, &original, true, true); + expect_valid(&original, &original, true, false); + expect_valid(&original, &original, false, true); + expect_valid(&original, &original, false, false); + + expect_invalid(&original, &lossy, false, false); + expect_valid(&original, &lossy, true, false); + + expect_invalid(&original, &&original[..original.len()-2], true, true); + + // test relative comparison with some large values + expect_valid(&1_000_f32, &1_001_f32, true, false); + expect_invalid(&1_000_f32, &1_200_f32, true, false); + + expect_valid(&10_000_f32, &10_100_f32, true, false); + expect_invalid(&10_000_f32, &12_000_f32, true, false); + + expect_valid(&33_120_f32, &30_120_f32, true, false); + expect_invalid(&33_120_f32, &20_120_f32, true, false); + } + + #[test] + fn test_nan(){ + let original:&[f32] = &[ 0.0, f32::NAN, f32::NAN ]; + let lossy:&[f32] = &[ 0.0, f32::NAN, 0.0 ]; + + expect_valid(&original, &lossy, true, true); + expect_invalid(&lossy, &original, true, true); + + expect_valid(&lossy, &lossy, true, true); + expect_valid(&lossy, &lossy, false, true); + } + + #[test] + fn test_error(){ + + fn print_error(original: &T, lossy: &T, allow_lossy: bool){ + let message = original + .validate_result( + &lossy, + ValidationOptions { allow_lossy, .. Default::default() }, + || String::new() // type_name::().to_string() + ) + .unwrap_err(); + + println!("message: {}", message); + } + + let original:&[f32] = &[ 0.0, f32::NAN, f32::NAN ]; + let lossy:&[f32] = &[ 0.0, f32::NAN, 0.0 ]; + print_error(&original, &lossy, false); + + print_error(&2.0, &1.0, true); + print_error(&2.0, &1.0, false); + + print_error(&FlatSamples::F32(vec![0.1,0.1]), &FlatSamples::F32(vec![0.1,0.2]), false); + print_error(&FlatSamples::U32(vec![0,0]), &FlatSamples::F32(vec![0.1,0.2]), false); + + { + let image = crate::prelude::read_all_data_from_file("tests/images/valid/openexr/MultiResolution/Kapaa.exr").unwrap(); + + let mut mutated = image.clone(); + let samples = mutated.layer_data.first_mut().unwrap() + .channel_data.list.first_mut().unwrap().sample_data.levels_as_slice_mut().first_mut().unwrap(); + + match samples { + FlatSamples::F16(vals) => vals[100] = vals[1], + FlatSamples::F32(vals) => vals[100] = vals[1], + FlatSamples::U32(vals) => vals[100] = vals[1], + } + + print_error(&image, &mutated, false); + } + + // TODO check out more nested behaviour! + } + + #[test] + fn test_uncompressed(){ + use crate::prelude::*; + + let original_pixels: [(f32,f32,f32); 4] = [ + (0.0, -1.1, PI), + (0.0, -1.1, TAU), + (0.0, -1.1, f32::EPSILON), + (f32::NAN, 10000.1, -1024.009), + ]; + + let mut file_bytes = Vec::new(); + let original_image = Image::from_encoded_channels( + (2,2), + Encoding { + compression: Compression::Uncompressed, + line_order: Increasing, // FIXME unspecified may be optimized to increasing, which destroys test eq + .. 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::<(f32,f32,f32)>::constructor, PixelVec::set_pixel) + .first_valid_layer().all_attributes().from_buffered(Cursor::new(&file_bytes)).unwrap(); + + original_image.assert_equals_result(&original_image); + lossy_image.assert_equals_result(&lossy_image); + original_image.assert_equals_result(&lossy_image); + lossy_image.assert_equals_result(&original_image); + } + + #[test] + fn test_compiles(){ + use crate::prelude::*; + + fn accepts_validatable_value(_: &impl ValidateResult){} + + let object: Levels = Levels::Singular(FlatSamples::F32(Vec::default())); + accepts_validatable_value(&object); + + let object: AnyChannels> = AnyChannels::sort(SmallVec::default()); + accepts_validatable_value(&object); + + let layer: Layer>> = Layer::new((0,0), Default::default(), Default::default(), object); + accepts_validatable_value(&layer); + + let layers: Layers>> = Default::default(); + accepts_validatable_value(&layers); + + let object: Image>>> = Image::from_layer(layer); + object.assert_equals_result(&object); + } + } +} + + -- cgit v1.2.3