/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 | | } |