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(display_window: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 /// Read the value without validating.
741 pub fn read(read: &mut PeekRead<impl Read>, requirements: &Requirements, pedantic: bool) -> Result<Self> {
742 let max_string_len = if requirements.has_long_names { 256 } else { 32 }; // TODO DRY this information
743
744 // these required attributes will be filled when encountered while parsing
745 let mut tiles = None;
746 let mut block_type = None;
747 let mut version = None;
748 let mut chunk_count = None;
749 let mut max_samples_per_pixel = None;
750 let mut channels = None;
751 let mut compression = None;
752 let mut data_window = None;
753 let mut display_window = None;
754 let mut line_order = None;
755
756 let mut dwa_compression_level = None;
757
758 let mut layer_attributes = LayerAttributes::default();
759 let mut image_attributes = ImageAttributes::new(IntegerBounds::zero());
760
761 // read each attribute in this header
762 while !sequence_end::has_come(read)? {
763 let (attribute_name, value) = attribute::read(read, max_string_len)?;
764
765 // if the attribute value itself is ok, record it
766 match value {
767 Ok(value) => {
768 use crate::meta::header::standard_names as name;
769 use crate::meta::attribute::AttributeValue::*;
770
771 // if the attribute is a required attribute, set the corresponding variable directly.
772 // otherwise, add the attribute to the vector of custom attributes
773
774 // the following attributes will only be set if the type matches the commonly used type for that attribute
775 match (attribute_name.as_slice(), value) {
776 (name::BLOCK_TYPE, Text(value)) => block_type = Some(attribute::BlockType::parse(value)?),
777 (name::TILES, TileDescription(value)) => tiles = Some(value),
778 (name::CHANNELS, ChannelList(value)) => channels = Some(value),
779 (name::COMPRESSION, Compression(value)) => compression = Some(value),
780 (name::DATA_WINDOW, IntegerBounds(value)) => data_window = Some(value),
781 (name::DISPLAY_WINDOW, IntegerBounds(value)) => display_window = Some(value),
782 (name::LINE_ORDER, LineOrder(value)) => line_order = Some(value),
783 (name::DEEP_DATA_VERSION, I32(value)) => version = Some(value),
784
785 (name::MAX_SAMPLES, I32(value)) => max_samples_per_pixel = Some(
786 i32_to_usize(value, "max sample count")?
787 ),
788
789 (name::CHUNKS, I32(value)) => chunk_count = Some(
790 i32_to_usize(value, "chunk count")?
791 ),
792
793 (name::NAME, Text(value)) => layer_attributes.layer_name = Some(value),
794 (name::WINDOW_CENTER, FloatVec2(value)) => layer_attributes.screen_window_center = value,
795 (name::WINDOW_WIDTH, F32(value)) => layer_attributes.screen_window_width = value,
796
797 (name::WHITE_LUMINANCE, F32(value)) => layer_attributes.white_luminance = Some(value),
798 (name::ADOPTED_NEUTRAL, FloatVec2(value)) => layer_attributes.adopted_neutral = Some(value),
799 (name::RENDERING_TRANSFORM, Text(value)) => layer_attributes.rendering_transform_name = Some(value),
800 (name::LOOK_MOD_TRANSFORM, Text(value)) => layer_attributes.look_modification_transform_name = Some(value),
801 (name::X_DENSITY, F32(value)) => layer_attributes.horizontal_density = Some(value),
802
803 (name::OWNER, Text(value)) => layer_attributes.owner = Some(value),
804 (name::COMMENTS, Text(value)) => layer_attributes.comments = Some(value),
805 (name::CAPTURE_DATE, Text(value)) => layer_attributes.capture_date = Some(value),
806 (name::UTC_OFFSET, F32(value)) => layer_attributes.utc_offset = Some(value),
807 (name::LONGITUDE, F32(value)) => layer_attributes.longitude = Some(value),
808 (name::LATITUDE, F32(value)) => layer_attributes.latitude = Some(value),
809 (name::ALTITUDE, F32(value)) => layer_attributes.altitude = Some(value),
810 (name::FOCUS, F32(value)) => layer_attributes.focus = Some(value),
811 (name::EXPOSURE_TIME, F32(value)) => layer_attributes.exposure = Some(value),
812 (name::APERTURE, F32(value)) => layer_attributes.aperture = Some(value),
813 (name::ISO_SPEED, F32(value)) => layer_attributes.iso_speed = Some(value),
814 (name::ENVIRONMENT_MAP, EnvironmentMap(value)) => layer_attributes.environment_map = Some(value),
815 (name::KEY_CODE, KeyCode(value)) => layer_attributes.film_key_code = Some(value),
816 (name::WRAP_MODES, Text(value)) => layer_attributes.wrap_mode_name = Some(value),
817 (name::FRAMES_PER_SECOND, Rational(value)) => layer_attributes.frames_per_second = Some(value),
818 (name::MULTI_VIEW, TextVector(value)) => layer_attributes.multi_view_names = Some(value),
819 (name::WORLD_TO_CAMERA, Matrix4x4(value)) => layer_attributes.world_to_camera = Some(value),
820 (name::WORLD_TO_NDC, Matrix4x4(value)) => layer_attributes.world_to_normalized_device = Some(value),
821 (name::DEEP_IMAGE_STATE, Rational(value)) => layer_attributes.deep_image_state = Some(value),
822 (name::ORIGINAL_DATA_WINDOW, IntegerBounds(value)) => layer_attributes.original_data_window = Some(value),
823 (name::DWA_COMPRESSION_LEVEL, F32(value)) => dwa_compression_level = Some(value),
824 (name::PREVIEW, Preview(value)) => layer_attributes.preview = Some(value),
825 (name::VIEW, Text(value)) => layer_attributes.view_name = Some(value),
826
827 (name::NEAR, F32(value)) => layer_attributes.near_clip_plane = Some(value),
828 (name::FAR, F32(value)) => layer_attributes.far_clip_plane = Some(value),
829 (name::FOV_X, F32(value)) => layer_attributes.horizontal_field_of_view = Some(value),
830 (name::FOV_Y, F32(value)) => layer_attributes.vertical_field_of_view = Some(value),
831 (name::SOFTWARE, Text(value)) => layer_attributes.software_name = Some(value),
832
833 (name::PIXEL_ASPECT, F32(value)) => image_attributes.pixel_aspect = value,
834 (name::TIME_CODE, TimeCode(value)) => image_attributes.time_code = Some(value),
835 (name::CHROMATICITIES, Chromaticities(value)) => image_attributes.chromaticities = Some(value),
836
837 // insert unknown attributes of these types into image attributes,
838 // as these must be the same for all headers
839 (_, value @ Chromaticities(_)) |
840 (_, value @ TimeCode(_)) => {
841 image_attributes.other.insert(attribute_name, value);
842 },
843
844 // insert unknown attributes into layer attributes
845 (_, value) => {
846 layer_attributes.other.insert(attribute_name, value);
847 },
848
849 }
850 },
851
852 // in case the attribute value itself is not ok, but the rest of the image is
853 // only abort reading the image if desired
854 Err(error) => {
855 if pedantic { return Err(error); }
856 }
857 }
858 }
859
860 // construct compression with parameters from properties
861 let compression = match (dwa_compression_level, compression) {
862 (Some(level), Some(Compression::DWAA(_))) => Some(Compression::DWAA(Some(level))),
863 (Some(level), Some(Compression::DWAB(_))) => Some(Compression::DWAB(Some(level))),
864 (_, other) => other,
865 // FIXME dwa compression level gets lost if any other compression is used later in the process
866 };
867
868 let compression = compression.ok_or(missing_attribute("compression"))?;
869 image_attributes.display_window = display_window.ok_or(missing_attribute("display window"))?;
870
871 let data_window = data_window.ok_or(missing_attribute("data window"))?;
872 data_window.validate(None)?; // validate now to avoid errors when computing the chunk_count
873 layer_attributes.layer_position = data_window.position;
874
875
876 // validate now to avoid errors when computing the chunk_count
877 if let Some(tiles) = tiles { tiles.validate()?; }
878 let blocks = match block_type {
879 None if requirements.is_single_layer_and_tiled => {
880 BlockDescription::Tiles(tiles.ok_or(missing_attribute("tiles"))?)
881 },
882 Some(BlockType::Tile) | Some(BlockType::DeepTile) => {
883 BlockDescription::Tiles(tiles.ok_or(missing_attribute("tiles"))?)
884 },
885
886 _ => BlockDescription::ScanLines,
887 };
888
889 let computed_chunk_count = compute_chunk_count(compression, data_window.size, blocks);
890 if chunk_count.is_some() && pedantic && chunk_count != Some(computed_chunk_count) {
891 return Err(Error::invalid("chunk count not matching data size"));
892 }
893
894 let header = Header {
895 compression,
896
897 // always compute ourselves, because we cannot trust anyone out there 😱
898 chunk_count: computed_chunk_count,
899
900 layer_size: data_window.size,
901
902 shared_attributes: image_attributes,
903 own_attributes: layer_attributes,
904
905 channels: channels.ok_or(missing_attribute("channels"))?,
906 line_order: line_order.unwrap_or(LineOrder::Unspecified),
907
908 blocks,
909 max_samples_per_pixel,
910 deep_data_version: version,
911 deep: block_type == Some(BlockType::DeepScanLine) || block_type == Some(BlockType::DeepTile),
912 };
913
914 Ok(header)
915 }
916
917 /// Without validation, write this instance to the byte stream.
918 pub fn write(&self, write: &mut impl Write) -> UnitResult {
919
920 macro_rules! write_attributes {
921 ( $($name: ident : $variant: ident = $value: expr),* ) => { $(
922 attribute::write($name, & $variant ($value .clone()), write)?; // TODO without clone
923 )* };
924 }
925
926 macro_rules! write_optional_attributes {
927 ( $($name: ident : $variant: ident = $value: expr),* ) => { $(
928 if let Some(value) = $value {
929 attribute::write($name, & $variant (value.clone()), write)?; // TODO without clone
930 };
931 )* };
932 }
933
934 use crate::meta::header::standard_names::*;
935 use AttributeValue::*;
936
937 let (block_type, tiles) = match self.blocks {
938 BlockDescription::ScanLines => (attribute::BlockType::ScanLine, None),
939 BlockDescription::Tiles(tiles) => (attribute::BlockType::Tile, Some(tiles))
940 };
941
942 fn usize_as_i32(value: usize) -> AttributeValue {
943 I32(i32::try_from(value).expect("u32 exceeds i32 range"))
944 }
945
946 write_optional_attributes!(
947 TILES: TileDescription = &tiles,
948 DEEP_DATA_VERSION: I32 = &self.deep_data_version,
949 MAX_SAMPLES: usize_as_i32 = &self.max_samples_per_pixel
950 );
951
952 write_attributes!(
953 // chunks is not actually required, but always computed in this library anyways
954 CHUNKS: usize_as_i32 = &self.chunk_count,
955
956 BLOCK_TYPE: BlockType = &block_type,
957 CHANNELS: ChannelList = &self.channels,
958 COMPRESSION: Compression = &self.compression,
959 LINE_ORDER: LineOrder = &self.line_order,
960 DATA_WINDOW: IntegerBounds = &self.data_window(),
961
962 DISPLAY_WINDOW: IntegerBounds = &self.shared_attributes.display_window,
963 PIXEL_ASPECT: F32 = &self.shared_attributes.pixel_aspect,
964
965 WINDOW_CENTER: FloatVec2 = &self.own_attributes.screen_window_center,
966 WINDOW_WIDTH: F32 = &self.own_attributes.screen_window_width
967 );
968
969 write_optional_attributes!(
970 NAME: Text = &self.own_attributes.layer_name,
971 WHITE_LUMINANCE: F32 = &self.own_attributes.white_luminance,
972 ADOPTED_NEUTRAL: FloatVec2 = &self.own_attributes.adopted_neutral,
973 RENDERING_TRANSFORM: Text = &self.own_attributes.rendering_transform_name,
974 LOOK_MOD_TRANSFORM: Text = &self.own_attributes.look_modification_transform_name,
975 X_DENSITY: F32 = &self.own_attributes.horizontal_density,
976 OWNER: Text = &self.own_attributes.owner,
977 COMMENTS: Text = &self.own_attributes.comments,
978 CAPTURE_DATE: Text = &self.own_attributes.capture_date,
979 UTC_OFFSET: F32 = &self.own_attributes.utc_offset,
980 LONGITUDE: F32 = &self.own_attributes.longitude,
981 LATITUDE: F32 = &self.own_attributes.latitude,
982 ALTITUDE: F32 = &self.own_attributes.altitude,
983 FOCUS: F32 = &self.own_attributes.focus,
984 EXPOSURE_TIME: F32 = &self.own_attributes.exposure,
985 APERTURE: F32 = &self.own_attributes.aperture,
986 ISO_SPEED: F32 = &self.own_attributes.iso_speed,
987 ENVIRONMENT_MAP: EnvironmentMap = &self.own_attributes.environment_map,
988 KEY_CODE: KeyCode = &self.own_attributes.film_key_code,
989 TIME_CODE: TimeCode = &self.shared_attributes.time_code,
990 WRAP_MODES: Text = &self.own_attributes.wrap_mode_name,
991 FRAMES_PER_SECOND: Rational = &self.own_attributes.frames_per_second,
992 MULTI_VIEW: TextVector = &self.own_attributes.multi_view_names,
993 WORLD_TO_CAMERA: Matrix4x4 = &self.own_attributes.world_to_camera,
994 WORLD_TO_NDC: Matrix4x4 = &self.own_attributes.world_to_normalized_device,
995 DEEP_IMAGE_STATE: Rational = &self.own_attributes.deep_image_state,
996 ORIGINAL_DATA_WINDOW: IntegerBounds = &self.own_attributes.original_data_window,
997 CHROMATICITIES: Chromaticities = &self.shared_attributes.chromaticities,
998 PREVIEW: Preview = &self.own_attributes.preview,
999 VIEW: Text = &self.own_attributes.view_name,
1000 NEAR: F32 = &self.own_attributes.near_clip_plane,
1001 FAR: F32 = &self.own_attributes.far_clip_plane,
1002 FOV_X: F32 = &self.own_attributes.horizontal_field_of_view,
1003 FOV_Y: F32 = &self.own_attributes.vertical_field_of_view,
1004 SOFTWARE: Text = &self.own_attributes.software_name
1005 );
1006
1007 // dwa writes compression parameters as attribute.
1008 match self.compression {
1009 attribute::Compression::DWAA(Some(level)) |
1010 attribute::Compression::DWAB(Some(level)) =>
1011 attribute::write(DWA_COMPRESSION_LEVEL, &F32(level), write)?,
1012
1013 _ => {}
1014 };
1015
1016
1017 for (name, value) in &self.shared_attributes.other {
1018 attribute::write(name.as_slice(), value, write)?;
1019 }
1020
1021 for (name, value) in &self.own_attributes.other {
1022 attribute::write(name.as_slice(), value, write)?;
1023 }
1024
1025 sequence_end::write(write)?;
1026 Ok(())
1027 }
1028
1029 /// The rectangle describing the bounding box of this layer
1030 /// within the infinite global 2D space of the file.
1031 pub fn data_window(&self) -> IntegerBounds {
1032 IntegerBounds::new(self.own_attributes.layer_position, self.layer_size)
1033 }
1034}
1035
1036
1037
1038/// Collection of required attribute names.
1039pub mod standard_names {
1040 macro_rules! define_required_attribute_names {
1041 ( $($name: ident : $value: expr),* ) => {
1042
1043 /// A list containing all reserved names.
1044 pub const ALL: &'static [&'static [u8]] = &[
1045 $( $value ),*
1046 ];
1047
1048 $(
1049 /// The byte-string name of this required attribute as it appears in an exr file.
1050 pub const $name: &'static [u8] = $value;
1051 )*
1052 };
1053 }
1054
1055 define_required_attribute_names! {
1056 TILES: b"tiles",
1057 NAME: b"name",
1058 BLOCK_TYPE: b"type",
1059 DEEP_DATA_VERSION: b"version",
1060 CHUNKS: b"chunkCount",
1061 MAX_SAMPLES: b"maxSamplesPerPixel",
1062 CHANNELS: b"channels",
1063 COMPRESSION: b"compression",
1064 DATA_WINDOW: b"dataWindow",
1065 DISPLAY_WINDOW: b"displayWindow",
1066 LINE_ORDER: b"lineOrder",
1067 PIXEL_ASPECT: b"pixelAspectRatio",
1068 WINDOW_CENTER: b"screenWindowCenter",
1069 WINDOW_WIDTH: b"screenWindowWidth",
1070 WHITE_LUMINANCE: b"whiteLuminance",
1071 ADOPTED_NEUTRAL: b"adoptedNeutral",
1072 RENDERING_TRANSFORM: b"renderingTransform",
1073 LOOK_MOD_TRANSFORM: b"lookModTransform",
1074 X_DENSITY: b"xDensity",
1075 OWNER: b"owner",
1076 COMMENTS: b"comments",
1077 CAPTURE_DATE: b"capDate",
1078 UTC_OFFSET: b"utcOffset",
1079 LONGITUDE: b"longitude",
1080 LATITUDE: b"latitude",
1081 ALTITUDE: b"altitude",
1082 FOCUS: b"focus",
1083 EXPOSURE_TIME: b"expTime",
1084 APERTURE: b"aperture",
1085 ISO_SPEED: b"isoSpeed",
1086 ENVIRONMENT_MAP: b"envmap",
1087 KEY_CODE: b"keyCode",
1088 TIME_CODE: b"timeCode",
1089 WRAP_MODES: b"wrapmodes",
1090 FRAMES_PER_SECOND: b"framesPerSecond",
1091 MULTI_VIEW: b"multiView",
1092 WORLD_TO_CAMERA: b"worldToCamera",
1093 WORLD_TO_NDC: b"worldToNDC",
1094 DEEP_IMAGE_STATE: b"deepImageState",
1095 ORIGINAL_DATA_WINDOW: b"originalDataWindow",
1096 DWA_COMPRESSION_LEVEL: b"dwaCompressionLevel",
1097 PREVIEW: b"preview",
1098 VIEW: b"view",
1099 CHROMATICITIES: b"chromaticities",
1100 NEAR: b"near",
1101 FAR: b"far",
1102 FOV_X: b"fieldOfViewHorizontal",
1103 FOV_Y: b"fieldOfViewVertical",
1104 SOFTWARE: b"software"
1105 }
1106}
1107
1108
1109impl Default for LayerAttributes {
1110 fn default() -> Self {
1111 Self {
1112 layer_position: Vec2(0, 0),
1113 screen_window_center: Vec2(0.0, 0.0),
1114 screen_window_width: 1.0,
1115 layer_name: None,
1116 white_luminance: None,
1117 adopted_neutral: None,
1118 rendering_transform_name: None,
1119 look_modification_transform_name: None,
1120 horizontal_density: None,
1121 owner: None,
1122 comments: None,
1123 capture_date: None,
1124 utc_offset: None,
1125 longitude: None,
1126 latitude: None,
1127 altitude: None,
1128 focus: None,
1129 exposure: None,
1130 aperture: None,
1131 iso_speed: None,
1132 environment_map: None,
1133 film_key_code: None,
1134 wrap_mode_name: None,
1135 frames_per_second: None,
1136 multi_view_names: None,
1137 world_to_camera: None,
1138 world_to_normalized_device: None,
1139 deep_image_state: None,
1140 original_data_window: None,
1141 preview: None,
1142 view_name: None,
1143 software_name: None,
1144 near_clip_plane: None,
1145 far_clip_plane: None,
1146 horizontal_field_of_view: None,
1147 vertical_field_of_view: None,
1148 other: Default::default()
1149 }
1150 }
1151}
1152
1153impl std::fmt::Debug for LayerAttributes {
1154 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1155 let default_self = Self::default();
1156
1157 let mut debug = formatter.debug_struct("LayerAttributes (default values omitted)");
1158
1159 // always debug the following field
1160 debug.field("name", &self.layer_name);
1161
1162 macro_rules! debug_non_default_fields {
1163 ( $( $name: ident ),* ) => { $(
1164
1165 if self.$name != default_self.$name {
1166 debug.field(stringify!($name), &self.$name);
1167 }
1168
1169 )* };
1170 }
1171
1172 // only debug these fields if they are not the default value
1173 debug_non_default_fields! {
1174 screen_window_center, screen_window_width,
1175 white_luminance, adopted_neutral, horizontal_density,
1176 rendering_transform_name, look_modification_transform_name,
1177 owner, comments,
1178 capture_date, utc_offset,
1179 longitude, latitude, altitude,
1180 focus, exposure, aperture, iso_speed,
1181 environment_map, film_key_code, wrap_mode_name,
1182 frames_per_second, multi_view_names,
1183 world_to_camera, world_to_normalized_device,
1184 deep_image_state, original_data_window,
1185 preview, view_name,
1186 vertical_field_of_view, horizontal_field_of_view,
1187 near_clip_plane, far_clip_plane, software_name
1188 }
1189
1190 for (name, value) in &self.other {
1191 debug.field(&format!("\"{}\"", name), value);
1192 }
1193
1194 // debug.finish_non_exhaustive() TODO
1195 debug.finish()
1196 }
1197}
1198