exr/block/
mod.rs

1//! This is the low-level interface for the raw blocks of an image.
2//! See `exr::image` module for a high-level interface.
3//!
4//! Handle compressed and uncompressed pixel byte blocks. Includes compression and decompression,
5//! and reading a complete image into blocks.
6//!
7//! Start with the `block::read(...)`
8//! and `block::write(...)` functions.
9
10
11pub mod writer;
12pub mod reader;
13
14pub mod lines;
15pub mod samples;
16pub mod chunk;
17
18
19use std::io::{Read, Seek, Write};
20use crate::error::{Result, UnitResult, Error, usize_to_i32};
21use crate::meta::{Headers, MetaData, BlockDescription};
22use crate::math::Vec2;
23use crate::compression::ByteVec;
24use crate::block::chunk::{CompressedBlock, CompressedTileBlock, CompressedScanLineBlock, Chunk, TileCoordinates};
25use crate::meta::header::Header;
26use crate::block::lines::{LineIndex, LineRef, LineSlice, LineRefMut};
27use crate::meta::attribute::ChannelList;
28
29
30/// Specifies where a block of pixel data should be placed in the actual image.
31/// This is a globally unique identifier which
32/// includes the layer, level index, and pixel location.
33#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug)]
34pub struct BlockIndex {
35
36    /// Index of the layer.
37    pub layer: usize,
38
39    /// Index of the top left pixel from the block within the data window.
40    pub pixel_position: Vec2<usize>,
41
42    /// Number of pixels in this block, extending to the right and downwards.
43    /// Stays the same across all resolution levels.
44    pub pixel_size: Vec2<usize>,
45
46    /// Index of the mip or rip level in the image.
47    pub level: Vec2<usize>,
48}
49
50/// Contains a block of pixel data and where that data should be placed in the actual image.
51/// The bytes must be encoded in native-endian format.
52/// The conversion to little-endian format happens when converting to chunks (potentially in parallel).
53#[derive(Clone, Eq, PartialEq, Debug)]
54pub struct UncompressedBlock {
55
56    /// Location of the data inside the image.
57    pub index: BlockIndex,
58
59    /// Uncompressed pixel values of the whole block.
60    /// One or more scan lines may be stored together as a scan line block.
61    /// This byte vector contains all pixel rows, one after another.
62    /// For each line in the tile, for each channel, the row values are contiguous.
63    /// Stores all samples of the first channel, then all samples of the second channel, and so on.
64    /// This data is in native-endian format.
65    pub data: ByteVec,
66}
67
68/// Immediately reads the meta data from the file.
69/// Then, returns a reader that can be used to read all pixel blocks.
70/// From the reader, you can pull each compressed chunk from the file.
71/// Alternatively, you can create a decompressor, and pull the uncompressed data from it.
72/// The reader is assumed to be buffered.
73pub fn read<R: Read + Seek>(buffered_read: R, pedantic: bool) -> Result<self::reader::Reader<R>> {
74    self::reader::Reader::read_from_buffered(buffered_read, pedantic)
75}
76
77/// Immediately writes the meta data to the file.
78/// Then, calls a closure with a writer that can be used to write all pixel blocks.
79/// In the closure, you can push compressed chunks directly into the writer.
80/// Alternatively, you can create a compressor, wrapping the writer, and push the uncompressed data to it.
81/// The writer is assumed to be buffered.
82pub fn write<W: Write + Seek>(
83    buffered_write: W, headers: Headers, compatibility_checks: bool,
84    write_chunks: impl FnOnce(MetaData, &mut self::writer::ChunkWriter<W>) -> UnitResult
85) -> UnitResult {
86    self::writer::write_chunks_with(buffered_write, headers, compatibility_checks, write_chunks)
87}
88
89
90
91
92/// This iterator tells you the block indices of all blocks that must be in the image.
93/// The order of the blocks depends on the `LineOrder` attribute
94/// (unspecified line order is treated the same as increasing line order).
95/// The blocks written to the file must be exactly in this order,
96/// except for when the `LineOrder` is unspecified.
97/// The index represents the block index, in increasing line order, within the header.
98pub fn enumerate_ordered_header_block_indices(headers: &[Header]) -> impl '_ + Iterator<Item=(usize, BlockIndex)> {
99    headers.iter().enumerate().flat_map(|(layer_index, header)|{
100        header.enumerate_ordered_blocks().map(move |(index_in_header, tile)|{
101            let data_indices = header.get_absolute_block_pixel_coordinates(tile.location).expect("tile coordinate bug");
102
103            let block = BlockIndex {
104                layer: layer_index,
105                level: tile.location.level_index,
106                pixel_position: data_indices.position.to_usize("data indices start").expect("data index bug"),
107                pixel_size: data_indices.size,
108            };
109
110            (index_in_header, block)
111        })
112    })
113}
114
115
116impl UncompressedBlock {
117
118    /// Decompress the possibly compressed chunk and returns an `UncompressedBlock`.
119    // for uncompressed data, the ByteVec in the chunk is moved all the way
120    #[inline]
121    #[must_use]
122    pub fn decompress_chunk(chunk: Chunk, meta_data: &MetaData, pedantic: bool) -> Result<Self> {
123        let header: &Header = meta_data.headers.get(chunk.layer_index)
124            .ok_or(Error::invalid("chunk layer index"))?;
125
126        let tile_data_indices = header.get_block_data_indices(&chunk.compressed_block)?;
127        let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_data_indices)?;
128
129        absolute_indices.validate(Some(header.layer_size))?;
130
131        match chunk.compressed_block {
132            CompressedBlock::Tile(CompressedTileBlock { compressed_pixels_le, .. }) |
133            CompressedBlock::ScanLine(CompressedScanLineBlock { compressed_pixels_le, .. }) => {
134                Ok(UncompressedBlock {
135                    data: header.compression.decompress_image_section_from_le(header, compressed_pixels_le, absolute_indices, pedantic)?,
136                    index: BlockIndex {
137                        layer: chunk.layer_index,
138                        pixel_position: absolute_indices.position.to_usize("data indices start")?,
139                        level: tile_data_indices.level_index,
140                        pixel_size: absolute_indices.size,
141                    }
142                })
143            },
144
145            _ => return Err(Error::unsupported("deep data not supported yet"))
146        }
147    }
148
149    /// Consume this block by compressing it, returning a `Chunk`.
150    // for uncompressed data, the ByteVec in the chunk is moved all the way
151    #[inline]
152    #[must_use]
153    pub fn compress_to_chunk(self, headers: &[Header]) -> Result<Chunk> {
154        let UncompressedBlock { data, index } = self;
155
156        let header: &Header = headers.get(index.layer)
157            .expect("block layer index bug");
158
159        let expected_byte_size = header.channels.bytes_per_pixel * self.index.pixel_size.area(); // TODO sampling??
160        if expected_byte_size != data.len() {
161            panic!("get_line byte size should be {} but was {}", expected_byte_size, data.len());
162        }
163
164        let tile_coordinates = TileCoordinates {
165            // FIXME this calculation should not be made here but elsewhere instead (in meta::header?)
166            tile_index: index.pixel_position / header.max_block_pixel_size(), // TODO sampling??
167            level_index: index.level,
168        };
169
170        let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_coordinates)?;
171        absolute_indices.validate(Some(header.layer_size))?;
172
173        if !header.compression.may_loose_data() { debug_assert_eq!(
174            &header.compression.decompress_image_section_from_le(
175                header,
176                header.compression.compress_image_section_to_le(header, data.clone(), absolute_indices)?,
177                absolute_indices,
178                true
179            ).unwrap(),
180            &data,
181            "compression method not round trippin'"
182        ); }
183
184        let compressed_pixels_le = header.compression.compress_image_section_to_le(header, data, absolute_indices)?;
185
186        Ok(Chunk {
187            layer_index: index.layer,
188            compressed_block : match header.blocks {
189                BlockDescription::ScanLines => CompressedBlock::ScanLine(CompressedScanLineBlock {
190                    compressed_pixels_le,
191
192                    // FIXME this calculation should not be made here but elsewhere instead (in meta::header?)
193                    y_coordinate: usize_to_i32(index.pixel_position.y(), "pixel index")? + header.own_attributes.layer_position.y(), // TODO sampling??
194                }),
195
196                BlockDescription::Tiles(_) => CompressedBlock::Tile(CompressedTileBlock {
197                    compressed_pixels_le,
198                    coordinates: tile_coordinates,
199                }),
200            }
201        })
202    }
203
204    /// Iterate all the lines in this block.
205    /// Each line contains the all samples for one of the channels.
206    pub fn lines(&self, channels: &ChannelList) -> impl Iterator<Item=LineRef<'_>> {
207        LineIndex::lines_in_block(self.index, channels)
208            .map(move |(bytes, line)| LineSlice { location: line, value: &self.data[bytes] })
209    }
210
211    /* TODO pub fn lines_mut<'s>(&'s mut self, header: &Header) -> impl 's + Iterator<Item=LineRefMut<'s>> {
212        LineIndex::lines_in_block(self.index, &header.channels)
213            .map(move |(bytes, line)| LineSlice { location: line, value: &mut self.data[bytes] })
214    }*/
215
216    /*// TODO make iterator
217    /// Call a closure for each line of samples in this uncompressed block.
218    pub fn for_lines(
219        &self, header: &Header,
220        mut accept_line: impl FnMut(LineRef<'_>) -> UnitResult
221    ) -> UnitResult {
222        for (bytes, line) in LineIndex::lines_in_block(self.index, &header.channels) {
223            let line_ref = LineSlice { location: line, value: &self.data[bytes] };
224            accept_line(line_ref)?;
225        }
226
227        Ok(())
228    }*/
229
230    // TODO from iterator??
231    /// Create an uncompressed block byte vector by requesting one line of samples after another.
232    pub fn collect_block_data_from_lines(
233        channels: &ChannelList, block_index: BlockIndex,
234        mut extract_line: impl FnMut(LineRefMut<'_>)
235    ) -> Vec<u8>
236    {
237        let byte_count = block_index.pixel_size.area() * channels.bytes_per_pixel;
238        let mut block_bytes = vec![0_u8; byte_count];
239
240        for (byte_range, line_index) in LineIndex::lines_in_block(block_index, channels) {
241            extract_line(LineRefMut { // TODO subsampling
242                value: &mut block_bytes[byte_range],
243                location: line_index,
244            });
245        }
246
247        block_bytes
248    }
249
250    /// Create an uncompressed block by requesting one line of samples after another.
251    pub fn from_lines(
252        channels: &ChannelList, block_index: BlockIndex,
253        extract_line: impl FnMut(LineRefMut<'_>)
254    ) -> Self {
255        Self {
256            index: block_index,
257            data: Self::collect_block_data_from_lines(channels, block_index, extract_line)
258        }
259    }
260}