//! 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, } /// 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, } /// 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, /// Index of the Mip/Rip level. pub level_index: Vec2, } /// 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, /// 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, } /// 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, /// 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, } use crate::io::*; impl TileCoordinates { /// Without validation, write this instance to the byte stream. pub fn 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 { 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, max: Vec2) -> Result { 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, data_window: IntegerBounds) -> Result { 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(&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 { 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(&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 { 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(&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 { 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(&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 { 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 { 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) } }