aboutsummaryrefslogtreecommitdiff
path: root/vendor/exr/src/block/chunk.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/exr/src/block/chunk.rs')
-rw-r--r--vendor/exr/src/block/chunk.rs379
1 files changed, 379 insertions, 0 deletions
diff --git a/vendor/exr/src/block/chunk.rs b/vendor/exr/src/block/chunk.rs
new file mode 100644
index 0000000..ff138f8
--- /dev/null
+++ b/vendor/exr/src/block/chunk.rs
@@ -0,0 +1,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)
+ }
+}
+