Coverage Report

Created: 2026-03-10 07:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/exr-1.74.0/src/block/chunk.rs
Line
Count
Source
1
2
//! Read and write already compressed pixel data blocks.
3
//! Does not include the process of compression and decompression.
4
5
use crate::meta::attribute::{IntegerBounds};
6
7
/// A generic block of pixel information.
8
/// Contains pixel data and an index to the corresponding header.
9
/// All pixel data in a file is split into a list of chunks.
10
/// Also contains positioning information that locates this
11
/// data block in the referenced layer.
12
/// The byte data is in little-endian format,
13
/// as these bytes will be written into the file directly.
14
#[derive(Debug, Clone)]
15
pub struct Chunk {
16
17
    /// The index of the layer that the block belongs to.
18
    /// This is required as the pixel data can appear in any order in a file.
19
    // PDF says u64, but source code seems to be i32
20
    pub layer_index: usize,
21
22
    /// The compressed pixel contents.
23
    /// This data is compressed and in little-endian format.
24
    pub compressed_block: CompressedBlock,
25
}
26
27
/// The raw, possibly compressed pixel data of a file.
28
/// Each layer in a file can have a different type.
29
/// Also contains positioning information that locates this
30
/// data block in the corresponding layer.
31
/// Exists inside a `Chunk`.
32
/// The byte data is in little-endian format,
33
/// as these bytes will be written into the file directly.
34
#[derive(Debug, Clone)]
35
pub enum CompressedBlock {
36
37
    /// Scan line blocks of flat data.
38
    ScanLine(CompressedScanLineBlock),
39
40
    /// Tiles of flat data.
41
    Tile(CompressedTileBlock),
42
43
    /// Scan line blocks of deep data.
44
    DeepScanLine(CompressedDeepScanLineBlock),
45
46
    /// Tiles of deep data.
47
    DeepTile(CompressedDeepTileBlock),
48
}
49
50
/// A `Block` of possibly compressed flat scan lines.
51
/// Corresponds to type attribute `scanlineimage`.
52
/// The byte data is in little-endian format,
53
/// as these bytes will be written into the file directly.
54
#[derive(Debug, Clone)]
55
pub struct CompressedScanLineBlock {
56
57
    /// The block's y coordinate is the pixel space y coordinate of the top scan line in the block.
58
    /// The top scan line block in the image is aligned with the top edge of the data window.
59
    pub y_coordinate: i32,
60
61
    /// One or more scan lines may be stored together as a scan line block.
62
    /// The number of scan lines per block depends on how the pixel data are compressed.
63
    /// For each line in the tile, for each channel, the row values are contiguous.
64
    /// This data is compressed and in little-endian format.
65
    pub compressed_pixels_le: Vec<u8>,
66
}
67
68
/// This `Block` is a tile of flat (non-deep) data.
69
/// Corresponds to type attribute `tiledimage`.
70
/// The byte data is in little-endian format,
71
/// as these bytes will be written into the file directly.
72
#[derive(Debug, Clone)]
73
pub struct CompressedTileBlock {
74
75
    /// The tile location.
76
    pub coordinates: TileCoordinates,
77
78
    /// One or more scan lines may be stored together as a scan line block.
79
    /// The number of scan lines per block depends on how the pixel data are compressed.
80
    /// For each line in the tile, for each channel, the row values are contiguous.
81
    /// This data is compressed and in little-endian format.
82
    pub compressed_pixels_le: Vec<u8>,
83
}
84
85
/// Indicates the position and resolution level of a `TileBlock` or `DeepTileBlock`.
86
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
87
pub struct TileCoordinates {
88
89
    /// Index of the tile, not pixel position.
90
    pub tile_index: Vec2<usize>,
91
92
    /// Index of the Mip/Rip level.
93
    pub level_index: Vec2<usize>,
94
}
95
96
/// This `Block` consists of one or more deep scan lines.
97
/// Corresponds to type attribute `deepscanline`.
98
/// The byte data is in little-endian format,
99
/// as these bytes will be written into the file directly.
100
#[derive(Debug, Clone)]
101
pub struct CompressedDeepScanLineBlock {
102
103
    /// The block's y coordinate is the pixel space y coordinate of the top scan line in the block.
104
    /// The top scan line block in the image is aligned with the top edge of the data window.
105
    pub y_coordinate: i32,
106
107
    /// Count of samples.
108
    pub decompressed_sample_data_size: usize,
109
110
    /// The pixel offset table is a list of integers, one for each pixel column within the data window.
111
    /// Each entry in the table indicates the total number of samples required
112
    /// to store the pixel in it as well as all pixels to the left of it.
113
    pub compressed_pixel_offset_table: Vec<i8>,
114
115
    /// One or more scan lines may be stored together as a scan line block.
116
    /// The number of scan lines per block depends on how the pixel data are compressed.
117
    /// For each line in the tile, for each channel, the row values are contiguous.
118
    pub compressed_sample_data_le: Vec<u8>,
119
}
120
121
/// This `Block` is a tile of deep data.
122
/// Corresponds to type attribute `deeptile`.
123
/// The byte data is in little-endian format,
124
/// as these bytes will be written into the file directly.
125
#[derive(Debug, Clone)]
126
pub struct CompressedDeepTileBlock {
127
128
    /// The tile location.
129
    pub coordinates: TileCoordinates,
130
131
    /// Count of samples.
132
    pub decompressed_sample_data_size: usize,
133
134
    /// The pixel offset table is a list of integers, one for each pixel column within the data window.
135
    /// Each entry in the table indicates the total number of samples required
136
    /// to store the pixel in it as well as all pixels to the left of it.
137
    pub compressed_pixel_offset_table: Vec<i8>,
138
139
    /// One or more scan lines may be stored together as a scan line block.
140
    /// The number of scan lines per block depends on how the pixel data are compressed.
141
    /// For each line in the tile, for each channel, the row values are contiguous.
142
    pub compressed_sample_data_le: Vec<u8>,
143
}
144
145
146
use crate::io::*;
147
148
impl TileCoordinates {
149
150
    /// Without validation, write this instance to the byte stream.
151
0
    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
152
0
        i32::write_le(usize_to_i32(self.tile_index.x(), "tile x")?, write)?;
153
0
        i32::write_le(usize_to_i32(self.tile_index.y(), "tile y")?, write)?;
154
0
        i32::write_le(usize_to_i32(self.level_index.x(), "level x")?, write)?;
155
0
        i32::write_le(usize_to_i32(self.level_index.y(), "level y")?, write)?;
156
0
        Ok(())
157
0
    }
