exr/meta/
header.rs

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