//! Contains all meta data attributes. //! Each layer can have any number of [`Attribute`]s, including custom attributes. use smallvec::SmallVec; /// Contains one of all possible attributes. /// Includes a variant for custom attributes. #[derive(Debug, Clone, PartialEq)] pub enum AttributeValue { /// Channel meta data. ChannelList(ChannelList), /// Color space definition. Chromaticities(Chromaticities), /// Compression method of this layer. Compression(Compression), /// This image is an environment map. EnvironmentMap(EnvironmentMap), /// Film roll information. KeyCode(KeyCode), /// Order of the bocks in the file. LineOrder(LineOrder), /// A 3x3 matrix of floats. Matrix3x3(Matrix3x3), /// A 4x4 matrix of floats. Matrix4x4(Matrix4x4), /// 8-bit rgba Preview of the image. Preview(Preview), /// An integer dividend and divisor. Rational(Rational), /// Deep or flat and tiled or scan line. BlockType(BlockType), /// List of texts. TextVector(Vec), /// How to tile up the image. TileDescription(TileDescription), /// Timepoint and more. TimeCode(TimeCode), /// A string of byte-chars. Text(Text), /// 64-bit float F64(f64), /// 32-bit float F32(f32), /// 32-bit signed integer I32(i32), /// 2D integer rectangle. IntegerBounds(IntegerBounds), /// 2D float rectangle. FloatRect(FloatRect), /// 2D integer vector. IntVec2(Vec2), /// 2D float vector. FloatVec2(Vec2), /// 3D integer vector. IntVec3((i32, i32, i32)), /// 3D float vector. FloatVec3((f32, f32, f32)), /// A custom attribute. /// Contains the type name of this value. Custom { /// The name of the type this attribute is an instance of. kind: Text, /// The value, stored in little-endian byte order, of the value. /// Use the `exr::io::Data` trait to extract binary values from this vector. bytes: Vec }, } /// A byte array with each byte being a char. /// This is not UTF an must be constructed from a standard string. // TODO is this ascii? use a rust ascii crate? #[derive(Clone, PartialEq, Ord, PartialOrd, Default)] // hash implemented manually pub struct Text { bytes: TextBytes, } /// Contains time information for this frame within a sequence. /// Also defined methods to compile this information into a /// `TV60`, `TV50` or `Film24` bit sequence, packed into `u32`. /// /// Satisfies the [SMPTE standard 12M-1999](https://en.wikipedia.org/wiki/SMPTE_timecode). /// For more in-depth information, see [philrees.co.uk/timecode](http://www.philrees.co.uk/articles/timecode.htm). #[derive(Copy, Debug, Clone, Eq, PartialEq, Hash, Default)] pub struct TimeCode { /// Hours 0 - 23 are valid. pub hours: u8, /// Minutes 0 - 59 are valid. pub minutes: u8, /// Seconds 0 - 59 are valid. pub seconds: u8, /// Frame Indices 0 - 29 are valid. pub frame: u8, /// Whether this is a drop frame. pub drop_frame: bool, /// Whether this is a color frame. pub color_frame: bool, /// Field Phase. pub field_phase: bool, /// Flags for `TimeCode.binary_groups`. pub binary_group_flags: [bool; 3], /// The user-defined control codes. /// Every entry in this array can use at most 3 bits. /// This results in a maximum value of 15, including 0, for each `u8`. pub binary_groups: [u8; 8] } /// layer type, specifies block type and deepness. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum BlockType { /// Corresponds to the string value `scanlineimage`. ScanLine, /// Corresponds to the string value `tiledimage`. Tile, /// Corresponds to the string value `deepscanline`. DeepScanLine, /// Corresponds to the string value `deeptile`. DeepTile, } /// The string literals used to represent a `BlockType` in a file. pub mod block_type_strings { /// Type attribute text value of flat scan lines pub const SCAN_LINE: &'static [u8] = b"scanlineimage"; /// Type attribute text value of flat tiles pub const TILE: &'static [u8] = b"tiledimage"; /// Type attribute text value of deep scan lines pub const DEEP_SCAN_LINE: &'static [u8] = b"deepscanline"; /// Type attribute text value of deep tiles pub const DEEP_TILE: &'static [u8] = b"deeptile"; } pub use crate::compression::Compression; /// The integer rectangle describing where an layer is placed on the infinite 2D global space. pub type DataWindow = IntegerBounds; /// The integer rectangle limiting which part of the infinite 2D global space should be displayed. pub type DisplayWindow = IntegerBounds; /// An integer dividend and divisor, together forming a ratio. pub type Rational = (i32, u32); /// A float matrix with four rows and four columns. pub type Matrix4x4 = [f32; 4*4]; /// A float matrix with three rows and three columns. pub type Matrix3x3 = [f32; 3*3]; /// A rectangular section anywhere in 2D integer space. /// Valid from minimum coordinate (including) `-1,073,741,822` /// to maximum coordinate (including) `1,073,741,822`, the value of (`i32::MAX/2 -1`). #[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Hash)] pub struct IntegerBounds { /// The top left corner of this rectangle. /// The `Box2I32` includes this pixel if the size is not zero. pub position: Vec2, /// How many pixels to include in this `Box2I32`. /// Extends to the right and downwards. /// Does not include the actual boundary, just like `Vec::len()`. pub size: Vec2, } /// A rectangular section anywhere in 2D float space. #[derive(Clone, Copy, Debug, PartialEq)] pub struct FloatRect { /// The top left corner location of the rectangle (inclusive) pub min: Vec2, /// The bottom right corner location of the rectangle (inclusive) pub max: Vec2 } /// A List of channels. Channels must be sorted alphabetically. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ChannelList { /// The channels in this list. pub list: SmallVec<[ChannelDescription; 5]>, /// The number of bytes that one pixel in this image needs. // FIXME this needs to account for subsampling anywhere? pub bytes_per_pixel: usize, // FIXME only makes sense for flat images! /// The sample type of all channels, if all channels have the same type. pub uniform_sample_type: Option, } /// A single channel in an layer. /// Does not contain the actual pixel data, /// but instead merely describes it. #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ChannelDescription { /// One of "R", "G", or "B" most of the time. pub name: Text, /// U32, F16 or F32. pub sample_type: SampleType, /// This attribute only tells lossy compression methods /// whether this value should be quantized exponentially or linearly. /// /// Should be `false` for red, green, or blue channels. /// Should be `true` for hue, chroma, saturation, or 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, x and y sampling rates for all of its channels must be 1. pub sampling: Vec2, } /// The type of samples in this channel. #[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)] pub enum SampleType { /// This channel contains 32-bit unsigned int values. U32, /// This channel contains 16-bit float values. F16, /// This channel contains 32-bit float values. F32, } /// The color space of the pixels. /// /// If a file doesn't have a chromaticities attribute, display software /// should assume that the file's primaries and the white point match `Rec. ITU-R BT.709-3`. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Chromaticities { /// "Red" location on the CIE XY chromaticity diagram. pub red: Vec2, /// "Green" location on the CIE XY chromaticity diagram. pub green: Vec2, /// "Blue" location on the CIE XY chromaticity diagram. pub blue: Vec2, /// "White" location on the CIE XY chromaticity diagram. pub white: Vec2 } /// If this attribute is present, it describes /// how this texture should be projected onto an environment. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum EnvironmentMap { /// This image is an environment map projected like a world map. LatitudeLongitude, /// This image contains the six sides of a cube. Cube, } /// Uniquely identifies a motion picture film frame. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct KeyCode { /// Identifies a film manufacturer. pub film_manufacturer_code: i32, /// Identifies a film type. pub film_type: i32, /// Specifies the film roll prefix. pub film_roll_prefix: i32, /// Specifies the film count. pub count: i32, /// Specifies the perforation offset. pub perforation_offset: i32, /// Specifies the perforation count of each single frame. pub perforations_per_frame: i32, /// Specifies the perforation count of each single film. pub perforations_per_count: i32, } /// In what order the `Block`s of pixel data appear in a file. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum LineOrder { /// The blocks in the file are ordered in descending rows from left to right. /// When compressing in parallel, this option requires potentially large amounts of memory. /// In that case, use `LineOrder::Unspecified` for best performance. Increasing, /// The blocks in the file are ordered in ascending rows from right to left. /// When compressing in parallel, this option requires potentially large amounts of memory. /// In that case, use `LineOrder::Unspecified` for best performance. Decreasing, /// The blocks are not ordered in a specific way inside the file. /// In multi-core file writing, this option offers the best performance. Unspecified, } /// A small `rgba` image of `i8` values that approximates the real exr image. // TODO is this linear? #[derive(Clone, Eq, PartialEq)] pub struct Preview { /// The dimensions of the preview image. pub size: Vec2, /// An array with a length of 4 × width × height. /// The pixels are stored in `LineOrder::Increasing`. /// Each pixel consists of the four `u8` values red, green, blue, alpha. pub pixel_data: Vec, } /// Describes how the layer is divided into tiles. /// Specifies the size of each tile in the image /// and whether this image contains multiple resolution levels. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct TileDescription { /// The size of each tile. /// Stays the same number of pixels across all levels. pub tile_size: Vec2, /// Whether to also store smaller versions of the image. pub level_mode: LevelMode, /// Whether to round up or down when calculating Mip/Rip levels. pub rounding_mode: RoundingMode, } /// Whether to also store increasingly smaller versions of the original image. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub enum LevelMode { /// Only a single level. Singular, /// Levels with a similar aspect ratio. MipMap, /// Levels with all possible aspect ratios. RipMap, } /// The raw bytes that make up a string in an exr file. /// Each `u8` is a single char. // will mostly be "R", "G", "B" or "deepscanlineimage" pub type TextBytes = SmallVec<[u8; 24]>; /// A byte slice, interpreted as text pub type TextSlice = [u8]; use crate::io::*; use crate::meta::{sequence_end}; use crate::error::*; use crate::math::{RoundingMode, Vec2}; use half::f16; use std::convert::{TryFrom}; use std::borrow::Borrow; use std::hash::{Hash, Hasher}; use bit_field::BitField; fn invalid_type() -> Error { Error::invalid("attribute type mismatch") } impl Text { /// Create a `Text` from an `str` reference. /// Returns `None` if this string contains unsupported chars. pub fn new_or_none(string: impl AsRef) -> Option { let vec : Option = string.as_ref().chars() .map(|character| u8::try_from(character as u64).ok()) .collect(); vec.map(Self::from_bytes_unchecked) } /// Create a `Text` from an `str` reference. /// Panics if this string contains unsupported chars. pub fn new_or_panic(string: impl AsRef) -> Self { Self::new_or_none(string).expect("exr::Text contains unsupported characters") } /// Create a `Text` from a slice of bytes, /// without checking any of the bytes. pub fn from_slice_unchecked(text: &TextSlice) -> Self { Self::from_bytes_unchecked(SmallVec::from_slice(text)) } /// Create a `Text` from the specified bytes object, /// without checking any of the bytes. pub fn from_bytes_unchecked(bytes: TextBytes) -> Self { Text { bytes } } /// The internal ASCII bytes this text is made of. pub fn as_slice(&self) -> &TextSlice { self.bytes.as_slice() } /// Check whether this string is valid, adjusting `long_names` if required. /// If `long_names` is not provided, text length will be entirely unchecked. pub fn validate(&self, null_terminated: bool, long_names: Option<&mut bool>) -> UnitResult { Self::validate_bytes(self.as_slice(), null_terminated, long_names) } /// Check whether some bytes are valid, adjusting `long_names` if required. /// If `long_names` is not provided, text length will be entirely unchecked. pub fn validate_bytes(text: &TextSlice, null_terminated: bool, long_names: Option<&mut bool>) -> UnitResult { if null_terminated && text.is_empty() { return Err(Error::invalid("text must not be empty")); } if let Some(long) = long_names { if text.len() >= 256 { return Err(Error::invalid("text must not be longer than 255")); } if text.len() >= 32 { *long = true; } } Ok(()) } /// The byte count this string would occupy if it were encoded as a null-terminated string. pub fn null_terminated_byte_size(&self) -> usize { self.bytes.len() + sequence_end::byte_size() } /// The byte count this string would occupy if it were encoded as a size-prefixed string. pub fn i32_sized_byte_size(&self) -> usize { self.bytes.len() + i32::BYTE_SIZE } /// Write the length of a string and then the contents with that length. pub fn write_i32_sized(&self, write: &mut W) -> UnitResult { debug_assert!(self.validate( false, None).is_ok(), "text size bug"); i32::write(usize_to_i32(self.bytes.len()), write)?; Self::write_unsized_bytes(self.bytes.as_slice(), write) } /// Without validation, write this instance to the byte stream. fn write_unsized_bytes(bytes: &[u8], write: &mut W) -> UnitResult { u8::write_slice(write, bytes)?; Ok(()) } /// Read the length of a string and then the contents with that length. pub fn read_i32_sized(read: &mut R, max_size: usize) -> Result { let size = i32_to_usize(i32::read(read)?, "vector size")?; Ok(Text::from_bytes_unchecked(SmallVec::from_vec(u8::read_vec(read, size, 1024, Some(max_size), "text attribute length")?))) } /// Read the contents with that length. pub fn read_sized(read: &mut R, size: usize) -> Result { const SMALL_SIZE: usize = 24; // for small strings, read into small vec without heap allocation if size <= SMALL_SIZE { let mut buffer = [0_u8; SMALL_SIZE]; let data = &mut buffer[..size]; read.read_exact(data)?; Ok(Text::from_bytes_unchecked(SmallVec::from_slice(data))) } // for large strings, read a dynamic vec of arbitrary size else { Ok(Text::from_bytes_unchecked(SmallVec::from_vec(u8::read_vec(read, size, 1024, None, "text attribute length")?))) } } /// Write the string contents and a null-terminator. pub fn write_null_terminated(&self, write: &mut W) -> UnitResult { Self::write_null_terminated_bytes(self.as_slice(), write) } /// Write the string contents and a null-terminator. fn write_null_terminated_bytes(bytes: &[u8], write: &mut W) -> UnitResult { debug_assert!(!bytes.is_empty(), "text is empty bug"); // required to avoid mixup with "sequece_end" Text::write_unsized_bytes(bytes, write)?; sequence_end::write(write)?; Ok(()) } /// Read a string until the null-terminator is found. Then skips the null-terminator. pub fn read_null_terminated(read: &mut R, max_len: usize) -> Result { let mut bytes = smallvec![ u8::read(read)? ]; // null-terminated strings are always at least 1 byte loop { match u8::read(read)? { 0 => break, non_terminator => bytes.push(non_terminator), } if bytes.len() > max_len { return Err(Error::invalid("text too long")) } } Ok(Text { bytes }) } /// Allows any text length since it is only used for attribute values, /// but not attribute names, attribute type names, or channel names. fn read_vec_of_i32_sized( read: &mut PeekRead, total_byte_size: usize ) -> Result> { let mut result = Vec::with_capacity(2); // length of the text-vector can be inferred from attribute size let mut processed_bytes = 0; while processed_bytes < total_byte_size { let text = Text::read_i32_sized(read, total_byte_size)?; processed_bytes += ::std::mem::size_of::(); // size i32 of the text processed_bytes += text.bytes.len(); result.push(text); } // the expected byte size did not match the actual text byte size if processed_bytes != total_byte_size { return Err(Error::invalid("text array byte size")) } Ok(result) } /// Allows any text length since it is only used for attribute values, /// but not attribute names, attribute type names, or channel names. fn write_vec_of_i32_sized_texts(write: &mut W, texts: &[Text]) -> UnitResult { // length of the text-vector can be inferred from attribute size for text in texts { text.write_i32_sized(write)?; } Ok(()) } /// The underlying bytes that represent this text. pub fn bytes(&self) -> &[u8] { self.bytes.as_slice() } /// Iterate over the individual chars in this text, similar to `String::chars()`. /// Does not do any heap-allocation but borrows from this instance instead. pub fn chars(&self) -> impl '_ + Iterator { self.bytes.iter().map(|&byte| byte as char) } /// Compare this `exr::Text` with a plain `&str`. pub fn eq(&self, string: &str) -> bool { string.chars().eq(self.chars()) } /// Compare this `exr::Text` with a plain `&str` ignoring capitalization. pub fn eq_case_insensitive(&self, string: &str) -> bool { // this is technically not working for a "turkish i", but those cannot be encoded in exr files anyways let self_chars = self.chars().map(|char| char.to_ascii_lowercase()); let string_chars = string.chars().flat_map(|ch| ch.to_lowercase()); string_chars.eq(self_chars) } } impl PartialEq for Text { fn eq(&self, other: &str) -> bool { self.eq(other) } } impl PartialEq for str { fn eq(&self, other: &Text) -> bool { other.eq(self) } } impl Eq for Text {} impl Borrow for Text { fn borrow(&self) -> &TextSlice { self.as_slice() } } // forwarding implementation. guarantees `text.borrow().hash() == text.hash()` (required for Borrow) impl Hash for Text { fn hash(&self, state: &mut H) { self.bytes.hash(state) } } impl Into for Text { fn into(self) -> String { self.to_string() } } impl<'s> From<&'s str> for Text { /// Panics if the string contains an unsupported character fn from(str: &'s str) -> Self { Self::new_or_panic(str) } } /* TODO (currently conflicts with From<&str>) impl<'s> TryFrom<&'s str> for Text { type Error = String; fn try_from(value: &'s str) -> std::result::Result { Text::new_or_none(value) .ok_or_else(|| format!( "exr::Text does not support all characters in the string `{}`", value )) } }*/ impl ::std::fmt::Debug for Text { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { write!(f, "exr::Text(\"{}\")", self) } } // automatically implements to_string for us impl ::std::fmt::Display for Text { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { use std::fmt::Write; for &byte in self.bytes.iter() { f.write_char(byte as char)?; } Ok(()) } } impl ChannelList { /// Does not validate channel order. pub fn new(channels: SmallVec<[ChannelDescription; 5]>) -> Self { let uniform_sample_type = { if let Some(first) = channels.first() { let has_uniform_types = channels.iter().skip(1) .all(|chan| chan.sample_type == first.sample_type); if has_uniform_types { Some(first.sample_type) } else { None } } else { None } }; ChannelList { bytes_per_pixel: channels.iter().map(|channel| channel.sample_type.bytes_per_sample()).sum(), list: channels, uniform_sample_type, } } /// Iterate over the channels, and adds to each channel the byte offset of the channels sample type. /// Assumes the internal channel list is properly sorted. pub fn channels_with_byte_offset(&self) -> impl Iterator { self.list.iter().scan(0, |byte_position, channel|{ let previous_position = *byte_position; *byte_position += channel.sample_type.bytes_per_sample(); Some((previous_position, channel)) }) } /// Return the index of the channel with the exact name, case sensitive, or none. /// Potentially uses less than linear time. pub fn find_index_of_channel(&self, exact_name: &Text) -> Option { self.list.binary_search_by_key(&exact_name.bytes(), |chan| chan.name.bytes()).ok() } // TODO use this in compression methods /*pub fn pixel_section_indices(&self, bounds: IntegerBounds) -> impl '_ + Iterator { (bounds.position.y() .. bounds.end().y()).flat_map(|y| { self.list .filter(|channel| mod_p(y, usize_to_i32(channel.sampling.1)) == 0) .flat_map(|channel|{ (bounds.position.x() .. bounds.end().x()) .filter(|x| mod_p(*x, usize_to_i32(channel.sampling.0)) == 0) .map(|x| (channel, x, y)) }) }) }*/ } impl BlockType { /// The corresponding attribute type name literal const TYPE_NAME: &'static [u8] = type_names::TEXT; /// Return a `BlockType` object from the specified attribute text value. pub fn parse(text: Text) -> Result { match text.as_slice() { block_type_strings::SCAN_LINE => Ok(BlockType::ScanLine), block_type_strings::TILE => Ok(BlockType::Tile), block_type_strings::DEEP_SCAN_LINE => Ok(BlockType::DeepScanLine), block_type_strings::DEEP_TILE => Ok(BlockType::DeepTile), _ => Err(Error::invalid("block type attribute value")), } } /// Without validation, write this instance to the byte stream. pub fn write(&self, write: &mut impl Write) -> UnitResult { u8::write_slice(write, self.to_text_bytes())?; Ok(()) } /// Returns the raw attribute text value this type is represented by in a file. pub fn to_text_bytes(&self) -> &[u8] { match self { BlockType::ScanLine => block_type_strings::SCAN_LINE, BlockType::Tile => block_type_strings::TILE, BlockType::DeepScanLine => block_type_strings::DEEP_SCAN_LINE, BlockType::DeepTile => block_type_strings::DEEP_TILE, } } /// Number of bytes this would consume in an exr file. pub fn byte_size(&self) -> usize { self.to_text_bytes().len() } } impl IntegerBounds { /// Create a box with no size located at (0,0). pub fn zero() -> Self { Self::from_dimensions(Vec2(0, 0)) } /// Create a box with a size starting at zero. pub fn from_dimensions(size: impl Into>) -> Self { Self::new(Vec2(0,0), size) } /// Create a box with a size and an origin point. pub fn new(start: impl Into>, size: impl Into>) -> Self { Self { position: start.into(), size: size.into() } } /// Returns the top-right coordinate of the rectangle. /// The row and column described by this vector are not included in the rectangle, /// just like `Vec::len()`. pub fn end(self) -> Vec2 { self.position + self.size.to_i32() // larger than max int32 is panic } /// Returns the maximum coordinate that a value in this rectangle may have. pub fn max(self) -> Vec2 { self.end() - Vec2(1,1) } /// Validate this instance. pub fn validate(&self, max_size: Option>) -> UnitResult { if let Some(max_size) = max_size { if self.size.width() > max_size.width() || self.size.height() > max_size.height() { return Err(Error::invalid("window attribute dimension value")); } } let min_i64 = Vec2(self.position.x() as i64, self.position.y() as i64); let max_i64 = Vec2( self.position.x() as i64 + self.size.width() as i64, self.position.y() as i64 + self.size.height() as i64, ); Self::validate_min_max_u64(min_i64, max_i64) } fn validate_min_max_u64(min: Vec2, max: Vec2) -> UnitResult { let max_box_size_as_i64 = (i32::MAX / 2) as i64; // as defined in the original c++ library if max.x() >= max_box_size_as_i64 || max.y() >= max_box_size_as_i64 || min.x() <= -max_box_size_as_i64 || min.y() <= -max_box_size_as_i64 { return Err(Error::invalid("window size exceeding integer maximum")); } Ok(()) } /// Number of bytes this would consume in an exr file. pub fn byte_size() -> usize { 4 * i32::BYTE_SIZE } /// Without validation, write this instance to the byte stream. pub fn write(&self, write: &mut W) -> UnitResult { let Vec2(x_min, y_min) = self.position; let Vec2(x_max, y_max) = self.max(); x_min.write(write)?; y_min.write(write)?; x_max.write(write)?; y_max.write(write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { let x_min = i32::read(read)?; let y_min = i32::read(read)?; let x_max = i32::read(read)?; let y_max = i32::read(read)?; let min = Vec2(x_min.min(x_max), y_min.min(y_max)); let max = Vec2(x_min.max(x_max), y_min.max(y_max)); // prevent addition overflow Self::validate_min_max_u64( Vec2(min.x() as i64, min.y() as i64), Vec2(max.x() as i64, max.y() as i64), )?; // add one to max because the max inclusive, but the size is not let size = Vec2(max.x() + 1 - min.x(), max.y() + 1 - min.y()); let size = size.to_usize("box coordinates")?; Ok(IntegerBounds { position: min, size }) } /// Create a new rectangle which is offset by the specified origin. pub fn with_origin(self, origin: Vec2) -> Self { // TODO rename to "move" or "translate"? IntegerBounds { position: self.position + origin, .. self } } /// Returns whether the specified rectangle is equal to or inside this rectangle. pub fn contains(self, subset: Self) -> bool { subset.position.x() >= self.position.x() && subset.position.y() >= self.position.y() && subset.end().x() <= self.end().x() && subset.end().y() <= self.end().y() } } impl FloatRect { /// Number of bytes this would consume in an exr file. pub fn byte_size() -> usize { 4 * f32::BYTE_SIZE } /// Without validation, write this instance to the byte stream. pub fn write(&self, write: &mut W) -> UnitResult { self.min.x().write(write)?; self.min.y().write(write)?; self.max.x().write(write)?; self.max.y().write(write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { let x_min = f32::read(read)?; let y_min = f32::read(read)?; let x_max = f32::read(read)?; let y_max = f32::read(read)?; Ok(FloatRect { min: Vec2(x_min, y_min), max: Vec2(x_max, y_max) }) } } impl SampleType { /// How many bytes a single sample takes up. pub fn bytes_per_sample(&self) -> usize { match self { SampleType::F16 => f16::BYTE_SIZE, SampleType::F32 => f32::BYTE_SIZE, SampleType::U32 => u32::BYTE_SIZE, } } /// Number of bytes this would consume in an exr file. pub fn byte_size() -> usize { i32::BYTE_SIZE } /// Without validation, write this instance to the byte stream. pub fn write(&self, write: &mut W) -> UnitResult { match *self { SampleType::U32 => 0_i32, SampleType::F16 => 1_i32, SampleType::F32 => 2_i32, }.write(write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { // there's definitely going to be more than 255 different pixel types in the future Ok(match i32::read(read)? { 0 => SampleType::U32, 1 => SampleType::F16, 2 => SampleType::F32, _ => return Err(Error::invalid("pixel type attribute value")), }) } } impl ChannelDescription { /// Choose whether to compress samples linearly or not, based on the channel name. /// Luminance-based channels will be compressed differently than linear data such as alpha. pub fn guess_quantization_linearity(name: &Text) -> bool { !( name.eq_case_insensitive("R") || name.eq_case_insensitive("G") || name.eq_case_insensitive("B") || name.eq_case_insensitive("L") || name.eq_case_insensitive("Y") || name.eq_case_insensitive("X") || name.eq_case_insensitive("Z") ) } /// Create a new channel with the specified properties and a sampling rate of (1,1). /// Automatically chooses the linearity for compression based on the channel name. pub fn named(name: impl Into, sample_type: SampleType) -> Self { let name = name.into(); let linearity = Self::guess_quantization_linearity(&name); Self::new(name, sample_type, linearity) } /*pub fn from_name + Default>(name: impl Into) -> Self { Self::named(name, T::default().into().sample_type()) }*/ /// Create a new channel with the specified properties and a sampling rate of (1,1). pub fn new(name: impl Into, sample_type: SampleType, quantize_linearly: bool) -> Self { Self { name: name.into(), sample_type, quantize_linearly, sampling: Vec2(1, 1) } } /// The count of pixels this channel contains, respecting subsampling. // FIXME this must be used everywhere pub fn subsampled_pixels(&self, dimensions: Vec2) -> usize { self.subsampled_resolution(dimensions).area() } /// The resolution pf this channel, respecting subsampling. pub fn subsampled_resolution(&self, dimensions: Vec2) -> Vec2 { dimensions / self.sampling } /// Number of bytes this would consume in an exr file. pub fn byte_size(&self) -> usize { self.name.null_terminated_byte_size() + SampleType::byte_size() + 1 // is_linear + 3 // reserved bytes + 2 * u32::BYTE_SIZE // sampling x, y } /// Without validation, write this instance to the byte stream. pub fn write(&self, write: &mut W) -> UnitResult { Text::write_null_terminated(&self.name, write)?; self.sample_type.write(write)?; match self.quantize_linearly { false => 0_u8, true => 1_u8, }.write(write)?; i8::write_slice(write, &[0_i8, 0_i8, 0_i8])?; i32::write(usize_to_i32(self.sampling.x()), write)?; i32::write(usize_to_i32(self.sampling.y()), write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { let name = Text::read_null_terminated(read, 256)?; let sample_type = SampleType::read(read)?; let is_linear = match u8::read(read)? { 1 => true, 0 => false, _ => return Err(Error::invalid("channel linearity attribute value")), }; let mut reserved = [0_i8; 3]; i8::read_slice(read, &mut reserved)?; let x_sampling = i32_to_usize(i32::read(read)?, "x channel sampling")?; let y_sampling = i32_to_usize(i32::read(read)?, "y channel sampling")?; Ok(ChannelDescription { name, sample_type, quantize_linearly: is_linear, sampling: Vec2(x_sampling, y_sampling), }) } /// Validate this instance. pub fn validate(&self, allow_sampling: bool, data_window: IntegerBounds, strict: bool) -> UnitResult { self.name.validate(true, None)?; // TODO spec says this does not affect `requirements.long_names` but is that true? if self.sampling.x() == 0 || self.sampling.y() == 0 { return Err(Error::invalid("zero sampling factor")); } if strict && !allow_sampling && self.sampling != Vec2(1,1) { return Err(Error::invalid("subsampling is only allowed in flat scan line images")); } if data_window.position.x() % self.sampling.x() as i32 != 0 || data_window.position.y() % self.sampling.y() as i32 != 0 { return Err(Error::invalid("channel sampling factor not dividing data window position")); } if data_window.size.x() % self.sampling.x() != 0 || data_window.size.y() % self.sampling.y() != 0 { return Err(Error::invalid("channel sampling factor not dividing data window size")); } if self.sampling != Vec2(1,1) { // TODO this must only be implemented in the crate::image module and child modules, // should not be too difficult return Err(Error::unsupported("channel subsampling not supported yet")); } Ok(()) } } impl ChannelList { /// Number of bytes this would consume in an exr file. pub fn byte_size(&self) -> usize { self.list.iter().map(ChannelDescription::byte_size).sum::() + sequence_end::byte_size() } /// Without validation, write this instance to the byte stream. /// Assumes channels are sorted alphabetically and all values are validated. pub fn write(&self, write: &mut impl Write) -> UnitResult { for channel in &self.list { channel.write(write)?; } sequence_end::write(write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut PeekRead) -> Result { let mut channels = SmallVec::new(); while !sequence_end::has_come(read)? { channels.push(ChannelDescription::read(read)?); } Ok(ChannelList::new(channels)) } /// Check if channels are valid and sorted. pub fn validate(&self, allow_sampling: bool, data_window: IntegerBounds, strict: bool) -> UnitResult { let mut iter = self.list.iter().map(|chan| chan.validate(allow_sampling, data_window, strict).map(|_| &chan.name)); let mut previous = iter.next().ok_or(Error::invalid("at least one channel is required"))??; for result in iter { let value = result?; if strict && previous == value { return Err(Error::invalid("channel names are not unique")); } else if previous > value { return Err(Error::invalid("channel names are not sorted alphabetically")); } else { previous = value; } } Ok(()) } } fn u8_to_decimal32(binary: u8) -> u32 { let units = binary as u32 % 10; let tens = (binary as u32 / 10) % 10; units | (tens << 4) } // assumes value fits into u8 fn u8_from_decimal32(coded: u32) -> u8 { ((coded & 0x0f) + 10 * ((coded >> 4) & 0x0f)) as u8 } // https://github.com/AcademySoftwareFoundation/openexr/blob/master/src/lib/OpenEXR/ImfTimeCode.cpp impl TimeCode { /// Number of bytes this would consume in an exr file. pub const BYTE_SIZE: usize = 2 * u32::BYTE_SIZE; /// Returns an error if this time code is considered invalid. pub fn validate(&self, strict: bool) -> UnitResult { if strict { if self.frame > 29 { Err(Error::invalid("time code frame larger than 29")) } else if self.seconds > 59 { Err(Error::invalid("time code seconds larger than 59")) } else if self.minutes > 59 { Err(Error::invalid("time code minutes larger than 59")) } else if self.hours > 23 { Err(Error::invalid("time code hours larger than 23")) } else if self.binary_groups.iter().any(|&group| group > 15) { Err(Error::invalid("time code binary group value too large for 3 bits")) } else { Ok(()) } } else { Ok(()) } } /// Pack the SMPTE time code into a u32 value, according to TV60 packing. /// This is the encoding which is used within a binary exr file. pub fn pack_time_as_tv60_u32(&self) -> Result { // validate strictly to prevent set_bit panic! below self.validate(true)?; Ok(*0_u32 .set_bits(0..6, u8_to_decimal32(self.frame)) .set_bit(6, self.drop_frame) .set_bit(7, self.color_frame) .set_bits(8..15, u8_to_decimal32(self.seconds)) .set_bit(15, self.field_phase) .set_bits(16..23, u8_to_decimal32(self.minutes)) .set_bit(23, self.binary_group_flags[0]) .set_bits(24..30, u8_to_decimal32(self.hours)) .set_bit(30, self.binary_group_flags[1]) .set_bit(31, self.binary_group_flags[2]) ) } /// Unpack a time code from one TV60 encoded u32 value and the encoded user data. /// This is the encoding which is used within a binary exr file. pub fn from_tv60_time(tv60_time: u32, user_data: u32) -> Self { Self { frame: u8_from_decimal32(tv60_time.get_bits(0..6)), // cast cannot fail, as these are less than 8 bits drop_frame: tv60_time.get_bit(6), color_frame: tv60_time.get_bit(7), seconds: u8_from_decimal32(tv60_time.get_bits(8..15)), // cast cannot fail, as these are less than 8 bits field_phase: tv60_time.get_bit(15), minutes: u8_from_decimal32(tv60_time.get_bits(16..23)), // cast cannot fail, as these are less than 8 bits hours: u8_from_decimal32(tv60_time.get_bits(24..30)), // cast cannot fail, as these are less than 8 bits binary_group_flags: [ tv60_time.get_bit(23), tv60_time.get_bit(30), tv60_time.get_bit(31), ], binary_groups: Self::unpack_user_data_from_u32(user_data) } } /// Pack the SMPTE time code into a u32 value, according to TV50 packing. /// This encoding does not support the `drop_frame` flag, it will be lost. pub fn pack_time_as_tv50_u32(&self) -> Result { Ok(*self.pack_time_as_tv60_u32()? // swap some fields by replacing some bits in the packed u32 .set_bit(6, false) .set_bit(15, self.binary_group_flags[0]) .set_bit(30, self.binary_group_flags[1]) .set_bit(23, self.binary_group_flags[2]) .set_bit(31, self.field_phase) ) } /// Unpack a time code from one TV50 encoded u32 value and the encoded user data. /// This encoding does not support the `drop_frame` flag, it will always be false. pub fn from_tv50_time(tv50_time: u32, user_data: u32) -> Self { Self { drop_frame: false, // do not use bit [6] // swap some fields: field_phase: tv50_time.get_bit(31), binary_group_flags: [ tv50_time.get_bit(15), tv50_time.get_bit(30), tv50_time.get_bit(23), ], .. Self::from_tv60_time(tv50_time, user_data) } } /// Pack the SMPTE time code into a u32 value, according to FILM24 packing. /// This encoding does not support the `drop_frame` and `color_frame` flags, they will be lost. pub fn pack_time_as_film24_u32(&self) -> Result { Ok(*self.pack_time_as_tv60_u32()? .set_bit(6, false) .set_bit(7, false) ) } /// Unpack a time code from one TV60 encoded u32 value and the encoded user data. /// This encoding does not support the `drop_frame` and `color_frame` flags, they will always be `false`. pub fn from_film24_time(film24_time: u32, user_data: u32) -> Self { Self { drop_frame: false, // bit [6] color_frame: false, // bit [7] .. Self::from_tv60_time(film24_time, user_data) } } // in rust, group index starts at zero, not at one. fn user_data_bit_indices(group_index: usize) -> std::ops::Range { let min_bit = 4 * group_index; min_bit .. min_bit + 4 // +4, not +3, as `Range` is exclusive } /// Pack the user data `u8` array into one u32. /// User data values are clamped to the valid range (maximum value is 4). pub fn pack_user_data_as_u32(&self) -> u32 { let packed = self.binary_groups.iter().enumerate().fold(0_u32, |mut packed, (group_index, group_value)| *packed.set_bits(Self::user_data_bit_indices(group_index), *group_value.min(&15) as u32) ); debug_assert_eq!(Self::unpack_user_data_from_u32(packed), self.binary_groups, "round trip user data encoding"); packed } // Unpack the encoded u32 user data to an array of bytes, each byte having a value from 0 to 4. fn unpack_user_data_from_u32(user_data: u32) -> [u8; 8] { (0..8).map(|group_index| user_data.get_bits(Self::user_data_bit_indices(group_index)) as u8) .collect::>().into_inner().expect("array index bug") } /// Write this time code to the byte stream, encoded as TV60 integers. /// Returns an `Error::Invalid` if the fields are out of the allowed range. pub fn write(&self, write: &mut W) -> UnitResult { self.pack_time_as_tv60_u32()?.write(write)?; // will validate self.pack_user_data_as_u32().write(write)?; Ok(()) } /// Read the time code, without validating, extracting from TV60 integers. pub fn read(read: &mut R) -> Result { let time_and_flags = u32::read(read)?; let user_data = u32::read(read)?; Ok(Self::from_tv60_time(time_and_flags, user_data)) } } impl Chromaticities { /// Number of bytes this would consume in an exr file. pub fn byte_size() -> usize { 8 * f32::BYTE_SIZE } /// Without validation, write this instance to the byte stream. pub fn write(&self, write: &mut W) -> UnitResult { self.red.x().write(write)?; self.red.y().write(write)?; self.green.x().write(write)?; self.green.y().write(write)?; self.blue.x().write(write)?; self.blue.y().write(write)?; self.white.x().write(write)?; self.white.y().write(write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { Ok(Chromaticities { red: Vec2(f32::read(read)?, f32::read(read)?), green: Vec2(f32::read(read)?, f32::read(read)?), blue: Vec2(f32::read(read)?, f32::read(read)?), white: Vec2(f32::read(read)?, f32::read(read)?), }) } } impl Compression { /// Number of bytes this would consume in an exr file. pub fn byte_size() -> usize { u8::BYTE_SIZE } /// Without validation, write this instance to the byte stream. pub fn write(self, write: &mut W) -> UnitResult { use self::Compression::*; match self { Uncompressed => 0_u8, RLE => 1_u8, ZIP1 => 2_u8, ZIP16 => 3_u8, PIZ => 4_u8, PXR24 => 5_u8, B44 => 6_u8, B44A => 7_u8, DWAA(_) => 8_u8, DWAB(_) => 9_u8, }.write(write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { use self::Compression::*; Ok(match u8::read(read)? { 0 => Uncompressed, 1 => RLE, 2 => ZIP1, 3 => ZIP16, 4 => PIZ, 5 => PXR24, 6 => B44, 7 => B44A, 8 => DWAA(None), 9 => DWAB(None), _ => return Err(Error::unsupported("unknown compression method")), }) } } impl EnvironmentMap { /// Number of bytes this would consume in an exr file. pub fn byte_size() -> usize { u8::BYTE_SIZE } /// Without validation, write this instance to the byte stream. pub fn write(self, write: &mut W) -> UnitResult { use self::EnvironmentMap::*; match self { LatitudeLongitude => 0_u8, Cube => 1_u8 }.write(write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { use self::EnvironmentMap::*; Ok(match u8::read(read)? { 0 => LatitudeLongitude, 1 => Cube, _ => return Err(Error::invalid("environment map attribute value")), }) } } impl KeyCode { /// Number of bytes this would consume in an exr file. pub fn byte_size() -> usize { 6 * i32::BYTE_SIZE } /// Without validation, write this instance to the byte stream. pub fn write(&self, write: &mut W) -> UnitResult { self.film_manufacturer_code.write(write)?; self.film_type.write(write)?; self.film_roll_prefix.write(write)?; self.count.write(write)?; self.perforation_offset.write(write)?; self.perforations_per_count.write(write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { Ok(KeyCode { film_manufacturer_code: i32::read(read)?, film_type: i32::read(read)?, film_roll_prefix: i32::read(read)?, count: i32::read(read)?, perforation_offset: i32::read(read)?, perforations_per_frame: i32::read(read)?, perforations_per_count: i32::read(read)?, }) } } impl LineOrder { /// Number of bytes this would consume in an exr file. pub fn byte_size() -> usize { u8::BYTE_SIZE } /// Without validation, write this instance to the byte stream. pub fn write(self, write: &mut W) -> UnitResult { use self::LineOrder::*; match self { Increasing => 0_u8, Decreasing => 1_u8, Unspecified => 2_u8, }.write(write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { use self::LineOrder::*; Ok(match u8::read(read)? { 0 => Increasing, 1 => Decreasing, 2 => Unspecified, _ => return Err(Error::invalid("line order attribute value")), }) } } impl Preview { /// Number of bytes this would consume in an exr file. pub fn byte_size(&self) -> usize { 2 * u32::BYTE_SIZE + self.pixel_data.len() } /// Without validation, write this instance to the byte stream. pub fn write(&self, write: &mut W) -> UnitResult { u32::write(self.size.width() as u32, write)?; u32::write(self.size.height() as u32, write)?; i8::write_slice(write, &self.pixel_data)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { let width = u32::read(read)? as usize; let height = u32::read(read)? as usize; if let Some(pixel_count) = width.checked_mul(height) { // Multiply by the number of bytes per pixel. if let Some(byte_count) = pixel_count.checked_mul(4) { let pixel_data = i8::read_vec( read, byte_count, 1024 * 1024 * 4, None, "preview attribute pixel count", )?; let preview = Preview { size: Vec2(width, height), pixel_data, }; return Ok(preview); } } return Err(Error::invalid( format!("Overflow while calculating preview image Attribute size \ (width: {}, height: {}).", width, height))); } /// Validate this instance. pub fn validate(&self, strict: bool) -> UnitResult { if strict && (self.size.area() * 4 != self.pixel_data.len()) { return Err(Error::invalid("preview dimensions do not match content length")) } Ok(()) } } impl ::std::fmt::Debug for Preview { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { write!(f, "Preview ({}x{} px)", self.size.width(), self.size.height()) } } impl TileDescription { /// Number of bytes this would consume in an exr file. pub fn byte_size() -> usize { 2 * u32::BYTE_SIZE + 1 // size x,y + (level mode + rounding mode) } /// Without validation, write this instance to the byte stream. pub fn write(&self, write: &mut W) -> UnitResult { u32::write(self.tile_size.width() as u32, write)?; u32::write(self.tile_size.height() as u32, write)?; let level_mode = match self.level_mode { LevelMode::Singular => 0_u8, LevelMode::MipMap => 1_u8, LevelMode::RipMap => 2_u8, }; let rounding_mode = match self.rounding_mode { RoundingMode::Down => 0_u8, RoundingMode::Up => 1_u8, }; let mode: u8 = level_mode + (rounding_mode * 16); mode.write(write)?; Ok(()) } /// Read the value without validating. pub fn read(read: &mut R) -> Result { let x_size = u32::read(read)? as usize; let y_size = u32::read(read)? as usize; let mode = u8::read(read)?; // wow you really saved that one byte here // mode = level_mode + (rounding_mode * 16) let level_mode = mode & 0b00001111; // wow that works let rounding_mode = mode >> 4; // wow that works let level_mode = match level_mode { 0 => LevelMode::Singular, 1 => LevelMode::MipMap, 2 => LevelMode::RipMap, _ => return Err(Error::invalid("tile description level mode")), }; let rounding_mode = match rounding_mode { 0 => RoundingMode::Down, 1 => RoundingMode::Up, _ => return Err(Error::invalid("tile description rounding mode")), }; Ok(TileDescription { tile_size: Vec2(x_size, y_size), level_mode, rounding_mode, }) } /// Validate this instance. pub fn validate(&self) -> UnitResult { let max = i32::MAX as i64 / 2; if self.tile_size.width() == 0 || self.tile_size.height() == 0 || self.tile_size.width() as i64 >= max || self.tile_size.height() as i64 >= max { return Err(Error::invalid("tile size")) } Ok(()) } } /// Number of bytes this attribute would consume in an exr file. // TODO instead of pre calculating byte size, write to a tmp buffer whose length is inspected before actually writing? pub fn byte_size(name: &Text, value: &AttributeValue) -> usize { name.null_terminated_byte_size() + value.kind_name().len() + sequence_end::byte_size() + i32::BYTE_SIZE // serialized byte size + value.byte_size() } /// Without validation, write this attribute to the byte stream. pub fn write(name: &[u8], value: &AttributeValue, write: &mut W) -> UnitResult { Text::write_null_terminated_bytes(name, write)?; Text::write_null_terminated_bytes(value.kind_name(), write)?; i32::write(value.byte_size() as i32, write)?; value.write(write) } /// Read the attribute without validating. The result may be `Ok` even if this single attribute is invalid. pub fn read(read: &mut PeekRead, max_size: usize) -> Result<(Text, Result)> { let name = Text::read_null_terminated(read, max_size)?; let kind = Text::read_null_terminated(read, max_size)?; let size = i32_to_usize(i32::read(read)?, "attribute size")?; let value = AttributeValue::read(read, kind, size)?; Ok((name, value)) } /// Validate this attribute. pub fn validate(name: &Text, value: &AttributeValue, long_names: &mut bool, allow_sampling: bool, data_window: IntegerBounds, strict: bool) -> UnitResult { name.validate(true, Some(long_names))?; // only name text has length restriction value.validate(allow_sampling, data_window, strict) // attribute value text length is never restricted } impl AttributeValue { /// Number of bytes this would consume in an exr file. pub fn byte_size(&self) -> usize { use self::AttributeValue::*; match *self { IntegerBounds(_) => self::IntegerBounds::byte_size(), FloatRect(_) => self::FloatRect::byte_size(), I32(_) => i32::BYTE_SIZE, F32(_) => f32::BYTE_SIZE, F64(_) => f64::BYTE_SIZE, Rational(_) => { i32::BYTE_SIZE + u32::BYTE_SIZE }, TimeCode(_) => self::TimeCode::BYTE_SIZE, IntVec2(_) => { 2 * i32::BYTE_SIZE }, FloatVec2(_) => { 2 * f32::BYTE_SIZE }, IntVec3(_) => { 3 * i32::BYTE_SIZE }, FloatVec3(_) => { 3 * f32::BYTE_SIZE }, ChannelList(ref channels) => channels.byte_size(), Chromaticities(_) => self::Chromaticities::byte_size(), Compression(_) => self::Compression::byte_size(), EnvironmentMap(_) => self::EnvironmentMap::byte_size(), KeyCode(_) => self::KeyCode::byte_size(), LineOrder(_) => self::LineOrder::byte_size(), Matrix3x3(ref value) => value.len() * f32::BYTE_SIZE, Matrix4x4(ref value) => value.len() * f32::BYTE_SIZE, Preview(ref value) => value.byte_size(), // attribute value texts never have limited size. // also, don't serialize size, as it can be inferred from attribute size Text(ref value) => value.bytes.len(), TextVector(ref value) => value.iter().map(self::Text::i32_sized_byte_size).sum(), TileDescription(_) => self::TileDescription::byte_size(), Custom { ref bytes, .. } => bytes.len(), BlockType(ref kind) => kind.byte_size() } } /// The exr name string of the type that an attribute can have. pub fn kind_name(&self) -> &[u8] { use self::AttributeValue::*; use self::type_names as ty; match *self { IntegerBounds(_) => ty::I32BOX2, FloatRect(_) => ty::F32BOX2, I32(_) => ty::I32, F32(_) => ty::F32, F64(_) => ty::F64, Rational(_) => ty::RATIONAL, TimeCode(_) => ty::TIME_CODE, IntVec2(_) => ty::I32VEC2, FloatVec2(_) => ty::F32VEC2, IntVec3(_) => ty::I32VEC3, FloatVec3(_) => ty::F32VEC3, ChannelList(_) => ty::CHANNEL_LIST, Chromaticities(_) => ty::CHROMATICITIES, Compression(_) => ty::COMPRESSION, EnvironmentMap(_) => ty::ENVIRONMENT_MAP, KeyCode(_) => ty::KEY_CODE, LineOrder(_) => ty::LINE_ORDER, Matrix3x3(_) => ty::F32MATRIX3X3, Matrix4x4(_) => ty::F32MATRIX4X4, Preview(_) => ty::PREVIEW, Text(_) => ty::TEXT, TextVector(_) => ty::TEXT_VECTOR, TileDescription(_) => ty::TILES, Custom { ref kind, .. } => &kind.bytes, BlockType(_) => super::BlockType::TYPE_NAME, } } /// Without validation, write this instance to the byte stream. pub fn write(&self, write: &mut W) -> UnitResult { use self::AttributeValue::*; match *self { IntegerBounds(value) => value.write(write)?, FloatRect(value) => value.write(write)?, I32(value) => value.write(write)?, F32(value) => value.write(write)?, F64(value) => value.write(write)?, Rational((a, b)) => { a.write(write)?; b.write(write)?; }, TimeCode(codes) => { codes.write(write)?; }, IntVec2(Vec2(x, y)) => { x.write(write)?; y.write(write)?; }, FloatVec2(Vec2(x, y)) => { x.write(write)?; y.write(write)?; }, IntVec3((x, y, z)) => { x.write(write)?; y.write(write)?; z.write(write)?; }, FloatVec3((x, y, z)) => { x.write(write)?; y.write(write)?; z.write(write)?; }, ChannelList(ref channels) => channels.write(write)?, Chromaticities(ref value) => value.write(write)?, Compression(value) => value.write(write)?, EnvironmentMap(value) => value.write(write)?, KeyCode(value) => value.write(write)?, LineOrder(value) => value.write(write)?, Matrix3x3(mut value) => f32::write_slice(write, &mut value)?, Matrix4x4(mut value) => f32::write_slice(write, &mut value)?, Preview(ref value) => { value.write(write)?; }, // attribute value texts never have limited size. // also, don't serialize size, as it can be inferred from attribute size Text(ref value) => u8::write_slice(write, value.bytes.as_slice())?, TextVector(ref value) => self::Text::write_vec_of_i32_sized_texts(write, value)?, TileDescription(ref value) => value.write(write)?, Custom { ref bytes, .. } => u8::write_slice(write, &bytes)?, // write.write(&bytes).map(|_| ()), BlockType(kind) => kind.write(write)? }; Ok(()) } /// Read the value without validating. /// Returns `Ok(Ok(attribute))` for valid attributes. /// Returns `Ok(Err(Error))` for invalid attributes from a valid byte source. /// Returns `Err(Error)` for invalid byte sources, for example for invalid files. pub fn read(read: &mut PeekRead, kind: Text, byte_size: usize) -> Result> { use self::AttributeValue::*; use self::type_names as ty; // always read bytes let attribute_bytes = u8::read_vec(read, byte_size, 128, None, "attribute value size")?; // TODO no allocation for small attributes // : SmallVec<[u8; 64]> = smallvec![0; byte_size]; let parse_attribute = move || { let reader = &mut attribute_bytes.as_slice(); Ok(match kind.bytes.as_slice() { ty::I32BOX2 => IntegerBounds(self::IntegerBounds::read(reader)?), ty::F32BOX2 => FloatRect(self::FloatRect::read(reader)?), ty::I32 => I32(i32::read(reader)?), ty::F32 => F32(f32::read(reader)?), ty::F64 => F64(f64::read(reader)?), ty::RATIONAL => Rational({ let a = i32::read(reader)?; let b = u32::read(reader)?; (a, b) }), ty::TIME_CODE => TimeCode(self::TimeCode::read(reader)?), ty::I32VEC2 => IntVec2({ let a = i32::read(reader)?; let b = i32::read(reader)?; Vec2(a, b) }), ty::F32VEC2 => FloatVec2({ let a = f32::read(reader)?; let b = f32::read(reader)?; Vec2(a, b) }), ty::I32VEC3 => IntVec3({ let a = i32::read(reader)?; let b = i32::read(reader)?; let c = i32::read(reader)?; (a, b, c) }), ty::F32VEC3 => FloatVec3({ let a = f32::read(reader)?; let b = f32::read(reader)?; let c = f32::read(reader)?; (a, b, c) }), ty::CHANNEL_LIST => ChannelList(self::ChannelList::read(&mut PeekRead::new(attribute_bytes.as_slice()))?), ty::CHROMATICITIES => Chromaticities(self::Chromaticities::read(reader)?), ty::COMPRESSION => Compression(self::Compression::read(reader)?), ty::ENVIRONMENT_MAP => EnvironmentMap(self::EnvironmentMap::read(reader)?), ty::KEY_CODE => KeyCode(self::KeyCode::read(reader)?), ty::LINE_ORDER => LineOrder(self::LineOrder::read(reader)?), ty::F32MATRIX3X3 => Matrix3x3({ let mut result = [0.0_f32; 9]; f32::read_slice(reader, &mut result)?; result }), ty::F32MATRIX4X4 => Matrix4x4({ let mut result = [0.0_f32; 16]; f32::read_slice(reader, &mut result)?; result }), ty::PREVIEW => Preview(self::Preview::read(reader)?), ty::TEXT => Text(self::Text::read_sized(reader, byte_size)?), // the number of strings can be inferred from the total attribute size ty::TEXT_VECTOR => TextVector(self::Text::read_vec_of_i32_sized( &mut PeekRead::new(attribute_bytes.as_slice()), byte_size )?), ty::TILES => TileDescription(self::TileDescription::read(reader)?), _ => Custom { kind: kind.clone(), bytes: attribute_bytes.clone() } // TODO no clone }) }; Ok(parse_attribute()) } /// Validate this instance. pub fn validate(&self, allow_sampling: bool, data_window: IntegerBounds, strict: bool) -> UnitResult { use self::AttributeValue::*; match *self { ChannelList(ref channels) => channels.validate(allow_sampling, data_window, strict)?, TileDescription(ref value) => value.validate()?, Preview(ref value) => value.validate(strict)?, TimeCode(ref time_code) => time_code.validate(strict)?, TextVector(ref vec) => if strict && vec.is_empty() { return Err(Error::invalid("text vector may not be empty")) }, _ => {} }; Ok(()) } /// Return `Ok(i32)` if this attribute is an i32. pub fn to_i32(&self) -> Result { match *self { AttributeValue::I32(value) => Ok(value), _ => Err(invalid_type()) } } /// Return `Ok(f32)` if this attribute is an f32. pub fn to_f32(&self) -> Result { match *self { AttributeValue::F32(value) => Ok(value), _ => Err(invalid_type()) } } /// Return `Ok(Text)` if this attribute is a text. pub fn into_text(self) -> Result { match self { AttributeValue::Text(value) => Ok(value), _ => Err(invalid_type()) } } /// Return `Ok(Text)` if this attribute is a text. pub fn to_text(&self) -> Result<&Text> { match self { AttributeValue::Text(value) => Ok(value), _ => Err(invalid_type()) } } /// Return `Ok(Chromaticities)` if this attribute is a chromaticities attribute. pub fn to_chromaticities(&self) -> Result { match *self { AttributeValue::Chromaticities(value) => Ok(value), _ => Err(invalid_type()) } } /// Return `Ok(TimeCode)` if this attribute is a time code. pub fn to_time_code(&self) -> Result { match *self { AttributeValue::TimeCode(value) => Ok(value), _ => Err(invalid_type()) } } } /// Contains string literals identifying the type of an attribute. pub mod type_names { macro_rules! define_attribute_type_names { ( $($name: ident : $value: expr),* ) => { $( /// The byte-string name of this attribute type as it appears in an exr file. pub const $name: &'static [u8] = $value; )* }; } define_attribute_type_names! { I32BOX2: b"box2i", F32BOX2: b"box2f", I32: b"int", F32: b"float", F64: b"double", RATIONAL: b"rational", TIME_CODE: b"timecode", I32VEC2: b"v2i", F32VEC2: b"v2f", I32VEC3: b"v3i", F32VEC3: b"v3f", CHANNEL_LIST: b"chlist", CHROMATICITIES: b"chromaticities", COMPRESSION: b"compression", ENVIRONMENT_MAP:b"envmap", KEY_CODE: b"keycode", LINE_ORDER: b"lineOrder", F32MATRIX3X3: b"m33f", F32MATRIX4X4: b"m44f", PREVIEW: b"preview", TEXT: b"string", TEXT_VECTOR: b"stringvector", TILES: b"tiledesc" } } #[cfg(test)] mod test { use super::*; use ::std::io::Cursor; use rand::{random, thread_rng, Rng}; #[test] fn text_ord() { for _ in 0..1024 { let text1 = Text::from_bytes_unchecked((0..4).map(|_| rand::random::()).collect()); let text2 = Text::from_bytes_unchecked((0..4).map(|_| rand::random::()).collect()); assert_eq!(text1.to_string().cmp(&text2.to_string()), text1.cmp(&text2), "in text {:?} vs {:?}", text1, text2); } } #[test] fn rounding_up(){ let round_up = RoundingMode::Up; assert_eq!(round_up.divide(10, 10), 1, "divide equal"); assert_eq!(round_up.divide(10, 2), 5, "divide even"); assert_eq!(round_up.divide(10, 5), 2, "divide even"); assert_eq!(round_up.divide(8, 5), 2, "round up"); assert_eq!(round_up.divide(10, 3), 4, "round up"); assert_eq!(round_up.divide(100, 50), 2, "divide even"); assert_eq!(round_up.divide(100, 49), 3, "round up"); } #[test] fn rounding_down(){ let round_down = RoundingMode::Down; assert_eq!(round_down.divide(8, 5), 1, "round down"); assert_eq!(round_down.divide(10, 3), 3, "round down"); assert_eq!(round_down.divide(100, 50), 2, "divide even"); assert_eq!(round_down.divide(100, 49), 2, "round down"); assert_eq!(round_down.divide(100, 51), 1, "round down"); } #[test] fn tile_description_write_read_roundtrip(){ let tiles = [ TileDescription { tile_size: Vec2(31, 7), level_mode: LevelMode::MipMap, rounding_mode: RoundingMode::Down, }, TileDescription { tile_size: Vec2(0, 0), level_mode: LevelMode::Singular, rounding_mode: RoundingMode::Up, }, TileDescription { tile_size: Vec2(4294967294, 4294967295), level_mode: LevelMode::RipMap, rounding_mode: RoundingMode::Down, }, ]; for tile in &tiles { let mut bytes = Vec::new(); tile.write(&mut bytes).unwrap(); let new_tile = TileDescription::read(&mut Cursor::new(bytes)).unwrap(); assert_eq!(*tile, new_tile, "tile round trip"); } } #[test] fn attribute_write_read_roundtrip_and_byte_size(){ let attributes = [ ( Text::from("greeting"), AttributeValue::Text(Text::from("hello")), ), ( Text::from("age"), AttributeValue::I32(923), ), ( Text::from("leg count"), AttributeValue::F64(9.114939599234), ), ( Text::from("rabbit area"), AttributeValue::FloatRect(FloatRect { min: Vec2(23.4234, 345.23), max: Vec2(68623.0, 3.12425926538), }), ), ( Text::from("rabbit area int"), AttributeValue::IntegerBounds(IntegerBounds { position: Vec2(23, 345), size: Vec2(68623, 3), }), ), ( Text::from("rabbit area int"), AttributeValue::IntegerBounds(IntegerBounds { position: Vec2(-(i32::MAX / 2 - 1), -(i32::MAX / 2 - 1)), size: Vec2(i32::MAX as usize - 2, i32::MAX as usize - 2), }), ), ( Text::from("rabbit area int 2"), AttributeValue::IntegerBounds(IntegerBounds { position: Vec2(0, 0), size: Vec2(i32::MAX as usize / 2 - 1, i32::MAX as usize / 2 - 1), }), ), ( Text::from("tests are difficult"), AttributeValue::TextVector(vec![ Text::from("sdoifjpsdv"), Text::from("sdoifjpsdvxxxx"), Text::from("sdoifjasd"), Text::from("sdoifj"), Text::from("sdoifjddddddddasdasd"), ]), ), ( Text::from("what should we eat tonight"), AttributeValue::Preview(Preview { size: Vec2(10, 30), pixel_data: vec![31; 10 * 30 * 4], }), ), ( Text::from("leg count, again"), AttributeValue::ChannelList(ChannelList::new(smallvec![ ChannelDescription { name: Text::from("Green"), sample_type: SampleType::F16, quantize_linearly: false, sampling: Vec2(1,2) }, ChannelDescription { name: Text::from("Red"), sample_type: SampleType::F32, quantize_linearly: true, sampling: Vec2(1,2) }, ChannelDescription { name: Text::from("Purple"), sample_type: SampleType::U32, quantize_linearly: false, sampling: Vec2(0,0) } ], )), ), ]; for (name, value) in &attributes { let mut bytes = Vec::new(); super::write(name.as_slice(), value, &mut bytes).unwrap(); assert_eq!(super::byte_size(name, value), bytes.len(), "attribute.byte_size() for {:?}", (name, value)); let new_attribute = super::read(&mut PeekRead::new(Cursor::new(bytes)), 300).unwrap(); assert_eq!((name.clone(), value.clone()), (new_attribute.0, new_attribute.1.unwrap()), "attribute round trip"); } { let (name, value) = ( Text::from("asdkaspfokpaosdkfpaokswdpoakpsfokaposdkf"), AttributeValue::I32(0), ); let mut long_names = false; super::validate(&name, &value, &mut long_names, false, IntegerBounds::zero(), false).unwrap(); assert!(long_names); } { let (name, value) = ( Text::from("sdöksadöofkaspdolkpöasolfkcöalsod,kfcöaslodkcpöasolkfposdöksadöofkaspdolkpöasolfkcöalsod,kfcöaslodkcpöasolkfposdöksadöofkaspdolkpöasolfkcöalsod,kfcöaslodkcpöasolkfposdöksadöofkaspdolkpöasolfkcöalsod,kfcöaslodkcpöasolkfposdöksadöofkaspdolkpöasolfkcöalsod,kfcöaslodkcpöasolkfposdöksadöofkaspdolkpöasolfkcöalsod,kfcöaslodkcpöasolkfpo"), AttributeValue::I32(0), ); super::validate(&name, &value, &mut false, false, IntegerBounds::zero(), false).expect_err("name length check failed"); } } #[test] fn time_code_pack(){ let mut rng = thread_rng(); let codes = std::iter::repeat_with(|| TimeCode { hours: rng.gen_range(0 .. 24), minutes: rng.gen_range(0 .. 60), seconds: rng.gen_range(0 .. 60), frame: rng.gen_range(0 .. 29), drop_frame: random(), color_frame: random(), field_phase: random(), binary_group_flags: [random(),random(),random()], binary_groups: std::iter::repeat_with(|| rng.gen_range(0 .. 16)).take(8) .collect::>().into_inner().unwrap() }); for code in codes.take(500) { code.validate(true).expect("invalid timecode test input"); { // through tv60 packing, roundtrip let packed_tv60 = code.pack_time_as_tv60_u32().expect("invalid timecode test input"); let packed_user = code.pack_user_data_as_u32(); assert_eq!(TimeCode::from_tv60_time(packed_tv60, packed_user), code); } { // through bytes, roundtrip let mut bytes = Vec::::new(); code.write(&mut bytes).unwrap(); let decoded = TimeCode::read(&mut bytes.as_slice()).unwrap(); assert_eq!(code, decoded); } { let tv50_code = TimeCode { drop_frame: false, // apparently, tv50 does not support drop frame, so do not use this value .. code }; let packed_tv50 = code.pack_time_as_tv50_u32().expect("invalid timecode test input"); let packed_user = code.pack_user_data_as_u32(); assert_eq!(TimeCode::from_tv50_time(packed_tv50, packed_user), tv50_code); } { let film24_code = TimeCode { // apparently, film24 does not support some flags, so do not use those values color_frame: false, drop_frame: false, .. code }; let packed_film24 = code.pack_time_as_film24_u32().expect("invalid timecode test input"); let packed_user = code.pack_user_data_as_u32(); assert_eq!(TimeCode::from_film24_time(packed_film24, packed_user), film24_code); } } } }