| 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 | |