exr/block/
chunk.rs

1
2//! Read and write already compressed pixel data blocks.
3//! Does not include the process of compression and decompression.
4
5use 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)]
15pub 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)]
35pub 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)]
55pub 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)]
73pub 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)]
87pub 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)]
101pub 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)]
126pub 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
146use crate::io::*;
147
148impl TileCoordinates {
149
150    /// Without validation, write this instance to the byte stream.
151    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
152        i32::write_le(usize_to_i32(self.tile_index.x(), "tile x")?, write)?;
153        i32::write_le(usize_to_i32(self.tile_index.y(), "tile y")?, write)?;
154        i32::write_le(usize_to_i32(self.level_index.x(), "level x")?, write)?;
155        i32::write_le(usize_to_i32(self.level_index.y(), "level y")?, write)?;
156        Ok(())
157    }
158
159    /// Read the value without validating.
160    pub fn read(read: &mut impl Read) -> Result<Self> {
161        let tile_x = i32::read_le(read)?;
162        let tile_y = i32::read_le(read)?;
163
164        let level_x = i32::read_le(read)?;
165        let level_y = i32::read_le(read)?;
166
167        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            return Err(Error::invalid("level index exceeding integer maximum"));
171        }
172
173        Ok(TileCoordinates {
174            tile_index: Vec2(tile_x, tile_y).to_usize("tile coordinate index")?,
175            level_index: Vec2(level_x, level_y).to_usize("tile coordinate level")?
176        })
177    }
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    pub fn to_data_indices(&self, tile_size: Vec2<usize>, max: Vec2<usize>) -> Result<IntegerBounds> {
183        let x = self.tile_index.x() * tile_size.width();
184        let y = self.tile_index.y() * tile_size.height();
185
186        if x >= max.x() || y >= max.y() {
187            Err(Error::invalid("tile index"))
188        }
189        else {
190            Ok(IntegerBounds {
191                position: Vec2(usize_to_i32(x, "tile x")?, usize_to_i32(y, "tile y")?),
192                size: Vec2(
193                    calculate_block_size(max.x(), tile_size.width(), x)?,
194                    calculate_block_size(max.y(), tile_size.height(), y)?,
195                ),
196            })
197        }
198    }
199
200    /// Absolute coordinates inside the global 2D space of a file, may be negative.
201    pub fn to_absolute_indices(&self, tile_size: Vec2<usize>, data_window: IntegerBounds) -> Result<IntegerBounds> {
202        let data = self.to_data_indices(tile_size, data_window.size)?;
203        Ok(data.with_origin(data_window.position))
204    }
205
206    /// Returns if this is the original resolution or a smaller copy.
207    pub fn is_largest_resolution_level(&self) -> bool {
208        self.level_index == Vec2(0, 0)
209    }
210}
211
212
213
214use crate::meta::{MetaData, BlockDescription, calculate_block_size};
215
216impl CompressedScanLineBlock {
217
218    /// Without validation, write this instance to the byte stream.
219    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
220        debug_assert_ne!(self.compressed_pixels_le.len(), 0, "empty blocks should not be put in the file bug");
221
222        i32::write_le(self.y_coordinate, write)?;
223        u8::write_i32_sized_slice_le(write, &self.compressed_pixels_le)?;
224        Ok(())
225    }
226
227    /// Read the value without validating.
228    pub fn read(read: &mut impl Read, max_block_byte_size: usize) -> Result<Self> {
229        let y_coordinate = i32::read_le(read)?;
230        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        Ok(CompressedScanLineBlock { y_coordinate, compressed_pixels_le })
232    }
233}
234
235impl CompressedTileBlock {
236
237    /// Without validation, write this instance to the byte stream.
238    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
239        debug_assert_ne!(self.compressed_pixels_le.len(), 0, "empty blocks should not be put in the file bug");
240
241        self.coordinates.write(write)?;
242        u8::write_i32_sized_slice_le(write, &self.compressed_pixels_le)?;
243        Ok(())
244    }
245
246    /// Read the value without validating.
247    pub fn read(read: &mut impl Read, max_block_byte_size: usize) -> Result<Self> {
248        let coordinates = TileCoordinates::read(read)?;
249        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        Ok(CompressedTileBlock { coordinates, compressed_pixels_le })
251    }
252}
253
254impl CompressedDeepScanLineBlock {
255
256    /// Without validation, write this instance to the byte stream.
257    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
258        debug_assert_ne!(self.compressed_sample_data_le.len(), 0, "empty blocks should not be put in the file bug");
259
260        i32::write_le(self.y_coordinate, write)?;
261        u64::write_le(self.compressed_pixel_offset_table.len() as u64, write)?;
262        u64::write_le(self.compressed_sample_data_le.len() as u64, write)?; // TODO just guessed
263        u64::write_le(self.decompressed_sample_data_size as u64, write)?;
264        i8::write_slice_le(write, &self.compressed_pixel_offset_table)?;
265        u8::write_slice_le(write, &self.compressed_sample_data_le)?;
266        Ok(())
267    }
268
269    /// Read the value without validating.
270    pub fn read(read: &mut impl Read, max_block_byte_size: usize) -> Result<Self> {
271        let y_coordinate = i32::read_le(read)?;
272        let compressed_pixel_offset_table_size = u64_to_usize(u64::read_le(read)?, "deep table size")?;
273        let compressed_sample_data_size = u64_to_usize(u64::read_le(read)?, "deep size")?;
274        let decompressed_sample_data_size = u64_to_usize(u64::read_le(read)?, "raw deep size")?;
275
276        // doc said i32, try u8
277        let compressed_pixel_offset_table = i8::read_vec_le(
278            read, compressed_pixel_offset_table_size,
279            6 * u16::MAX as usize, Some(max_block_byte_size),
280            "deep scan line block table size"
281        )?;
282
283        let compressed_sample_data_le = u8::read_vec_le(
284            read, compressed_sample_data_size,
285            6 * u16::MAX as usize, Some(max_block_byte_size),
286            "deep scan line block sample count"
287        )?;
288
289        Ok(CompressedDeepScanLineBlock {
290            y_coordinate,
291            decompressed_sample_data_size,
292            compressed_pixel_offset_table,
293            compressed_sample_data_le,
294        })
295    }
296}
297
298
299impl CompressedDeepTileBlock {
300
301    /// Without validation, write this instance to the byte stream.
302    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
303        debug_assert_ne!(self.compressed_sample_data_le.len(), 0, "empty blocks should not be put in the file bug");
304
305        self.coordinates.write(write)?;
306        u64::write_le(self.compressed_pixel_offset_table.len() as u64, write)?;
307        u64::write_le(self.compressed_sample_data_le.len() as u64, write)?; // TODO just guessed
308        u64::write_le(self.decompressed_sample_data_size as u64, write)?;
309        i8::write_slice_le(write, &self.compressed_pixel_offset_table)?;
310        u8::write_slice_le(write, &self.compressed_sample_data_le)?;
311        Ok(())
312    }
313
314    /// Read the value without validating.
315    pub fn read(read: &mut impl Read, hard_max_block_byte_size: usize) -> Result<Self> {
316        let coordinates = TileCoordinates::read(read)?;
317        let compressed_pixel_offset_table_size = u64_to_usize(u64::read_le(read)?,"deep table size")?;
318        let compressed_sample_data_size = u64_to_usize(u64::read_le(read)?, "deep size")?; // TODO u64 just guessed
319        let decompressed_sample_data_size = u64_to_usize(u64::read_le(read)?, "raw deep size")?;
320
321        let compressed_pixel_offset_table = i8::read_vec_le(
322            read, compressed_pixel_offset_table_size,
323            6 * u16::MAX as usize, Some(hard_max_block_byte_size),
324            "deep tile block table size"
325        )?;
326
327        let compressed_sample_data_le = u8::read_vec_le(
328            read, compressed_sample_data_size,
329            6 * u16::MAX as usize, Some(hard_max_block_byte_size),
330            "deep tile block sample count"
331        )?;
332
333        Ok(CompressedDeepTileBlock {
334            coordinates,
335            decompressed_sample_data_size,
336            compressed_pixel_offset_table,
337            compressed_sample_data_le,
338        })
339    }
340}
341
342use crate::error::{UnitResult, Result, Error, u64_to_usize, usize_to_i32, i32_to_usize};
343use crate::math::Vec2;
344
345/// Validation of chunks is done while reading and writing the actual data. (For example in exr::full_image)
346impl Chunk {
347
348    /// Without validation, write this instance to the byte stream.
349    pub fn write(&self, write: &mut impl Write, header_count: usize) -> UnitResult {
350        debug_assert!(self.layer_index < header_count, "layer index bug"); // validation is done in full_image or simple_image
351
352        if header_count != 1 {  usize_to_i32(self.layer_index, "layer index")?.write_le(write)?; }
353        else { assert_eq!(self.layer_index, 0, "invalid header index for single layer file"); }
354
355        match self.compressed_block {
356            CompressedBlock::ScanLine     (ref value) => value.write(write),
357            CompressedBlock::Tile         (ref value) => value.write(write),
358            CompressedBlock::DeepScanLine (ref value) => value.write(write),
359            CompressedBlock::DeepTile     (ref value) => value.write(write),
360        }
361    }
362
363    /// Read the value without validating.
364    pub fn read(read: &mut impl Read, meta_data: &MetaData) -> Result<Self> {
365        let layer_number = i32_to_usize(
366            if meta_data.requirements.is_multilayer() { i32::read_le(read)? } // documentation says u64, but is i32
367            else { 0_i32 }, // reference the first header for single-layer images
368            "chunk data part number"
369        )?;
370
371        if layer_number >= meta_data.headers.len() {
372            return Err(Error::invalid("chunk data part number"));
373        }
374
375        let header = &meta_data.headers[layer_number];
376        let max_block_byte_size = header.max_block_byte_size();
377
378        let chunk = Chunk {
379            layer_index: layer_number,
380            compressed_block: match header.blocks {
381                // flat data
382                BlockDescription::ScanLines if !header.deep => CompressedBlock::ScanLine(CompressedScanLineBlock::read(read, max_block_byte_size)?),
383                BlockDescription::Tiles(_) if !header.deep     => CompressedBlock::Tile(CompressedTileBlock::read(read, max_block_byte_size)?),
384
385                // deep data
386                BlockDescription::ScanLines   => CompressedBlock::DeepScanLine(CompressedDeepScanLineBlock::read(read, max_block_byte_size)?),
387                BlockDescription::Tiles(_)    => CompressedBlock::DeepTile(CompressedDeepTileBlock::read(read, max_block_byte_size)?),
388            },
389        };
390
391        Ok(chunk)
392    }
393}
394