| 1 |
|
| 2 | //! Contains all meta data attributes.
|
| 3 | //! Each layer can have any number of [`Attribute`]s, including custom attributes.
|
| 4 |
|
| 5 | use smallvec::SmallVec;
|
| 6 |
|
| 7 |
|
| 8 | /// Contains one of all possible attributes.
|
| 9 | /// Includes a variant for custom attributes.
|
| 10 | #[derive (Debug, Clone, PartialEq)]
|
| 11 | pub enum AttributeValue {
|
| 12 |
|
| 13 | /// Channel meta data.
|
| 14 | ChannelList(ChannelList),
|
| 15 |
|
| 16 | /// Color space definition.
|
| 17 | Chromaticities(Chromaticities),
|
| 18 |
|
| 19 | /// Compression method of this layer.
|
| 20 | Compression(Compression),
|
| 21 |
|
| 22 | /// This image is an environment map.
|
| 23 | EnvironmentMap(EnvironmentMap),
|
| 24 |
|
| 25 | /// Film roll information.
|
| 26 | KeyCode(KeyCode),
|
| 27 |
|
| 28 | /// Order of the bocks in the file.
|
| 29 | LineOrder(LineOrder),
|
| 30 |
|
| 31 | /// A 3x3 matrix of floats.
|
| 32 | Matrix3x3(Matrix3x3),
|
| 33 |
|
| 34 | /// A 4x4 matrix of floats.
|
| 35 | Matrix4x4(Matrix4x4),
|
| 36 |
|
| 37 | /// 8-bit rgba Preview of the image.
|
| 38 | Preview(Preview),
|
| 39 |
|
| 40 | /// An integer dividend and divisor.
|
| 41 | Rational(Rational),
|
| 42 |
|
| 43 | /// Deep or flat and tiled or scan line.
|
| 44 | BlockType(BlockType),
|
| 45 |
|
| 46 | /// List of texts.
|
| 47 | TextVector(Vec<Text>),
|
| 48 |
|
| 49 | /// How to tile up the image.
|
| 50 | TileDescription(TileDescription),
|
| 51 |
|
| 52 | /// Timepoint and more.
|
| 53 | TimeCode(TimeCode),
|
| 54 |
|
| 55 | /// A string of byte-chars.
|
| 56 | Text(Text),
|
| 57 |
|
| 58 | /// 64-bit float
|
| 59 | F64(f64),
|
| 60 |
|
| 61 | /// 32-bit float
|
| 62 | F32(f32),
|
| 63 |
|
| 64 | /// 32-bit signed integer
|
| 65 | I32(i32),
|
| 66 |
|
| 67 | /// 2D integer rectangle.
|
| 68 | IntegerBounds(IntegerBounds),
|
| 69 |
|
| 70 | /// 2D float rectangle.
|
| 71 | FloatRect(FloatRect),
|
| 72 |
|
| 73 | /// 2D integer vector.
|
| 74 | IntVec2(Vec2<i32>),
|
| 75 |
|
| 76 | /// 2D float vector.
|
| 77 | FloatVec2(Vec2<f32>),
|
| 78 |
|
| 79 | /// 3D integer vector.
|
| 80 | IntVec3((i32, i32, i32)),
|
| 81 |
|
| 82 | /// 3D float vector.
|
| 83 | FloatVec3((f32, f32, f32)),
|
| 84 |
|
| 85 | /// A custom attribute.
|
| 86 | /// Contains the type name of this value.
|
| 87 | Custom {
|
| 88 |
|
| 89 | /// The name of the type this attribute is an instance of.
|
| 90 | kind: Text,
|
| 91 |
|
| 92 | /// The value, stored in little-endian byte order, of the value.
|
| 93 | /// Use the `exr::io::Data` trait to extract binary values from this vector.
|
| 94 | bytes: Vec<u8>
|
| 95 | },
|
| 96 | }
|
| 97 |
|
| 98 | /// A byte array with each byte being a char.
|
| 99 | /// This is not UTF an must be constructed from a standard string.
|
| 100 | // TODO is this ascii? use a rust ascii crate?
|
| 101 | #[derive (Clone, PartialEq, Ord, PartialOrd, Default)] // hash implemented manually
|
| 102 | pub struct Text {
|
| 103 | bytes: TextBytes,
|
| 104 | }
|
| 105 |
|
| 106 | /// Contains time information for this frame within a sequence.
|
| 107 | /// Also defined methods to compile this information into a
|
| 108 | /// `TV60`, `TV50` or `Film24` bit sequence, packed into `u32`.
|
| 109 | ///
|
| 110 | /// Satisfies the [SMPTE standard 12M-1999](https://en.wikipedia.org/wiki/SMPTE_timecode).
|
| 111 | /// For more in-depth information, see [philrees.co.uk/timecode](http://www.philrees.co.uk/articles/timecode.htm).
|
| 112 | #[derive (Copy, Debug, Clone, Eq, PartialEq, Hash, Default)]
|
| 113 | pub struct TimeCode {
|
| 114 |
|
| 115 | /// Hours 0 - 23 are valid.
|
| 116 | pub hours: u8,
|
| 117 |
|
| 118 | /// Minutes 0 - 59 are valid.
|
| 119 | pub minutes: u8,
|
| 120 |
|
| 121 | /// Seconds 0 - 59 are valid.
|
| 122 | pub seconds: u8,
|
| 123 |
|
| 124 | /// Frame Indices 0 - 29 are valid.
|
| 125 | pub frame: u8,
|
| 126 |
|
| 127 | /// Whether this is a drop frame.
|
| 128 | pub drop_frame: bool,
|
| 129 |
|
| 130 | /// Whether this is a color frame.
|
| 131 | pub color_frame: bool,
|
| 132 |
|
| 133 | /// Field Phase.
|
| 134 | pub field_phase: bool,
|
| 135 |
|
| 136 | /// Flags for `TimeCode.binary_groups`.
|
| 137 | pub binary_group_flags: [bool; 3],
|
| 138 |
|
| 139 | /// The user-defined control codes.
|
| 140 | /// Every entry in this array can use at most 3 bits.
|
| 141 | /// This results in a maximum value of 15, including 0, for each `u8`.
|
| 142 | pub binary_groups: [u8; 8]
|
| 143 | }
|
| 144 |
|
| 145 | /// layer type, specifies block type and deepness.
|
| 146 | #[derive (Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
| 147 | pub enum BlockType {
|
| 148 |
|
| 149 | /// Corresponds to the string value `scanlineimage`.
|
| 150 | ScanLine,
|
| 151 |
|
| 152 | /// Corresponds to the string value `tiledimage`.
|
| 153 | Tile,
|
| 154 |
|
| 155 | /// Corresponds to the string value `deepscanline`.
|
| 156 | DeepScanLine,
|
| 157 |
|
| 158 | /// Corresponds to the string value `deeptile`.
|
| 159 | DeepTile,
|
| 160 | }
|
| 161 |
|
| 162 | /// The string literals used to represent a `BlockType` in a file.
|
| 163 | pub mod block_type_strings {
|
| 164 |
|
| 165 | /// Type attribute text value of flat scan lines
|
| 166 | pub const SCAN_LINE: &'static [u8] = b"scanlineimage" ;
|
| 167 |
|
| 168 | /// Type attribute text value of flat tiles
|
| 169 | pub const TILE: &'static [u8] = b"tiledimage" ;
|
| 170 |
|
| 171 | /// Type attribute text value of deep scan lines
|
| 172 | pub const DEEP_SCAN_LINE: &'static [u8] = b"deepscanline" ;
|
| 173 |
|
| 174 | /// Type attribute text value of deep tiles
|
| 175 | pub const DEEP_TILE: &'static [u8] = b"deeptile" ;
|
| 176 | }
|
| 177 |
|
| 178 |
|
| 179 | pub use crate::compression::Compression;
|
| 180 |
|
| 181 | /// The integer rectangle describing where an layer is placed on the infinite 2D global space.
|
| 182 | pub type DataWindow = IntegerBounds;
|
| 183 |
|
| 184 | /// The integer rectangle limiting which part of the infinite 2D global space should be displayed.
|
| 185 | pub type DisplayWindow = IntegerBounds;
|
| 186 |
|
| 187 | /// An integer dividend and divisor, together forming a ratio.
|
| 188 | pub type Rational = (i32, u32);
|
| 189 |
|
| 190 | /// A float matrix with four rows and four columns.
|
| 191 | pub type Matrix4x4 = [f32; 4*4];
|
| 192 |
|
| 193 | /// A float matrix with three rows and three columns.
|
| 194 | pub type Matrix3x3 = [f32; 3*3];
|
| 195 |
|
| 196 | /// A rectangular section anywhere in 2D integer space.
|
| 197 | /// Valid from minimum coordinate (including) `-1,073,741,822`
|
| 198 | /// to maximum coordinate (including) `1,073,741,822`, the value of (`i32::MAX/2 -1`).
|
| 199 | #[derive (Clone, Copy, Debug, Eq, PartialEq, Default, Hash)]
|
| 200 | pub struct IntegerBounds {
|
| 201 |
|
| 202 | /// The top left corner of this rectangle.
|
| 203 | /// The `Box2I32` includes this pixel if the size is not zero.
|
| 204 | pub position: Vec2<i32>,
|
| 205 |
|
| 206 | /// How many pixels to include in this `Box2I32`.
|
| 207 | /// Extends to the right and downwards.
|
| 208 | /// Does not include the actual boundary, just like `Vec::len()`.
|
| 209 | pub size: Vec2<usize>,
|
| 210 | }
|
| 211 |
|
| 212 | /// A rectangular section anywhere in 2D float space.
|
| 213 | #[derive (Clone, Copy, Debug, PartialEq)]
|
| 214 | pub struct FloatRect {
|
| 215 |
|
| 216 | /// The top left corner location of the rectangle (inclusive)
|
| 217 | pub min: Vec2<f32>,
|
| 218 |
|
| 219 | /// The bottom right corner location of the rectangle (inclusive)
|
| 220 | pub max: Vec2<f32>
|
| 221 | }
|
| 222 |
|
| 223 | /// A List of channels. Channels must be sorted alphabetically.
|
| 224 | #[derive (Clone, Debug, Eq, PartialEq, Hash)]
|
| 225 | pub struct ChannelList {
|
| 226 |
|
| 227 | /// The channels in this list.
|
| 228 | pub list: SmallVec<[ChannelDescription; 5]>,
|
| 229 |
|
| 230 | /// The number of bytes that one pixel in this image needs.
|
| 231 | // FIXME this needs to account for subsampling anywhere?
|
| 232 | pub bytes_per_pixel: usize, // FIXME only makes sense for flat images!
|
| 233 |
|
| 234 | /// The sample type of all channels, if all channels have the same type.
|
| 235 | pub uniform_sample_type: Option<SampleType>,
|
| 236 | }
|
| 237 |
|
| 238 | /// A single channel in an layer.
|
| 239 | /// Does not contain the actual pixel data,
|
| 240 | /// but instead merely describes it.
|
| 241 | #[derive (Clone, Debug, Eq, PartialEq, Hash)]
|
| 242 | pub struct ChannelDescription {
|
| 243 |
|
| 244 | /// One of "R", "G", or "B" most of the time.
|
| 245 | pub name: Text,
|
| 246 |
|
| 247 | /// U32, F16 or F32.
|
| 248 | pub sample_type: SampleType,
|
| 249 |
|
| 250 | /// This attribute only tells lossy compression methods
|
| 251 | /// whether this value should be quantized exponentially or linearly.
|
| 252 | ///
|
| 253 | /// Should be `false` for red, green, or blue channels.
|
| 254 | /// Should be `true` for hue, chroma, saturation, or alpha channels.
|
| 255 | pub quantize_linearly: bool,
|
| 256 |
|
| 257 | /// How many of the samples are skipped compared to the other channels in this layer.
|
| 258 | ///
|
| 259 | /// Can be used for chroma subsampling for manual lossy data compression.
|
| 260 | /// Values other than 1 are allowed only in flat, scan-line based images.
|
| 261 | /// If an image is deep or tiled, x and y sampling rates for all of its channels must be 1.
|
| 262 | pub sampling: Vec2<usize>,
|
| 263 | }
|
| 264 |
|
| 265 | /// The type of samples in this channel.
|
| 266 | #[derive (Clone, Debug, Eq, PartialEq, Copy, Hash)]
|
| 267 | pub enum SampleType {
|
| 268 |
|
| 269 | /// This channel contains 32-bit unsigned int values.
|
| 270 | U32,
|
| 271 |
|
| 272 | /// This channel contains 16-bit float values.
|
| 273 | F16,
|
| 274 |
|
| 275 | /// This channel contains 32-bit float values.
|
| 276 | F32,
|
| 277 | }
|
| 278 |
|
| 279 | /// The color space of the pixels.
|
| 280 | ///
|
| 281 | /// If a file doesn't have a chromaticities attribute, display software
|
| 282 | /// should assume that the file's primaries and the white point match `Rec. ITU-R BT.709-3`.
|
| 283 | #[derive (Debug, Clone, Copy, PartialEq)]
|
| 284 | pub struct Chromaticities {
|
| 285 |
|
| 286 | /// "Red" location on the CIE XY chromaticity diagram.
|
| 287 | pub red: Vec2<f32>,
|
| 288 |
|
| 289 | /// "Green" location on the CIE XY chromaticity diagram.
|
| 290 | pub green: Vec2<f32>,
|
| 291 |
|
| 292 | /// "Blue" location on the CIE XY chromaticity diagram.
|
| 293 | pub blue: Vec2<f32>,
|
| 294 |
|
| 295 | /// "White" location on the CIE XY chromaticity diagram.
|
| 296 | pub white: Vec2<f32>
|
| 297 | }
|
| 298 |
|
| 299 | /// If this attribute is present, it describes
|
| 300 | /// how this texture should be projected onto an environment.
|
| 301 | #[derive (Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
| 302 | pub enum EnvironmentMap {
|
| 303 |
|
| 304 | /// This image is an environment map projected like a world map.
|
| 305 | LatitudeLongitude,
|
| 306 |
|
| 307 | /// This image contains the six sides of a cube.
|
| 308 | Cube,
|
| 309 | }
|
| 310 |
|
| 311 | /// Uniquely identifies a motion picture film frame.
|
| 312 | #[derive (Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
| 313 | pub struct KeyCode {
|
| 314 |
|
| 315 | /// Identifies a film manufacturer.
|
| 316 | pub film_manufacturer_code: i32,
|
| 317 |
|
| 318 | /// Identifies a film type.
|
| 319 | pub film_type: i32,
|
| 320 |
|
| 321 | /// Specifies the film roll prefix.
|
| 322 | pub film_roll_prefix: i32,
|
| 323 |
|
| 324 | /// Specifies the film count.
|
| 325 | pub count: i32,
|
| 326 |
|
| 327 | /// Specifies the perforation offset.
|
| 328 | pub perforation_offset: i32,
|
| 329 |
|
| 330 | /// Specifies the perforation count of each single frame.
|
| 331 | pub perforations_per_frame: i32,
|
| 332 |
|
| 333 | /// Specifies the perforation count of each single film.
|
| 334 | pub perforations_per_count: i32,
|
| 335 | }
|
| 336 |
|
| 337 | /// In what order the `Block`s of pixel data appear in a file.
|
| 338 | #[derive (Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
| 339 | pub enum LineOrder {
|
| 340 |
|
| 341 | /// The blocks in the file are ordered in descending rows from left to right.
|
| 342 | /// When compressing in parallel, this option requires potentially large amounts of memory.
|
| 343 | /// In that case, use `LineOrder::Unspecified` for best performance.
|
| 344 | Increasing,
|
| 345 |
|
| 346 | /// The blocks in the file are ordered in ascending rows from right to left.
|
| 347 | /// When compressing in parallel, this option requires potentially large amounts of memory.
|
| 348 | /// In that case, use `LineOrder::Unspecified` for best performance.
|
| 349 | Decreasing,
|
| 350 |
|
| 351 | /// The blocks are not ordered in a specific way inside the file.
|
| 352 | /// In multi-core file writing, this option offers the best performance.
|
| 353 | Unspecified,
|
| 354 | }
|
| 355 |
|
| 356 | /// A small `rgba` image of `i8` values that approximates the real exr image.
|
| 357 | // TODO is this linear?
|
| 358 | #[derive (Clone, Eq, PartialEq)]
|
| 359 | pub struct Preview {
|
| 360 |
|
| 361 | /// The dimensions of the preview image.
|
| 362 | pub size: Vec2<usize>,
|
| 363 |
|
| 364 | /// An array with a length of 4 × width × height.
|
| 365 | /// The pixels are stored in `LineOrder::Increasing`.
|
| 366 | /// Each pixel consists of the four `u8` values red, green, blue, alpha.
|
| 367 | pub pixel_data: Vec<i8>,
|
| 368 | }
|
| 369 |
|
| 370 | /// Describes how the layer is divided into tiles.
|
| 371 | /// Specifies the size of each tile in the image
|
| 372 | /// and whether this image contains multiple resolution levels.
|
| 373 | #[derive (Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
| 374 | pub struct TileDescription {
|
| 375 |
|
| 376 | /// The size of each tile.
|
| 377 | /// Stays the same number of pixels across all levels.
|
| 378 | pub tile_size: Vec2<usize>,
|
| 379 |
|
| 380 | /// Whether to also store smaller versions of the image.
|
| 381 | pub level_mode: LevelMode,
|
| 382 |
|
| 383 | /// Whether to round up or down when calculating Mip/Rip levels.
|
| 384 | pub rounding_mode: RoundingMode,
|
| 385 | }
|
| 386 |
|
| 387 | /// Whether to also store increasingly smaller versions of the original image.
|
| 388 | #[derive (Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
| 389 | pub enum LevelMode {
|
| 390 |
|
| 391 | /// Only a single level.
|
| 392 | Singular,
|
| 393 |
|
| 394 | /// Levels with a similar aspect ratio.
|
| 395 | MipMap,
|
| 396 |
|
| 397 | /// Levels with all possible aspect ratios.
|
| 398 | RipMap,
|
| 399 | }
|
| 400 |
|
| 401 |
|
| 402 | /// The raw bytes that make up a string in an exr file.
|
| 403 | /// Each `u8` is a single char.
|
| 404 | // will mostly be "R", "G", "B" or "deepscanlineimage"
|
| 405 | pub type TextBytes = SmallVec<[u8; 24]>;
|
| 406 |
|
| 407 | /// A byte slice, interpreted as text
|
| 408 | pub type TextSlice = [u8];
|
| 409 |
|
| 410 |
|
| 411 | use crate::io::*;
|
| 412 | use crate::meta::{sequence_end};
|
| 413 | use crate::error::*;
|
| 414 | use crate::math::{RoundingMode, Vec2};
|
| 415 | use half::f16;
|
| 416 | use std::convert::{TryFrom};
|
| 417 | use std::borrow::Borrow;
|
| 418 | use std::hash::{Hash, Hasher};
|
| 419 | use bit_field::BitField;
|
| 420 |
|
| 421 |
|
| 422 | fn invalid_type() -> Error {
|
| 423 | Error::invalid(message:"attribute type mismatch" )
|
| 424 | }
|
| 425 |
|
| 426 |
|
| 427 | impl Text {
|
| 428 |
|
| 429 | /// Create a `Text` from an `str` reference.
|
| 430 | /// Returns `None` if this string contains unsupported chars.
|
| 431 | pub fn new_or_none(string: impl AsRef<str>) -> Option<Self> {
|
| 432 | let vec : Option<TextBytes> = string.as_ref().chars()
|
| 433 | .map(|character| u8::try_from(character as u64).ok())
|
| 434 | .collect();
|
| 435 |
|
| 436 | vec.map(Self::from_bytes_unchecked)
|
| 437 | }
|
| 438 |
|
| 439 | /// Create a `Text` from an `str` reference.
|
| 440 | /// Panics if this string contains unsupported chars.
|
| 441 | pub fn new_or_panic(string: impl AsRef<str>) -> Self {
|
| 442 | Self::new_or_none(string).expect("exr::Text contains unsupported characters" )
|
| 443 | }
|
| 444 |
|
| 445 | /// Create a `Text` from a slice of bytes,
|
| 446 | /// without checking any of the bytes.
|
| 447 | pub fn from_slice_unchecked(text: &TextSlice) -> Self {
|
| 448 | Self::from_bytes_unchecked(SmallVec::from_slice(text))
|
| 449 | }
|
| 450 |
|
| 451 | /// Create a `Text` from the specified bytes object,
|
| 452 | /// without checking any of the bytes.
|
| 453 | pub fn from_bytes_unchecked(bytes: TextBytes) -> Self {
|
| 454 | Text { bytes }
|
| 455 | }
|
| 456 |
|
| 457 | /// The internal ASCII bytes this text is made of.
|
| 458 | pub fn as_slice(&self) -> &TextSlice {
|
| 459 | self.bytes.as_slice()
|
| 460 | }
|
| 461 |
|
| 462 | /// Check whether this string is valid, adjusting `long_names` if required.
|
| 463 | /// If `long_names` is not provided, text length will be entirely unchecked.
|
| 464 | pub fn validate(&self, null_terminated: bool, long_names: Option<&mut bool>) -> UnitResult {
|
| 465 | Self::validate_bytes(self.as_slice(), null_terminated, long_names)
|
| 466 | }
|
| 467 |
|
| 468 | /// Check whether some bytes are valid, adjusting `long_names` if required.
|
| 469 | /// If `long_names` is not provided, text length will be entirely unchecked.
|
| 470 | pub fn validate_bytes(text: &TextSlice, null_terminated: bool, long_names: Option<&mut bool>) -> UnitResult {
|
| 471 | if null_terminated && text.is_empty() {
|
| 472 | return Err(Error::invalid("text must not be empty" ));
|
| 473 | }
|
| 474 |
|
| 475 | if let Some(long) = long_names {
|
| 476 | if text.len() >= 256 { return Err(Error::invalid("text must not be longer than 255" )); }
|
| 477 | if text.len() >= 32 { *long = true; }
|
| 478 | }
|
| 479 |
|
| 480 | Ok(())
|
| 481 | }
|
| 482 |
|
| 483 | /// The byte count this string would occupy if it were encoded as a null-terminated string.
|
| 484 | pub fn null_terminated_byte_size(&self) -> usize {
|
| 485 | self.bytes.len() + sequence_end::byte_size()
|
| 486 | }
|
| 487 |
|
| 488 | /// The byte count this string would occupy if it were encoded as a size-prefixed string.
|
| 489 | pub fn i32_sized_byte_size(&self) -> usize {
|
| 490 | self.bytes.len() + i32::BYTE_SIZE
|
| 491 | }
|
| 492 |
|
| 493 | /// Write the length of a string and then the contents with that length.
|
| 494 | pub fn write_i32_sized<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 495 | debug_assert!(self.validate( false, None).is_ok(), "text size bug" );
|
| 496 | i32::write(usize_to_i32(self.bytes.len()), write)?;
|
| 497 | Self::write_unsized_bytes(self.bytes.as_slice(), write)
|
| 498 | }
|
| 499 |
|
| 500 | /// Without validation, write this instance to the byte stream.
|
| 501 | fn write_unsized_bytes<W: Write>(bytes: &[u8], write: &mut W) -> UnitResult {
|
| 502 | u8::write_slice(write, bytes)?;
|
| 503 | Ok(())
|
| 504 | }
|
| 505 |
|
| 506 | /// Read the length of a string and then the contents with that length.
|
| 507 | pub fn read_i32_sized<R: Read>(read: &mut R, max_size: usize) -> Result<Self> {
|
| 508 | let size = i32_to_usize(i32::read(read)?, "vector size" )?;
|
| 509 | Ok(Text::from_bytes_unchecked(SmallVec::from_vec(u8::read_vec(read, size, 1024, Some(max_size), "text attribute length" )?)))
|
| 510 | }
|
| 511 |
|
| 512 | /// Read the contents with that length.
|
| 513 | pub fn read_sized<R: Read>(read: &mut R, size: usize) -> Result<Self> {
|
| 514 | const SMALL_SIZE: usize = 24;
|
| 515 |
|
| 516 | // for small strings, read into small vec without heap allocation
|
| 517 | if size <= SMALL_SIZE {
|
| 518 | let mut buffer = [0_u8; SMALL_SIZE];
|
| 519 | let data = &mut buffer[..size];
|
| 520 |
|
| 521 | read.read_exact(data)?;
|
| 522 | Ok(Text::from_bytes_unchecked(SmallVec::from_slice(data)))
|
| 523 | }
|
| 524 |
|
| 525 | // for large strings, read a dynamic vec of arbitrary size
|
| 526 | else {
|
| 527 | Ok(Text::from_bytes_unchecked(SmallVec::from_vec(u8::read_vec(read, size, 1024, None, "text attribute length" )?)))
|
| 528 | }
|
| 529 | }
|
| 530 |
|
| 531 | /// Write the string contents and a null-terminator.
|
| 532 | pub fn write_null_terminated<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 533 | Self::write_null_terminated_bytes(self.as_slice(), write)
|
| 534 | }
|
| 535 |
|
| 536 | /// Write the string contents and a null-terminator.
|
| 537 | fn write_null_terminated_bytes<W: Write>(bytes: &[u8], write: &mut W) -> UnitResult {
|
| 538 | debug_assert!(!bytes.is_empty(), "text is empty bug" ); // required to avoid mixup with "sequece_end"
|
| 539 |
|
| 540 | Text::write_unsized_bytes(bytes, write)?;
|
| 541 | sequence_end::write(write)?;
|
| 542 | Ok(())
|
| 543 | }
|
| 544 |
|
| 545 | /// Read a string until the null-terminator is found. Then skips the null-terminator.
|
| 546 | pub fn read_null_terminated<R: Read>(read: &mut R, max_len: usize) -> Result<Self> {
|
| 547 | let mut bytes = smallvec![ u8::read(read)? ]; // null-terminated strings are always at least 1 byte
|
| 548 |
|
| 549 | loop {
|
| 550 | match u8::read(read)? {
|
| 551 | 0 => break,
|
| 552 | non_terminator => bytes.push(non_terminator),
|
| 553 | }
|
| 554 |
|
| 555 | if bytes.len() > max_len {
|
| 556 | return Err(Error::invalid("text too long" ))
|
| 557 | }
|
| 558 | }
|
| 559 |
|
| 560 | Ok(Text { bytes })
|
| 561 | }
|
| 562 |
|
| 563 | /// Allows any text length since it is only used for attribute values,
|
| 564 | /// but not attribute names, attribute type names, or channel names.
|
| 565 | fn read_vec_of_i32_sized(
|
| 566 | read: &mut PeekRead<impl Read>,
|
| 567 | total_byte_size: usize
|
| 568 | ) -> Result<Vec<Text>>
|
| 569 | {
|
| 570 | let mut result = Vec::with_capacity(2);
|
| 571 |
|
| 572 | // length of the text-vector can be inferred from attribute size
|
| 573 | let mut processed_bytes = 0;
|
| 574 |
|
| 575 | while processed_bytes < total_byte_size {
|
| 576 | let text = Text::read_i32_sized(read, total_byte_size)?;
|
| 577 | processed_bytes += ::std::mem::size_of::<i32>(); // size i32 of the text
|
| 578 | processed_bytes += text.bytes.len();
|
| 579 | result.push(text);
|
| 580 | }
|
| 581 |
|
| 582 | // the expected byte size did not match the actual text byte size
|
| 583 | if processed_bytes != total_byte_size {
|
| 584 | return Err(Error::invalid("text array byte size" ))
|
| 585 | }
|
| 586 |
|
| 587 | Ok(result)
|
| 588 | }
|
| 589 |
|
| 590 | /// Allows any text length since it is only used for attribute values,
|
| 591 | /// but not attribute names, attribute type names, or channel names.
|
| 592 | fn write_vec_of_i32_sized_texts<W: Write>(write: &mut W, texts: &[Text]) -> UnitResult {
|
| 593 | // length of the text-vector can be inferred from attribute size
|
| 594 | for text in texts {
|
| 595 | text.write_i32_sized(write)?;
|
| 596 | }
|
| 597 |
|
| 598 | Ok(())
|
| 599 | }
|
| 600 |
|
| 601 | /// The underlying bytes that represent this text.
|
| 602 | pub fn bytes(&self) -> &[u8] {
|
| 603 | self.bytes.as_slice()
|
| 604 | }
|
| 605 |
|
| 606 | /// Iterate over the individual chars in this text, similar to `String::chars()`.
|
| 607 | /// Does not do any heap-allocation but borrows from this instance instead.
|
| 608 | pub fn chars(&self) -> impl '_ + Iterator<Item = char> {
|
| 609 | self.bytes.iter().map(|&byte| byte as char)
|
| 610 | }
|
| 611 |
|
| 612 | /// Compare this `exr::Text` with a plain `&str`.
|
| 613 | pub fn eq(&self, string: &str) -> bool {
|
| 614 | string.chars().eq(self.chars())
|
| 615 | }
|
| 616 |
|
| 617 | /// Compare this `exr::Text` with a plain `&str` ignoring capitalization.
|
| 618 | pub fn eq_case_insensitive(&self, string: &str) -> bool {
|
| 619 | // this is technically not working for a "turkish i", but those cannot be encoded in exr files anyways
|
| 620 | let self_chars = self.chars().map(|char| char.to_ascii_lowercase());
|
| 621 | let string_chars = string.chars().flat_map(|ch| ch.to_lowercase());
|
| 622 |
|
| 623 | string_chars.eq(self_chars)
|
| 624 | }
|
| 625 | }
|
| 626 |
|
| 627 | impl PartialEq<str> for Text {
|
| 628 | fn eq(&self, other: &str) -> bool {
|
| 629 | self.eq(string:other)
|
| 630 | }
|
| 631 | }
|
| 632 |
|
| 633 | impl PartialEq<Text> for str {
|
| 634 | fn eq(&self, other: &Text) -> bool {
|
| 635 | other.eq(self)
|
| 636 | }
|
| 637 | }
|
| 638 |
|
| 639 | impl Eq for Text {}
|
| 640 |
|
| 641 | impl Borrow<TextSlice> for Text {
|
| 642 | fn borrow(&self) -> &TextSlice {
|
| 643 | self.as_slice()
|
| 644 | }
|
| 645 | }
|
| 646 |
|
| 647 | // forwarding implementation. guarantees `text.borrow().hash() == text.hash()` (required for Borrow)
|
| 648 | impl Hash for Text {
|
| 649 | fn hash<H: Hasher>(&self, state: &mut H) {
|
| 650 | self.bytes.hash(state)
|
| 651 | }
|
| 652 | }
|
| 653 |
|
| 654 | impl Into<String> for Text {
|
| 655 | fn into(self) -> String {
|
| 656 | self.to_string()
|
| 657 | }
|
| 658 | }
|
| 659 |
|
| 660 | impl<'s> From<&'s str> for Text {
|
| 661 |
|
| 662 | /// Panics if the string contains an unsupported character
|
| 663 | fn from(str: &'s str) -> Self {
|
| 664 | Self::new_or_panic(string:str)
|
| 665 | }
|
| 666 | }
|
| 667 |
|
| 668 |
|
| 669 | /* TODO (currently conflicts with From<&str>)
|
| 670 | impl<'s> TryFrom<&'s str> for Text {
|
| 671 | type Error = String;
|
| 672 |
|
| 673 | fn try_from(value: &'s str) -> std::result::Result<Self, Self::Error> {
|
| 674 | Text::new_or_none(value)
|
| 675 | .ok_or_else(|| format!(
|
| 676 | "exr::Text does not support all characters in the string `{}`",
|
| 677 | value
|
| 678 | ))
|
| 679 | }
|
| 680 | }*/
|
| 681 |
|
| 682 |
|
| 683 | impl ::std::fmt::Debug for Text {
|
| 684 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
| 685 | write!(f, "exr::Text( \"{}\")" , self)
|
| 686 | }
|
| 687 | }
|
| 688 |
|
| 689 | // automatically implements to_string for us
|
| 690 | impl ::std::fmt::Display for Text {
|
| 691 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
| 692 | use std::fmt::Write;
|
| 693 |
|
| 694 | for &byte: u8 in self.bytes.iter() {
|
| 695 | f.write_char(byte as char)?;
|
| 696 | }
|
| 697 |
|
| 698 | Ok(())
|
| 699 | }
|
| 700 | }
|
| 701 |
|
| 702 |
|
| 703 | impl ChannelList {
|
| 704 |
|
| 705 | /// Does not validate channel order.
|
| 706 | pub fn new(channels: SmallVec<[ChannelDescription; 5]>) -> Self {
|
| 707 | let uniform_sample_type = {
|
| 708 | if let Some(first) = channels.first() {
|
| 709 | let has_uniform_types = channels.iter().skip(1)
|
| 710 | .all(|chan| chan.sample_type == first.sample_type);
|
| 711 |
|
| 712 | if has_uniform_types { Some(first.sample_type) } else { None }
|
| 713 | }
|
| 714 | else { None }
|
| 715 | };
|
| 716 |
|
| 717 | ChannelList {
|
| 718 | bytes_per_pixel: channels.iter().map(|channel| channel.sample_type.bytes_per_sample()).sum(),
|
| 719 | list: channels, uniform_sample_type,
|
| 720 | }
|
| 721 | }
|
| 722 |
|
| 723 | /// Iterate over the channels, and adds to each channel the byte offset of the channels sample type.
|
| 724 | /// Assumes the internal channel list is properly sorted.
|
| 725 | pub fn channels_with_byte_offset(&self) -> impl Iterator<Item=(usize, &ChannelDescription)> {
|
| 726 | self.list.iter().scan(0, |byte_position, channel|{
|
| 727 | let previous_position = *byte_position;
|
| 728 | *byte_position += channel.sample_type.bytes_per_sample();
|
| 729 | Some((previous_position, channel))
|
| 730 | })
|
| 731 | }
|
| 732 |
|
| 733 | /// Return the index of the channel with the exact name, case sensitive, or none.
|
| 734 | /// Potentially uses less than linear time.
|
| 735 | pub fn find_index_of_channel(&self, exact_name: &Text) -> Option<usize> {
|
| 736 | self.list.binary_search_by_key(&exact_name.bytes(), |chan| chan.name.bytes()).ok()
|
| 737 | }
|
| 738 |
|
| 739 | // TODO use this in compression methods
|
| 740 | /*pub fn pixel_section_indices(&self, bounds: IntegerBounds) -> impl '_ + Iterator<Item=(&Channel, usize, usize)> {
|
| 741 | (bounds.position.y() .. bounds.end().y()).flat_map(|y| {
|
| 742 | self.list
|
| 743 | .filter(|channel| mod_p(y, usize_to_i32(channel.sampling.1)) == 0)
|
| 744 | .flat_map(|channel|{
|
| 745 | (bounds.position.x() .. bounds.end().x())
|
| 746 | .filter(|x| mod_p(*x, usize_to_i32(channel.sampling.0)) == 0)
|
| 747 | .map(|x| (channel, x, y))
|
| 748 | })
|
| 749 | })
|
| 750 | }*/
|
| 751 | }
|
| 752 |
|
| 753 | impl BlockType {
|
| 754 |
|
| 755 | /// The corresponding attribute type name literal
|
| 756 | const TYPE_NAME: &'static [u8] = type_names::TEXT;
|
| 757 |
|
| 758 | /// Return a `BlockType` object from the specified attribute text value.
|
| 759 | pub fn parse(text: Text) -> Result<Self> {
|
| 760 | match text.as_slice() {
|
| 761 | block_type_strings::SCAN_LINE => Ok(BlockType::ScanLine),
|
| 762 | block_type_strings::TILE => Ok(BlockType::Tile),
|
| 763 |
|
| 764 | block_type_strings::DEEP_SCAN_LINE => Ok(BlockType::DeepScanLine),
|
| 765 | block_type_strings::DEEP_TILE => Ok(BlockType::DeepTile),
|
| 766 |
|
| 767 | _ => Err(Error::invalid("block type attribute value" )),
|
| 768 | }
|
| 769 | }
|
| 770 |
|
| 771 | /// Without validation, write this instance to the byte stream.
|
| 772 | pub fn write(&self, write: &mut impl Write) -> UnitResult {
|
| 773 | u8::write_slice(write, self.to_text_bytes())?;
|
| 774 | Ok(())
|
| 775 | }
|
| 776 |
|
| 777 | /// Returns the raw attribute text value this type is represented by in a file.
|
| 778 | pub fn to_text_bytes(&self) -> &[u8] {
|
| 779 | match self {
|
| 780 | BlockType::ScanLine => block_type_strings::SCAN_LINE,
|
| 781 | BlockType::Tile => block_type_strings::TILE,
|
| 782 | BlockType::DeepScanLine => block_type_strings::DEEP_SCAN_LINE,
|
| 783 | BlockType::DeepTile => block_type_strings::DEEP_TILE,
|
| 784 | }
|
| 785 | }
|
| 786 |
|
| 787 | /// Number of bytes this would consume in an exr file.
|
| 788 | pub fn byte_size(&self) -> usize {
|
| 789 | self.to_text_bytes().len()
|
| 790 | }
|
| 791 | }
|
| 792 |
|
| 793 |
|
| 794 | impl IntegerBounds {
|
| 795 |
|
| 796 | /// Create a box with no size located at (0,0).
|
| 797 | pub fn zero() -> Self {
|
| 798 | Self::from_dimensions(Vec2(0, 0))
|
| 799 | }
|
| 800 |
|
| 801 | /// Create a box with a size starting at zero.
|
| 802 | pub fn from_dimensions(size: impl Into<Vec2<usize>>) -> Self {
|
| 803 | Self::new(Vec2(0,0), size)
|
| 804 | }
|
| 805 |
|
| 806 | /// Create a box with a size and an origin point.
|
| 807 | pub fn new(start: impl Into<Vec2<i32>>, size: impl Into<Vec2<usize>>) -> Self {
|
| 808 | Self { position: start.into(), size: size.into() }
|
| 809 | }
|
| 810 |
|
| 811 | /// Returns the top-right coordinate of the rectangle.
|
| 812 | /// The row and column described by this vector are not included in the rectangle,
|
| 813 | /// just like `Vec::len()`.
|
| 814 | pub fn end(self) -> Vec2<i32> {
|
| 815 | self.position + self.size.to_i32() // larger than max int32 is panic
|
| 816 | }
|
| 817 |
|
| 818 | /// Returns the maximum coordinate that a value in this rectangle may have.
|
| 819 | pub fn max(self) -> Vec2<i32> {
|
| 820 | self.end() - Vec2(1,1)
|
| 821 | }
|
| 822 |
|
| 823 | /// Validate this instance.
|
| 824 | pub fn validate(&self, max_size: Option<Vec2<usize>>) -> UnitResult {
|
| 825 | if let Some(max_size) = max_size {
|
| 826 | if self.size.width() > max_size.width() || self.size.height() > max_size.height() {
|
| 827 | return Err(Error::invalid("window attribute dimension value" ));
|
| 828 | }
|
| 829 | }
|
| 830 |
|
| 831 | let min_i64 = Vec2(self.position.x() as i64, self.position.y() as i64);
|
| 832 |
|
| 833 | let max_i64 = Vec2(
|
| 834 | self.position.x() as i64 + self.size.width() as i64,
|
| 835 | self.position.y() as i64 + self.size.height() as i64,
|
| 836 | );
|
| 837 |
|
| 838 | Self::validate_min_max_u64(min_i64, max_i64)
|
| 839 | }
|
| 840 |
|
| 841 | fn validate_min_max_u64(min: Vec2<i64>, max: Vec2<i64>) -> UnitResult {
|
| 842 | let max_box_size_as_i64 = (i32::MAX / 2) as i64; // as defined in the original c++ library
|
| 843 |
|
| 844 | if max.x() >= max_box_size_as_i64
|
| 845 | || max.y() >= max_box_size_as_i64
|
| 846 | || min.x() <= -max_box_size_as_i64
|
| 847 | || min.y() <= -max_box_size_as_i64
|
| 848 | {
|
| 849 | return Err(Error::invalid("window size exceeding integer maximum" ));
|
| 850 | }
|
| 851 |
|
| 852 | Ok(())
|
| 853 | }
|
| 854 |
|
| 855 | /// Number of bytes this would consume in an exr file.
|
| 856 | pub fn byte_size() -> usize {
|
| 857 | 4 * i32::BYTE_SIZE
|
| 858 | }
|
| 859 |
|
| 860 | /// Without validation, write this instance to the byte stream.
|
| 861 | pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 862 | let Vec2(x_min, y_min) = self.position;
|
| 863 | let Vec2(x_max, y_max) = self.max();
|
| 864 |
|
| 865 | x_min.write(write)?;
|
| 866 | y_min.write(write)?;
|
| 867 | x_max.write(write)?;
|
| 868 | y_max.write(write)?;
|
| 869 | Ok(())
|
| 870 | }
|
| 871 |
|
| 872 | /// Read the value without validating.
|
| 873 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 874 | let x_min = i32::read(read)?;
|
| 875 | let y_min = i32::read(read)?;
|
| 876 | let x_max = i32::read(read)?;
|
| 877 | let y_max = i32::read(read)?;
|
| 878 |
|
| 879 | let min = Vec2(x_min.min(x_max), y_min.min(y_max));
|
| 880 | let max = Vec2(x_min.max(x_max), y_min.max(y_max));
|
| 881 |
|
| 882 | // prevent addition overflow
|
| 883 | Self::validate_min_max_u64(
|
| 884 | Vec2(min.x() as i64, min.y() as i64),
|
| 885 | Vec2(max.x() as i64, max.y() as i64),
|
| 886 | )?;
|
| 887 |
|
| 888 | // add one to max because the max inclusive, but the size is not
|
| 889 | let size = Vec2(max.x() + 1 - min.x(), max.y() + 1 - min.y());
|
| 890 | let size = size.to_usize("box coordinates" )?;
|
| 891 |
|
| 892 | Ok(IntegerBounds { position: min, size })
|
| 893 | }
|
| 894 |
|
| 895 | /// Create a new rectangle which is offset by the specified origin.
|
| 896 | pub fn with_origin(self, origin: Vec2<i32>) -> Self { // TODO rename to "move" or "translate"?
|
| 897 | IntegerBounds { position: self.position + origin, .. self }
|
| 898 | }
|
| 899 |
|
| 900 | /// Returns whether the specified rectangle is equal to or inside this rectangle.
|
| 901 | pub fn contains(self, subset: Self) -> bool {
|
| 902 | subset.position.x() >= self.position.x()
|
| 903 | && subset.position.y() >= self.position.y()
|
| 904 | && subset.end().x() <= self.end().x()
|
| 905 | && subset.end().y() <= self.end().y()
|
| 906 | }
|
| 907 | }
|
| 908 |
|
| 909 |
|
| 910 | impl FloatRect {
|
| 911 |
|
| 912 | /// Number of bytes this would consume in an exr file.
|
| 913 | pub fn byte_size() -> usize {
|
| 914 | 4 * f32::BYTE_SIZE
|
| 915 | }
|
| 916 |
|
| 917 | /// Without validation, write this instance to the byte stream.
|
| 918 | pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 919 | self.min.x().write(write)?;
|
| 920 | self.min.y().write(write)?;
|
| 921 | self.max.x().write(write)?;
|
| 922 | self.max.y().write(write)?;
|
| 923 | Ok(())
|
| 924 | }
|
| 925 |
|
| 926 | /// Read the value without validating.
|
| 927 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 928 | let x_min = f32::read(read)?;
|
| 929 | let y_min = f32::read(read)?;
|
| 930 | let x_max = f32::read(read)?;
|
| 931 | let y_max = f32::read(read)?;
|
| 932 |
|
| 933 | Ok(FloatRect {
|
| 934 | min: Vec2(x_min, y_min),
|
| 935 | max: Vec2(x_max, y_max)
|
| 936 | })
|
| 937 | }
|
| 938 | }
|
| 939 |
|
| 940 | impl SampleType {
|
| 941 |
|
| 942 | /// How many bytes a single sample takes up.
|
| 943 | pub fn bytes_per_sample(&self) -> usize {
|
| 944 | match self {
|
| 945 | SampleType::F16 => f16::BYTE_SIZE,
|
| 946 | SampleType::F32 => f32::BYTE_SIZE,
|
| 947 | SampleType::U32 => u32::BYTE_SIZE,
|
| 948 | }
|
| 949 | }
|
| 950 |
|
| 951 | /// Number of bytes this would consume in an exr file.
|
| 952 | pub fn byte_size() -> usize {
|
| 953 | i32::BYTE_SIZE
|
| 954 | }
|
| 955 |
|
| 956 | /// Without validation, write this instance to the byte stream.
|
| 957 | pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 958 | match *self {
|
| 959 | SampleType::U32 => 0_i32,
|
| 960 | SampleType::F16 => 1_i32,
|
| 961 | SampleType::F32 => 2_i32,
|
| 962 | }.write(write)?;
|
| 963 |
|
| 964 | Ok(())
|
| 965 | }
|
| 966 |
|
| 967 | /// Read the value without validating.
|
| 968 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 969 | // there's definitely going to be more than 255 different pixel types in the future
|
| 970 | Ok(match i32::read(read)? {
|
| 971 | 0 => SampleType::U32,
|
| 972 | 1 => SampleType::F16,
|
| 973 | 2 => SampleType::F32,
|
| 974 | _ => return Err(Error::invalid("pixel type attribute value" )),
|
| 975 | })
|
| 976 | }
|
| 977 | }
|
| 978 |
|
| 979 | impl ChannelDescription {
|
| 980 | /// Choose whether to compress samples linearly or not, based on the channel name.
|
| 981 | /// Luminance-based channels will be compressed differently than linear data such as alpha.
|
| 982 | pub fn guess_quantization_linearity(name: &Text) -> bool {
|
| 983 | !(
|
| 984 | name.eq_case_insensitive("R" ) || name.eq_case_insensitive("G" ) ||
|
| 985 | name.eq_case_insensitive("B" ) || name.eq_case_insensitive("L" ) ||
|
| 986 | name.eq_case_insensitive("Y" ) || name.eq_case_insensitive("X" ) ||
|
| 987 | name.eq_case_insensitive("Z" )
|
| 988 | )
|
| 989 | }
|
| 990 |
|
| 991 | /// Create a new channel with the specified properties and a sampling rate of (1,1).
|
| 992 | /// Automatically chooses the linearity for compression based on the channel name.
|
| 993 | pub fn named(name: impl Into<Text>, sample_type: SampleType) -> Self {
|
| 994 | let name = name.into();
|
| 995 | let linearity = Self::guess_quantization_linearity(&name);
|
| 996 | Self::new(name, sample_type, linearity)
|
| 997 | }
|
| 998 |
|
| 999 | /*pub fn from_name<T: Into<Sample> + Default>(name: impl Into<Text>) -> Self {
|
| 1000 | Self::named(name, T::default().into().sample_type())
|
| 1001 | }*/
|
| 1002 |
|
| 1003 | /// Create a new channel with the specified properties and a sampling rate of (1,1).
|
| 1004 | pub fn new(name: impl Into<Text>, sample_type: SampleType, quantize_linearly: bool) -> Self {
|
| 1005 | Self { name: name.into(), sample_type, quantize_linearly, sampling: Vec2(1, 1) }
|
| 1006 | }
|
| 1007 |
|
| 1008 | /// The count of pixels this channel contains, respecting subsampling.
|
| 1009 | // FIXME this must be used everywhere
|
| 1010 | pub fn subsampled_pixels(&self, dimensions: Vec2<usize>) -> usize {
|
| 1011 | self.subsampled_resolution(dimensions).area()
|
| 1012 | }
|
| 1013 |
|
| 1014 | /// The resolution pf this channel, respecting subsampling.
|
| 1015 | pub fn subsampled_resolution(&self, dimensions: Vec2<usize>) -> Vec2<usize> {
|
| 1016 | dimensions / self.sampling
|
| 1017 | }
|
| 1018 |
|
| 1019 | /// Number of bytes this would consume in an exr file.
|
| 1020 | pub fn byte_size(&self) -> usize {
|
| 1021 | self.name.null_terminated_byte_size()
|
| 1022 | + SampleType::byte_size()
|
| 1023 | + 1 // is_linear
|
| 1024 | + 3 // reserved bytes
|
| 1025 | + 2 * u32::BYTE_SIZE // sampling x, y
|
| 1026 | }
|
| 1027 |
|
| 1028 | /// Without validation, write this instance to the byte stream.
|
| 1029 | pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 1030 | Text::write_null_terminated(&self.name, write)?;
|
| 1031 | self.sample_type.write(write)?;
|
| 1032 |
|
| 1033 | match self.quantize_linearly {
|
| 1034 | false => 0_u8,
|
| 1035 | true => 1_u8,
|
| 1036 | }.write(write)?;
|
| 1037 |
|
| 1038 | i8::write_slice(write, &[0_i8, 0_i8, 0_i8])?;
|
| 1039 | i32::write(usize_to_i32(self.sampling.x()), write)?;
|
| 1040 | i32::write(usize_to_i32(self.sampling.y()), write)?;
|
| 1041 | Ok(())
|
| 1042 | }
|
| 1043 |
|
| 1044 | /// Read the value without validating.
|
| 1045 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 1046 | let name = Text::read_null_terminated(read, 256)?;
|
| 1047 | let sample_type = SampleType::read(read)?;
|
| 1048 |
|
| 1049 | let is_linear = match u8::read(read)? {
|
| 1050 | 1 => true,
|
| 1051 | 0 => false,
|
| 1052 | _ => return Err(Error::invalid("channel linearity attribute value" )),
|
| 1053 | };
|
| 1054 |
|
| 1055 | let mut reserved = [0_i8; 3];
|
| 1056 | i8::read_slice(read, &mut reserved)?;
|
| 1057 |
|
| 1058 | let x_sampling = i32_to_usize(i32::read(read)?, "x channel sampling" )?;
|
| 1059 | let y_sampling = i32_to_usize(i32::read(read)?, "y channel sampling" )?;
|
| 1060 |
|
| 1061 | Ok(ChannelDescription {
|
| 1062 | name, sample_type,
|
| 1063 | quantize_linearly: is_linear,
|
| 1064 | sampling: Vec2(x_sampling, y_sampling),
|
| 1065 | })
|
| 1066 | }
|
| 1067 |
|
| 1068 | /// Validate this instance.
|
| 1069 | pub fn validate(&self, allow_sampling: bool, data_window: IntegerBounds, strict: bool) -> UnitResult {
|
| 1070 | self.name.validate(true, None)?; // TODO spec says this does not affect `requirements.long_names` but is that true?
|
| 1071 |
|
| 1072 | if self.sampling.x() == 0 || self.sampling.y() == 0 {
|
| 1073 | return Err(Error::invalid("zero sampling factor" ));
|
| 1074 | }
|
| 1075 |
|
| 1076 | if strict && !allow_sampling && self.sampling != Vec2(1,1) {
|
| 1077 | return Err(Error::invalid("subsampling is only allowed in flat scan line images" ));
|
| 1078 | }
|
| 1079 |
|
| 1080 | if data_window.position.x() % self.sampling.x() as i32 != 0 || data_window.position.y() % self.sampling.y() as i32 != 0 {
|
| 1081 | return Err(Error::invalid("channel sampling factor not dividing data window position" ));
|
| 1082 | }
|
| 1083 |
|
| 1084 | if data_window.size.x() % self.sampling.x() != 0 || data_window.size.y() % self.sampling.y() != 0 {
|
| 1085 | return Err(Error::invalid("channel sampling factor not dividing data window size" ));
|
| 1086 | }
|
| 1087 |
|
| 1088 | if self.sampling != Vec2(1,1) {
|
| 1089 | // TODO this must only be implemented in the crate::image module and child modules,
|
| 1090 | // should not be too difficult
|
| 1091 |
|
| 1092 | return Err(Error::unsupported("channel subsampling not supported yet" ));
|
| 1093 | }
|
| 1094 |
|
| 1095 | Ok(())
|
| 1096 | }
|
| 1097 | }
|
| 1098 |
|
| 1099 | impl ChannelList {
|
| 1100 |
|
| 1101 | /// Number of bytes this would consume in an exr file.
|
| 1102 | pub fn byte_size(&self) -> usize {
|
| 1103 | self.list.iter().map(ChannelDescription::byte_size).sum::<usize>() + sequence_end::byte_size()
|
| 1104 | }
|
| 1105 |
|
| 1106 | /// Without validation, write this instance to the byte stream.
|
| 1107 | /// Assumes channels are sorted alphabetically and all values are validated.
|
| 1108 | pub fn write(&self, write: &mut impl Write) -> UnitResult {
|
| 1109 | for channel in &self.list {
|
| 1110 | channel.write(write)?;
|
| 1111 | }
|
| 1112 |
|
| 1113 | sequence_end::write(write)?;
|
| 1114 | Ok(())
|
| 1115 | }
|
| 1116 |
|
| 1117 | /// Read the value without validating.
|
| 1118 | pub fn read(read: &mut PeekRead<impl Read>) -> Result<Self> {
|
| 1119 | let mut channels = SmallVec::new();
|
| 1120 | while !sequence_end::has_come(read)? {
|
| 1121 | channels.push(ChannelDescription::read(read)?);
|
| 1122 | }
|
| 1123 |
|
| 1124 | Ok(ChannelList::new(channels))
|
| 1125 | }
|
| 1126 |
|
| 1127 | /// Check if channels are valid and sorted.
|
| 1128 | pub fn validate(&self, allow_sampling: bool, data_window: IntegerBounds, strict: bool) -> UnitResult {
|
| 1129 | let mut iter = self.list.iter().map(|chan| chan.validate(allow_sampling, data_window, strict).map(|_| &chan.name));
|
| 1130 | let mut previous = iter.next().ok_or(Error::invalid("at least one channel is required" ))??;
|
| 1131 |
|
| 1132 | for result in iter {
|
| 1133 | let value = result?;
|
| 1134 | if strict && previous == value { return Err(Error::invalid("channel names are not unique" )); }
|
| 1135 | else if previous > value { return Err(Error::invalid("channel names are not sorted alphabetically" )); }
|
| 1136 | else { previous = value; }
|
| 1137 | }
|
| 1138 |
|
| 1139 | Ok(())
|
| 1140 | }
|
| 1141 | }
|
| 1142 |
|
| 1143 | fn u8_to_decimal32(binary: u8) -> u32 {
|
| 1144 | let units: u32 = binary as u32 % 10;
|
| 1145 | let tens: u32 = (binary as u32 / 10) % 10;
|
| 1146 | units | (tens << 4)
|
| 1147 | }
|
| 1148 |
|
| 1149 | // assumes value fits into u8
|
| 1150 | fn u8_from_decimal32(coded: u32) -> u8 {
|
| 1151 | ((coded & 0x0f) + 10 * ((coded >> 4) & 0x0f)) as u8
|
| 1152 | }
|
| 1153 |
|
| 1154 | // https://github.com/AcademySoftwareFoundation/openexr/blob/master/src/lib/OpenEXR/ImfTimeCode.cpp
|
| 1155 | impl TimeCode {
|
| 1156 |
|
| 1157 | /// Number of bytes this would consume in an exr file.
|
| 1158 | pub const BYTE_SIZE: usize = 2 * u32::BYTE_SIZE;
|
| 1159 |
|
| 1160 | /// Returns an error if this time code is considered invalid.
|
| 1161 | pub fn validate(&self, strict: bool) -> UnitResult {
|
| 1162 | if strict {
|
| 1163 | if self.frame > 29 { Err(Error::invalid("time code frame larger than 29" )) }
|
| 1164 | else if self.seconds > 59 { Err(Error::invalid("time code seconds larger than 59" )) }
|
| 1165 | else if self.minutes > 59 { Err(Error::invalid("time code minutes larger than 59" )) }
|
| 1166 | else if self.hours > 23 { Err(Error::invalid("time code hours larger than 23" )) }
|
| 1167 | else if self.binary_groups.iter().any(|&group| group > 15) {
|
| 1168 | Err(Error::invalid("time code binary group value too large for 3 bits" ))
|
| 1169 | }
|
| 1170 | else { Ok(()) }
|
| 1171 | }
|
| 1172 | else { Ok(()) }
|
| 1173 | }
|
| 1174 |
|
| 1175 |
|
| 1176 | /// Pack the SMPTE time code into a u32 value, according to TV60 packing.
|
| 1177 | /// This is the encoding which is used within a binary exr file.
|
| 1178 | pub fn pack_time_as_tv60_u32(&self) -> Result<u32> {
|
| 1179 | // validate strictly to prevent set_bit panic! below
|
| 1180 | self.validate(true)?;
|
| 1181 |
|
| 1182 | Ok(*0_u32
|
| 1183 | .set_bits(0..6, u8_to_decimal32(self.frame))
|
| 1184 | .set_bit(6, self.drop_frame)
|
| 1185 | .set_bit(7, self.color_frame)
|
| 1186 | .set_bits(8..15, u8_to_decimal32(self.seconds))
|
| 1187 | .set_bit(15, self.field_phase)
|
| 1188 | .set_bits(16..23, u8_to_decimal32(self.minutes))
|
| 1189 | .set_bit(23, self.binary_group_flags[0])
|
| 1190 | .set_bits(24..30, u8_to_decimal32(self.hours))
|
| 1191 | .set_bit(30, self.binary_group_flags[1])
|
| 1192 | .set_bit(31, self.binary_group_flags[2])
|
| 1193 | )
|
| 1194 | }
|
| 1195 |
|
| 1196 | /// Unpack a time code from one TV60 encoded u32 value and the encoded user data.
|
| 1197 | /// This is the encoding which is used within a binary exr file.
|
| 1198 | pub fn from_tv60_time(tv60_time: u32, user_data: u32) -> Self {
|
| 1199 | Self {
|
| 1200 | frame: u8_from_decimal32(tv60_time.get_bits(0..6)), // cast cannot fail, as these are less than 8 bits
|
| 1201 | drop_frame: tv60_time.get_bit(6),
|
| 1202 | color_frame: tv60_time.get_bit(7),
|
| 1203 | seconds: u8_from_decimal32(tv60_time.get_bits(8..15)), // cast cannot fail, as these are less than 8 bits
|
| 1204 | field_phase: tv60_time.get_bit(15),
|
| 1205 | minutes: u8_from_decimal32(tv60_time.get_bits(16..23)), // cast cannot fail, as these are less than 8 bits
|
| 1206 | hours: u8_from_decimal32(tv60_time.get_bits(24..30)), // cast cannot fail, as these are less than 8 bits
|
| 1207 | binary_group_flags: [
|
| 1208 | tv60_time.get_bit(23),
|
| 1209 | tv60_time.get_bit(30),
|
| 1210 | tv60_time.get_bit(31),
|
| 1211 | ],
|
| 1212 |
|
| 1213 | binary_groups: Self::unpack_user_data_from_u32(user_data)
|
| 1214 | }
|
| 1215 | }
|
| 1216 |
|
| 1217 | /// Pack the SMPTE time code into a u32 value, according to TV50 packing.
|
| 1218 | /// This encoding does not support the `drop_frame` flag, it will be lost.
|
| 1219 | pub fn pack_time_as_tv50_u32(&self) -> Result<u32> {
|
| 1220 | Ok(*self.pack_time_as_tv60_u32()?
|
| 1221 |
|
| 1222 | // swap some fields by replacing some bits in the packed u32
|
| 1223 | .set_bit(6, false)
|
| 1224 | .set_bit(15, self.binary_group_flags[0])
|
| 1225 | .set_bit(30, self.binary_group_flags[1])
|
| 1226 | .set_bit(23, self.binary_group_flags[2])
|
| 1227 | .set_bit(31, self.field_phase)
|
| 1228 | )
|
| 1229 | }
|
| 1230 |
|
| 1231 | /// Unpack a time code from one TV50 encoded u32 value and the encoded user data.
|
| 1232 | /// This encoding does not support the `drop_frame` flag, it will always be false.
|
| 1233 | pub fn from_tv50_time(tv50_time: u32, user_data: u32) -> Self {
|
| 1234 | Self {
|
| 1235 | drop_frame: false, // do not use bit [6]
|
| 1236 |
|
| 1237 | // swap some fields:
|
| 1238 | field_phase: tv50_time.get_bit(31),
|
| 1239 | binary_group_flags: [
|
| 1240 | tv50_time.get_bit(15),
|
| 1241 | tv50_time.get_bit(30),
|
| 1242 | tv50_time.get_bit(23),
|
| 1243 | ],
|
| 1244 |
|
| 1245 | .. Self::from_tv60_time(tv50_time, user_data)
|
| 1246 | }
|
| 1247 | }
|
| 1248 |
|
| 1249 |
|
| 1250 | /// Pack the SMPTE time code into a u32 value, according to FILM24 packing.
|
| 1251 | /// This encoding does not support the `drop_frame` and `color_frame` flags, they will be lost.
|
| 1252 | pub fn pack_time_as_film24_u32(&self) -> Result<u32> {
|
| 1253 | Ok(*self.pack_time_as_tv60_u32()?
|
| 1254 | .set_bit(6, false)
|
| 1255 | .set_bit(7, false)
|
| 1256 | )
|
| 1257 | }
|
| 1258 |
|
| 1259 | /// Unpack a time code from one TV60 encoded u32 value and the encoded user data.
|
| 1260 | /// This encoding does not support the `drop_frame` and `color_frame` flags, they will always be `false`.
|
| 1261 | pub fn from_film24_time(film24_time: u32, user_data: u32) -> Self {
|
| 1262 | Self {
|
| 1263 | drop_frame: false, // bit [6]
|
| 1264 | color_frame: false, // bit [7]
|
| 1265 | .. Self::from_tv60_time(film24_time, user_data)
|
| 1266 | }
|
| 1267 | }
|
| 1268 |
|
| 1269 |
|
| 1270 | // in rust, group index starts at zero, not at one.
|
| 1271 | fn user_data_bit_indices(group_index: usize) -> std::ops::Range<usize> {
|
| 1272 | let min_bit = 4 * group_index;
|
| 1273 | min_bit .. min_bit + 4 // +4, not +3, as `Range` is exclusive
|
| 1274 | }
|
| 1275 |
|
| 1276 | /// Pack the user data `u8` array into one u32.
|
| 1277 | /// User data values are clamped to the valid range (maximum value is 4).
|
| 1278 | pub fn pack_user_data_as_u32(&self) -> u32 {
|
| 1279 | let packed = self.binary_groups.iter().enumerate().fold(0_u32, |mut packed, (group_index, group_value)|
|
| 1280 | *packed.set_bits(Self::user_data_bit_indices(group_index), *group_value.min(&15) as u32)
|
| 1281 | );
|
| 1282 |
|
| 1283 | debug_assert_eq!(Self::unpack_user_data_from_u32(packed), self.binary_groups, "round trip user data encoding" );
|
| 1284 | packed
|
| 1285 | }
|
| 1286 |
|
| 1287 | // Unpack the encoded u32 user data to an array of bytes, each byte having a value from 0 to 4.
|
| 1288 | fn unpack_user_data_from_u32(user_data: u32) -> [u8; 8] {
|
| 1289 | (0..8).map(|group_index| user_data.get_bits(Self::user_data_bit_indices(group_index)) as u8)
|
| 1290 | .collect::<SmallVec<[u8;8]>>().into_inner().expect("array index bug" )
|
| 1291 | }
|
| 1292 |
|
| 1293 |
|
| 1294 | /// Write this time code to the byte stream, encoded as TV60 integers.
|
| 1295 | /// Returns an `Error::Invalid` if the fields are out of the allowed range.
|
| 1296 | pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 1297 | self.pack_time_as_tv60_u32()?.write(write)?; // will validate
|
| 1298 | self.pack_user_data_as_u32().write(write)?;
|
| 1299 | Ok(())
|
| 1300 | }
|
| 1301 |
|
| 1302 | /// Read the time code, without validating, extracting from TV60 integers.
|
| 1303 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 1304 | let time_and_flags = u32::read(read)?;
|
| 1305 | let user_data = u32::read(read)?;
|
| 1306 | Ok(Self::from_tv60_time(time_and_flags, user_data))
|
| 1307 | }
|
| 1308 | }
|
| 1309 |
|
| 1310 | impl Chromaticities {
|
| 1311 |
|
| 1312 | /// Number of bytes this would consume in an exr file.
|
| 1313 | pub fn byte_size() -> usize {
|
| 1314 | 8 * f32::BYTE_SIZE
|
| 1315 | }
|
| 1316 |
|
| 1317 | /// Without validation, write this instance to the byte stream.
|
| 1318 | pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 1319 | self.red.x().write(write)?;
|
| 1320 | self.red.y().write(write)?;
|
| 1321 |
|
| 1322 | self.green.x().write(write)?;
|
| 1323 | self.green.y().write(write)?;
|
| 1324 |
|
| 1325 | self.blue.x().write(write)?;
|
| 1326 | self.blue.y().write(write)?;
|
| 1327 |
|
| 1328 | self.white.x().write(write)?;
|
| 1329 | self.white.y().write(write)?;
|
| 1330 | Ok(())
|
| 1331 | }
|
| 1332 |
|
| 1333 | /// Read the value without validating.
|
| 1334 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 1335 | Ok(Chromaticities {
|
| 1336 | red: Vec2(f32::read(read)?, f32::read(read)?),
|
| 1337 | green: Vec2(f32::read(read)?, f32::read(read)?),
|
| 1338 | blue: Vec2(f32::read(read)?, f32::read(read)?),
|
| 1339 | white: Vec2(f32::read(read)?, f32::read(read)?),
|
| 1340 | })
|
| 1341 | }
|
| 1342 | }
|
| 1343 |
|
| 1344 | impl Compression {
|
| 1345 |
|
| 1346 | /// Number of bytes this would consume in an exr file.
|
| 1347 | pub fn byte_size() -> usize { u8::BYTE_SIZE }
|
| 1348 |
|
| 1349 | /// Without validation, write this instance to the byte stream.
|
| 1350 | pub fn write<W: Write>(self, write: &mut W) -> UnitResult {
|
| 1351 | use self::Compression::*;
|
| 1352 | match self {
|
| 1353 | Uncompressed => 0_u8,
|
| 1354 | RLE => 1_u8,
|
| 1355 | ZIP1 => 2_u8,
|
| 1356 | ZIP16 => 3_u8,
|
| 1357 | PIZ => 4_u8,
|
| 1358 | PXR24 => 5_u8,
|
| 1359 | B44 => 6_u8,
|
| 1360 | B44A => 7_u8,
|
| 1361 | DWAA(_) => 8_u8,
|
| 1362 | DWAB(_) => 9_u8,
|
| 1363 | }.write(write)?;
|
| 1364 | Ok(())
|
| 1365 | }
|
| 1366 |
|
| 1367 | /// Read the value without validating.
|
| 1368 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 1369 | use self::Compression::*;
|
| 1370 | Ok(match u8::read(read)? {
|
| 1371 | 0 => Uncompressed,
|
| 1372 | 1 => RLE,
|
| 1373 | 2 => ZIP1,
|
| 1374 | 3 => ZIP16,
|
| 1375 | 4 => PIZ,
|
| 1376 | 5 => PXR24,
|
| 1377 | 6 => B44,
|
| 1378 | 7 => B44A,
|
| 1379 | 8 => DWAA(None),
|
| 1380 | 9 => DWAB(None),
|
| 1381 | _ => return Err(Error::unsupported("unknown compression method" )),
|
| 1382 | })
|
| 1383 | }
|
| 1384 | }
|
| 1385 |
|
| 1386 | impl EnvironmentMap {
|
| 1387 |
|
| 1388 | /// Number of bytes this would consume in an exr file.
|
| 1389 | pub fn byte_size() -> usize {
|
| 1390 | u8::BYTE_SIZE
|
| 1391 | }
|
| 1392 |
|
| 1393 | /// Without validation, write this instance to the byte stream.
|
| 1394 | pub fn write<W: Write>(self, write: &mut W) -> UnitResult {
|
| 1395 | use self::EnvironmentMap::*;
|
| 1396 | match self {
|
| 1397 | LatitudeLongitude => 0_u8,
|
| 1398 | Cube => 1_u8
|
| 1399 | }.write(write)?;
|
| 1400 |
|
| 1401 | Ok(())
|
| 1402 | }
|
| 1403 |
|
| 1404 | /// Read the value without validating.
|
| 1405 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 1406 | use self::EnvironmentMap::*;
|
| 1407 | Ok(match u8::read(read)? {
|
| 1408 | 0 => LatitudeLongitude,
|
| 1409 | 1 => Cube,
|
| 1410 | _ => return Err(Error::invalid("environment map attribute value" )),
|
| 1411 | })
|
| 1412 | }
|
| 1413 | }
|
| 1414 |
|
| 1415 | impl KeyCode {
|
| 1416 |
|
| 1417 | /// Number of bytes this would consume in an exr file.
|
| 1418 | pub fn byte_size() -> usize {
|
| 1419 | 6 * i32::BYTE_SIZE
|
| 1420 | }
|
| 1421 |
|
| 1422 | /// Without validation, write this instance to the byte stream.
|
| 1423 | pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 1424 | self.film_manufacturer_code.write(write)?;
|
| 1425 | self.film_type.write(write)?;
|
| 1426 | self.film_roll_prefix.write(write)?;
|
| 1427 | self.count.write(write)?;
|
| 1428 | self.perforation_offset.write(write)?;
|
| 1429 | self.perforations_per_count.write(write)?;
|
| 1430 | Ok(())
|
| 1431 | }
|
| 1432 |
|
| 1433 | /// Read the value without validating.
|
| 1434 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 1435 | Ok(KeyCode {
|
| 1436 | film_manufacturer_code: i32::read(read)?,
|
| 1437 | film_type: i32::read(read)?,
|
| 1438 | film_roll_prefix: i32::read(read)?,
|
| 1439 | count: i32::read(read)?,
|
| 1440 | perforation_offset: i32::read(read)?,
|
| 1441 | perforations_per_frame: i32::read(read)?,
|
| 1442 | perforations_per_count: i32::read(read)?,
|
| 1443 | })
|
| 1444 | }
|
| 1445 | }
|
| 1446 |
|
| 1447 | impl LineOrder {
|
| 1448 |
|
| 1449 | /// Number of bytes this would consume in an exr file.
|
| 1450 | pub fn byte_size() -> usize {
|
| 1451 | u8::BYTE_SIZE
|
| 1452 | }
|
| 1453 |
|
| 1454 | /// Without validation, write this instance to the byte stream.
|
| 1455 | pub fn write<W: Write>(self, write: &mut W) -> UnitResult {
|
| 1456 | use self::LineOrder::*;
|
| 1457 | match self {
|
| 1458 | Increasing => 0_u8,
|
| 1459 | Decreasing => 1_u8,
|
| 1460 | Unspecified => 2_u8,
|
| 1461 | }.write(write)?;
|
| 1462 |
|
| 1463 | Ok(())
|
| 1464 | }
|
| 1465 |
|
| 1466 | /// Read the value without validating.
|
| 1467 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 1468 | use self::LineOrder::*;
|
| 1469 | Ok(match u8::read(read)? {
|
| 1470 | 0 => Increasing,
|
| 1471 | 1 => Decreasing,
|
| 1472 | 2 => Unspecified,
|
| 1473 | _ => return Err(Error::invalid("line order attribute value" )),
|
| 1474 | })
|
| 1475 | }
|
| 1476 | }
|
| 1477 |
|
| 1478 |
|
| 1479 |
|
| 1480 |
|
| 1481 | impl Preview {
|
| 1482 |
|
| 1483 | /// Number of bytes this would consume in an exr file.
|
| 1484 | pub fn byte_size(&self) -> usize {
|
| 1485 | 2 * u32::BYTE_SIZE + self.pixel_data.len()
|
| 1486 | }
|
| 1487 |
|
| 1488 | /// Without validation, write this instance to the byte stream.
|
| 1489 | pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 1490 | u32::write(self.size.width() as u32, write)?;
|
| 1491 | u32::write(self.size.height() as u32, write)?;
|
| 1492 |
|
| 1493 | i8::write_slice(write, &self.pixel_data)?;
|
| 1494 | Ok(())
|
| 1495 | }
|
| 1496 |
|
| 1497 | /// Read the value without validating.
|
| 1498 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 1499 | let width = u32::read(read)? as usize;
|
| 1500 | let height = u32::read(read)? as usize;
|
| 1501 |
|
| 1502 | if let Some(pixel_count) = width.checked_mul(height) {
|
| 1503 | // Multiply by the number of bytes per pixel.
|
| 1504 | if let Some(byte_count) = pixel_count.checked_mul(4) {
|
| 1505 | let pixel_data = i8::read_vec(
|
| 1506 | read,
|
| 1507 | byte_count,
|
| 1508 | 1024 * 1024 * 4,
|
| 1509 | None,
|
| 1510 | "preview attribute pixel count" ,
|
| 1511 | )?;
|
| 1512 |
|
| 1513 | let preview = Preview {
|
| 1514 | size: Vec2(width, height),
|
| 1515 | pixel_data,
|
| 1516 | };
|
| 1517 |
|
| 1518 | return Ok(preview);
|
| 1519 | }
|
| 1520 | }
|
| 1521 |
|
| 1522 | return Err(Error::invalid(
|
| 1523 | format!("Overflow while calculating preview image Attribute size \
|
| 1524 | (width: {}, height: {})." ,
|
| 1525 | width,
|
| 1526 | height)));
|
| 1527 | }
|
| 1528 |
|
| 1529 | /// Validate this instance.
|
| 1530 | pub fn validate(&self, strict: bool) -> UnitResult {
|
| 1531 | if strict && (self.size.area() * 4 != self.pixel_data.len()) {
|
| 1532 | return Err(Error::invalid("preview dimensions do not match content length" ))
|
| 1533 | }
|
| 1534 |
|
| 1535 | Ok(())
|
| 1536 | }
|
| 1537 | }
|
| 1538 |
|
| 1539 | impl ::std::fmt::Debug for Preview {
|
| 1540 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
| 1541 | write!(f, "Preview ( {}x {} px)" , self.size.width(), self.size.height())
|
| 1542 | }
|
| 1543 | }
|
| 1544 |
|
| 1545 | impl TileDescription {
|
| 1546 |
|
| 1547 | /// Number of bytes this would consume in an exr file.
|
| 1548 | pub fn byte_size() -> usize {
|
| 1549 | 2 * u32::BYTE_SIZE + 1 // size x,y + (level mode + rounding mode)
|
| 1550 | }
|
| 1551 |
|
| 1552 | /// Without validation, write this instance to the byte stream.
|
| 1553 | pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 1554 | u32::write(self.tile_size.width() as u32, write)?;
|
| 1555 | u32::write(self.tile_size.height() as u32, write)?;
|
| 1556 |
|
| 1557 | let level_mode = match self.level_mode {
|
| 1558 | LevelMode::Singular => 0_u8,
|
| 1559 | LevelMode::MipMap => 1_u8,
|
| 1560 | LevelMode::RipMap => 2_u8,
|
| 1561 | };
|
| 1562 |
|
| 1563 | let rounding_mode = match self.rounding_mode {
|
| 1564 | RoundingMode::Down => 0_u8,
|
| 1565 | RoundingMode::Up => 1_u8,
|
| 1566 | };
|
| 1567 |
|
| 1568 | let mode: u8 = level_mode + (rounding_mode * 16);
|
| 1569 | mode.write(write)?;
|
| 1570 | Ok(())
|
| 1571 | }
|
| 1572 |
|
| 1573 | /// Read the value without validating.
|
| 1574 | pub fn read<R: Read>(read: &mut R) -> Result<Self> {
|
| 1575 | let x_size = u32::read(read)? as usize;
|
| 1576 | let y_size = u32::read(read)? as usize;
|
| 1577 |
|
| 1578 | let mode = u8::read(read)?;
|
| 1579 |
|
| 1580 | // wow you really saved that one byte here
|
| 1581 | // mode = level_mode + (rounding_mode * 16)
|
| 1582 | let level_mode = mode & 0b00001111; // wow that works
|
| 1583 | let rounding_mode = mode >> 4; // wow that works
|
| 1584 |
|
| 1585 | let level_mode = match level_mode {
|
| 1586 | 0 => LevelMode::Singular,
|
| 1587 | 1 => LevelMode::MipMap,
|
| 1588 | 2 => LevelMode::RipMap,
|
| 1589 | _ => return Err(Error::invalid("tile description level mode" )),
|
| 1590 | };
|
| 1591 |
|
| 1592 | let rounding_mode = match rounding_mode {
|
| 1593 | 0 => RoundingMode::Down,
|
| 1594 | 1 => RoundingMode::Up,
|
| 1595 | _ => return Err(Error::invalid("tile description rounding mode" )),
|
| 1596 | };
|
| 1597 |
|
| 1598 | Ok(TileDescription { tile_size: Vec2(x_size, y_size), level_mode, rounding_mode, })
|
| 1599 | }
|
| 1600 |
|
| 1601 | /// Validate this instance.
|
| 1602 | pub fn validate(&self) -> UnitResult {
|
| 1603 | let max = i32::MAX as i64 / 2;
|
| 1604 |
|
| 1605 | if self.tile_size.width() == 0 || self.tile_size.height() == 0
|
| 1606 | || self.tile_size.width() as i64 >= max || self.tile_size.height() as i64 >= max
|
| 1607 | {
|
| 1608 | return Err(Error::invalid("tile size" ))
|
| 1609 | }
|
| 1610 |
|
| 1611 | Ok(())
|
| 1612 | }
|
| 1613 | }
|
| 1614 |
|
| 1615 |
|
| 1616 | /// Number of bytes this attribute would consume in an exr file.
|
| 1617 | // TODO instead of pre calculating byte size, write to a tmp buffer whose length is inspected before actually writing?
|
| 1618 | pub fn byte_size(name: &Text, value: &AttributeValue) -> usize {
|
| 1619 | name.null_terminated_byte_size()
|
| 1620 | + value.kind_name().len() + sequence_end::byte_size()
|
| 1621 | + i32::BYTE_SIZE // serialized byte size
|
| 1622 | + value.byte_size()
|
| 1623 | }
|
| 1624 |
|
| 1625 | /// Without validation, write this attribute to the byte stream.
|
| 1626 | pub fn write<W: Write>(name: &TextSlice, value: &AttributeValue, write: &mut W) -> UnitResult {
|
| 1627 | Text::write_null_terminated_bytes(bytes:name, write)?;
|
| 1628 | Text::write_null_terminated_bytes(bytes:value.kind_name(), write)?;
|
| 1629 | i32::write(self:value.byte_size() as i32, write)?;
|
| 1630 | value.write(write)
|
| 1631 | }
|
| 1632 |
|
| 1633 | /// Read the attribute without validating. The result may be `Ok` even if this single attribute is invalid.
|
| 1634 | pub fn read(read: &mut PeekRead<impl Read>, max_size: usize) -> Result<(Text, Result<AttributeValue>)> {
|
| 1635 | let name: Text = Text::read_null_terminated(read, max_len:max_size)?;
|
| 1636 | let kind: Text = Text::read_null_terminated(read, max_len:max_size)?;
|
| 1637 | let size: usize = i32_to_usize(value:i32::read(read)?, error_message:"attribute size" )?;
|
| 1638 | let value: Result = AttributeValue::read(read, kind, size)?;
|
| 1639 | Ok((name, value))
|
| 1640 | }
|
| 1641 |
|
| 1642 | /// Validate this attribute.
|
| 1643 | pub fn validate(name: &Text, value: &AttributeValue, long_names: &mut bool, allow_sampling: bool, data_window: IntegerBounds, strict: bool) -> UnitResult {
|
| 1644 | name.validate(null_terminated:true, long_names:Some(long_names))?; // only name text has length restriction
|
| 1645 | value.validate(allow_sampling, data_window, strict) // attribute value text length is never restricted
|
| 1646 | }
|
| 1647 |
|
| 1648 |
|
| 1649 | impl AttributeValue {
|
| 1650 |
|
| 1651 | /// Number of bytes this would consume in an exr file.
|
| 1652 | pub fn byte_size(&self) -> usize {
|
| 1653 | use self::AttributeValue::*;
|
| 1654 |
|
| 1655 | match *self {
|
| 1656 | IntegerBounds(_) => self::IntegerBounds::byte_size(),
|
| 1657 | FloatRect(_) => self::FloatRect::byte_size(),
|
| 1658 |
|
| 1659 | I32(_) => i32::BYTE_SIZE,
|
| 1660 | F32(_) => f32::BYTE_SIZE,
|
| 1661 | F64(_) => f64::BYTE_SIZE,
|
| 1662 |
|
| 1663 | Rational(_) => { i32::BYTE_SIZE + u32::BYTE_SIZE },
|
| 1664 | TimeCode(_) => self::TimeCode::BYTE_SIZE,
|
| 1665 |
|
| 1666 | IntVec2(_) => { 2 * i32::BYTE_SIZE },
|
| 1667 | FloatVec2(_) => { 2 * f32::BYTE_SIZE },
|
| 1668 | IntVec3(_) => { 3 * i32::BYTE_SIZE },
|
| 1669 | FloatVec3(_) => { 3 * f32::BYTE_SIZE },
|
| 1670 |
|
| 1671 | ChannelList(ref channels) => channels.byte_size(),
|
| 1672 | Chromaticities(_) => self::Chromaticities::byte_size(),
|
| 1673 | Compression(_) => self::Compression::byte_size(),
|
| 1674 | EnvironmentMap(_) => self::EnvironmentMap::byte_size(),
|
| 1675 |
|
| 1676 | KeyCode(_) => self::KeyCode::byte_size(),
|
| 1677 | LineOrder(_) => self::LineOrder::byte_size(),
|
| 1678 |
|
| 1679 | Matrix3x3(ref value) => value.len() * f32::BYTE_SIZE,
|
| 1680 | Matrix4x4(ref value) => value.len() * f32::BYTE_SIZE,
|
| 1681 |
|
| 1682 | Preview(ref value) => value.byte_size(),
|
| 1683 |
|
| 1684 | // attribute value texts never have limited size.
|
| 1685 | // also, don't serialize size, as it can be inferred from attribute size
|
| 1686 | Text(ref value) => value.bytes.len(),
|
| 1687 |
|
| 1688 | TextVector(ref value) => value.iter().map(self::Text::i32_sized_byte_size).sum(),
|
| 1689 | TileDescription(_) => self::TileDescription::byte_size(),
|
| 1690 | Custom { ref bytes, .. } => bytes.len(),
|
| 1691 | BlockType(ref kind) => kind.byte_size()
|
| 1692 | }
|
| 1693 | }
|
| 1694 |
|
| 1695 | /// The exr name string of the type that an attribute can have.
|
| 1696 | pub fn kind_name(&self) -> &TextSlice {
|
| 1697 | use self::AttributeValue::*;
|
| 1698 | use self::type_names as ty;
|
| 1699 |
|
| 1700 | match *self {
|
| 1701 | IntegerBounds(_) => ty::I32BOX2,
|
| 1702 | FloatRect(_) => ty::F32BOX2,
|
| 1703 | I32(_) => ty::I32,
|
| 1704 | F32(_) => ty::F32,
|
| 1705 | F64(_) => ty::F64,
|
| 1706 | Rational(_) => ty::RATIONAL,
|
| 1707 | TimeCode(_) => ty::TIME_CODE,
|
| 1708 | IntVec2(_) => ty::I32VEC2,
|
| 1709 | FloatVec2(_) => ty::F32VEC2,
|
| 1710 | IntVec3(_) => ty::I32VEC3,
|
| 1711 | FloatVec3(_) => ty::F32VEC3,
|
| 1712 | ChannelList(_) => ty::CHANNEL_LIST,
|
| 1713 | Chromaticities(_) => ty::CHROMATICITIES,
|
| 1714 | Compression(_) => ty::COMPRESSION,
|
| 1715 | EnvironmentMap(_) => ty::ENVIRONMENT_MAP,
|
| 1716 | KeyCode(_) => ty::KEY_CODE,
|
| 1717 | LineOrder(_) => ty::LINE_ORDER,
|
| 1718 | Matrix3x3(_) => ty::F32MATRIX3X3,
|
| 1719 | Matrix4x4(_) => ty::F32MATRIX4X4,
|
| 1720 | Preview(_) => ty::PREVIEW,
|
| 1721 | Text(_) => ty::TEXT,
|
| 1722 | TextVector(_) => ty::TEXT_VECTOR,
|
| 1723 | TileDescription(_) => ty::TILES,
|
| 1724 | BlockType(_) => super::BlockType::TYPE_NAME,
|
| 1725 | Custom { ref kind, .. } => kind.as_slice(),
|
| 1726 | }
|
| 1727 | }
|
| 1728 |
|
| 1729 | /// Without validation, write this instance to the byte stream.
|
| 1730 | pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
|
| 1731 | use self::AttributeValue::*;
|
| 1732 | match *self {
|
| 1733 | IntegerBounds(value) => value.write(write)?,
|
| 1734 | FloatRect(value) => value.write(write)?,
|
| 1735 |
|
| 1736 | I32(value) => value.write(write)?,
|
| 1737 | F32(value) => value.write(write)?,
|
| 1738 | F64(value) => value.write(write)?,
|
| 1739 |
|
| 1740 | Rational((a, b)) => { a.write(write)?; b.write(write)?; },
|
| 1741 | TimeCode(codes) => { codes.write(write)?; },
|
| 1742 |
|
| 1743 | IntVec2(Vec2(x, y)) => { x.write(write)?; y.write(write)?; },
|
| 1744 | FloatVec2(Vec2(x, y)) => { x.write(write)?; y.write(write)?; },
|
| 1745 | IntVec3((x, y, z)) => { x.write(write)?; y.write(write)?; z.write(write)?; },
|
| 1746 | FloatVec3((x, y, z)) => { x.write(write)?; y.write(write)?; z.write(write)?; },
|
| 1747 |
|
| 1748 | ChannelList(ref channels) => channels.write(write)?,
|
| 1749 | Chromaticities(ref value) => value.write(write)?,
|
| 1750 | Compression(value) => value.write(write)?,
|
| 1751 | EnvironmentMap(value) => value.write(write)?,
|
| 1752 |
|
| 1753 | KeyCode(value) => value.write(write)?,
|
| 1754 | LineOrder(value) => value.write(write)?,
|
| 1755 |
|
| 1756 | Matrix3x3(mut value) => f32::write_slice(write, &mut value)?,
|
| 1757 | Matrix4x4(mut value) => f32::write_slice(write, &mut value)?,
|
| 1758 |
|
| 1759 | Preview(ref value) => { value.write(write)?; },
|
| 1760 |
|
| 1761 | // attribute value texts never have limited size.
|
| 1762 | // also, don't serialize size, as it can be inferred from attribute size
|
| 1763 | Text(ref value) => u8::write_slice(write, value.bytes.as_slice())?,
|
| 1764 |
|
| 1765 | TextVector(ref value) => self::Text::write_vec_of_i32_sized_texts(write, value)?,
|
| 1766 | TileDescription(ref value) => value.write(write)?,
|
| 1767 | Custom { ref bytes, .. } => u8::write_slice(write, &bytes)?, // write.write(&bytes).map(|_| ()),
|
| 1768 | BlockType(kind) => kind.write(write)?
|
| 1769 | };
|
| 1770 |
|
| 1771 | Ok(())
|
| 1772 | }
|
| 1773 |
|
| 1774 | /// Read the value without validating.
|
| 1775 | /// Returns `Ok(Ok(attribute))` for valid attributes.
|
| 1776 | /// Returns `Ok(Err(Error))` for invalid attributes from a valid byte source.
|
| 1777 | /// Returns `Err(Error)` for invalid byte sources, for example for invalid files.
|
| 1778 | pub fn read(read: &mut PeekRead<impl Read>, kind: Text, byte_size: usize) -> Result<Result<Self>> {
|
| 1779 | use self::AttributeValue::*;
|
| 1780 | use self::type_names as ty;
|
| 1781 |
|
| 1782 | // always read bytes
|
| 1783 | let attribute_bytes = u8::read_vec(read, byte_size, 128, None, "attribute value size" )?;
|
| 1784 | // TODO no allocation for small attributes // : SmallVec<[u8; 64]> = smallvec![0; byte_size];
|
| 1785 |
|
| 1786 | let parse_attribute = move || {
|
| 1787 | let reader = &mut attribute_bytes.as_slice();
|
| 1788 |
|
| 1789 | Ok(match kind.bytes.as_slice() {
|
| 1790 | ty::I32BOX2 => IntegerBounds(self::IntegerBounds::read(reader)?),
|
| 1791 | ty::F32BOX2 => FloatRect(self::FloatRect::read(reader)?),
|
| 1792 |
|
| 1793 | ty::I32 => I32(i32::read(reader)?),
|
| 1794 | ty::F32 => F32(f32::read(reader)?),
|
| 1795 | ty::F64 => F64(f64::read(reader)?),
|
| 1796 |
|
| 1797 | ty::RATIONAL => Rational({
|
| 1798 | let a = i32::read(reader)?;
|
| 1799 | let b = u32::read(reader)?;
|
| 1800 | (a, b)
|
| 1801 | }),
|
| 1802 |
|
| 1803 | ty::TIME_CODE => TimeCode(self::TimeCode::read(reader)?),
|
| 1804 |
|
| 1805 | ty::I32VEC2 => IntVec2({
|
| 1806 | let a = i32::read(reader)?;
|
| 1807 | let b = i32::read(reader)?;
|
| 1808 | Vec2(a, b)
|
| 1809 | }),
|
| 1810 |
|
| 1811 | ty::F32VEC2 => FloatVec2({
|
| 1812 | let a = f32::read(reader)?;
|
| 1813 | let b = f32::read(reader)?;
|
| 1814 | Vec2(a, b)
|
| 1815 | }),
|
| 1816 |
|
| 1817 | ty::I32VEC3 => IntVec3({
|
| 1818 | let a = i32::read(reader)?;
|
| 1819 | let b = i32::read(reader)?;
|
| 1820 | let c = i32::read(reader)?;
|
| 1821 | (a, b, c)
|
| 1822 | }),
|
| 1823 |
|
| 1824 | ty::F32VEC3 => FloatVec3({
|
| 1825 | let a = f32::read(reader)?;
|
| 1826 | let b = f32::read(reader)?;
|
| 1827 | let c = f32::read(reader)?;
|
| 1828 | (a, b, c)
|
| 1829 | }),
|
| 1830 |
|
| 1831 | ty::CHANNEL_LIST => ChannelList(self::ChannelList::read(&mut PeekRead::new(attribute_bytes.as_slice()))?),
|
| 1832 | ty::CHROMATICITIES => Chromaticities(self::Chromaticities::read(reader)?),
|
| 1833 | ty::COMPRESSION => Compression(self::Compression::read(reader)?),
|
| 1834 | ty::ENVIRONMENT_MAP => EnvironmentMap(self::EnvironmentMap::read(reader)?),
|
| 1835 |
|
| 1836 | ty::KEY_CODE => KeyCode(self::KeyCode::read(reader)?),
|
| 1837 | ty::LINE_ORDER => LineOrder(self::LineOrder::read(reader)?),
|
| 1838 |
|
| 1839 | ty::F32MATRIX3X3 => Matrix3x3({
|
| 1840 | let mut result = [0.0_f32; 9];
|
| 1841 | f32::read_slice(reader, &mut result)?;
|
| 1842 | result
|
| 1843 | }),
|
| 1844 |
|
| 1845 | ty::F32MATRIX4X4 => Matrix4x4({
|
| 1846 | let mut result = [0.0_f32; 16];
|
| 1847 | f32::read_slice(reader, &mut result)?;
|
| 1848 | result
|
| 1849 | }),
|
| 1850 |
|
| 1851 | ty::PREVIEW => Preview(self::Preview::read(reader)?),
|
| 1852 | ty::TEXT => Text(self::Text::read_sized(reader, byte_size)?),
|
| 1853 |
|
| 1854 | // the number of strings can be inferred from the total attribute size
|
| 1855 | ty::TEXT_VECTOR => TextVector(self::Text::read_vec_of_i32_sized(
|
| 1856 | &mut PeekRead::new(attribute_bytes.as_slice()),
|
| 1857 | byte_size
|
| 1858 | )?),
|
| 1859 |
|
| 1860 | ty::TILES => TileDescription(self::TileDescription::read(reader)?),
|
| 1861 |
|
| 1862 | _ => Custom { kind: kind.clone(), bytes: attribute_bytes.clone() } // TODO no clone
|
| 1863 | })
|
| 1864 | };
|
| 1865 |
|
| 1866 | Ok(parse_attribute())
|
| 1867 | }
|
| 1868 |
|
| 1869 | /// Validate this instance.
|
| 1870 | pub fn validate(&self, allow_sampling: bool, data_window: IntegerBounds, strict: bool) -> UnitResult {
|
| 1871 | use self::AttributeValue::*;
|
| 1872 |
|
| 1873 | match *self {
|
| 1874 | ChannelList(ref channels) => channels.validate(allow_sampling, data_window, strict)?,
|
| 1875 | TileDescription(ref value) => value.validate()?,
|
| 1876 | Preview(ref value) => value.validate(strict)?,
|
| 1877 | TimeCode(ref time_code) => time_code.validate(strict)?,
|
| 1878 |
|
| 1879 | TextVector(ref vec) => if strict && vec.is_empty() {
|
| 1880 | return Err(Error::invalid("text vector may not be empty" ))
|
| 1881 | },
|
| 1882 |
|
| 1883 | _ => {}
|
| 1884 | };
|
| 1885 |
|
| 1886 | Ok(())
|
| 1887 | }
|
| 1888 |
|
| 1889 |
|
| 1890 | /// Return `Ok(i32)` if this attribute is an i32.
|
| 1891 | pub fn to_i32(&self) -> Result<i32> {
|
| 1892 | match *self {
|
| 1893 | AttributeValue::I32(value) => Ok(value),
|
| 1894 | _ => Err(invalid_type())
|
| 1895 | }
|
| 1896 | }
|
| 1897 |
|
| 1898 | /// Return `Ok(f32)` if this attribute is an f32.
|
| 1899 | pub fn to_f32(&self) -> Result<f32> {
|
| 1900 | match *self {
|
| 1901 | AttributeValue::F32(value) => Ok(value),
|
| 1902 | _ => Err(invalid_type())
|
| 1903 | }
|
| 1904 | }
|
| 1905 |
|
| 1906 | /// Return `Ok(Text)` if this attribute is a text.
|
| 1907 | pub fn into_text(self) -> Result<Text> {
|
| 1908 | match self {
|
| 1909 | AttributeValue::Text(value) => Ok(value),
|
| 1910 | _ => Err(invalid_type())
|
| 1911 | }
|
| 1912 | }
|
| 1913 |
|
| 1914 | /// Return `Ok(Text)` if this attribute is a text.
|
| 1915 | pub fn to_text(&self) -> Result<&Text> {
|
| 1916 | match self {
|
| 1917 | AttributeValue::Text(value) => Ok(value),
|
| 1918 | _ => Err(invalid_type())
|
| 1919 | }
|
| 1920 | }
|
| 1921 |
|
| 1922 | /// Return `Ok(Chromaticities)` if this attribute is a chromaticities attribute.
|
| 1923 | pub fn to_chromaticities(&self) -> Result<Chromaticities> {
|
| 1924 | match *self {
|
| 1925 | AttributeValue::Chromaticities(value) => Ok(value),
|
| 1926 | _ => Err(invalid_type())
|
| 1927 | }
|
| 1928 | }
|
| 1929 |
|
| 1930 | /// Return `Ok(TimeCode)` if this attribute is a time code.
|
| 1931 | pub fn to_time_code(&self) -> Result<TimeCode> {
|
| 1932 | match *self {
|
| 1933 | AttributeValue::TimeCode(value) => Ok(value),
|
| 1934 | _ => Err(invalid_type())
|
| 1935 | }
|
| 1936 | }
|
| 1937 | }
|
| 1938 |
|
| 1939 |
|
| 1940 |
|
| 1941 | /// Contains string literals identifying the type of an attribute.
|
| 1942 | pub mod type_names {
|
| 1943 | macro_rules! define_attribute_type_names {
|
| 1944 | ( $($name: ident : $value: expr),* ) => {
|
| 1945 | $(
|
| 1946 | /// The byte-string name of this attribute type as it appears in an exr file.
|
| 1947 | pub const $name: &'static [u8] = $value;
|
| 1948 | )*
|
| 1949 | };
|
| 1950 | }
|
| 1951 |
|
| 1952 | define_attribute_type_names! {
|
| 1953 | I32BOX2: b"box2i" ,
|
| 1954 | F32BOX2: b"box2f" ,
|
| 1955 | I32: b"int" ,
|
| 1956 | F32: b"float" ,
|
| 1957 | F64: b"double" ,
|
| 1958 | RATIONAL: b"rational" ,
|
| 1959 | TIME_CODE: b"timecode" ,
|
| 1960 | I32VEC2: b"v2i" ,
|
| 1961 | F32VEC2: b"v2f" ,
|
| 1962 | I32VEC3: b"v3i" ,
|
| 1963 | F32VEC3: b"v3f" ,
|
| 1964 | CHANNEL_LIST: b"chlist" ,
|
| 1965 | CHROMATICITIES: b"chromaticities" ,
|
| 1966 | COMPRESSION: b"compression" ,
|
| 1967 | ENVIRONMENT_MAP:b"envmap" ,
|
| 1968 | KEY_CODE: b"keycode" ,
|
| 1969 | LINE_ORDER: b"lineOrder" ,
|
| 1970 | F32MATRIX3X3: b"m33f" ,
|
| 1971 | F32MATRIX4X4: b"m44f" ,
|
| 1972 | PREVIEW: b"preview" ,
|
| 1973 | TEXT: b"string" ,
|
| 1974 | TEXT_VECTOR: b"stringvector" ,
|
| 1975 | TILES: b"tiledesc"
|
| 1976 | }
|
| 1977 | }
|
| 1978 |
|
| 1979 |
|
| 1980 | #[cfg (test)]
|
| 1981 | mod test {
|
| 1982 | use super::*;
|
| 1983 | use ::std::io::Cursor;
|
| 1984 | use rand::{random, thread_rng, Rng};
|
| 1985 |
|
| 1986 | #[test ]
|
| 1987 | fn text_ord() {
|
| 1988 | for _ in 0..1024 {
|
| 1989 | let text1 = Text::from_bytes_unchecked((0..4).map(|_| rand::random::<u8>()).collect());
|
| 1990 | let text2 = Text::from_bytes_unchecked((0..4).map(|_| rand::random::<u8>()).collect());
|
| 1991 |
|
| 1992 | assert_eq!(text1.to_string().cmp(&text2.to_string()), text1.cmp(&text2), "in text {:?} vs {:?}" , text1, text2);
|
| 1993 | }
|
| 1994 | }
|
| 1995 |
|
| 1996 | #[test ]
|
| 1997 | fn rounding_up(){
|
| 1998 | let round_up = RoundingMode::Up;
|
| 1999 | assert_eq!(round_up.divide(10, 10), 1, "divide equal" );
|
| 2000 | assert_eq!(round_up.divide(10, 2), 5, "divide even" );
|
| 2001 | assert_eq!(round_up.divide(10, 5), 2, "divide even" );
|
| 2002 |
|
| 2003 | assert_eq!(round_up.divide(8, 5), 2, "round up" );
|
| 2004 | assert_eq!(round_up.divide(10, 3), 4, "round up" );
|
| 2005 | assert_eq!(round_up.divide(100, 50), 2, "divide even" );
|
| 2006 | assert_eq!(round_up.divide(100, 49), 3, "round up" );
|
| 2007 | }
|
| 2008 |
|
| 2009 | #[test ]
|
| 2010 | fn rounding_down(){
|
| 2011 | let round_down = RoundingMode::Down;
|
| 2012 | assert_eq!(round_down.divide(8, 5), 1, "round down" );
|
| 2013 | assert_eq!(round_down.divide(10, 3), 3, "round down" );
|
| 2014 | assert_eq!(round_down.divide(100, 50), 2, "divide even" );
|
| 2015 | assert_eq!(round_down.divide(100, 49), 2, "round down" );
|
| 2016 | assert_eq!(round_down.divide(100, 51), 1, "round down" );
|
| 2017 | }
|
| 2018 |
|
| 2019 | #[test ]
|
| 2020 | fn tile_description_write_read_roundtrip(){
|
| 2021 | let tiles = [
|
| 2022 | TileDescription {
|
| 2023 | tile_size: Vec2(31, 7),
|
| 2024 | level_mode: LevelMode::MipMap,
|
| 2025 | rounding_mode: RoundingMode::Down,
|
| 2026 | },
|
| 2027 |
|
| 2028 | TileDescription {
|
| 2029 | tile_size: Vec2(0, 0),
|
| 2030 | level_mode: LevelMode::Singular,
|
| 2031 | rounding_mode: RoundingMode::Up,
|
| 2032 | },
|
| 2033 |
|
| 2034 | TileDescription {
|
| 2035 | tile_size: Vec2(4294967294, 4294967295),
|
| 2036 | level_mode: LevelMode::RipMap,
|
| 2037 | rounding_mode: RoundingMode::Down,
|
| 2038 | },
|
| 2039 | ];
|
| 2040 |
|
| 2041 | for tile in &tiles {
|
| 2042 | let mut bytes = Vec::new();
|
| 2043 | tile.write(&mut bytes).unwrap();
|
| 2044 |
|
| 2045 | let new_tile = TileDescription::read(&mut Cursor::new(bytes)).unwrap();
|
| 2046 | assert_eq!(*tile, new_tile, "tile round trip" );
|
| 2047 | }
|
| 2048 | }
|
| 2049 |
|
| 2050 | #[test ]
|
| 2051 | fn attribute_write_read_roundtrip_and_byte_size(){
|
| 2052 | let attributes = [
|
| 2053 | (
|
| 2054 | Text::from("greeting" ),
|
| 2055 | AttributeValue::Text(Text::from("hello" )),
|
| 2056 | ),
|
| 2057 | (
|
| 2058 | Text::from("age" ),
|
| 2059 | AttributeValue::I32(923),
|
| 2060 | ),
|
| 2061 | (
|
| 2062 | Text::from("leg count" ),
|
| 2063 | AttributeValue::F64(9.114939599234),
|
| 2064 | ),
|
| 2065 | (
|
| 2066 | Text::from("rabbit area" ),
|
| 2067 | AttributeValue::FloatRect(FloatRect {
|
| 2068 | min: Vec2(23.4234, 345.23),
|
| 2069 | max: Vec2(68623.0, 3.12425926538),
|
| 2070 | }),
|
| 2071 | ),
|
| 2072 | (
|
| 2073 | Text::from("rabbit area int" ),
|
| 2074 | AttributeValue::IntegerBounds(IntegerBounds {
|
| 2075 | position: Vec2(23, 345),
|
| 2076 | size: Vec2(68623, 3),
|
| 2077 | }),
|
| 2078 | ),
|
| 2079 | (
|
| 2080 | Text::from("rabbit area int" ),
|
| 2081 | AttributeValue::IntegerBounds(IntegerBounds {
|
| 2082 | position: Vec2(-(i32::MAX / 2 - 1), -(i32::MAX / 2 - 1)),
|
| 2083 | size: Vec2(i32::MAX as usize - 2, i32::MAX as usize - 2),
|
| 2084 | }),
|
| 2085 | ),
|
| 2086 | (
|
| 2087 | Text::from("rabbit area int 2" ),
|
| 2088 | AttributeValue::IntegerBounds(IntegerBounds {
|
| 2089 | position: Vec2(0, 0),
|
| 2090 | size: Vec2(i32::MAX as usize / 2 - 1, i32::MAX as usize / 2 - 1),
|
| 2091 | }),
|
| 2092 | ),
|
| 2093 | (
|
| 2094 | Text::from("tests are difficult" ),
|
| 2095 | AttributeValue::TextVector(vec![
|
| 2096 | Text::from("sdoifjpsdv" ),
|
| 2097 | Text::from("sdoifjpsdvxxxx" ),
|
| 2098 | Text::from("sdoifjasd" ),
|
| 2099 | Text::from("sdoifj" ),
|
| 2100 | Text::from("sdoifjddddddddasdasd" ),
|
| 2101 | ]),
|
| 2102 | ),
|
| 2103 | (
|
| 2104 | Text::from("what should we eat tonight" ),
|
| 2105 | AttributeValue::Preview(Preview {
|
| 2106 | size: Vec2(10, 30),
|
| 2107 | pixel_data: vec![31; 10 * 30 * 4],
|
| 2108 | }),
|
| 2109 | ),
|
| 2110 | (
|
| 2111 | Text::from("leg count, again" ),
|
| 2112 | AttributeValue::ChannelList(ChannelList::new(smallvec![
|
| 2113 | ChannelDescription {
|
| 2114 | name: Text::from("Green" ),
|
| 2115 | sample_type: SampleType::F16,
|
| 2116 | quantize_linearly: false,
|
| 2117 | sampling: Vec2(1,2)
|
| 2118 | },
|
| 2119 | ChannelDescription {
|
| 2120 | name: Text::from("Red" ),
|
| 2121 | sample_type: SampleType::F32,
|
| 2122 | quantize_linearly: true,
|
| 2123 | sampling: Vec2(1,2)
|
| 2124 | },
|
| 2125 | ChannelDescription {
|
| 2126 | name: Text::from("Purple" ),
|
| 2127 | sample_type: SampleType::U32,
|
| 2128 | quantize_linearly: false,
|
| 2129 | sampling: Vec2(0,0)
|
| 2130 | }
|
| 2131 | ],
|
| 2132 | )),
|
| 2133 | ),
|
| 2134 | ];
|
| 2135 |
|
| 2136 | for (name, value) in &attributes {
|
| 2137 | let mut bytes = Vec::new();
|
| 2138 | super::write(name.as_slice(), value, &mut bytes).unwrap();
|
| 2139 | assert_eq!(super::byte_size(name, value), bytes.len(), "attribute.byte_size() for {:?}" , (name, value));
|
| 2140 |
|
| 2141 | let new_attribute = super::read(&mut PeekRead::new(Cursor::new(bytes)), 300).unwrap();
|
| 2142 | assert_eq!((name.clone(), value.clone()), (new_attribute.0, new_attribute.1.unwrap()), "attribute round trip" );
|
| 2143 | }
|
| 2144 |
|
| 2145 |
|
| 2146 | {
|
| 2147 | let (name, value) = (
|
| 2148 | Text::from("asdkaspfokpaosdkfpaokswdpoakpsfokaposdkf" ),
|
| 2149 | AttributeValue::I32(0),
|
| 2150 | );
|
| 2151 |
|
| 2152 | let mut long_names = false;
|
| 2153 | super::validate(&name, &value, &mut long_names, false, IntegerBounds::zero(), false).unwrap();
|
| 2154 | assert!(long_names);
|
| 2155 | }
|
| 2156 |
|
| 2157 | {
|
| 2158 | let (name, value) = (
|
| 2159 | 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" ),
|
| 2160 | AttributeValue::I32(0),
|
| 2161 | );
|
| 2162 |
|
| 2163 | super::validate(&name, &value, &mut false, false, IntegerBounds::zero(), false).expect_err("name length check failed" );
|
| 2164 | }
|
| 2165 | }
|
| 2166 |
|
| 2167 | #[test ]
|
| 2168 | fn time_code_pack(){
|
| 2169 | let mut rng = thread_rng();
|
| 2170 |
|
| 2171 | let codes = std::iter::repeat_with(|| TimeCode {
|
| 2172 | hours: rng.gen_range(0 .. 24),
|
| 2173 | minutes: rng.gen_range(0 .. 60),
|
| 2174 | seconds: rng.gen_range(0 .. 60),
|
| 2175 | frame: rng.gen_range(0 .. 29),
|
| 2176 | drop_frame: random(),
|
| 2177 | color_frame: random(),
|
| 2178 | field_phase: random(),
|
| 2179 | binary_group_flags: [random(),random(),random()],
|
| 2180 | binary_groups: std::iter::repeat_with(|| rng.gen_range(0 .. 16)).take(8)
|
| 2181 | .collect::<SmallVec<[u8;8]>>().into_inner().unwrap()
|
| 2182 | });
|
| 2183 |
|
| 2184 | for code in codes.take(500) {
|
| 2185 | code.validate(true).expect("invalid timecode test input" );
|
| 2186 |
|
| 2187 | { // through tv60 packing, roundtrip
|
| 2188 | let packed_tv60 = code.pack_time_as_tv60_u32().expect("invalid timecode test input" );
|
| 2189 | let packed_user = code.pack_user_data_as_u32();
|
| 2190 | assert_eq!(TimeCode::from_tv60_time(packed_tv60, packed_user), code);
|
| 2191 | }
|
| 2192 |
|
| 2193 | { // through bytes, roundtrip
|
| 2194 | let mut bytes = Vec::<u8>::new();
|
| 2195 | code.write(&mut bytes).unwrap();
|
| 2196 | let decoded = TimeCode::read(&mut bytes.as_slice()).unwrap();
|
| 2197 | assert_eq!(code, decoded);
|
| 2198 | }
|
| 2199 |
|
| 2200 | {
|
| 2201 | let tv50_code = TimeCode {
|
| 2202 | drop_frame: false, // apparently, tv50 does not support drop frame, so do not use this value
|
| 2203 | .. code
|
| 2204 | };
|
| 2205 |
|
| 2206 | let packed_tv50 = code.pack_time_as_tv50_u32().expect("invalid timecode test input" );
|
| 2207 | let packed_user = code.pack_user_data_as_u32();
|
| 2208 | assert_eq!(TimeCode::from_tv50_time(packed_tv50, packed_user), tv50_code);
|
| 2209 | }
|
| 2210 |
|
| 2211 | {
|
| 2212 | let film24_code = TimeCode {
|
| 2213 | // apparently, film24 does not support some flags, so do not use those values
|
| 2214 | color_frame: false,
|
| 2215 | drop_frame: false,
|
| 2216 | .. code
|
| 2217 | };
|
| 2218 |
|
| 2219 | let packed_film24 = code.pack_time_as_film24_u32().expect("invalid timecode test input" );
|
| 2220 | let packed_user = code.pack_user_data_as_u32();
|
| 2221 | assert_eq!(TimeCode::from_film24_time(packed_film24, packed_user), film24_code);
|
| 2222 | }
|
| 2223 | }
|
| 2224 | }
|
| 2225 |
|
| 2226 | }
|
| 2227 | |