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 = i32::read(read)?;
215 let compressed_pixels: Vec = u8::read_i32_sized_vec(read, soft_max:max_block_byte_size, hard_max:Some(max_block_byte_size), purpose:"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 = TileCoordinates::read(read)?;
234 let compressed_pixels: Vec = u8::read_i32_sized_vec(read, soft_max:max_block_byte_size, hard_max:Some(max_block_byte_size), purpose:"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
380