Unexecuted instantiation: <exr::block::chunk::TileCoordinates>::write::<_>
Unexecuted instantiation: <exr::block::chunk::TileCoordinates>::write::<exr::io::Tracking<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
Unexecuted instantiation: <exr::block::chunk::TileCoordinates>::write::<exr::io::Tracking<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>
158
159
    /// Read the value without validating.
160
0
    pub fn read(read: &mut impl Read) -> Result<Self> {
161
0
        let tile_x = i32::read_le(read)?;
162
0
        let tile_y = i32::read_le(read)?;
163
164
0
        let level_x = i32::read_le(read)?;
165
0
        let level_y = i32::read_le(read)?;
166
167
0
        if level_x > 31 || level_y > 31 {
168
            // there can be at most 31 levels, because the largest level would have a size of 2^31,
169
            // which exceeds the maximum 32-bit integer value.
170
0
            return Err(Error::invalid("level index exceeding integer maximum"));
171
0
        }
172
173
        Ok(TileCoordinates {
174
0
            tile_index: Vec2(tile_x, tile_y).to_usize("tile coordinate index")?,
175
0
            level_index: Vec2(level_x, level_y).to_usize("tile coordinate level")?
176
        })
177
0
    }
Unexecuted instantiation: <exr::block::chunk::TileCoordinates>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>>
Unexecuted instantiation: <exr::block::chunk::TileCoordinates>::read::<_>
Unexecuted instantiation: <exr::block::chunk::TileCoordinates>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>>
178
179
    /// The indices which can be used to index into the arrays of a data window.
180
    /// These coordinates are only valid inside the corresponding one header.
181
    /// Will start at 0 and always be positive.
182
0
    pub fn to_data_indices(&self, tile_size: Vec2<usize>, max: Vec2<usize>) -> Result<IntegerBounds> {
183
0
        let x = self.tile_index.x() * tile_size.width();
184
0
        let y = self.tile_index.y() * tile_size.height();
185
186
0
        if x >= max.x() || y >= max.y() {
187
0
            Err(Error::invalid("tile index"))
188
        }
189
        else {
190
            Ok(IntegerBounds {
191
0
                position: Vec2(usize_to_i32(x, "tile x")?, usize_to_i32(y, "tile y")?),
192
                size: Vec2(
193
0
                    calculate_block_size(max.x(), tile_size.width(), x)?,
194
0
                    calculate_block_size(max.y(), tile_size.height(), y)?,
195
                ),
196
            })
197
        }
198
0
    }
