1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379

//! Read and write already compressed pixel data blocks.
//! Does not include the process of compression and decompression.

use crate::meta::attribute::{IntegerBounds};

/// A generic block of pixel information.
/// Contains pixel data and an index to the corresponding header.
/// All pixel data in a file is split into a list of chunks.
/// Also contains positioning information that locates this
/// data block in the referenced layer.
#[derive(Debug, Clone)]
pub struct Chunk {

    /// The index of the layer that the block belongs to.
    /// This is required as the pixel data can appear in any order in a file.
    // PDF says u64, but source code seems to be i32
    pub layer_index: usize,

    /// The compressed pixel contents.
    pub compressed_block: CompressedBlock,
}

/// The raw, possibly compressed pixel data of a file.
/// Each layer in a file can have a different type.
/// Also contains positioning information that locates this
/// data block in the corresponding layer.
/// Exists inside a `Chunk`.
#[derive(Debug, Clone)]
pub enum CompressedBlock {

    /// Scan line blocks of flat data.
    ScanLine(CompressedScanLineBlock),

    /// Tiles of flat data.
    Tile(CompressedTileBlock),

    /// Scan line blocks of deep data.
    DeepScanLine(CompressedDeepScanLineBlock),

    /// Tiles of deep data.
    DeepTile(CompressedDeepTileBlock),
}

/// A `Block` of possibly compressed flat scan lines.
/// Corresponds to type attribute `scanlineimage`.
#[derive(Debug, Clone)]
pub struct CompressedScanLineBlock {

    /// The block's y coordinate is the pixel space y coordinate of the top scan line in the block.
    /// The top scan line block in the image is aligned with the top edge of the data window.
    pub y_coordinate: i32,

    /// One or more scan lines may be stored together as a scan line block.
    /// The number of scan lines per block depends on how the pixel data are compressed.
    /// For each line in the tile, for each channel, the row values are contiguous.
    pub compressed_pixels: Vec<u8>,
}

/// This `Block` is a tile of flat (non-deep) data.
/// Corresponds to type attribute `tiledimage`.
#[derive(Debug, Clone)]
pub struct CompressedTileBlock {

    /// The tile location.
    pub coordinates: TileCoordinates,

    /// One or more scan lines may be stored together as a scan line block.
    /// The number of scan lines per block depends on how the pixel data are compressed.
    /// For each line in the tile, for each channel, the row values are contiguous.
    pub compressed_pixels: Vec<u8>,
}

/// Indicates the position and resolution level of a `TileBlock` or `DeepTileBlock`.
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct TileCoordinates {

    /// Index of the tile, not pixel position.
    pub tile_index: Vec2<usize>,

    /// Index of the Mip/Rip level.
    pub level_index: Vec2<usize>,
}

/// This `Block` consists of one or more deep scan lines.
/// Corresponds to type attribute `deepscanline`.
#[derive(Debug, Clone)]
pub struct CompressedDeepScanLineBlock {

    /// The block's y coordinate is the pixel space y coordinate of the top scan line in the block.
    /// The top scan line block in the image is aligned with the top edge of the data window.
    pub y_coordinate: i32,

    /// Count of samples.
    pub decompressed_sample_data_size: usize,

    /// The pixel offset table is a list of integers, one for each pixel column within the data window.
    /// Each entry in the table indicates the total number of samples required
    /// to store the pixel in it as well as all pixels to the left of it.
    pub compressed_pixel_offset_table: Vec<i8>,

    /// One or more scan lines may be stored together as a scan line block.
    /// The number of scan lines per block depends on how the pixel data are compressed.
    /// For each line in the tile, for each channel, the row values are contiguous.
    pub compressed_sample_data: Vec<u8>,
}

/// This `Block` is a tile of deep data.
/// Corresponds to type attribute `deeptile`.
#[derive(Debug, Clone)]
pub struct CompressedDeepTileBlock {

    /// The tile location.
    pub coordinates: TileCoordinates,

    /// Count of samples.
    pub decompressed_sample_data_size: usize,

    /// The pixel offset table is a list of integers, one for each pixel column within the data window.
    /// Each entry in the table indicates the total number of samples required
    /// to store the pixel in it as well as all pixels to the left of it.
    pub compressed_pixel_offset_table: Vec<i8>,

    /// One or more scan lines may be stored together as a scan line block.
    /// The number of scan lines per block depends on how the pixel data are compressed.
    /// For each line in the tile, for each channel, the row values are contiguous.
    pub compressed_sample_data: Vec<u8>,
}


use crate::io::*;

impl TileCoordinates {

