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 | #[derive (Debug, Clone)]
|
13 | pub 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)]
|
30 | pub 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)]
|
48 | pub 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)]
|
63 | pub 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)]
|
76 | pub 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)]
|
88 | pub 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)]
|
111 | pub 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 |
|
131 | use crate::io::*;
|
132 |
|
133 | impl 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 |
|
199 | use crate::meta::{MetaData, BlockDescription, calculate_block_size};
|
200 |
|
201 | impl 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 |
|
220 | impl 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 |
|
239 | impl 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 |
|
284 | impl 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 |
|
327 | use crate::error::{UnitResult, Result, Error, u64_to_usize, usize_to_i32, i32_to_usize};
|
328 | use crate::math::Vec2;
|
329 |
|
330 | /// Validation of chunks is done while reading and writing the actual data. (For example in exr::full_image)
|
331 | impl 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 | |