199
200
    /// Absolute coordinates inside the global 2D space of a file, may be negative.
201
0
    pub fn to_absolute_indices(&self, tile_size: Vec2<usize>, data_window: IntegerBounds) -> Result<IntegerBounds> {
202
0
        let data = self.to_data_indices(tile_size, data_window.size)?;
203
0
        Ok(data.with_origin(data_window.position))
204
0
    }
205
206
    /// Returns if this is the original resolution or a smaller copy.
207
0
    pub fn is_largest_resolution_level(&self) -> bool {
208
0
        self.level_index == Vec2(0, 0)
209
0
    }
210
}
211
212
213
214
use crate::meta::{MetaData, BlockDescription, calculate_block_size};
215
216
impl CompressedScanLineBlock {
217
218
    /// Without validation, write this instance to the byte stream.
219
0
    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
220
0
        debug_assert_ne!(self.compressed_pixels_le.len(), 0, "empty blocks should not be put in the file bug");
221
222
0
        i32::write_le(self.y_coordinate, write)?;
223
0
        u8::write_i32_sized_slice_le(write, &self.compressed_pixels_le)?;
224
0
        Ok(())
225
0
    }
Unexecuted instantiation: <exr::block::chunk::CompressedScanLineBlock>::write::<_>
Unexecuted instantiation: <exr::block::chunk::CompressedScanLineBlock>::write::<exr::io::Tracking<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
Unexecuted instantiation: <exr::block::chunk::CompressedScanLineBlock>::write::<exr::io::Tracking<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>
226
227
    /// Read the value without validating.
228
0
    pub fn read(read: &mut impl Read, max_block_byte_size: usize) -> Result<Self> {
229
0
        let y_coordinate = i32::read_le(read)?;
230
0
        let compressed_pixels_le = u8::read_i32_sized_vec_le(read, max_block_byte_size, Some(max_block_byte_size), "scan line block sample count")?;
231
0
        Ok(CompressedScanLineBlock { y_coordinate, compressed_pixels_le })
232
0
    }
Unexecuted instantiation: <exr::block::chunk::CompressedScanLineBlock>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>>
Unexecuted instantiation: <exr::block::chunk::CompressedScanLineBlock>::read::<_>
Unexecuted instantiation: <exr::block::chunk::CompressedScanLineBlock>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>>
233
}
234
235
impl CompressedTileBlock {
236
237
    /// Without validation, write this instance to the byte stream.
238
0
    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
239
0
        debug_assert_ne!(self.compressed_pixels_le.len(), 0, "empty blocks should not be put in the file bug");
240
241
0
        self.coordinates.write(write)?;
242
0
        u8::write_i32_sized_slice_le(write, &self.compressed_pixels_le)?;
243
0
        Ok(())
244
0
    }
Unexecuted instantiation: <exr::block::chunk::CompressedTileBlock>::write::<_>
Unexecuted instantiation: <exr::block::chunk::CompressedTileBlock>::write::<exr::io::Tracking<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
Unexecuted instantiation: <exr::block::chunk::CompressedTileBlock>::write::<exr::io::Tracking<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>
245
246
    /// Read the value without validating.
247
0
    pub fn read(read: &mut impl Read, max_block_byte_size: usize) -> Result<Self> {
248
0
        let coordinates = TileCoordinates::read(read)?;
249
0
        let compressed_pixels_le = u8::read_i32_sized_vec_le(read, max_block_byte_size, Some(max_block_byte_size), "tile block sample count")?;
250
0
        Ok(CompressedTileBlock { coordinates, compressed_pixels_le })
251
0
    }
Unexecuted instantiation: <exr::block::chunk::CompressedTileBlock>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>>
Unexecuted instantiation: <exr::block::chunk::CompressedTileBlock>::read::<_>
Unexecuted instantiation: <exr::block::chunk::CompressedTileBlock>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>>
252
}
253
254
impl CompressedDeepScanLineBlock {
255
256
    /// Without validation, write this instance to the byte stream.
257
0
    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
258
0
        debug_assert_ne!(self.compressed_sample_data_le.len(), 0, "empty blocks should not be put in the file bug");
259
260
0
        i32::write_le(self.y_coordinate, write)?;
261
0
        u64::write_le(self.compressed_pixel_offset_table.len() as u64, write)?;
262
0
        u64::write_le(self.compressed_sample_data_le.len() as u64, write)?; // TODO just guessed
263
0
        u64::write_le(self.decompressed_sample_data_size as u64, write)?;
264
0
        i8::write_slice_le(write, &self.compressed_pixel_offset_table)?;
265
0
        u8::write_slice_le(write, &self.compressed_sample_data_le)?;
266
0
        Ok(())
267
0
    }