    /// Without validation, write this instance to the byte stream.
    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
        i32::write(usize_to_i32(self.tile_index.x()), write)?;
        i32::write(usize_to_i32(self.tile_index.y()), write)?;
        i32::write(usize_to_i32(self.level_index.x()), write)?;
        i32::write(usize_to_i32(self.level_index.y()), write)?;
        Ok(())
    }

    /// Read the value without validating.
    pub fn read(read: &mut impl Read) -> Result<Self> {
        let tile_x = i32::read(read)?;
        let tile_y = i32::read(read)?;

        let level_x = i32::read(read)?;
        let level_y = i32::read(read)?;

        if level_x > 31 || level_y > 31 {
            // there can be at most 31 levels, because the largest level would have a size of 2^31,
            // which exceeds the maximum 32-bit integer value.
            return Err(Error::invalid("level index exceeding integer maximum"));
        }

        Ok(TileCoordinates {
            tile_index: Vec2(tile_x, tile_y).to_usize("tile coordinate index")?,
            level_index: Vec2(level_x, level_y).to_usize("tile coordinate level")?
        })
    }

    /// The indices which can be used to index into the arrays of a data window.
    /// These coordinates are only valid inside the corresponding one header.
    /// Will start at 0 and always be positive.
    pub fn to_data_indices(&self, tile_size: Vec2<usize>, max: Vec2<usize>) -> Result<IntegerBounds> {
        let x = self.tile_index.x() * tile_size.width();
        let y = self.tile_index.y() * tile_size.height();

        if x >= max.x() || y >= max.y() {
            Err(Error::invalid("tile index"))
        }
        else {
            Ok(IntegerBounds {
                position: Vec2(usize_to_i32(x), usize_to_i32(y)),
                size: Vec2(
                    calculate_block_size(max.x(), tile_size.width(), x)?,
                    calculate_block_size(max.y(), tile_size.height(), y)?,
                ),
            })
        }
    }

    /// Absolute coordinates inside the global 2D space of a file, may be negative.
    pub fn to_absolute_indices(&self, tile_size: Vec2<usize>, data_window: IntegerBounds) -> Result<IntegerBounds> {
        let data = self.to_data_indices(tile_size, data_window.size)?;
        Ok(data.with_origin(data_window.position))
    }

    /// Returns if this is the original resolution or a smaller copy.
    pub fn is_largest_resolution_level(&self) -> bool {
        self.level_index == Vec2(0, 0)
    }
}



use crate::meta::{MetaData, BlockDescription, calculate_block_size};

impl CompressedScanLineBlock {

    /// Without validation, write this instance to the byte stream.
    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
        debug_assert_ne!(self.compressed_pixels.len(), 0, "empty blocks should not be put in the file bug");

        i32::write(self.y_coordinate, write)?;
        u8::write_i32_sized_slice(write, &self.compressed_pixels)?;
        Ok(())
    }

    /// Read the value without validating.
    pub fn read(read: &mut impl Read, max_block_byte_size: usize) -> Result<Self> {
        let y_coordinate = i32::read(read)?;
        let compressed_pixels = u8::read_i32_sized_vec(read, max_block_byte_size, Some(max_block_byte_size), "scan line block sample count")?;
        Ok(CompressedScanLineBlock { y_coordinate, compressed_pixels })
    }
}

impl CompressedTileBlock {

    /// Without validation, write this instance to the byte stream.
    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
        debug_assert_ne!(self.compressed_pixels.len(), 0, "empty blocks should not be put in the file bug");

        self.coordinates.write(write)?;
        u8::write_i32_sized_slice(write, &self.compressed_pixels)?;
        Ok(())
    }

    /// Read the value without validating.
    pub fn read(read: &mut impl Read, max_block_byte_size: usize) -> Result<Self> {
        let coordinates = TileCoordinates::read(read)?;
        let compressed_pixels = u8::read_i32_sized_vec(read, max_block_byte_size, Some(max_block_byte_size), "tile block sample count")?;
        Ok(CompressedTileBlock { coordinates, compressed_pixels })
    }
}

impl CompressedDeepScanLineBlock {

    /// Without validation, write this instance to the byte stream.
    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
        debug_assert_ne!(self.compressed_sample_data.len(), 0, "empty blocks should not be put in the file bug");

        i32::write(self.y_coordinate, write)?;
        u64::write(self.compressed_pixel_offset_table.len() as u64, write)?;
        u64::write(self.compressed_sample_data.len() as u64, write)?; // TODO just guessed
        u64::write(self.decompressed_sample_data_size as u64, write)?;
        i8::write_slice(write, &self.compressed_pixel_offset_table)?;
        u8::write_slice(write, &self.compressed_sample_data)?;
        Ok(())
    }

    /// Read the value without validating.
    pub fn read(read: &mut impl Read, max_block_byte_size: usize) -> Result<Self> {
        let y_coordinate = i32::read(read)?;
        let compressed_pixel_offset_table_size = u64_to_usize(u64::read(read)?);
        let compressed_sample_data_size = u64_to_usize(u64::read(read)?);
        let decompressed_sample_data_size = u64_to_usize(u64::read(read)?);

        // doc said i32, try u8
        let compressed_pixel_offset_table = i8::read_vec(
            read, compressed_pixel_offset_table_size,
            6 * u16::MAX as usize, Some(max_block_byte_size),
            "deep scan line block table size"
        )?;

        let compressed_sample_data = u8::read_vec(
            read, compressed_sample_data_size,
            6 * u16::MAX as usize, Some(max_block_byte_size),
            "deep scan line block sample count"
        )?;

        Ok(CompressedDeepScanLineBlock {
            y_coordinate,
            decompressed_sample_data_size,
            compressed_pixel_offset_table,
            compressed_sample_data,
        })
    }
}


