Coverage Report

Created: 2026-08-14 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/exr-1.74.2/src/meta/mod.rs
Line
Count
Source
1
//! Describes all meta data possible in an exr file.
2
//! Contains functionality to read and write meta data from bytes.
3
//! Browse the `exr::image` module to get started with the high-level interface.
4
5
pub mod attribute;
6
pub mod header;
7
8
use std::{collections::HashSet, convert::TryFrom, fs::File, io::BufReader};
9
10
use ::smallvec::SmallVec;
11
12
use self::attribute::*;
13
use crate::{
14
    block::{
15
        chunk::{CompressedBlock, TileCoordinates},
16
        BlockIndex, UncompressedBlock,
17
    },
18
    error::*,
19
    io::*,
20
    math::*,
21
    meta::header::Header,
22
};
23
24
// TODO rename MetaData to ImageInfo?
25
26
/// Contains the complete meta data of an exr image.
27
///
28
/// Defines how the image is split up in the file,
29
/// the number and type of images and channels,
30
/// and various other attributes.
31
/// The usage of custom attributes is encouraged.
32
#[derive(Debug, Clone, PartialEq)]
33
pub struct MetaData {
34
    /// Some flags summarizing the features that must be supported to decode the
35
    /// file.
36
    pub requirements: Requirements,
37
38
    /// One header to describe each layer in this file.
39
    // TODO rename to layer descriptions?
40
    pub headers: Headers,
41
}
42
43
/// List of `Header`s.
44
pub type Headers = SmallVec<[Header; 3]>;
45
46
/// List of `OffsetTable`s.
47
pub type OffsetTables = SmallVec<[OffsetTable; 3]>;
48
49
/// The offset table is an ordered list of indices referencing pixel data in the
50
/// exr file.
51
///
52
/// For each pixel tile in the image, an index exists, which points to the
53
/// byte-location of the corresponding pixel data in the file. That index can be
54
/// used to load specific portions of an image without processing all bytes in a
55
/// file. For each header, an offset table exists with its indices ordered by
56
/// `LineOrder::Increasing`.
57
// If the multipart bit is unset and the chunkCount attribute is not present,
58
// the number of entries in the chunk table is computed using the
59
// dataWindow, tileDesc, and compression attribute.
60
//
61
// If the multipart bit is set, the header must contain a
62
// chunkCount attribute, that contains the length of the offset table.
63
pub type OffsetTable = Vec<u64>;
64
65
/// A summary of requirements that must be met to read this exr file.
66
///
67
/// Used to determine whether this file can be read by a given reader.
68
/// It includes the `OpenEXR` version number. This library aims to support
69
/// version `2.0`.
70
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
71
pub struct Requirements {
72
    /// This library supports reading version 1 and 2, and writing version 2.
73
    // TODO write version 1 for simple images
74
    pub file_format_version: u8,
75
76
    /// If true, this image has tiled blocks and contains only a single layer.
77
    /// If false and not deep and not multilayer, this image is a single layer
78
    /// image with scan line blocks.
79
    pub is_single_layer_and_tiled: bool,
80
81
    // in c or bad c++ this might have been relevant (omg is he allowed to say that)
82
    /// Whether this file has strings with a length greater than 31.
83
    /// Strings can never be longer than 255.
84
    pub has_long_names: bool,
85
86
    /// This image contains at least one layer with deep data.
87
    pub has_deep_data: bool,
88
89
    /// Whether this file contains multiple layers.
90
    pub has_multiple_layers: bool,
91
}
92
93
/// Locates a rectangular section of pixels in an image.
94
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
95
pub struct TileIndices {
96
    /// Index of the tile.
97
    pub location: TileCoordinates,
98
99
    /// Pixel size of the tile.
100
    pub size: Vec2<usize>,
101
}
102
103
/// How the image pixels are split up into separate blocks.
104
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
105
pub enum BlockDescription {
106
    /// The image is divided into scan line blocks.
107
    /// The number of scan lines in a block depends on the compression method.
108
    ScanLines,
109
110
    /// The image is divided into tile blocks.
111
    /// Also specifies the size of each tile in the image
112
    /// and whether this image contains multiple resolution levels.
113
    Tiles(TileDescription),
114
}
115
116
// impl TileIndices {
117
// pub fn cmp(&self, other: &Self) -> Ordering {
118
// match self.location.level_index.1.cmp(&other.location.level_index.1) {
119
// Ordering::Equal => {
120
// match self.location.level_index.0.cmp(&other.location.level_index.0) {
121
// Ordering::Equal => {
122
// match self.location.tile_index.1.cmp(&other.location.tile_index.1) {
123
// Ordering::Equal => {
124
// self.location.tile_index.0.cmp(&other.location.tile_index.0)
125
// },
126
//
127
// other => other,
128
// }
129
// },
130
//
131
// other => other
132
// }
133
// },
134
//
135
// other => other
136
// }
137
// }
138
// }
139
140
impl BlockDescription {
141
    /// Whether this image is tiled. If false, this image is divided into scan
142
    /// line blocks.
143
0
    pub fn has_tiles(&self) -> bool {
144
0
        match self {
145
            Self::Tiles {
146
                ..
147
0
            } => true,
148
0
            _ => false,
149
        }
150
0
    }
151
}
152
153
/// The first four bytes of each exr file.
154
/// Used to abort reading non-exr files.
155
pub mod magic_number {
156
    use super::*;
157
158
    /// The first four bytes of each exr file.
159
    pub const BYTES: [u8; 4] = [0x76, 0x2f, 0x31, 0x01];
160
161
    /// Without validation, write this instance to the byte stream.
162
0
    pub fn write(write: &mut impl Write) -> Result<()> {
163
0
        u8::write_slice_ne(write, &self::BYTES)
164
0
    }
Unexecuted instantiation: exr::meta::magic_number::write::<_>
Unexecuted instantiation: exr::meta::magic_number::write::<exr::io::Tracking<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
Unexecuted instantiation: exr::meta::magic_number::write::<exr::io::Tracking<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>
165
166
    /// Consumes four bytes from the reader and returns whether the file may be
167
    /// an exr file.
168
    // TODO check if exr before allocating BufRead
169
0
    pub fn is_exr(read: &mut impl Read) -> Result<bool> {
170
0
        let mut magic_num = [0; 4];
171
0
        u8::read_slice_ne(read, &mut magic_num)?;
172
0
        Ok(magic_num == self::BYTES)
173
0
    }
Unexecuted instantiation: exr::meta::magic_number::is_exr::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>>
Unexecuted instantiation: exr::meta::magic_number::is_exr::<_>
Unexecuted instantiation: exr::meta::magic_number::is_exr::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>>
174
175
    /// Validate this image. If it is an exr file, return `Ok(())`.
176
0
    pub fn validate_exr(read: &mut impl Read) -> UnitResult {
177
0
        if self::is_exr(read)? {
178
0
            Ok(())
179
        } else {
180
0
            Err(Error::invalid("file identifier missing"))
181
        }
182
0
    }
Unexecuted instantiation: exr::meta::magic_number::validate_exr::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>>
Unexecuted instantiation: exr::meta::magic_number::validate_exr::<_>
Unexecuted instantiation: exr::meta::magic_number::validate_exr::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>>
183
}
184
185
/// A `0_u8` at the end of a sequence.
186
pub mod sequence_end {
187
    use super::*;
188
189
    /// Number of bytes this would consume in an exr file.
190
0
    pub fn byte_size() -> usize {
191
0
        1
192
0
    }
193
194
    /// Without validation, write this instance to the byte stream.
195
0
    pub fn write<W: Write>(write: &mut W) -> UnitResult {
196
0
        0_u8.write_le(write)
197
0
    }
Unexecuted instantiation: exr::meta::sequence_end::write::<_>
Unexecuted instantiation: exr::meta::sequence_end::write::<exr::io::Tracking<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
Unexecuted instantiation: exr::meta::sequence_end::write::<exr::io::Tracking<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>
198
199
    /// Peeks the next byte. If it is zero, consumes the byte and returns true.
200
0
    pub fn has_come(read: &mut PeekRead<impl Read>) -> Result<bool> {
201
0
        Ok(read.skip_if_eq(0)?)
202
0
    }
Unexecuted instantiation: exr::meta::sequence_end::has_come::<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>
Unexecuted instantiation: exr::meta::sequence_end::has_come::<&[u8]>
Unexecuted instantiation: exr::meta::sequence_end::has_come::<_>
Unexecuted instantiation: exr::meta::sequence_end::has_come::<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
203
}
204
205
0
fn missing_attribute(name: &str) -> Error {
206
0
    Error::invalid(format!("missing or invalid {name} attribute"))
207
0
}
208
209
/// Compute the number of tiles required to contain all values.
210
0
pub fn compute_block_count(full_res: usize, tile_size: usize) -> usize {
211
    // round up, because if the image is not evenly divisible by the tiles,
212
    // we add another tile at the end (which is only partially used)
213
0
    RoundingMode::Up.divide(full_res, tile_size)
214
0
}
215
216
/// Compute the start position and size of a block inside a dimension.
217
#[inline]
218
0
pub fn calculate_block_position_and_size(
219
0
    total_size: usize,
220
0
    block_size: usize,
221
0
    block_index: usize,
222
0
) -> Result<(usize, usize)> {
223
0
    let block_position = block_size * block_index;
224
225
0
    Ok((block_position, calculate_block_size(total_size, block_size, block_position)?))
226
0
}
227
228
/// Calculate the size of a single block. If this is the last block,
229
/// this only returns the required size, which is always smaller than the
230
/// default block size.
231
// TODO use this method everywhere instead of convoluted formulas
232
#[inline]
233
0
pub fn calculate_block_size(
234
0
    total_size: usize,
235
0
    block_size: usize,
236
0
    block_position: usize,
237
0
) -> Result<usize> {
238
0
    if block_position >= total_size {
239
0
        return Err(Error::invalid(format!(
240
0
            "block position {block_position} exceeds total size {total_size}"
241
0
        )));
242
0
    }
243
244
0
    if block_position + block_size <= total_size {
245
0
        Ok(block_size)
246
    } else {
247
0
        Ok(total_size - block_position)
248
    }
249
0
}
250
251
/// Calculate number of mip levels in a given resolution.
252
// TODO this should be cached? log2 may be very expensive
253
0
pub fn compute_level_count(round: RoundingMode, full_res: usize) -> usize {
254
0
    usize::try_from(round.log2(u32::try_from(full_res).unwrap())).unwrap() + 1
255
0
}
256
257
/// Calculate the size of a single mip level by index.
258
// TODO this should be cached? log2 may be very expensive
259
0
pub fn compute_level_size(round: RoundingMode, full_res: usize, level_index: usize) -> usize {
260
0
    assert!(
261
0
        level_index < std::mem::size_of::<usize>() * 8,
262
        "largest level size exceeds maximum integer value"
263
    );
264
0
    round.divide(full_res, 1 << level_index).max(1)
265
0
}
266
267
/// Iterates over all rip map level resolutions of a given size, including the
268
/// indices of each level. The order of iteration conforms to
269
/// `LineOrder::Increasing`.
270
// TODO cache these?
271
// TODO compute these directly instead of summing up an iterator?
272
0
pub fn rip_map_levels(
273
0
    round: RoundingMode,
274
0
    max_resolution: Vec2<usize>,
275
0
) -> impl Iterator<Item = (Vec2<usize>, Vec2<usize>)> {
276
0
    rip_map_indices(round, max_resolution).map(move |level_indices| {
277
        // TODO progressively divide instead??
278
0
        let width = compute_level_size(round, max_resolution.width(), level_indices.x());
279
0
        let height = compute_level_size(round, max_resolution.height(), level_indices.y());
280
0
        (level_indices, Vec2(width, height))
281
0
    })
282
0
}
283
284
/// Iterates over all mip map level resolutions of a given size, including the
285
/// indices of each level. The order of iteration conforms to
286
/// `LineOrder::Increasing`.
287
// TODO cache all these level values when computing table offset size??
288
// TODO compute these directly instead of summing up an iterator?
289
0
pub fn mip_map_levels(
290
0
    round: RoundingMode,
291
0
    max_resolution: Vec2<usize>,
292
0
) -> impl Iterator<Item = (usize, Vec2<usize>)> {
293
0
    mip_map_indices(round, max_resolution).map(move |level_index| {
294
        // TODO progressively divide instead??
295
0
        let width = compute_level_size(round, max_resolution.width(), level_index);
296
0
        let height = compute_level_size(round, max_resolution.height(), level_index);
297
0
        (level_index, Vec2(width, height))
298
0
    })
299
0
}
300
301
/// Iterates over all rip map level indices of a given size.
302
/// The order of iteration conforms to `LineOrder::Increasing`.
303
0
pub fn rip_map_indices(
304
0
    round: RoundingMode,
305
0
    max_resolution: Vec2<usize>,
306
0
) -> impl Iterator<Item = Vec2<usize>> {
307
0
    let (width, height) = (
308
0
        compute_level_count(round, max_resolution.width()),
309
0
        compute_level_count(round, max_resolution.height()),
310
0
    );
311
312
0
    (0..height).flat_map(move |y_level| (0..width).map(move |x_level| Vec2(x_level, y_level)))
313
0
}
314
315
/// Iterates over all mip map level indices of a given size.
316
/// The order of iteration conforms to `LineOrder::Increasing`.
317
0
pub fn mip_map_indices(
318
0
    round: RoundingMode,
319
0
    max_resolution: Vec2<usize>,
320
0
) -> impl Iterator<Item = usize> {
321
0
    0..compute_level_count(round, max_resolution.width().max(max_resolution.height()))
322
0
}
323
324
/// Compute the number of chunks that an image is divided into. May be an
325
/// expensive operation.
326
// If not multilayer and chunkCount not present,
327
// the number of entries in the chunk table is computed
328
// using the dataWindow and tileDesc attributes and the compression format
329
0
pub fn compute_chunk_count(
330
0
    compression: Compression,
331
0
    data_size: Vec2<usize>,
332
0
    blocks: BlockDescription,
333
0
) -> usize {
334
0
    if let BlockDescription::Tiles(tiles) = blocks {
335
0
        let round = tiles.rounding_mode;
336
0
        let Vec2(tile_width, tile_height) = tiles.tile_size;
337
338
        // TODO cache all these level values??
339
        use crate::meta::attribute::LevelMode::*;
340
0
        match tiles.level_mode {
341
            Singular => {
342
0
                let tiles_x = compute_block_count(data_size.width(), tile_width);
343
0
                let tiles_y = compute_block_count(data_size.height(), tile_height);
344
0
                tiles_x * tiles_y
345
            }
346
347
0
            MipMap => mip_map_levels(round, data_size)
348
0
                .map(|(_, Vec2(level_width, level_height))| {
349
0
                    compute_block_count(level_width, tile_width)
350
0
                        * compute_block_count(level_height, tile_height)
351
0
                })
352
0
                .sum(),
353
354
0
            RipMap => rip_map_levels(round, data_size)
355
0
                .map(|(_, Vec2(level_width, level_height))| {
356
0
                    compute_block_count(level_width, tile_width)
357
0
                        * compute_block_count(level_height, tile_height)
358
0
                })
359
0
                .sum(),
360
        }
361
    }
362
    // scan line blocks never have mip maps
363
    else {
364
0
        compute_block_count(data_size.height(), compression.scan_lines_per_block())
365
    }
366
0
}
367
368
impl MetaData {
369
    /// Read the exr meta data from a file.
370
    /// Use `read_from_unbuffered` instead if you do not have a file.
371
    /// Does not validate the meta data.
372
    #[must_use]
373
0
    pub fn read_from_file(path: impl AsRef<::std::path::Path>, pedantic: bool) -> Result<Self> {
374
0
        Self::read_from_unbuffered(File::open(path)?, pedantic)
375
0
    }
376
377
    /// Buffer the reader and then read the exr meta data from it.
378
    /// Use `read_from_buffered` if your reader is an in-memory reader.
379
    /// Use `read_from_file` if you have a file path.
380
    /// Does not validate the meta data.
381
    #[must_use]
382
0
    pub fn read_from_unbuffered(unbuffered: impl Read, pedantic: bool) -> Result<Self> {
383
0
        Self::read_from_buffered(BufReader::new(unbuffered), pedantic)
384
0
    }
385
386
    /// Read the exr meta data from a reader.
387
    /// Use `read_from_file` if you have a file path.
388
    /// Use `read_from_unbuffered` if this is not an in-memory reader.
389
    /// Does not validate the meta data.
390
    #[must_use]
391
0
    pub fn read_from_buffered(buffered: impl Read, pedantic: bool) -> Result<Self> {
392
0
        let mut read = PeekRead::new(buffered);
393
0
        Self::read_unvalidated_from_buffered_peekable(&mut read, pedantic)
394
0
    }
395
396
    /// Does __not validate__ the meta data completely.
397
    #[must_use]
398
0
    pub(crate) fn read_unvalidated_from_buffered_peekable(
399
0
        read: &mut PeekRead<impl Read>,
400
0
        pedantic: bool,
401
0
    ) -> Result<Self> {
402
0
        magic_number::validate_exr(read)?;
403
404
0
        let requirements = Requirements::read(read)?;
405
406
        // do this check now in order to fast-fail for newer versions and features than
407
        // version 2
408
0
        requirements.validate()?;
409
410
0
        let headers = Header::read_all(read, &requirements, pedantic)?;
411
412
        // TODO check if supporting requirements 2 always implies supporting
413
        // requirements 1
414
0
        Ok(Self {
415
0
            requirements,
416
0
            headers,
417
0
        })
418
0
    }
Unexecuted instantiation: <exr::meta::MetaData>::read_unvalidated_from_buffered_peekable::<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>
Unexecuted instantiation: <exr::meta::MetaData>::read_unvalidated_from_buffered_peekable::<_>
Unexecuted instantiation: <exr::meta::MetaData>::read_unvalidated_from_buffered_peekable::<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
419
420
    /// Validates the meta data.
421
    #[must_use]
422
0
    pub(crate) fn read_validated_from_buffered_peekable(
423
0
        read: &mut PeekRead<impl Read>,
424
0
        pedantic: bool,
425
0
    ) -> Result<Self> {
426
0
        let meta_data = Self::read_unvalidated_from_buffered_peekable(read, !pedantic)?;
427
0
        Self::validate(meta_data.headers.as_slice(), pedantic)?;
428
0
        Ok(meta_data)
429
0
    }
Unexecuted instantiation: <exr::meta::MetaData>::read_validated_from_buffered_peekable::<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>
Unexecuted instantiation: <exr::meta::MetaData>::read_validated_from_buffered_peekable::<_>
Unexecuted instantiation: <exr::meta::MetaData>::read_validated_from_buffered_peekable::<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
430
431
    /// Validates the meta data and writes it to the stream.
432
    /// If pedantic, throws errors for files that may produce errors in other
433
    /// exr readers. Returns the automatically detected minimum requirement
434
    /// flags.
435
0
    pub(crate) fn write_validating_to_buffered(
436
0
        write: &mut impl Write,
437
0
        headers: &[Header],
438
0
        pedantic: bool,
439
0
    ) -> Result<Requirements> {
440
        // pedantic validation to not allow slightly invalid files
441
        // that still could be read correctly in theory
442
0
        let minimal_requirements = Self::validate(headers, pedantic)?;
443
444
0
        magic_number::write(write)?;
445
0
        minimal_requirements.write(write)?;
446
0
        Header::write_all(headers, write, minimal_requirements.has_multiple_layers)?;
447
0
        Ok(minimal_requirements)
448
0
    }
Unexecuted instantiation: <exr::meta::MetaData>::write_validating_to_buffered::<_>
Unexecuted instantiation: <exr::meta::MetaData>::write_validating_to_buffered::<exr::io::Tracking<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
Unexecuted instantiation: <exr::meta::MetaData>::write_validating_to_buffered::<exr::io::Tracking<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>
449
450
    /// Read one offset table from the reader for each header.
451
0
    pub fn read_offset_tables(
452
0
        read: &mut PeekRead<impl Read>,
453
0
        headers: &Headers,
454
0
    ) -> Result<OffsetTables> {
455
0
        headers
456
0
            .iter()
457
0
            .map(|header| {
458
0
                u64::read_vec_le(
459
0
                    read,
460
0
                    header.chunk_count,
461
0
                    u16::MAX as usize,
462
0
                    None,
463
                    "offset table size",
464
                )
465
0
            })
Unexecuted instantiation: <exr::meta::MetaData>::read_offset_tables::<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>::{closure#0}
Unexecuted instantiation: <exr::meta::MetaData>::read_offset_tables::<_>::{closure#0}
Unexecuted instantiation: <exr::meta::MetaData>::read_offset_tables::<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>::{closure#0}
466
0
            .collect()
467
0
    }
Unexecuted instantiation: <exr::meta::MetaData>::read_offset_tables::<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>
Unexecuted instantiation: <exr::meta::MetaData>::read_offset_tables::<_>
Unexecuted instantiation: <exr::meta::MetaData>::read_offset_tables::<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
468
469
    /// Skip the offset tables by advancing the reader by the required byte
470
    /// count.
471
    // TODO use seek for large (probably all) tables!
472
0
    pub fn skip_offset_tables(read: &mut PeekRead<impl Read>, headers: &Headers) -> Result<usize> {
473
0
        let chunk_count: usize = headers.iter().map(|header| header.chunk_count).sum();
474
0
        crate::io::skip_bytes(read, chunk_count * u64::BYTE_SIZE)?; // TODO this should seek for large tables
475
0
        Ok(chunk_count)
476
0
    }
477
478
    /// This iterator tells you the block indices of all blocks that must be in
479
    /// the image. The order of the blocks depends on the `LineOrder`
480
    /// attribute (unspecified line order is treated the same as increasing
481
    /// line order). The blocks written to the file must be exactly in this
482
    /// order, except for when the `LineOrder` is unspecified.
483
    /// The index represents the block index, in increasing line order, within
484
    /// the header.
485
0
    pub fn enumerate_ordered_header_block_indices(
486
0
        &self,
487
0
    ) -> impl '_ + Iterator<Item = (usize, BlockIndex)> {
488
0
        crate::block::enumerate_ordered_header_block_indices(&self.headers)
489
0
    }
490
491
    /// Go through all the block indices in the correct order and call the
492
    /// specified closure for each of these blocks. That way, the blocks
493
    /// indices are filled with real block data and returned as an iterator.
494
    /// The closure returns the an `UncompressedBlock` for each block index.
495
0
    pub fn collect_ordered_blocks<'s>(
496
0
        &'s self,
497
0
        mut get_block: impl 's + FnMut(BlockIndex) -> UncompressedBlock,
498
0
    ) -> impl 's + Iterator<Item = (usize, UncompressedBlock)> {
499
0
        self.enumerate_ordered_header_block_indices()
500
0
            .map(move |(index_in_header, block_index)| (index_in_header, get_block(block_index)))
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_blocks::<_>::{closure#0}
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_blocks::<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_blocks::<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_blocks::<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_blocks::<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>::{closure#0}
501
0
    }
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_blocks::<_>
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_blocks::<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_blocks::<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_blocks::<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_blocks::<<exr::meta::MetaData>::collect_ordered_block_data<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}>
502
503
    /// Go through all the block indices in the correct order and call the
504
    /// specified closure for each of these blocks. That way, the blocks
505
    /// indices are filled with real block data and returned as an iterator.
506
    /// The closure returns the byte data for each block index.
507
0
    pub fn collect_ordered_block_data<'s>(
508
0
        &'s self,
509
0
        mut get_block_data: impl 's + FnMut(BlockIndex) -> Vec<u8>,
510
0
    ) -> impl 's + Iterator<Item = (usize, UncompressedBlock)> {
511
0
        self.collect_ordered_blocks(move |block_index| UncompressedBlock {
512
0
            index: block_index,
513
0
            data: get_block_data(block_index),
514
0
        })
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_block_data::<_>::{closure#0}
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_block_data::<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_block_data::<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_block_data::<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_block_data::<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>::{closure#0}
515
0
    }
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_block_data::<_>
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_block_data::<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_block_data::<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_block_data::<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>
Unexecuted instantiation: <exr::meta::MetaData>::collect_ordered_block_data::<<exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}::{closure#0}>
516
517
    /// Validates this meta data. Returns the minimal possible requirements.
518
0
    pub fn validate(headers: &[Header], pedantic: bool) -> Result<Requirements> {
519
0
        if headers.is_empty() {
520
0
            return Err(Error::invalid("at least one layer is required"));
521
0
        }
522
523
0
        let deep = false; // TODO deep data
524
0
        let is_multilayer = headers.len() > 1;
525
0
        let first_header_has_tiles =
526
0
            headers.iter().next().map_or(false, |header| header.blocks.has_tiles());
527
528
0
        let mut minimal_requirements = Requirements {
529
            // according to the spec, version 2  should only be necessary if `is_multilayer ||
530
            // deep`. but the current open exr library does not support images with
531
            // version 1, so always use version 2.
532
            file_format_version: 2,
533
534
            // start as low as possible, later increasing if required
535
            has_long_names: false,
536
537
0
            is_single_layer_and_tiled: !is_multilayer && first_header_has_tiles,
538
0
            has_multiple_layers: is_multilayer,
539
0
            has_deep_data: deep,
540
        };
541
542
0
        for header in headers {
543
0
            if header.deep {
544
                // TODO deep data (and then remove this check)
545
0
                return Err(Error::unsupported("deep data not supported yet"));
546
0
            }
547
548
0
            header.validate(is_multilayer, &mut minimal_requirements.has_long_names, pedantic)?;
549
        }
550
551
        // TODO validation fn!
552
        // if let Some(max) = max_pixel_bytes {
553
        // let byte_size: usize = headers.iter()
554
        // .map(|header| header.total_pixel_bytes())
555
        // .sum();
556
        //
557
        // if byte_size > max {
558
        // return Err(Error::invalid("image larger than specified maximum"));
559
        // }
560
        // }
561
562
0
        if pedantic {
563
            // check for duplicate header names
564
0
            let mut header_names = HashSet::with_capacity(headers.len());
565
0
            for header in headers {
566
0
                if !header_names.insert(&header.own_attributes.layer_name) {
567
0
                    return Err(Error::invalid(format!(
568
0
                        "duplicate layer name: `{}`",
569
0
                        header.own_attributes.layer_name.as_ref().expect("header validation bug")
570
0
                    )));
571
0
                }
572
            }
573
0
        }
574
575
0
        if pedantic {
576
0
            let must_share =
577
0
                headers.iter().flat_map(|header| header.own_attributes.other.iter()).any(
578
0
                    |(_, value)| value.to_chromaticities().is_ok() || value.to_time_code().is_ok(),
579
                );
580
581
0
            if must_share {
582
0
                return Err(Error::invalid("chromaticities and time code attributes must must not exist in own attributes but shared instead"));
583
0
            }
584
0
        }
585
586
0
        if pedantic && headers.len() > 1 {
587
            // check for attributes that should not differ in between headers
588
0
            let first_header = headers.first().expect("header count validation bug");
589
0
            let first_header_attributes = &first_header.shared_attributes;
590
591
0
            for header in &headers[1..] {
592
0
                if &header.shared_attributes != first_header_attributes {
593
0
                    return Err(Error::invalid("display window, pixel aspect, chromaticities, and time code attributes must be equal for all headers"));
594
0
                }
595
            }
596
0
        }
597
598
0
        debug_assert!(minimal_requirements.validate().is_ok(), "inferred requirements are invalid");
599
0
        Ok(minimal_requirements)
600
0
    }
601
}
602
603
impl Requirements {
604
    // this is actually used for control flow, as the number of headers may be 1 in
605
    // a multilayer file
606
    /// Is this file declared to contain multiple layers?
607
0
    pub fn is_multilayer(&self) -> bool {
608
0
        self.has_multiple_layers
609
0
    }
610
611
    /// Read the value without validating.
612
0
    pub fn read<R: Read>(read: &mut R) -> Result<Self> {
613
        use ::bit_field::BitField;
614
615
0
        let version_and_flags = u32::read_le(read)?;
616
617
        // take the 8 least significant bits, they contain the file format version
618
        // number
619
0
        let version = (version_and_flags & 0x000F) as u8;
620
621
        // the 24 most significant bits are treated as a set of boolean flags
622
0
        let is_single_tile = version_and_flags.get_bit(9);
623
0
        let has_long_names = version_and_flags.get_bit(10);
624
0
        let has_deep_data = version_and_flags.get_bit(11);
625
0
        let has_multiple_layers = version_and_flags.get_bit(12);
626
627
        // all remaining bits except 9, 10, 11 and 12 are reserved and should be 0
628
        // if a file has any of these bits set to 1, it means this file contains
629
        // a feature that we don't support
630
0
        let unknown_flags = version_and_flags >> 13; // all flags excluding the 12 bits we already parsed
631
632
0
        if unknown_flags != 0 {
633
            // TODO test if this correctly detects unsupported files
634
0
            return Err(Error::unsupported("too new file feature flags"));
635
0
        }
636
637
0
        let version = Self {
638
0
            file_format_version: version,
639
0
            is_single_layer_and_tiled: is_single_tile,
640
0
            has_long_names,
641
0
            has_deep_data,
642
0
            has_multiple_layers,
643
0
        };
644
645
0
        Ok(version)
646
0
    }
Unexecuted instantiation: <exr::meta::Requirements>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>>
Unexecuted instantiation: <exr::meta::Requirements>::read::<_>
Unexecuted instantiation: <exr::meta::Requirements>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>>
647
648
    /// Without validation, write this instance to the byte stream.
649
0
    pub fn write<W: Write>(self, write: &mut W) -> UnitResult {
650
        use ::bit_field::BitField;
651
652
        // the 8 least significant bits contain the file format version number
653
        // and the flags are set to 0
654
0
        let mut version_and_flags = u32::from(self.file_format_version);
655
656
        // the 24 most significant bits are treated as a set of boolean flags
657
0
        version_and_flags.set_bit(9, self.is_single_layer_and_tiled);
658
0
        version_and_flags.set_bit(10, self.has_long_names);
659
0
        version_and_flags.set_bit(11, self.has_deep_data);
660
0
        version_and_flags.set_bit(12, self.has_multiple_layers);
661
        // all remaining bits except 9, 10, 11 and 12 are reserved and should be 0
662
663
0
        version_and_flags.write_le(write)?;
664
0
        Ok(())
665
0
    }
Unexecuted instantiation: <exr::meta::Requirements>::write::<_>
Unexecuted instantiation: <exr::meta::Requirements>::write::<exr::io::Tracking<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
Unexecuted instantiation: <exr::meta::Requirements>::write::<exr::io::Tracking<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>
666
667
    /// Validate this instance.
668
0
    pub fn validate(&self) -> UnitResult {
669
0
        if self.file_format_version == 2 {
670
0
            match (
671
0
                self.is_single_layer_and_tiled,
672
0
                self.has_deep_data,
673
0
                self.has_multiple_layers,
674
0
                self.file_format_version,
675
0
            ) {
676
                // Single-part scan line. One normal scan line image.
677
0
                (false, false, false, 1..=2) => Ok(()),
678
679
                // Single-part tile. One normal tiled image.
680
0
                (true, false, false, 1..=2) => Ok(()),
681
682
                // Multi-part (new in 2.0).
683
                // Multiple normal images (scan line and/or tiled).
684
0
                (false, false, true, 2) => Ok(()),
685
686
                // Single-part deep data (new in 2.0).
687
                // One deep tile or deep scan line part
688
0
                (false, true, false, 2) => Ok(()),
689
690
                // Multi-part deep data (new in 2.0).
691
                // Multiple parts (any combination of:
692
                // tiles, scan lines, deep tiles and/or deep scan lines).
693
0
                (false, true, true, 2) => Ok(()),
694
695
0
                _ => Err(Error::invalid("file feature flags")),
696
            }
697
        } else {
698
0
            Err(Error::unsupported("file versions other than 2.0 are not supported"))
699
        }
700
0
    }
701
}
702
703
#[cfg(test)]
704
mod test {
705
    use super::*;
706
    use crate::meta::header::{ImageAttributes, LayerAttributes};
707
708
    #[test]
709
    fn round_trip_requirements() {
710
        let requirements = Requirements {
711
            file_format_version: 2,
712
            is_single_layer_and_tiled: true,
713
            has_long_names: false,
714
            has_deep_data: true,
715
            has_multiple_layers: false,
716
        };
717
718
        let mut data: Vec<u8> = Vec::new();
719
        requirements.write(&mut data).unwrap();
720
        let read = Requirements::read(&mut data.as_slice()).unwrap();
721
        assert_eq!(requirements, read);
722
    }
723
724
    #[test]
725
    fn round_trip() {
726
        let header = Header {
727
            channels: ChannelList::new(smallvec![ChannelDescription {
728
                name: Text::from("main"),
729
                sample_type: SampleType::U32,
730
                quantize_linearly: false,
731
                sampling: Vec2(1, 1)
732
            }]),
733
            compression: Compression::Uncompressed,
734
            line_order: LineOrder::Increasing,
735
            deep_data_version: Some(1),
736
            chunk_count: compute_chunk_count(
737
                Compression::Uncompressed,
738
                Vec2(2000, 333),
739
                BlockDescription::ScanLines,
740
            ),
741
            max_samples_per_pixel: Some(4),
742
            shared_attributes: ImageAttributes {
743
                pixel_aspect: 3.0,
744
                ..ImageAttributes::new(IntegerBounds {
745
                    position: Vec2(2, 1),
746
                    size: Vec2(11, 9),
747
                })
748
            },
749
750
            blocks: BlockDescription::ScanLines,
751
            deep: false,
752
            layer_size: Vec2(2000, 333),
753
            own_attributes: LayerAttributes {
754
                layer_name: Some(Text::from("test name lol")),
755
                layer_position: Vec2(3, -5),
756
                screen_window_center: Vec2(0.3, 99.0),
757
                screen_window_width: 0.19,
758
                ..Default::default()
759
            },
760
        };
761
762
        let meta = MetaData {
763
            requirements: Requirements {
764
                file_format_version: 2,
765
                is_single_layer_and_tiled: false,
766
                has_long_names: false,
767
                has_deep_data: false,
768
                has_multiple_layers: false,
769
            },
770
            headers: smallvec![header],
771
        };
772
773
        let mut data: Vec<u8> = Vec::new();
774
        MetaData::write_validating_to_buffered(&mut data, meta.headers.as_slice(), true).unwrap();
775
        let meta2 = MetaData::read_from_buffered(data.as_slice(), false).unwrap();
776
        MetaData::validate(meta2.headers.as_slice(), true).unwrap();
777
        assert_eq!(meta, meta2);
778
    }
779
780
    #[test]
781
    fn infer_low_requirements() {
782
        let header_version_1_short_names = Header {
783
            channels: ChannelList::new(smallvec![ChannelDescription {
784
                name: Text::from("main"),
785
                sample_type: SampleType::U32,
786
                quantize_linearly: false,
787
                sampling: Vec2(1, 1)
788
            }]),
789
            compression: Compression::Uncompressed,
790
            line_order: LineOrder::Increasing,
791
            deep_data_version: Some(1),
792
            chunk_count: compute_chunk_count(
793
                Compression::Uncompressed,
794
                Vec2(2000, 333),
795
                BlockDescription::ScanLines,
796
            ),
797
            max_samples_per_pixel: Some(4),
798
            shared_attributes: ImageAttributes {
799
                pixel_aspect: 3.0,
800
                ..ImageAttributes::new(IntegerBounds {
801
                    position: Vec2(2, 1),
802
                    size: Vec2(11, 9),
803
                })
804
            },
805
            blocks: BlockDescription::ScanLines,
806
            deep: false,
807
            layer_size: Vec2(2000, 333),
808
            own_attributes: LayerAttributes {
809
                other: vec![
810
                    (Text::from("x"), AttributeValue::F32(3.0)),
811
                    (Text::from("y"), AttributeValue::F32(-1.0)),
812
                ]
813
                .into_iter()
814
                .collect(),
815
                ..Default::default()
816
            },
817
        };
818
819
        let low_requirements = MetaData::validate(&[header_version_1_short_names], true).unwrap();
820
821
        assert!(!low_requirements.has_long_names);
822
        assert_eq!(low_requirements.file_format_version, 2); // always have version 2
823
        assert!(!low_requirements.has_deep_data);
824
        assert!(!low_requirements.has_multiple_layers);
825
    }
826
827
    #[test]
828
    fn infer_high_requirements() {
829
        let header_version_2_long_names = Header {
830
            channels: ChannelList::new(smallvec![ChannelDescription {
831
                name: Text::new_or_panic("main"),
832
                sample_type: SampleType::U32,
833
                quantize_linearly: false,
834
                sampling: Vec2(1, 1)
835
            }]),
836
            compression: Compression::Uncompressed,
837
            line_order: LineOrder::Increasing,
838
            deep_data_version: Some(1),
839
            chunk_count: compute_chunk_count(
840
                Compression::Uncompressed,
841
                Vec2(2000, 333),
842
                BlockDescription::ScanLines,
843
            ),
844
            max_samples_per_pixel: Some(4),
845
            shared_attributes: ImageAttributes {
846
                pixel_aspect: 3.0,
847
                ..ImageAttributes::new(IntegerBounds {
848
                    position: Vec2(2, 1),
849
                    size: Vec2(11, 9),
850
                })
851
            },
852
            blocks: BlockDescription::ScanLines,
853
            deep: false,
854
            layer_size: Vec2(2000, 333),
855
            own_attributes: LayerAttributes {
856
                layer_name: Some(Text::new_or_panic("oasdasoidfj")),
857
                other: vec![
858
                    (
859
                        Text::new_or_panic(
860
                            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
861
                        ),
862
                        AttributeValue::F32(3.0),
863
                    ),
864
                    (Text::new_or_panic("y"), AttributeValue::F32(-1.0)),
865
                ]
866
                .into_iter()
867
                .collect(),
868
                ..Default::default()
869
            },
870
        };
871
872
        let mut layer_2 = header_version_2_long_names.clone();
873
        layer_2.own_attributes.layer_name = Some(Text::new_or_panic("anythingelse"));
874
875
        let low_requirements =
876
            MetaData::validate(&[header_version_2_long_names, layer_2], true).unwrap();
877
878
        assert!(low_requirements.has_long_names);
879
        assert_eq!(low_requirements.file_format_version, 2);
880
        assert!(!low_requirements.has_deep_data);
881
        assert!(low_requirements.has_multiple_layers);
882
    }
883
}