Unexecuted instantiation: <exr::block::chunk::CompressedDeepScanLineBlock>::write::<_>
Unexecuted instantiation: <exr::block::chunk::CompressedDeepScanLineBlock>::write::<exr::io::Tracking<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
Unexecuted instantiation: <exr::block::chunk::CompressedDeepScanLineBlock>::write::<exr::io::Tracking<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>
268
269
    /// Read the value without validating.
270
0
    pub fn read(read: &mut impl Read, max_block_byte_size: usize) -> Result<Self> {
271
0
        let y_coordinate = i32::read_le(read)?;
272
0
        let compressed_pixel_offset_table_size = u64_to_usize(u64::read_le(read)?, "deep table size")?;
273
0
        let compressed_sample_data_size = u64_to_usize(u64::read_le(read)?, "deep size")?;
274
0
        let decompressed_sample_data_size = u64_to_usize(u64::read_le(read)?, "raw deep size")?;
275
276
        // doc said i32, try u8
277
0
        let compressed_pixel_offset_table = i8::read_vec_le(
278
0
            read, compressed_pixel_offset_table_size,
279
0
            6 * u16::MAX as usize, Some(max_block_byte_size),
280
            "deep scan line block table size"
281
0
        )?;
282
283
0
        let compressed_sample_data_le = u8::read_vec_le(
284
0
            read, compressed_sample_data_size,
285
0
            6 * u16::MAX as usize, Some(max_block_byte_size),
286
            "deep scan line block sample count"
287
0
        )?;
288
289
0
        Ok(CompressedDeepScanLineBlock {
290
0
            y_coordinate,
291
0
            decompressed_sample_data_size,
292
0
            compressed_pixel_offset_table,
293
0
            compressed_sample_data_le,
294
0
        })
295
0
    }
Unexecuted instantiation: <exr::block::chunk::CompressedDeepScanLineBlock>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>>
Unexecuted instantiation: <exr::block::chunk::CompressedDeepScanLineBlock>::read::<_>
Unexecuted instantiation: <exr::block::chunk::CompressedDeepScanLineBlock>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>>
296
}
297
298
299
impl CompressedDeepTileBlock {
300
301
    /// Without validation, write this instance to the byte stream.
302
0
    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
303
0
        debug_assert_ne!(self.compressed_sample_data_le.len(), 0, "empty blocks should not be put in the file bug");
304
305
0
        self.coordinates.write(write)?;
306
0
        u64::write_le(self.compressed_pixel_offset_table.len() as u64, write)?;
307
0
        u64::write_le(self.compressed_sample_data_le.len() as u64, write)?; // TODO just guessed
308
0
        u64::write_le(self.decompressed_sample_data_size as u64, write)?;
309
0
        i8::write_slice_le(write, &self.compressed_pixel_offset_table)?;
310
0
        u8::write_slice_le(write, &self.compressed_sample_data_le)?;
311
0
        Ok(())
312
0
    }
Unexecuted instantiation: <exr::block::chunk::CompressedDeepTileBlock>::write::<_>
Unexecuted instantiation: <exr::block::chunk::CompressedDeepTileBlock>::write::<exr::io::Tracking<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
Unexecuted instantiation: <exr::block::chunk::CompressedDeepTileBlock>::write::<exr::io::Tracking<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>
313
314
    /// Read the value without validating.
