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