aboutsummaryrefslogtreecommitdiff
path: root/vendor/exr/src/block/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/exr/src/block/mod.rs')
-rw-r--r--vendor/exr/src/block/mod.rs257
1 files changed, 257 insertions, 0 deletions
diff --git a/vendor/exr/src/block/mod.rs b/vendor/exr/src/block/mod.rs
new file mode 100644
index 0000000..1d20aa8
--- /dev/null
+++ b/vendor/exr/src/block/mod.rs
@@ -0,0 +1,257 @@
+//! This is the low-level interface for the raw blocks of an image.
+//! See `exr::image` module for a high-level interface.
+//!
+//! Handle compressed and uncompressed pixel byte blocks. Includes compression and decompression,
+//! and reading a complete image into blocks.
+//!
+//! Start with the `block::read(...)`
+//! and `block::write(...)` functions.
+
+
+pub mod writer;
+pub mod reader;
+
+pub mod lines;
+pub mod samples;
+pub mod chunk;
+
+
+use std::io::{Read, Seek, Write};
+use crate::error::{Result, UnitResult, Error, usize_to_i32};
+use crate::meta::{Headers, MetaData, BlockDescription};
+use crate::math::Vec2;
+use crate::compression::ByteVec;
+use crate::block::chunk::{CompressedBlock, CompressedTileBlock, CompressedScanLineBlock, Chunk, TileCoordinates};
+use crate::meta::header::Header;
+use crate::block::lines::{LineIndex, LineRef, LineSlice, LineRefMut};
+use crate::meta::attribute::ChannelList;
+
+
+/// Specifies where a block of pixel data should be placed in the actual image.
+/// This is a globally unique identifier which
+/// includes the layer, level index, and pixel location.
+#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug)]
+pub struct BlockIndex {
+
+ /// Index of the layer.
+ pub layer: usize,
+
+ /// Index of the top left pixel from the block within the data window.
+ pub pixel_position: Vec2<usize>,
+
+ /// Number of pixels in this block, extending to the right and downwards.
+ /// Stays the same across all resolution levels.
+ pub pixel_size: Vec2<usize>,
+
+ /// Index of the mip or rip level in the image.
+ pub level: Vec2<usize>,
+}
+
+/// Contains a block of pixel data and where that data should be placed in the actual image.
+#[derive(Clone, Eq, PartialEq, Debug)]
+pub struct UncompressedBlock {
+
+ /// Location of the data inside the image.
+ pub index: BlockIndex,
+
+ /// Uncompressed pixel values of the whole block.
+ /// One or more scan lines may be stored together as a scan line block.
+ /// This byte vector contains all pixel rows, one after another.
+ /// For each line in the tile, for each channel, the row values are contiguous.
+ /// Stores all samples of the first channel, then all samples of the second channel, and so on.
+ pub data: ByteVec,
+}
+
+/// Immediately reads the meta data from the file.
+/// Then, returns a reader that can be used to read all pixel blocks.
+/// From the reader, you can pull each compressed chunk from the file.
+/// Alternatively, you can create a decompressor, and pull the uncompressed data from it.
+/// The reader is assumed to be buffered.
+pub fn read<R: Read + Seek>(buffered_read: R, pedantic: bool) -> Result<self::reader::Reader<R>> {
+ self::reader::Reader::read_from_buffered(buffered_read, pedantic)
+}
+
+/// Immediately writes the meta data to the file.
+/// Then, calls a closure with a writer that can be used to write all pixel blocks.
+/// In the closure, you can push compressed chunks directly into the writer.
+/// Alternatively, you can create a compressor, wrapping the writer, and push the uncompressed data to it.
+/// The writer is assumed to be buffered.
+pub fn write<W: Write + Seek>(
+ buffered_write: W, headers: Headers, compatibility_checks: bool,
+ write_chunks: impl FnOnce(MetaData, &mut self::writer::ChunkWriter<W>) -> UnitResult
+) -> UnitResult {
+ self::writer::write_chunks_with(buffered_write, headers, compatibility_checks, write_chunks)
+}
+
+
+
+
+/// This iterator tells you the block indices of all blocks that must be in the image.
+/// The order of the blocks depends on the `LineOrder` attribute
+/// (unspecified line order is treated the same as increasing line order).
+/// The blocks written to the file must be exactly in this order,
+/// except for when the `LineOrder` is unspecified.
+/// The index represents the block index, in increasing line order, within the header.
+pub fn enumerate_ordered_header_block_indices(headers: &[Header]) -> impl '_ + Iterator<Item=(usize, BlockIndex)> {
+ headers.iter().enumerate().flat_map(|(layer_index, header)|{
+ header.enumerate_ordered_blocks().map(move |(index_in_header, tile)|{
+ let data_indices = header.get_absolute_block_pixel_coordinates(tile.location).expect("tile coordinate bug");
+
+ let block = BlockIndex {
+ layer: layer_index,
+ level: tile.location.level_index,
+ pixel_position: data_indices.position.to_usize("data indices start").expect("data index bug"),
+ pixel_size: data_indices.size,
+ };
+
+ (index_in_header, block)
+ })
+ })
+}
+
+
+impl UncompressedBlock {
+
+ /// Decompress the possibly compressed chunk and returns an `UncompressedBlock`.
+ // for uncompressed data, the ByteVec in the chunk is moved all the way
+ #[inline]
+ #[must_use]
+ pub fn decompress_chunk(chunk: Chunk, meta_data: &MetaData, pedantic: bool) -> Result<Self> {
+ let header: &Header = meta_data.headers.get(chunk.layer_index)
+ .ok_or(Error::invalid("chunk layer index"))?;
+
+ let tile_data_indices = header.get_block_data_indices(&chunk.compressed_block)?;
+ let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_data_indices)?;
+
+ absolute_indices.validate(Some(header.layer_size))?;
+
+ match chunk.compressed_block {
+ CompressedBlock::Tile(CompressedTileBlock { compressed_pixels, .. }) |
+ CompressedBlock::ScanLine(CompressedScanLineBlock { compressed_pixels, .. }) => {
+ Ok(UncompressedBlock {
+ data: header.compression.decompress_image_section(header, compressed_pixels, absolute_indices, pedantic)?,
+ index: BlockIndex {
+ layer: chunk.layer_index,
+ pixel_position: absolute_indices.position.to_usize("data indices start")?,
+ level: tile_data_indices.level_index,
+ pixel_size: absolute_indices.size,
+ }
+ })
+ },
+
+ _ => return Err(Error::unsupported("deep data not supported yet"))
+ }
+ }
+
+ /// Consume this block by compressing it, returning a `Chunk`.
+ // for uncompressed data, the ByteVec in the chunk is moved all the way
+ #[inline]
+ #[must_use]
+ pub fn compress_to_chunk(self, headers: &[Header]) -> Result<Chunk> {
+ let UncompressedBlock { data, index } = self;
+
+ let header: &Header = headers.get(index.layer)
+ .expect("block layer index bug");
+
+ let expected_byte_size = header.channels.bytes_per_pixel * self.index.pixel_size.area(); // TODO sampling??
+ if expected_byte_size != data.len() {
+ panic!("get_line byte size should be {} but was {}", expected_byte_size, data.len());
+ }
+
+ let tile_coordinates = TileCoordinates {
+ // FIXME this calculation should not be made here but elsewhere instead (in meta::header?)
+ tile_index: index.pixel_position / header.max_block_pixel_size(), // TODO sampling??
+ level_index: index.level,
+ };
+
+ let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_coordinates)?;
+ absolute_indices.validate(Some(header.layer_size))?;
+
+ if !header.compression.may_loose_data() { debug_assert_eq!(
+ &header.compression.decompress_image_section(
+ header,
+ header.compression.compress_image_section(header, data.clone(), absolute_indices)?,
+ absolute_indices,
+ true
+ ).unwrap(),
+ &data,
+ "compression method not round trippin'"
+ ); }
+
+ let compressed_data = header.compression.compress_image_section(header, data, absolute_indices)?;
+
+ Ok(Chunk {
+ layer_index: index.layer,
+ compressed_block : match header.blocks {
+ BlockDescription::ScanLines => CompressedBlock::ScanLine(CompressedScanLineBlock {
+ compressed_pixels: compressed_data,
+
+ // FIXME this calculation should not be made here but elsewhere instead (in meta::header?)
+ y_coordinate: usize_to_i32(index.pixel_position.y()) + header.own_attributes.layer_position.y(), // TODO sampling??
+ }),
+
+ BlockDescription::Tiles(_) => CompressedBlock::Tile(CompressedTileBlock {
+ compressed_pixels: compressed_data,
+ coordinates: tile_coordinates,
+ }),
+ }
+ })
+ }
+
+ /// Iterate all the lines in this block.
+ /// Each line contains the all samples for one of the channels.
+ pub fn lines(&self, channels: &ChannelList) -> impl Iterator<Item=LineRef<'_>> {
+ LineIndex::lines_in_block(self.index, channels)
+ .map(move |(bytes, line)| LineSlice { location: line, value: &self.data[bytes] })
+ }
+
+ /* TODO pub fn lines_mut<'s>(&'s mut self, header: &Header) -> impl 's + Iterator<Item=LineRefMut<'s>> {
+ LineIndex::lines_in_block(self.index, &header.channels)
+ .map(move |(bytes, line)| LineSlice { location: line, value: &mut self.data[bytes] })
+ }*/
+
+ /*// TODO make iterator
+ /// Call a closure for each line of samples in this uncompressed block.
+ pub fn for_lines(
+ &self, header: &Header,
+ mut accept_line: impl FnMut(LineRef<'_>) -> UnitResult
+ ) -> UnitResult {
+ for (bytes, line) in LineIndex::lines_in_block(self.index, &header.channels) {
+ let line_ref = LineSlice { location: line, value: &self.data[bytes] };
+ accept_line(line_ref)?;
+ }
+
+ Ok(())
+ }*/
+
+ // TODO from iterator??
+ /// Create an uncompressed block byte vector by requesting one line of samples after another.
+ pub fn collect_block_data_from_lines(
+ channels: &ChannelList, block_index: BlockIndex,
+ mut extract_line: impl FnMut(LineRefMut<'_>)
+ ) -> Vec<u8>
+ {
+ let byte_count = block_index.pixel_size.area() * channels.bytes_per_pixel;
+ let mut block_bytes = vec![0_u8; byte_count];
+
+ for (byte_range, line_index) in LineIndex::lines_in_block(block_index, channels) {
+ extract_line(LineRefMut { // TODO subsampling
+ value: &mut block_bytes[byte_range],
+ location: line_index,
+ });
+ }
+
+ block_bytes
+ }
+
+ /// Create an uncompressed block by requesting one line of samples after another.
+ pub fn from_lines(
+ channels: &ChannelList, block_index: BlockIndex,
+ extract_line: impl FnMut(LineRefMut<'_>)
+ ) -> Self {
+ Self {
+ index: block_index,
+ data: Self::collect_block_data_from_lines(channels, block_index, extract_line)
+ }
+ }
+} \ No newline at end of file