Skip to main content

exr/meta/
header.rs

1//! Contains collections of common attributes.
2//! Defines some data types that list all standard attributes.
3
4use std::collections::HashMap;
5
6use crate::meta::attribute::*; // FIXME shouldn't this need some more imports????
7use crate::{math::Vec2, meta::*};
8
9// TODO rename header to LayerDescription!
10
11/// Describes a single layer in a file.
12/// A file can have any number of layers.
13/// The meta data contains one header per layer.
14#[derive(Clone, Debug, PartialEq)]
15pub struct Header {
16    /// List of channels in this layer.
17    pub channels: ChannelList,
18
19    /// How the pixel data of all channels in this layer is compressed. May be
20    /// `Compression::Uncompressed`.
21    pub compression: Compression,
22
23    /// Describes how the pixels of this layer are divided into smaller blocks.
24    /// A single block can be loaded without processing all bytes of a file.
25    ///
26    /// Also describes whether a file contains multiple resolution levels: mip
27    /// maps or rip maps. This allows loading not the full resolution, but
28    /// the smallest sensible resolution.
29    // Required if file contains deep data or multiple layers.
30    // Note: This value must agree with the version field's tile bit and deep data bit.
31    // In this crate, this attribute will always have a value, for simplicity.
32    pub blocks: BlockDescription,
33
34    /// In what order the tiles of this header occur in the file.
35    pub line_order: LineOrder,
36
37    /// The resolution of this layer. Equivalent to the size of the
38    /// `DataWindow`.
39    pub layer_size: Vec2<usize>,
40
41    /// Whether this layer contains deep data.
42    pub deep: bool,
43
44    /// This library supports only deep data version 1.
45    pub deep_data_version: Option<i32>,
46
47    /// Number of chunks, that is, scan line blocks or tiles, that this image
48    /// has been divided into. This number is calculated once at the
49    /// beginning of the read process or when creating a header object.
50    ///
51    /// This value includes all chunks of all resolution levels.
52    ///
53    ///
54    /// __Warning__
55    /// _This value is relied upon. You should probably use
56    /// `Header::with_encoding`, which automatically updates the chunk
57    /// count._
58    pub chunk_count: usize,
59
60    // Required for deep data (deepscanline and deeptile) layers.
61    // Note: Since the value of "maxSamplesPerPixel"
62    // maybe be unknown at the time of opening the
63    // file, the value “ -1 ” is written to the file to
64    // indicate an unknown value. When the file is
65    // closed, this will be overwritten with the correct value.
66    // If file writing does not complete
67    // correctly due to an error, the value -1 will
68    // remain. In this case, the value must be derived
69    // by decoding each chunk in the layer
70    /// Maximum number of samples in a single pixel in a deep image.
71    pub max_samples_per_pixel: Option<usize>,
72
73    /// Includes mandatory fields like pixel aspect or display window
74    /// which must be the same for all layers.
75    pub shared_attributes: ImageAttributes,
76
77    /// Does not include the attributes required for reading the file contents.
78    /// Excludes standard fields that must be the same for all headers.
79    pub own_attributes: LayerAttributes,
80}
81
82/// Includes mandatory fields like pixel aspect or display window
83/// which must be the same for all layers.
84/// For more attributes, see struct `LayerAttributes`.
85#[derive(Clone, PartialEq, Debug)]
86pub struct ImageAttributes {
87    /// The rectangle anywhere in the global infinite 2D space
88    /// that clips all contents of the file.
89    pub display_window: IntegerBounds,
90
91    /// Aspect ratio of each pixel in this header.
92    pub pixel_aspect: f32,
93
94    /// The chromaticities attribute of the image. See the `Chromaticities`
95    /// type.
96    pub chromaticities: Option<Chromaticities>,
97
98    /// The time code of the image.
99    pub time_code: Option<TimeCode>,
100
101    /// Contains custom attributes.
102    /// Does not contain the attributes already present in the
103    /// `ImageAttributes`. Contains only attributes that are standardized to
104    /// be the same for all headers: chromaticities and time codes.
105    pub other: HashMap<Text, AttributeValue>,
106}
107
108/// Does not include the attributes required for reading the file contents.
109/// Excludes standard fields that must be the same for all headers.
110/// For more attributes, see struct `ImageAttributes`.
111#[derive(Clone, PartialEq)]
112pub struct LayerAttributes {
113    /// The name of this layer.
114    /// Required if this file contains deep data or multiple layers.
115    // As this is an attribute value, it is not restricted in length, may even be empty
116    pub layer_name: Option<Text>,
117
118    /// The top left corner of the rectangle that positions this layer
119    /// within the global infinite 2D space of the whole file.
120    /// This represents the position of the `DataWindow`.
121    pub layer_position: Vec2<i32>,
122
123    /// Part of the perspective projection. Default should be `(0, 0)`.
124    // TODO same for all layers?
125    pub screen_window_center: Vec2<f32>,
126
127    // TODO same for all layers?
128    /// Part of the perspective projection. Default should be `1`.
129    pub screen_window_width: f32,
130
131    /// The white luminance of the colors.
132    /// Defines the luminance in candelas per square meter, Nits, of the rgb
133    /// value `(1, 1, 1)`.
134    // If the chromaticities and the whiteLuminance of an RGB image are
135    // known, then it is possible to convert the image's pixels from RGB
136    // to CIE XYZ tristimulus values (see function RGBtoXYZ() in header
137    // file ImfChromaticities.h).
138    pub white_luminance: Option<f32>,
139
140    /// The adopted neutral of the colors. Specifies the CIE (x,y) frequency
141    /// coordinates that should be considered neutral during color
142    /// rendering. Pixels in the image whose CIE (x,y) frequency coordinates
143    /// match the adopted neutral value should be mapped to neutral values
144    /// on the given display.
145    pub adopted_neutral: Option<Vec2<f32>>,
146
147    /// Name of the color transform function that is applied for rendering the
148    /// image.
149    pub rendering_transform_name: Option<Text>,
150
151    /// Name of the color transform function that computes the look modification
152    /// of the image.
153    pub look_modification_transform_name: Option<Text>,
154
155    /// The horizontal density, in pixels per inch.
156    /// The image's vertical output density can be computed using
157    /// `horizontal_density * pixel_aspect_ratio`.
158    pub horizontal_density: Option<f32>,
159
160    /// Name of the owner.
161    pub owner: Option<Text>,
162
163    /// Additional textual information.
164    pub comments: Option<Text>,
165
166    /// The date of image creation, in `YYYY:MM:DD hh:mm:ss` format.
167    // TODO parse!
168    pub capture_date: Option<Text>,
169
170    /// Time offset from UTC.
171    pub utc_offset: Option<f32>,
172
173    /// Geographical image location.
174    pub longitude: Option<f32>,
175
176    /// Geographical image location.
177    pub latitude: Option<f32>,
178
179    /// Geographical image location.
180    pub altitude: Option<f32>,
181
182    /// Camera focus in meters.
183    pub focus: Option<f32>,
184
185    /// Exposure time in seconds.
186    pub exposure: Option<f32>,
187
188    /// Camera aperture measured in f-stops. Equals the focal length
189    /// of the lens divided by the diameter of the iris opening.
190    pub aperture: Option<f32>,
191
192    /// Iso-speed of the camera sensor.
193    pub iso_speed: Option<f32>,
194
195    /// If this is an environment map, specifies how to interpret it.
196    pub environment_map: Option<EnvironmentMap>,
197
198    /// Identifies film manufacturer, film type, film roll and frame position
199    /// within the roll.
200    pub film_key_code: Option<KeyCode>,
201
202    /// Specifies how texture map images are extrapolated.
203    /// Values can be `black`, `clamp`, `periodic`, or `mirror`.
204    pub wrap_mode_name: Option<Text>,
205
206    /// Frames per second if this is a frame in a sequence.
207    pub frames_per_second: Option<Rational>,
208
209    /// Specifies the view names for multi-view, for example stereo, image
210    /// files.
211    pub multi_view_names: Option<Vec<Text>>,
212
213    /// The matrix that transforms 3D points from the world to the camera
214    /// coordinate space. Left-handed coordinate system, y up, z forward.
215    pub world_to_camera: Option<Matrix4x4>,
216
217    /// The matrix that transforms 3D points from the world to the "Normalized
218    /// Device Coordinate" space. Left-handed coordinate system, y up, z
219    /// forward.
220    pub world_to_normalized_device: Option<Matrix4x4>,
221
222    /// Specifies whether the pixels in a deep image are sorted and
223    /// non-overlapping.
224    pub deep_image_state: Option<Rational>,
225
226    /// If the image was cropped, contains the original data window.
227    pub original_data_window: Option<IntegerBounds>,
228
229    /// An 8-bit rgba image representing the rendered image.
230    pub preview: Option<Preview>,
231
232    /// Name of the view, which is typically either `"right"` or `"left"` for a
233    /// stereoscopic image.
234    pub view_name: Option<Text>,
235
236    /// The name of the software that produced this image.
237    pub software_name: Option<Text>,
238
239    /// The near clip plane of the virtual camera projection.
240    pub near_clip_plane: Option<f32>,
241
242    /// The far clip plane of the virtual camera projection.
243    pub far_clip_plane: Option<f32>,
244
245    /// The field of view angle, along the horizontal axis, in degrees.
246    pub horizontal_field_of_view: Option<f32>,
247
248    /// The field of view angle, along the horizontal axis, in degrees.
249    pub vertical_field_of_view: Option<f32>,
250
251    /// Contains custom attributes.
252    /// Does not contain the attributes already present in the `Header` or
253    /// `LayerAttributes` struct. Does not contain attributes that are
254    /// standardized to be the same for all layers: no chromaticities and no
255    /// time codes.
256    pub other: HashMap<Text, AttributeValue>,
257}
258
259impl LayerAttributes {
260    /// Create default layer attributes with a data position of zero.
261    pub fn named(layer_name: impl Into<Text>) -> Self {
262        Self {
263            layer_name: Some(layer_name.into()),
264            ..Self::default()
265        }
266    }
267
268    /// Set the data position of this layer.
269    #[must_use]
270    pub fn with_position(self, data_position: Vec2<i32>) -> Self {
271        Self {
272            layer_position: data_position,
273            ..self
274        }
275    }
276
277    /// Set all common camera projection attributes at once.
278    pub fn with_camera_frustum(
279        self,
280        world_to_camera: Matrix4x4,
281        world_to_normalized_device: Matrix4x4,
282        field_of_view: impl Into<Vec2<f32>>,
283        depth_clip_range: std::ops::Range<f32>,
284    ) -> Self {
285        let fov = field_of_view.into();
286
287        Self {
288            world_to_normalized_device: Some(world_to_normalized_device),
289            world_to_camera: Some(world_to_camera),
290            horizontal_field_of_view: Some(fov.x()),
291            vertical_field_of_view: Some(fov.y()),
292            near_clip_plane: Some(depth_clip_range.start),
293            far_clip_plane: Some(depth_clip_range.end),
294            ..self
295        }
296    }
297}
298
299impl ImageAttributes {
300    /// Set the display position and size of this image.
301    #[must_use]
302    pub fn new(display_window: IntegerBounds) -> Self {
303        Self {
304            pixel_aspect: 1.0,
305            chromaticities: None,
306            time_code: None,
307            other: Default::default(),
308            display_window,
309        }
310    }
311
312    /// Set the display position to zero and use the specified size for this
313    /// image.
314    pub fn with_size(size: impl Into<Vec2<usize>>) -> Self {
315        Self::new(IntegerBounds::from_dimensions(size))
316    }
317}
318
319impl Header {
320    /// Create a new Header with the specified name, display window and
321    /// channels. Use `Header::with_encoding` and the similar methods to add
322    /// further properties to the header.
323    ///
324    /// The other settings are left to their default values:
325    /// - RLE compression
326    /// - display window equal to data window
327    /// - tiles (64 x 64 px)
328    /// - unspecified line order
329    /// - no custom attributes
330    pub fn new(
331        name: Text,
332        data_size: impl Into<Vec2<usize>>,
333        channels: SmallVec<[ChannelDescription; 5]>,
334    ) -> Self {
335        let data_size: Vec2<usize> = data_size.into();
336
337        let compression = Compression::RLE;
338        let blocks = BlockDescription::Tiles(TileDescription {
339            tile_size: Vec2(64, 64),
340            level_mode: LevelMode::Singular,
341            rounding_mode: RoundingMode::Down,
342        });
343
344        Self {
345            layer_size: data_size,
346            compression,
347            blocks,
348
349            channels: ChannelList::new(channels),
350            line_order: LineOrder::Unspecified,
351
352            shared_attributes: ImageAttributes::with_size(data_size),
353            own_attributes: LayerAttributes::named(name),
354
355            chunk_count: compute_chunk_count(compression, data_size, blocks),
356
357            deep: false,
358            deep_data_version: None,
359            max_samples_per_pixel: None,
360        }
361    }
362
363    /// Set the display window, that is, the global clipping rectangle.
364    /// __Must be the same for all headers of a file.__
365    #[must_use]
366    pub const fn with_display_window(mut self, display_window: IntegerBounds) -> Self {
367        self.shared_attributes.display_window = display_window;
368        self
369    }
370
371    /// Set the offset of this layer.
372    #[must_use]
373    pub const fn with_position(mut self, position: Vec2<i32>) -> Self {
374        self.own_attributes.layer_position = position;
375        self
376    }
377
378    /// Set compression, tiling, and line order. Automatically computes chunk
379    /// count.
380    #[must_use]
381    pub fn with_encoding(
382        self,
383        compression: Compression,
384        blocks: BlockDescription,
385        line_order: LineOrder,
386    ) -> Self {
387        Self {
388            chunk_count: compute_chunk_count(compression, self.layer_size, blocks),
389            compression,
390            blocks,
391            line_order,
392            ..self
393        }
394    }
395
396    /// Set **all** attributes of the header that are not shared with all other
397    /// headers in the image.
398    #[must_use]
399    pub fn with_attributes(self, own_attributes: LayerAttributes) -> Self {
400        Self {
401            own_attributes,
402            ..self
403        }
404    }
405
406    /// Set **all** attributes of the header that are shared with all other
407    /// headers in the image.
408    #[must_use]
409    pub fn with_shared_attributes(self, shared_attributes: ImageAttributes) -> Self {
410        Self {
411            shared_attributes,
412            ..self
413        }
414    }
415
416    /// Iterate over all blocks, in the order specified by the headers line
417    /// order attribute. Unspecified line order is treated as increasing
418    /// line order. Also enumerates the index of each block in the header,
419    /// as if it were sorted in increasing line order.
420    pub fn enumerate_ordered_blocks(&self) -> impl Iterator<Item = (usize, TileIndices)> + Send {
421        let increasing_y = self.blocks_increasing_y_order().enumerate();
422
423        // TODO without box?
424        let ordered: Box<dyn Send + Iterator<Item = (usize, TileIndices)>> = {
425            if self.line_order == LineOrder::Decreasing {
426                Box::new(increasing_y.rev())
427            } else {
428                Box::new(increasing_y)
429            }
430        };
431
432        ordered
433    }
434
435    // /// Iterate over all blocks, in the order specified by the headers line order
436    // attribute. Also includes an index of the block if it were
437    // `LineOrder::Increasing`, starting at zero for this header.
438    // pub fn enumerate_ordered_blocks(&self) -> impl Iterator<Item = (usize,
439    // TileIndices)> + Send { let increasing_y =
440    // self.blocks_increasing_y_order().enumerate();
441    //
442    // let ordered: Box<dyn Send + Iterator<Item = (usize, TileIndices)>> = {
443    // if self.line_order == LineOrder::Decreasing {
444    // Box::new(increasing_y.rev()) // TODO without box?
445    // }
446    // else {
447    // Box::new(increasing_y)
448    // }
449    // };
450    //
451    // ordered
452    // }
453
454    /// Iterate over all tile indices in this header in `LineOrder::Increasing`
455    /// order.
456    pub fn blocks_increasing_y_order(
457        &self,
458    ) -> impl ExactSizeIterator<Item = TileIndices> + DoubleEndedIterator {
459        fn tiles_of(
460            image_size: Vec2<usize>,
461            tile_size: Vec2<usize>,
462            level_index: Vec2<usize>,
463        ) -> impl Iterator<Item = TileIndices> {
464            fn divide_and_rest(
465                total_size: usize,
466                block_size: usize,
467            ) -> impl Iterator<Item = (usize, usize)> {
468                let block_count = compute_block_count(total_size, block_size);
469                (0..block_count).map(move |block_index| {
470                    (
471                        block_index,
472                        calculate_block_size(total_size, block_size, block_index)
473                            .expect("block size calculation bug"),
474                    )
475                })
476            }
477
478            divide_and_rest(image_size.height(), tile_size.height()).flat_map(
479                move |(y_index, tile_height)| {
480                    divide_and_rest(image_size.width(), tile_size.width()).map(
481                        move |(x_index, tile_width)| TileIndices {
482                            size: Vec2(tile_width, tile_height),
483                            location: TileCoordinates {
484                                tile_index: Vec2(x_index, y_index),
485                                level_index,
486                            },
487                        },
488                    )
489                },
490            )
491        }
492
493        let vec: Vec<TileIndices> = {
494            if let BlockDescription::Tiles(tiles) = self.blocks {
495                match tiles.level_mode {
496                    LevelMode::Singular => {
497                        tiles_of(self.layer_size, tiles.tile_size, Vec2(0, 0)).collect()
498                    }
499                    LevelMode::MipMap => mip_map_levels(tiles.rounding_mode, self.layer_size)
500                        .flat_map(move |(level_index, level_size)| {
501                            tiles_of(level_size, tiles.tile_size, Vec2(level_index, level_index))
502                        })
503                        .collect(),
504                    LevelMode::RipMap => rip_map_levels(tiles.rounding_mode, self.layer_size)
505                        .flat_map(move |(level_index, level_size)| {
506                            tiles_of(level_size, tiles.tile_size, level_index)
507                        })
508                        .collect(),
509                }
510            } else {
511                let tiles = Vec2(self.layer_size.0, self.compression.scan_lines_per_block());
512                tiles_of(self.layer_size, tiles, Vec2(0, 0)).collect()
513            }
514        };
515
516        vec.into_iter() // TODO without collect
517    }
518
519    // TODO
520    // The block indices of this header, ordered as they would appear in the file.
521    // pub fn ordered_block_indices<'s>(&'s self, layer_index: usize) -> impl 's +
522    // Iterator<Item=BlockIndex> { self.enumerate_ordered_blocks().
523    // map(|(chunk_index, tile)|{ let data_indices =
524    // self.get_absolute_block_pixel_coordinates(tile.location).expect("tile
525    // coordinate bug");
526    //
527    // BlockIndex {
528    // layer: layer_index,
529    // level: tile.location.level_index,
530    // pixel_position: data_indices.position.to_usize("data indices
531    // start").expect("data index bug"), pixel_size: data_indices.size,
532    // }
533    // })
534    // }
535
536    // TODO reuse this function everywhere
537    /// The default pixel resolution of a single block (tile or scan line
538    /// block). Not all blocks have this size, because they may be cutoff at
539    /// the end of the image.
540    pub const fn max_block_pixel_size(&self) -> Vec2<usize> {
541        match self.blocks {
542            BlockDescription::ScanLines => {
543                Vec2(self.layer_size.0, self.compression.scan_lines_per_block())
544            }
545            BlockDescription::Tiles(tiles) => tiles.tile_size,
546        }
547    }
548
549    /// Calculate the position of a block in the global infinite 2D space of a
550    /// file. May be negative.
551    pub fn get_block_data_window_pixel_coordinates(
552        &self,
553        tile: TileCoordinates,
554    ) -> Result<IntegerBounds> {
555        let data = self.get_absolute_block_pixel_coordinates(tile)?;
556        Ok(data.with_origin(self.own_attributes.layer_position))
557    }
558
559    /// Calculate the pixel index rectangle inside this header. Is not negative.
560    /// Starts at `0`.
561    pub fn get_absolute_block_pixel_coordinates(
562        &self,
563        tile: TileCoordinates,
564    ) -> Result<IntegerBounds> {
565        if let BlockDescription::Tiles(tiles) = self.blocks {
566            let Vec2(data_width, data_height) = self.layer_size;
567
568            let data_width =
569                compute_level_size(tiles.rounding_mode, data_width, tile.level_index.x());
570            let data_height =
571                compute_level_size(tiles.rounding_mode, data_height, tile.level_index.y());
572            let absolute_tile_coordinates =
573                tile.to_data_indices(tiles.tile_size, Vec2(data_width, data_height))?;
574
575            if i64::from(absolute_tile_coordinates.position.x()) >= data_width as i64
576                || i64::from(absolute_tile_coordinates.position.y()) >= data_height as i64
577            {
578                return Err(Error::invalid("data block tile index"));
579            }
580
581            Ok(absolute_tile_coordinates)
582        } else {
583            // this is a scanline image
584            debug_assert_eq!(tile.tile_index.0, 0, "block index calculation bug");
585
586            let (y, height) = calculate_block_position_and_size(
587                self.layer_size.height(),
588                self.compression.scan_lines_per_block(),
589                tile.tile_index.y(),
590            )?;
591
592            Ok(IntegerBounds {
593                position: Vec2(0, usize_to_i32(y, "tile y")?),
594                size: Vec2(self.layer_size.width(), height),
595            })
596        }
597
598        // TODO deep data?
599    }
600
601    /// Return the tile index, converting scan line block coordinates to tile
602    /// indices. Starts at `0` and is not negative.
603    pub fn get_block_data_indices(&self, block: &CompressedBlock) -> Result<TileCoordinates> {
604        Ok(match block {
605            CompressedBlock::Tile(ref tile) => tile.coordinates,
606
607            CompressedBlock::ScanLine(ref block) => {
608                let size = self.compression.scan_lines_per_block() as i32;
609
610                let diff = block
611                    .y_coordinate
612                    .checked_sub(self.own_attributes.layer_position.y())
613                    .ok_or_else(|| Error::invalid("invalid header"))?;
614                let y = diff.checked_div(size).ok_or_else(|| Error::invalid("invalid header"))?;
615
616                if y < 0 {
617                    return Err(Error::invalid("scan block y coordinate"));
618                }
619
620                TileCoordinates {
621                    tile_index: Vec2(0, y as usize),
622                    level_index: Vec2(0, 0),
623                }
624            }
625
626            _ => return Err(Error::unsupported("deep data not supported yet")),
627        })
628    }
629
630    /// Computes the absolute tile coordinate data indices, which start at `0`.
631    pub fn get_scan_line_block_tile_coordinates(
632        &self,
633        block_y_coordinate: i32,
634    ) -> Result<TileCoordinates> {
635        let size = self.compression.scan_lines_per_block() as i32;
636
637        let diff = block_y_coordinate
638            .checked_sub(self.own_attributes.layer_position.1)
639            .ok_or_else(|| Error::invalid("invalid header"))?;
640        let y = diff.checked_div(size).ok_or_else(|| Error::invalid("invalid header"))?;
641
642        if y < 0 {
643            return Err(Error::invalid("scan block y coordinate"));
644        }
645
646        Ok(TileCoordinates {
647            tile_index: Vec2(0, y as usize),
648            level_index: Vec2(0, 0),
649        })
650    }
651
652    /// Maximum byte length of an uncompressed or compressed block, used for
653    /// validation.
654    pub fn max_block_byte_size(&self) -> usize {
655        self.channels.bytes_per_pixel
656            * match self.blocks {
657                BlockDescription::Tiles(tiles) => tiles.tile_size.area(),
658                BlockDescription::ScanLines => {
659                    self.compression.scan_lines_per_block() * self.layer_size.width()
660                } // TODO What about deep data???
661            }
662    }
663
664    /// Returns the number of bytes that the pixels of this header will require
665    /// when stored without compression. Respects multi-resolution levels and
666    /// subsampling.
667    pub fn total_pixel_bytes(&self) -> usize {
668        assert!(!self.deep);
669
670        let pixel_count_of_levels = |size: Vec2<usize>| -> usize {
671            match self.blocks {
672                BlockDescription::ScanLines => size.area(),
673                BlockDescription::Tiles(tile_description) => match tile_description.level_mode {
674                    LevelMode::Singular => size.area(),
675
676                    LevelMode::MipMap => mip_map_levels(tile_description.rounding_mode, size)
677                        .map(|(_, size)| size.area())
678                        .sum(),
679
680                    LevelMode::RipMap => rip_map_levels(tile_description.rounding_mode, size)
681                        .map(|(_, size)| size.area())
682                        .sum(),
683                },
684            }
685        };
686
687        self.channels
688            .list
689            .iter()
690            .map(|channel: &ChannelDescription| {
691                pixel_count_of_levels(channel.subsampled_resolution(self.layer_size))
692                    * channel.sample_type.bytes_per_sample()
693            })
694            .sum()
695    }
696
697    /// Approximates the maximum number of bytes that the pixels of this header
698    /// will consume in a file. Due to compression, the actual byte size may
699    /// be smaller.
700    pub fn max_pixel_file_bytes(&self) -> usize {
701        assert!(!self.deep);
702
703        self.chunk_count * 64 // at most 64 bytes overhead for each chunk (header index, tile description, chunk size, and more)
704            + self.total_pixel_bytes()
705    }
706
707    /// Validate this instance.
708    pub fn validate(&self, is_multilayer: bool, long_names: &mut bool, strict: bool) -> UnitResult {
709        self.data_window().validate(None)?;
710        self.shared_attributes.display_window.validate(None)?;
711
712        if strict {
713            if is_multilayer && self.own_attributes.layer_name.is_none() {
714                return Err(missing_attribute("layer name for multi layer file"));
715            }
716
717            if self.blocks == BlockDescription::ScanLines
718                && self.line_order == LineOrder::Unspecified
719            {
720                return Err(Error::invalid("unspecified line order in scan line images"));
721            }
722
723            if self.layer_size == Vec2(0, 0) {
724                return Err(Error::invalid("empty data window"));
725            }
726
727            if self.shared_attributes.display_window.size == Vec2(0, 0) {
728                return Err(Error::invalid("empty display window"));
729            }
730
731            if !self.shared_attributes.pixel_aspect.is_normal()
732                || self.shared_attributes.pixel_aspect < 1.0e-6
733                || self.shared_attributes.pixel_aspect > 1.0e6
734            {
735                return Err(Error::invalid("pixel aspect ratio"));
736            }
737
738            if self.own_attributes.screen_window_width < 0.0 {
739                return Err(Error::invalid("screen window width"));
740            }
741        }
742
743        let allow_subsampling = !self.deep && self.blocks == BlockDescription::ScanLines;
744        self.channels.validate(allow_subsampling, self.data_window(), strict)?;
745
746        for (name, value) in &self.shared_attributes.other {
747            attribute::validate(
748                name,
749                value,
750                long_names,
751                allow_subsampling,
752                self.data_window(),
753                strict,
754            )?;
755        }
756
757        for (name, value) in &self.own_attributes.other {
758            attribute::validate(
759                name,
760                value,
761                long_names,
762                allow_subsampling,
763                self.data_window(),
764                strict,
765            )?;
766        }
767
768        // this is only to check whether someone tampered with our precious values, to
769        // avoid writing an invalid file
770        if self.chunk_count != compute_chunk_count(self.compression, self.layer_size, self.blocks) {
771            return Err(Error::invalid("chunk count attribute")); // TODO this may be an expensive check?
772        }
773
774        // check if attribute names appear twice
775        if strict {
776            for name in self.shared_attributes.other.keys() {
777                if self.own_attributes.other.contains_key(name) {
778                    return Err(Error::invalid(format!("duplicate attribute name: `{name}`")));
779                }
780            }
781
782            for &reserved in header::standard_names::ALL {
783                let name = Text::from_bytes_unchecked(SmallVec::from_slice(reserved));
784                if self.own_attributes.other.contains_key(&name)
785                    || self.shared_attributes.other.contains_key(&name)
786                {
787                    return Err(Error::invalid(format!(
788                        "attribute name `{}` is reserved and cannot be custom",
789                        Text::from_bytes_unchecked(reserved.into())
790                    )));
791                }
792            }
793        }
794
795        if self.deep {
796            if strict {
797                if self.own_attributes.layer_name.is_none() {
798                    return Err(missing_attribute("layer name for deep file"));
799                }
800
801                if self.max_samples_per_pixel.is_none() {
802                    return Err(Error::invalid(
803                        "missing max samples per pixel attribute for deepdata",
804                    ));
805                }
806            }
807
808            match self.deep_data_version {
809                Some(1) => {}
810                Some(_) => return Err(Error::unsupported("deep data version")),
811                None => return Err(missing_attribute("deep data version")),
812            }
813
814            if !self.compression.supports_deep_data() {
815                return Err(Error::invalid("compression method does not support deep data"));
816            }
817        }
818
819        Ok(())
820    }
821
822    /// Read the headers without validating them.
823    pub fn read_all(
824        read: &mut PeekRead<impl Read>,
825        version: &Requirements,
826        pedantic: bool,
827    ) -> Result<Headers> {
828        if version.is_multilayer() {
829            let mut headers = SmallVec::new();
830
831            while !sequence_end::has_come(read)? {
832                headers.push(Self::read(read, version, pedantic)?);
833            }
834
835            Ok(headers)
836        } else {
837            Ok(smallvec![Self::read(read, version, pedantic)?])
838        }
839    }
840
841    /// Without validation, write the headers to the byte stream.
842    pub fn write_all(headers: &[Self], write: &mut impl Write, is_multilayer: bool) -> UnitResult {
843        for header in headers {
844            header.write(write)?;
845        }
846
847        if is_multilayer {
848            sequence_end::write(write)?;
849        }
850
851        Ok(())
852    }
853
854    /// Iterate over all `(name, attribute_value)` pairs in this header that
855    /// would be written to a file. The order of attributes is arbitrary and
856    /// may change in future versions. Will always contain all strictly
857    /// required attributes, such as channels, compression, data window, and
858    /// similar. Hint: Use `attribute.kind_name()` to obtain the
859    /// standardized name of the attribute type. Does not validate the
860    /// header or attributes.
861    // This function is used for writing the attributes to files.
862    #[inline]
863    pub fn all_named_attributes(&self) -> impl '_ + Iterator<Item = (&TextSlice, AttributeValue)> {
864        use std::iter::{empty, once, once_with};
865
866        use AttributeValue::*;
867
868        use crate::meta::header::standard_names::*;
869
870        #[inline]
871        fn optional<'t, T: Clone>(
872            name: &'t TextSlice,
873            to_attribute: impl Fn(T) -> AttributeValue,
874            value: &'t Option<T>,
875        ) -> impl Iterator<Item = (&'t TextSlice, AttributeValue)> {
876            value.as_ref().map(move |value| (name, to_attribute(value.clone()))).into_iter()
877        }
878
879        #[inline]
880        fn required<'s, T: Clone>(
881            name: &'s TextSlice,
882            to_attribute: impl Fn(T) -> AttributeValue,
883            value: &'s T,
884        ) -> impl Iterator<Item = (&'s TextSlice, AttributeValue)> {
885            once((name, to_attribute((*value).clone())))
886        }
887
888        // used to type-check local variables. only requried because you cannot do `let
889        // i: impl Iterator<> = ...`
890        #[inline]
891        const fn expect_is_iter<'s, T: Iterator<Item = (&'s TextSlice, AttributeValue)>>(
892            val: T,
893        ) -> T {
894            val
895        }
896
897        macro_rules! iter_all {
898            ( $( $value:expr ),* ) => {
899                empty() $( .chain( $value ) )*
900            };
901        }
902
903        macro_rules! required_attributes {
904            ( $($name: ident : $variant: ident = $value: expr),* ) => {
905                expect_is_iter(iter_all!(
906                    $( required($name, $variant, $value) ),*
907                ))
908            };
909        }
910
911        macro_rules! optional_attributes {
912            ( $($name: ident : $variant: ident = $value: expr),* ) => {
913                expect_is_iter(iter_all!(
914                    $( optional($name, $variant, $value) ),*
915                ))
916            };
917        }
918
919        #[inline]
920        fn usize_as_i32(value: usize) -> AttributeValue {
921            I32(i32::try_from(value).expect("usize exceeds i32 range"))
922        }
923
924        let block_type_and_tiles = expect_is_iter(
925            once_with(move || {
926                let (block_type, tiles) = match self.blocks {
927                    BlockDescription::ScanLines => (attribute::BlockType::ScanLine, None),
928                    BlockDescription::Tiles(tiles) => (attribute::BlockType::Tile, Some(tiles)),
929                };
930
931                once((BLOCK_TYPE, BlockType(block_type)))
932                    .chain(tiles.map(|tiles| (TILES, TileDescription(tiles))))
933            })
934            .flatten(),
935        );
936
937        let data_window =
938            expect_is_iter(once_with(move || (DATA_WINDOW, IntegerBounds(self.data_window()))));
939
940        // dwa writes compression parameters as attribute.
941        let dwa_compr_level = expect_is_iter(
942            once_with(move || match self.compression {
943                attribute::Compression::DWAA(Some(level))
944                | attribute::Compression::DWAB(Some(level)) => {
945                    Some((DWA_COMPRESSION_LEVEL, F32(level)))
946                }
947
948                _ => None,
949            })
950            .flatten(),
951        );
952
953        let opt_core_attrs = optional_attributes!(
954            DEEP_DATA_VERSION: I32 = &self.deep_data_version,
955            MAX_SAMPLES: usize_as_i32 = &self.max_samples_per_pixel
956        )
957        .chain(block_type_and_tiles)
958        .chain(dwa_compr_level);
959
960        let req_core_attrs = required_attributes!(
961            // chunks is not actually required, but always computed in this library anyways
962            CHUNKS: usize_as_i32 = &self.chunk_count,
963
964            CHANNELS: ChannelList = &self.channels,
965            COMPRESSION: Compression = &self.compression,
966            LINE_ORDER: LineOrder = &self.line_order,
967
968            DISPLAY_WINDOW: IntegerBounds = &self.shared_attributes.display_window,
969            PIXEL_ASPECT: F32 = &self.shared_attributes.pixel_aspect,
970
971            WINDOW_CENTER: FloatVec2 = &self.own_attributes.screen_window_center,
972            WINDOW_WIDTH: F32 = &self.own_attributes.screen_window_width
973        )
974        .chain(data_window);
975
976        let opt_attr = optional_attributes!(
977            NAME: Text = &self.own_attributes.layer_name,
978            WHITE_LUMINANCE: F32 = &self.own_attributes.white_luminance,
979            ADOPTED_NEUTRAL: FloatVec2 = &self.own_attributes.adopted_neutral,
980            RENDERING_TRANSFORM: Text = &self.own_attributes.rendering_transform_name,
981            LOOK_MOD_TRANSFORM: Text = &self.own_attributes.look_modification_transform_name,
982            X_DENSITY: F32 = &self.own_attributes.horizontal_density,
983            OWNER: Text = &self.own_attributes.owner,
984            COMMENTS: Text = &self.own_attributes.comments,
985            CAPTURE_DATE: Text = &self.own_attributes.capture_date,
986            UTC_OFFSET: F32 = &self.own_attributes.utc_offset,
987            LONGITUDE: F32 = &self.own_attributes.longitude,
988            LATITUDE: F32 = &self.own_attributes.latitude,
989            ALTITUDE: F32 = &self.own_attributes.altitude,
990            FOCUS: F32 = &self.own_attributes.focus,
991            EXPOSURE_TIME: F32 = &self.own_attributes.exposure,
992            APERTURE: F32 = &self.own_attributes.aperture,
993            ISO_SPEED: F32 = &self.own_attributes.iso_speed,
994            ENVIRONMENT_MAP: EnvironmentMap = &self.own_attributes.environment_map,
995            KEY_CODE: KeyCode = &self.own_attributes.film_key_code,
996            TIME_CODE: TimeCode = &self.shared_attributes.time_code,
997            WRAP_MODES: Text = &self.own_attributes.wrap_mode_name,
998            FRAMES_PER_SECOND: Rational = &self.own_attributes.frames_per_second,
999            MULTI_VIEW: TextVector = &self.own_attributes.multi_view_names,
1000            WORLD_TO_CAMERA: Matrix4x4 = &self.own_attributes.world_to_camera,
1001            WORLD_TO_NDC: Matrix4x4 = &self.own_attributes.world_to_normalized_device,
1002            DEEP_IMAGE_STATE: Rational = &self.own_attributes.deep_image_state,
1003            ORIGINAL_DATA_WINDOW: IntegerBounds = &self.own_attributes.original_data_window,
1004            CHROMATICITIES: Chromaticities = &self.shared_attributes.chromaticities,
1005            PREVIEW: Preview = &self.own_attributes.preview,
1006            VIEW: Text = &self.own_attributes.view_name,
1007            NEAR: F32 = &self.own_attributes.near_clip_plane,
1008            FAR: F32 = &self.own_attributes.far_clip_plane,
1009            FOV_X: F32 = &self.own_attributes.horizontal_field_of_view,
1010            FOV_Y: F32 = &self.own_attributes.vertical_field_of_view,
1011            SOFTWARE: Text = &self.own_attributes.software_name
1012        );
1013
1014        let other = self
1015            .own_attributes
1016            .other
1017            .iter()
1018            .chain(self.shared_attributes.other.iter())
1019            .map(|(name, val)| (name.as_slice(), val.clone())); // TODO no clone
1020
1021        req_core_attrs.chain(opt_core_attrs).chain(opt_attr).chain(other)
1022    }
1023
1024    /// Read the value without validating.
1025    pub fn read(
1026        read: &mut PeekRead<impl Read>,
1027        requirements: &Requirements,
1028        pedantic: bool,
1029    ) -> Result<Self> {
1030        let max_string_len = if requirements.has_long_names {
1031            256
1032        } else {
1033            32
1034        }; // TODO DRY this information
1035
1036        // these required attributes will be filled when encountered while parsing
1037        let mut tiles = None;
1038        let mut block_type = None;
1039        let mut version = None;
1040        let mut chunk_count = None;
1041        let mut max_samples_per_pixel = None;
1042        let mut channels = None;
1043        let mut compression = None;
1044        let mut data_window = None;
1045        let mut display_window = None;
1046        let mut line_order = None;
1047        let mut dwa_compression_level = None;
1048
1049        let mut layer_attributes = LayerAttributes::default();
1050        let mut image_attributes = ImageAttributes::new(IntegerBounds::zero());
1051
1052        // read each attribute in this header
1053        while !sequence_end::has_come(read)? {
1054            let (attribute_name, value) = attribute::read(read, max_string_len)?;
1055
1056            // if the attribute value itself is ok, record it
1057            match value {
1058                Ok(value) => {
1059                    use crate::meta::{
1060                        attribute::AttributeValue::*, header::standard_names as name,
1061                    };
1062
1063                    // if the attribute is a required attribute, set the corresponding variable
1064                    // directly. otherwise, add the attribute to the vector of
1065                    // custom attributes
1066
1067                    // the following attributes will only be set if the type matches the commonly
1068                    // used type for that attribute
1069                    match (attribute_name.as_slice(), value) {
1070                        (name::BLOCK_TYPE, Text(value)) => {
1071                            block_type = Some(attribute::BlockType::parse(value)?);
1072                        }
1073                        (name::TILES, TileDescription(value)) => tiles = Some(value),
1074                        (name::CHANNELS, ChannelList(value)) => channels = Some(value),
1075                        (name::COMPRESSION, Compression(value)) => compression = Some(value),
1076                        (name::DATA_WINDOW, IntegerBounds(value)) => data_window = Some(value),
1077                        (name::DISPLAY_WINDOW, IntegerBounds(value)) => {
1078                            display_window = Some(value);
1079                        }
1080                        (name::LINE_ORDER, LineOrder(value)) => line_order = Some(value),
1081                        (name::DEEP_DATA_VERSION, I32(value)) => version = Some(value),
1082
1083                        (name::MAX_SAMPLES, I32(value)) => {
1084                            max_samples_per_pixel = Some(i32_to_usize(value, "max sample count")?);
1085                        }
1086
1087                        (name::CHUNKS, I32(value)) => {
1088                            chunk_count = Some(i32_to_usize(value, "chunk count")?);
1089                        }
1090
1091                        (name::NAME, Text(value)) => layer_attributes.layer_name = Some(value),
1092                        (name::WINDOW_CENTER, FloatVec2(value)) => {
1093                            layer_attributes.screen_window_center = value;
1094                        }
1095                        (name::WINDOW_WIDTH, F32(value)) => {
1096                            layer_attributes.screen_window_width = value;
1097                        }
1098
1099                        (name::WHITE_LUMINANCE, F32(value)) => {
1100                            layer_attributes.white_luminance = Some(value);
1101                        }
1102                        (name::ADOPTED_NEUTRAL, FloatVec2(value)) => {
1103                            layer_attributes.adopted_neutral = Some(value);
1104                        }
1105                        (name::RENDERING_TRANSFORM, Text(value)) => {
1106                            layer_attributes.rendering_transform_name = Some(value);
1107                        }
1108                        (name::LOOK_MOD_TRANSFORM, Text(value)) => {
1109                            layer_attributes.look_modification_transform_name = Some(value);
1110                        }
1111                        (name::X_DENSITY, F32(value)) => {
1112                            layer_attributes.horizontal_density = Some(value);
1113                        }
1114
1115                        (name::OWNER, Text(value)) => layer_attributes.owner = Some(value),
1116                        (name::COMMENTS, Text(value)) => layer_attributes.comments = Some(value),
1117                        (name::CAPTURE_DATE, Text(value)) => {
1118                            layer_attributes.capture_date = Some(value);
1119                        }
1120                        (name::UTC_OFFSET, F32(value)) => layer_attributes.utc_offset = Some(value),
1121                        (name::LONGITUDE, F32(value)) => layer_attributes.longitude = Some(value),
1122                        (name::LATITUDE, F32(value)) => layer_attributes.latitude = Some(value),
1123                        (name::ALTITUDE, F32(value)) => layer_attributes.altitude = Some(value),
1124                        (name::FOCUS, F32(value)) => layer_attributes.focus = Some(value),
1125                        (name::EXPOSURE_TIME, F32(value)) => {
1126                            layer_attributes.exposure = Some(value);
1127                        }
1128                        (name::APERTURE, F32(value)) => layer_attributes.aperture = Some(value),
1129                        (name::ISO_SPEED, F32(value)) => layer_attributes.iso_speed = Some(value),
1130                        (name::ENVIRONMENT_MAP, EnvironmentMap(value)) => {
1131                            layer_attributes.environment_map = Some(value);
1132                        }
1133                        (name::KEY_CODE, KeyCode(value)) => {
1134                            layer_attributes.film_key_code = Some(value);
1135                        }
1136                        (name::WRAP_MODES, Text(value)) => {
1137                            layer_attributes.wrap_mode_name = Some(value);
1138                        }
1139                        (name::FRAMES_PER_SECOND, Rational(value)) => {
1140                            layer_attributes.frames_per_second = Some(value);
1141                        }
1142                        (name::MULTI_VIEW, TextVector(value)) => {
1143                            layer_attributes.multi_view_names = Some(value);
1144                        }
1145                        (name::WORLD_TO_CAMERA, Matrix4x4(value)) => {
1146                            layer_attributes.world_to_camera = Some(value);
1147                        }
1148                        (name::WORLD_TO_NDC, Matrix4x4(value)) => {
1149                            layer_attributes.world_to_normalized_device = Some(value);
1150                        }
1151                        (name::DEEP_IMAGE_STATE, Rational(value)) => {
1152                            layer_attributes.deep_image_state = Some(value);
1153                        }
1154                        (name::ORIGINAL_DATA_WINDOW, IntegerBounds(value)) => {
1155                            layer_attributes.original_data_window = Some(value);
1156                        }
1157                        (name::DWA_COMPRESSION_LEVEL, F32(value)) => {
1158                            dwa_compression_level = Some(value);
1159                        }
1160                        (name::PREVIEW, Preview(value)) => layer_attributes.preview = Some(value),
1161                        (name::VIEW, Text(value)) => layer_attributes.view_name = Some(value),
1162
1163                        (name::NEAR, F32(value)) => layer_attributes.near_clip_plane = Some(value),
1164                        (name::FAR, F32(value)) => layer_attributes.far_clip_plane = Some(value),
1165                        (name::FOV_X, F32(value)) => {
1166                            layer_attributes.horizontal_field_of_view = Some(value);
1167                        }
1168                        (name::FOV_Y, F32(value)) => {
1169                            layer_attributes.vertical_field_of_view = Some(value);
1170                        }
1171                        (name::SOFTWARE, Text(value)) => {
1172                            layer_attributes.software_name = Some(value);
1173                        }
1174
1175                        (name::PIXEL_ASPECT, F32(value)) => image_attributes.pixel_aspect = value,
1176                        (name::TIME_CODE, TimeCode(value)) => {
1177                            image_attributes.time_code = Some(value);
1178                        }
1179                        (name::CHROMATICITIES, Chromaticities(value)) => {
1180                            image_attributes.chromaticities = Some(value);
1181                        }
1182
1183                        // insert unknown attributes of these types into image attributes,
1184                        // as these must be the same for all headers
1185                        (_, value @ (Chromaticities(_) | TimeCode(_))) => {
1186                            image_attributes.other.insert(attribute_name, value);
1187                        }
1188
1189                        // insert unknown attributes into layer attributes
1190                        (_, value) => {
1191                            layer_attributes.other.insert(attribute_name, value);
1192                        }
1193                    }
1194                }
1195
1196                // in case the attribute value itself is not ok, but the rest of the image is
1197                // only abort reading the image if desired
1198                Err(error) => {
1199                    if pedantic {
1200                        return Err(error);
1201                    }
1202                }
1203            }
1204        }
1205
1206        // construct compression with parameters from properties
1207        let compression = match (dwa_compression_level, compression) {
1208            (Some(level), Some(Compression::DWAA(_))) => Some(Compression::DWAA(Some(level))),
1209            (Some(level), Some(Compression::DWAB(_))) => Some(Compression::DWAB(Some(level))),
1210            (_, other) => other,
1211            // FIXME dwa compression level gets lost if any other compression is used later in the
1212            // process
1213        };
1214
1215        let compression = compression.ok_or_else(|| missing_attribute("compression"))?;
1216        image_attributes.display_window =
1217            display_window.ok_or_else(|| missing_attribute("display window"))?;
1218
1219        let data_window = data_window.ok_or_else(|| missing_attribute("data window"))?;
1220        data_window.validate(None)?; // validate now to avoid errors when computing the chunk_count
1221        layer_attributes.layer_position = data_window.position;
1222
1223        // validate now to avoid errors when computing the chunk_count
1224        if let Some(tiles) = tiles {
1225            tiles.validate()?;
1226        }
1227        let blocks = match block_type {
1228            None if requirements.is_single_layer_and_tiled => {
1229                BlockDescription::Tiles(tiles.ok_or_else(|| missing_attribute("tiles"))?)
1230            }
1231            Some(BlockType::Tile | BlockType::DeepTile) => {
1232                BlockDescription::Tiles(tiles.ok_or_else(|| missing_attribute("tiles"))?)
1233            }
1234
1235            _ => BlockDescription::ScanLines,
1236        };
1237
1238        let computed_chunk_count = compute_chunk_count(compression, data_window.size, blocks);
1239        if chunk_count.is_some() && pedantic && chunk_count != Some(computed_chunk_count) {
1240            return Err(Error::invalid("chunk count not matching data size"));
1241        }
1242
1243        let header = Self {
1244            compression,
1245
1246            // always compute ourselves, because we cannot trust anyone out there 😱
1247            chunk_count: computed_chunk_count,
1248
1249            layer_size: data_window.size,
1250
1251            shared_attributes: image_attributes,
1252            own_attributes: layer_attributes,
1253
1254            channels: channels.ok_or_else(|| missing_attribute("channels"))?,
1255            line_order: line_order.unwrap_or(LineOrder::Unspecified),
1256
1257            blocks,
1258            max_samples_per_pixel,
1259            deep_data_version: version,
1260            deep: block_type == Some(BlockType::DeepScanLine)
1261                || block_type == Some(BlockType::DeepTile),
1262        };
1263
1264        Ok(header)
1265    }
1266
1267    /// Without validation, write this instance to the byte stream.
1268    pub fn write(&self, write: &mut impl Write) -> UnitResult {
1269        for (name, value) in self.all_named_attributes() {
1270            attribute::write(name, &value, write)?;
1271        }
1272
1273        sequence_end::write(write)?;
1274        Ok(())
1275    }
1276
1277    /// The rectangle describing the bounding box of this layer
1278    /// within the infinite global 2D space of the file.
1279    pub fn data_window(&self) -> IntegerBounds {
1280        IntegerBounds::new(self.own_attributes.layer_position, self.layer_size)
1281    }
1282}
1283
1284/// Collection of required attribute names.
1285pub mod standard_names {
1286    macro_rules! define_required_attribute_names {
1287        ( $($name: ident  :  $value: expr),* ) => {
1288
1289            /// A list containing all reserved names.
1290            pub const ALL: &'static [&'static [u8]] = &[
1291                $( $value ),*
1292            ];
1293
1294            $(
1295                /// The byte-string name of this required attribute as it appears in an exr file.
1296                pub const $name: &'static [u8] = $value;
1297            )*
1298        };
1299    }
1300
1301    define_required_attribute_names! {
1302        TILES: b"tiles",
1303        NAME: b"name",
1304        BLOCK_TYPE: b"type",
1305        DEEP_DATA_VERSION: b"version",
1306        CHUNKS: b"chunkCount",
1307        MAX_SAMPLES: b"maxSamplesPerPixel",
1308        CHANNELS: b"channels",
1309        COMPRESSION: b"compression",
1310        DATA_WINDOW: b"dataWindow",
1311        DISPLAY_WINDOW: b"displayWindow",
1312        LINE_ORDER: b"lineOrder",
1313        PIXEL_ASPECT: b"pixelAspectRatio",
1314        WINDOW_CENTER: b"screenWindowCenter",
1315        WINDOW_WIDTH: b"screenWindowWidth",
1316        WHITE_LUMINANCE: b"whiteLuminance",
1317        ADOPTED_NEUTRAL: b"adoptedNeutral",
1318        RENDERING_TRANSFORM: b"renderingTransform",
1319        LOOK_MOD_TRANSFORM: b"lookModTransform",
1320        X_DENSITY: b"xDensity",
1321        OWNER: b"owner",
1322        COMMENTS: b"comments",
1323        CAPTURE_DATE: b"capDate",
1324        UTC_OFFSET: b"utcOffset",
1325        LONGITUDE: b"longitude",
1326        LATITUDE: b"latitude",
1327        ALTITUDE: b"altitude",
1328        FOCUS: b"focus",
1329        EXPOSURE_TIME: b"expTime",
1330        APERTURE: b"aperture",
1331        ISO_SPEED: b"isoSpeed",
1332        ENVIRONMENT_MAP: b"envmap",
1333        KEY_CODE: b"keyCode",
1334        TIME_CODE: b"timeCode",
1335        WRAP_MODES: b"wrapmodes",
1336        FRAMES_PER_SECOND: b"framesPerSecond",
1337        MULTI_VIEW: b"multiView",
1338        WORLD_TO_CAMERA: b"worldToCamera",
1339        WORLD_TO_NDC: b"worldToNDC",
1340        DEEP_IMAGE_STATE: b"deepImageState",
1341        ORIGINAL_DATA_WINDOW: b"originalDataWindow",
1342        DWA_COMPRESSION_LEVEL: b"dwaCompressionLevel",
1343        PREVIEW: b"preview",
1344        VIEW: b"view",
1345        CHROMATICITIES: b"chromaticities",
1346        NEAR: b"near",
1347        FAR: b"far",
1348        FOV_X: b"fieldOfViewHorizontal",
1349        FOV_Y: b"fieldOfViewVertical",
1350        SOFTWARE: b"software"
1351    }
1352}
1353
1354impl Default for LayerAttributes {
1355    fn default() -> Self {
1356        Self {
1357            layer_position: Vec2(0, 0),
1358            screen_window_center: Vec2(0.0, 0.0),
1359            screen_window_width: 1.0,
1360            layer_name: None,
1361            white_luminance: None,
1362            adopted_neutral: None,
1363            rendering_transform_name: None,
1364            look_modification_transform_name: None,
1365            horizontal_density: None,
1366            owner: None,
1367            comments: None,
1368            capture_date: None,
1369            utc_offset: None,
1370            longitude: None,
1371            latitude: None,
1372            altitude: None,
1373            focus: None,
1374            exposure: None,
1375            aperture: None,
1376            iso_speed: None,
1377            environment_map: None,
1378            film_key_code: None,
1379            wrap_mode_name: None,
1380            frames_per_second: None,
1381            multi_view_names: None,
1382            world_to_camera: None,
1383            world_to_normalized_device: None,
1384            deep_image_state: None,
1385            original_data_window: None,
1386            preview: None,
1387            view_name: None,
1388            software_name: None,
1389            near_clip_plane: None,
1390            far_clip_plane: None,
1391            horizontal_field_of_view: None,
1392            vertical_field_of_view: None,
1393            other: Default::default(),
1394        }
1395    }
1396}
1397
1398impl std::fmt::Debug for LayerAttributes {
1399    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1400        let default_self = Self::default();
1401
1402        let mut debug = formatter.debug_struct("LayerAttributes (default values omitted)");
1403
1404        // always debug the following field
1405        debug.field("name", &self.layer_name);
1406
1407        macro_rules! debug_non_default_fields {
1408            ( $( $name: ident ),* ) => { $(
1409
1410                if self.$name != default_self.$name {
1411                    debug.field(stringify!($name), &self.$name);
1412                }
1413
1414            )* };
1415        }
1416
1417        // only debug these fields if they are not the default value
1418        debug_non_default_fields! {
1419            screen_window_center, screen_window_width,
1420            white_luminance, adopted_neutral, horizontal_density,
1421            rendering_transform_name, look_modification_transform_name,
1422            owner, comments,
1423            capture_date, utc_offset,
1424            longitude, latitude, altitude,
1425            focus, exposure, aperture, iso_speed,
1426            environment_map, film_key_code, wrap_mode_name,
1427            frames_per_second, multi_view_names,
1428            world_to_camera, world_to_normalized_device,
1429            deep_image_state, original_data_window,
1430            preview, view_name,
1431            vertical_field_of_view, horizontal_field_of_view,
1432            near_clip_plane, far_clip_plane, software_name
1433        }
1434
1435        for (name, value) in &self.other {
1436            debug.field(&format!("\"{name}\""), value);
1437        }
1438
1439        // debug.finish_non_exhaustive() TODO
1440        debug.finish()
1441    }
1442}