315
0
    pub fn read(read: &mut impl Read, hard_max_block_byte_size: usize) -> Result<Self> {
316
0
        let coordinates = TileCoordinates::read(read)?;
317
0
        let compressed_pixel_offset_table_size = u64_to_usize(u64::read_le(read)?,"deep table size")?;
318
0
        let compressed_sample_data_size = u64_to_usize(u64::read_le(read)?, "deep size")?; // TODO u64 just guessed
319
0
        let decompressed_sample_data_size = u64_to_usize(u64::read_le(read)?, "raw deep size")?;
320
321
0
        let compressed_pixel_offset_table = i8::read_vec_le(
322
0
            read, compressed_pixel_offset_table_size,
323
0
            6 * u16::MAX as usize, Some(hard_max_block_byte_size),
324
            "deep tile block table size"
325
0
        )?;
326
327
0
        let compressed_sample_data_le = u8::read_vec_le(
328
0
            read, compressed_sample_data_size,
329
0
            6 * u16::MAX as usize, Some(hard_max_block_byte_size),
330
            "deep tile block sample count"
331
0
        )?;
332
333
0
        Ok(CompressedDeepTileBlock {
334
0
            coordinates,
335
0
            decompressed_sample_data_size,
336
0
            compressed_pixel_offset_table,
337
0
            compressed_sample_data_le,
338
0
        })
339
0
    }
Unexecuted instantiation: <exr::block::chunk::CompressedDeepTileBlock>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>>
Unexecuted instantiation: <exr::block::chunk::CompressedDeepTileBlock>::read::<_>
Unexecuted instantiation: <exr::block::chunk::CompressedDeepTileBlock>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>>
340
}
341
342
use crate::error::{UnitResult, Result, Error, u64_to_usize, usize_to_i32, i32_to_usize};
343
use crate::math::Vec2;
344
345
/// Validation of chunks is done while reading and writing the actual data. (For example in exr::full_image)
346
impl Chunk {
347
348
    /// Without validation, write this instance to the byte stream.
349
0
    pub fn write(&self, write: &mut impl Write, header_count: usize) -> UnitResult {
350
0
        debug_assert!(self.layer_index < header_count, "layer index bug"); // validation is done in full_image or simple_image
351
352
0
        if header_count != 1 {  usize_to_i32(self.layer_index, "layer index")?.write_le(write)?; }
353
0
        else { assert_eq!(self.layer_index, 0, "invalid header index for single layer file"); }
354
355
0
        match self.compressed_block {
356
0
            CompressedBlock::ScanLine     (ref value) => value.write(write),
357
0
            CompressedBlock::Tile         (ref value) => value.write(write),
358
0
            CompressedBlock::DeepScanLine (ref value) => value.write(write),
359
0
            CompressedBlock::DeepTile     (ref value) => value.write(write),
360
        }
361
0
    }
Unexecuted instantiation: <exr::block::chunk::Chunk>::write::<_>
Unexecuted instantiation: <exr::block::chunk::Chunk>::write::<exr::io::Tracking<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>
Unexecuted instantiation: <exr::block::chunk::Chunk>::write::<exr::io::Tracking<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>>
362
363
    /// Read the value without validating.
364
0
    pub fn read(read: &mut impl Read, meta_data: &MetaData) -> Result<Self> {
365
0
        let layer_number = i32_to_usize(
366
0
            if meta_data.requirements.is_multilayer() { i32::read_le(read)? } // documentation says u64, but is i32
367
0
            else { 0_i32 }, // reference the first header for single-layer images
368
            "chunk data part number"
369
0
        )?;
370
371
0
        if layer_number >= meta_data.headers.len() {
372
0
            return Err(Error::invalid("chunk data part number"));
373
0
        }
374
375
0
        let header = &meta_data.headers[layer_number];
376
0
        let max_block_byte_size = header.max_block_byte_size();
377
378
0
        let chunk = Chunk {
379
0
            layer_index: layer_number,
380
0
            compressed_block: match header.blocks {
381
                // flat data
382
0
                BlockDescription::ScanLines if !header.deep => CompressedBlock::ScanLine(CompressedScanLineBlock::read(read, max_block_byte_size)?),
383
0
                BlockDescription::Tiles(_) if !header.deep     => CompressedBlock::Tile(CompressedTileBlock::read(read, max_block_byte_size)?),
384
385
                // deep data
386
0
                BlockDescription::ScanLines   => CompressedBlock::DeepScanLine(CompressedDeepScanLineBlock::read(read, max_block_byte_size)?),
387
0
                BlockDescription::Tiles(_)    => CompressedBlock::DeepTile(CompressedDeepTileBlock::read(read, max_block_byte_size)?),
388
            },
389
        };
390
391
0
        Ok(chunk)
392
0
    }
Unexecuted instantiation: <exr::block::chunk::Chunk>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<&[u8]>>>>
Unexecuted instantiation: <exr::block::chunk::Chunk>::read::<_>
Unexecuted instantiation: <exr::block::chunk::Chunk>::read::<exr::io::PeekRead<exr::io::Tracking<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>>>
393
}
394