impl CompressedDeepTileBlock {

    /// Without validation, write this instance to the byte stream.
    pub fn write<W: Write>(&self, write: &mut W) -> UnitResult {
        debug_assert_ne!(self.compressed_sample_data.len(), 0, "empty blocks should not be put in the file bug");

        self.coordinates.write(write)?;
        u64::write(self.compressed_pixel_offset_table.len() as u64, write)?;
        u64::write(self.compressed_sample_data.len() as u64, write)?; // TODO just guessed
        u64::write(self.decompressed_sample_data_size as u64, write)?;
        i8::write_slice(write, &self.compressed_pixel_offset_table)?;
        u8::write_slice(write, &self.compressed_sample_data)?;
        Ok(())
    }

    /// Read the value without validating.
    pub fn read(read: &mut impl Read, hard_max_block_byte_size: usize) -> Result<Self> {
        let coordinates = TileCoordinates::read(read)?;
        let compressed_pixel_offset_table_size = u64_to_usize(u64::read(read)?);
        let compressed_sample_data_size = u64_to_usize(u64::read(read)?); // TODO u64 just guessed
        let decompressed_sample_data_size = u64_to_usize(u64::read(read)?);

        let compressed_pixel_offset_table = i8::read_vec(
            read, compressed_pixel_offset_table_size,
            6 * u16::MAX as usize, Some(hard_max_block_byte_size),
            "deep tile block table size"
        )?;

        let compressed_sample_data = u8::read_vec(
            read, compressed_sample_data_size,
            6 * u16::MAX as usize, Some(hard_max_block_byte_size),
            "deep tile block sample count"
        )?;

        Ok(CompressedDeepTileBlock {
            coordinates,
            decompressed_sample_data_size,
            compressed_pixel_offset_table,
            compressed_sample_data,
        })
    }
}

use crate::error::{UnitResult, Result, Error, u64_to_usize, usize_to_i32, i32_to_usize};
use crate::math::Vec2;

/// Validation of chunks is done while reading and writing the actual data. (For example in exr::full_image)
impl Chunk {

    /// Without validation, write this instance to the byte stream.
    pub fn write(&self, write: &mut impl Write, header_count: usize) -> UnitResult {
        debug_assert!(self.layer_index < header_count, "layer index bug"); // validation is done in full_image or simple_image

        if header_count != 1 {  usize_to_i32(self.layer_index).write(write)?; }
        else { assert_eq!(self.layer_index, 0, "invalid header index for single layer file"); }

        match self.compressed_block {
            CompressedBlock::ScanLine     (ref value) => value.write(write),
            CompressedBlock::Tile         (ref value) => value.write(write),
            CompressedBlock::DeepScanLine (ref value) => value.write(write),
            CompressedBlock::DeepTile     (ref value) => value.write(write),
        }
    }

    /// Read the value without validating.
    pub fn read(read: &mut impl Read, meta_data: &MetaData) -> Result<Self> {
        let layer_number = i32_to_usize(
            if meta_data.requirements.is_multilayer() { i32::read(read)? } // documentation says u64, but is i32
            else { 0_i32 }, // reference the first header for single-layer images
            "chunk data part number"
        )?;

        if layer_number >= meta_data.headers.len() {
            return Err(Error::invalid("chunk data part number"));
        }

        let header = &meta_data.headers[layer_number];
        let max_block_byte_size = header.max_block_byte_size();

        let chunk = Chunk {
            layer_index: layer_number,
            compressed_block: match header.blocks {
                // flat data
                BlockDescription::ScanLines if !header.deep => CompressedBlock::ScanLine(CompressedScanLineBlock::read(read, max_block_byte_size)?),
                BlockDescription::Tiles(_) if !header.deep     => CompressedBlock::Tile(CompressedTileBlock::read(read, max_block_byte_size)?),

                // deep data
                BlockDescription::ScanLines   => CompressedBlock::DeepScanLine(CompressedDeepScanLineBlock::read(read, max_block_byte_size)?),
                BlockDescription::Tiles(_)    => CompressedBlock::DeepTile(CompressedDeepTileBlock::read(read, max_block_byte_size)?),
            },
        };

        Ok(chunk)
    }
}