Coverage Report

Created: 2025-12-05 07:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/exr-1.74.0/src/block/mod.rs
Line
Count
Source
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
11
pub mod writer;
12
pub mod reader;
13
14
pub mod lines;
15
pub mod samples;
16
pub mod chunk;
17
18
19
use std::io::{Read, Seek, Write};
20
use crate::error::{Result, UnitResult, Error, usize_to_i32};
21
use crate::meta::{Headers, MetaData, BlockDescription};
22
use crate::math::Vec2;
23
use crate::compression::ByteVec;
24
use crate::block::chunk::{CompressedBlock, CompressedTileBlock, CompressedScanLineBlock, Chunk, TileCoordinates};
25
use crate::meta::header::Header;
26
use crate::block::lines::{LineIndex, LineRef, LineSlice, LineRefMut};
27
use 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)]
34
pub 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)]
54
pub 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.
73
6.16k
pub fn read<R: Read + Seek>(buffered_read: R, pedantic: bool) -> Result<self::reader::Reader<R>> {
74
6.16k
    self::reader::Reader::read_from_buffered(buffered_read, pedantic)
75
6.16k
}
exr::block::read::<std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
73
6.07k
pub fn read<R: Read + Seek>(buffered_read: R, pedantic: bool) -> Result<self::reader::Reader<R>> {
74
6.07k
    self::reader::Reader::read_from_buffered(buffered_read, pedantic)
75
6.07k
}
Unexecuted instantiation: exr::block::read::<_>
exr::block::read::<std::io::cursor::Cursor<alloc::vec::Vec<u8>>>
Line
Count
Source
73
88
pub fn read<R: Read + Seek>(buffered_read: R, pedantic: bool) -> Result<self::reader::Reader<R>> {
74
88
    self::reader::Reader::read_from_buffered(buffered_read, pedantic)
75
88
}
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.
82
88
pub fn write<W: Write + Seek>(
83
88
    buffered_write: W, headers: Headers, compatibility_checks: bool,
84
88
    write_chunks: impl FnOnce(MetaData, &mut self::writer::ChunkWriter<W>) -> UnitResult
85
88
) -> UnitResult {
86
88
    self::writer::write_chunks_with(buffered_write, headers, compatibility_checks, write_chunks)
87
88
}
Unexecuted instantiation: exr::block::write::<_, _>
Unexecuted instantiation: exr::block::write::<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>, <exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}>
Unexecuted instantiation: exr::block::write::<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>, <exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<&mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut &mut std::io::cursor::Cursor<alloc::vec::Vec<u8>>>::{closure#0}>
Unexecuted instantiation: exr::block::write::<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>, <exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}>
exr::block::write::<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>, <exr::image::write::WriteImageWithOptions<exr::image::Layer<exr::image::SpecificChannels<image::codecs::openexr::write_buffer<std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#1}, (exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription, exr::meta::attribute::ChannelDescription)>>, fn(f64)>>::to_buffered<&mut std::io::cursor::Cursor<&mut alloc::vec::Vec<u8>>>::{closure#0}>
Line
Count
Source
82
88
pub fn write<W: Write + Seek>(
83
88
    buffered_write: W, headers: Headers, compatibility_checks: bool,
84
88
    write_chunks: impl FnOnce(MetaData, &mut self::writer::ChunkWriter<W>) -> UnitResult
85
88
) -> UnitResult {
86
88
    self::writer::write_chunks_with(buffered_write, headers, compatibility_checks, write_chunks)
87
88
}
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.
98
88
pub fn enumerate_ordered_header_block_indices(headers: &[Header]) -> impl '_ + Iterator<Item=(usize, BlockIndex)> {
99
88
    headers.iter().enumerate().flat_map(|(layer_index, header)|{
100
753k
        header.enumerate_ordered_blocks().map(move |(index_in_header, tile)|{
101
753k
            let data_indices = header.get_absolute_block_pixel_coordinates(tile.location).expect("tile coordinate bug");
102
103
753k
            let block = BlockIndex {
104
753k
                layer: layer_index,
105
753k
                level: tile.location.level_index,
106
753k
                pixel_position: data_indices.position.to_usize("data indices start").expect("data index bug"),
107
753k
                pixel_size: data_indices.size,
108
753k
            };
109
110
753k
            (index_in_header, block)
111
753k
        })
Unexecuted instantiation: exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}
Unexecuted instantiation: exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}
exr::block::enumerate_ordered_header_block_indices::{closure#0}::{closure#0}
Line
Count
Source
100
753k
        header.enumerate_ordered_blocks().map(move |(index_in_header, tile)|{
101
753k
            let data_indices = header.get_absolute_block_pixel_coordinates(tile.location).expect("tile coordinate bug");
102
103
753k
            let block = BlockIndex {
104
753k
                layer: layer_index,
105
753k
                level: tile.location.level_index,
106
753k
                pixel_position: data_indices.position.to_usize("data indices start").expect("data index bug"),
107
753k
                pixel_size: data_indices.size,
108
753k
            };
109
110
753k
            (index_in_header, block)
111
753k
        })
112
88
    })
Unexecuted instantiation: exr::block::enumerate_ordered_header_block_indices::{closure#0}
Unexecuted instantiation: exr::block::enumerate_ordered_header_block_indices::{closure#0}
exr::block::enumerate_ordered_header_block_indices::{closure#0}
Line
Count
Source
99
88
    headers.iter().enumerate().flat_map(|(layer_index, header)|{
100
88
        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
88
    })
113
88
}
114
115
116
impl 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
761k
    pub fn decompress_chunk(chunk: Chunk, meta_data: &MetaData, pedantic: bool) -> Result<Self> {
123
761k
        let header: &Header = meta_data.headers.get(chunk.layer_index)
124
761k
            .ok_or(Error::invalid("chunk layer index"))?;
125
126
761k
        let tile_data_indices = header.get_block_data_indices(&chunk.compressed_block)?;
127
761k
        let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_data_indices)?;
128
129
760k
        absolute_indices.validate(Some(header.layer_size))?;
130
131
760k
        match chunk.compressed_block {
132
753k
            CompressedBlock::Tile(CompressedTileBlock { compressed_pixels_le, .. }) |
133
6.41k
            CompressedBlock::ScanLine(CompressedScanLineBlock { compressed_pixels_le, .. }) => {
134
                Ok(UncompressedBlock {
135
760k
                    data: header.compression.decompress_image_section_from_le(header, compressed_pixels_le, absolute_indices, pedantic)?,
136
                    index: BlockIndex {
137
759k
                        layer: chunk.layer_index,
138
759k
                        pixel_position: absolute_indices.position.to_usize("data indices start")?,
139
759k
                        level: tile_data_indices.level_index,
140
759k
                        pixel_size: absolute_indices.size,
141
                    }
142
                })
143
            },
144
145
0
            _ => return Err(Error::unsupported("deep data not supported yet"))
146
        }
147
761k
    }
<exr::block::UncompressedBlock>::decompress_chunk
Line
Count
Source
122
8.09k
    pub fn decompress_chunk(chunk: Chunk, meta_data: &MetaData, pedantic: bool) -> Result<Self> {
123
8.09k
        let header: &Header = meta_data.headers.get(chunk.layer_index)
124
8.09k
            .ok_or(Error::invalid("chunk layer index"))?;
125
126
8.09k
        let tile_data_indices = header.get_block_data_indices(&chunk.compressed_block)?;
127
7.95k
        let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_data_indices)?;
128
129
6.50k
        absolute_indices.validate(Some(header.layer_size))?;
130
131
6.50k
        match chunk.compressed_block {
132
94
            CompressedBlock::Tile(CompressedTileBlock { compressed_pixels_le, .. }) |
133
6.41k
            CompressedBlock::ScanLine(CompressedScanLineBlock { compressed_pixels_le, .. }) => {
134
                Ok(UncompressedBlock {
135
6.50k
                    data: header.compression.decompress_image_section_from_le(header, compressed_pixels_le, absolute_indices, pedantic)?,
136
                    index: BlockIndex {
137
6.16k
                        layer: chunk.layer_index,
138
6.16k
                        pixel_position: absolute_indices.position.to_usize("data indices start")?,
139
6.16k
                        level: tile_data_indices.level_index,
140
6.16k
                        pixel_size: absolute_indices.size,
141
                    }
142
                })
143
            },
144
145
0
            _ => return Err(Error::unsupported("deep data not supported yet"))
146
        }
147
8.09k
    }
Unexecuted instantiation: <exr::block::UncompressedBlock>::decompress_chunk
<exr::block::UncompressedBlock>::decompress_chunk
Line
Count
Source
122
753k
    pub fn decompress_chunk(chunk: Chunk, meta_data: &MetaData, pedantic: bool) -> Result<Self> {
123
753k
        let header: &Header = meta_data.headers.get(chunk.layer_index)
124
753k
            .ok_or(Error::invalid("chunk layer index"))?;
125
126
753k
        let tile_data_indices = header.get_block_data_indices(&chunk.compressed_block)?;
127
753k
        let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_data_indices)?;
128
129
753k
        absolute_indices.validate(Some(header.layer_size))?;
130
131
753k
        match chunk.compressed_block {
132
753k
            CompressedBlock::Tile(CompressedTileBlock { compressed_pixels_le, .. }) |
133
0
            CompressedBlock::ScanLine(CompressedScanLineBlock { compressed_pixels_le, .. }) => {
134
                Ok(UncompressedBlock {
135
753k
                    data: header.compression.decompress_image_section_from_le(header, compressed_pixels_le, absolute_indices, pedantic)?,
136
                    index: BlockIndex {
137
753k
                        layer: chunk.layer_index,
138
753k
                        pixel_position: absolute_indices.position.to_usize("data indices start")?,
139
753k
                        level: tile_data_indices.level_index,
140
753k
                        pixel_size: absolute_indices.size,
141
                    }
142
                })
143
            },
144
145
0
            _ => return Err(Error::unsupported("deep data not supported yet"))
146
        }
147
753k
    }
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
753k
    pub fn compress_to_chunk(self, headers: &[Header]) -> Result<Chunk> {
154
753k
        let UncompressedBlock { data, index } = self;
155
156
753k
        let header: &Header = headers.get(index.layer)
157
753k
            .expect("block layer index bug");
158
159
753k
        let expected_byte_size = header.channels.bytes_per_pixel * self.index.pixel_size.area(); // TODO sampling??
160
753k
        if expected_byte_size != data.len() {
161
0
            panic!("get_line byte size should be {} but was {}", expected_byte_size, data.len());
162
753k
        }
163
164
753k
        let tile_coordinates = TileCoordinates {
165
753k
            // FIXME this calculation should not be made here but elsewhere instead (in meta::header?)
166
753k
            tile_index: index.pixel_position / header.max_block_pixel_size(), // TODO sampling??
167
753k
            level_index: index.level,
168
753k
        };
169
170
753k
        let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_coordinates)?;
171
753k
        absolute_indices.validate(Some(header.layer_size))?;
172
173
753k
        if !header.compression.may_loose_data() { debug_assert_eq!(
174
0
            &header.compression.decompress_image_section_from_le(
175
0
                header,
176
0
                header.compression.compress_image_section_to_le(header, data.clone(), absolute_indices)?,
177
0
                absolute_indices,
178
                true
179
0
            ).unwrap(),
180
0
            &data,
181
0
            "compression method not round trippin'"
182
0
        ); }
183
184
753k
        let compressed_pixels_le = header.compression.compress_image_section_to_le(header, data, absolute_indices)?;
185
186
        Ok(Chunk {
187
753k
            layer_index: index.layer,
188
753k
            compressed_block : match header.blocks {
189
                BlockDescription::ScanLines => CompressedBlock::ScanLine(CompressedScanLineBlock {
190
0
                    compressed_pixels_le,
191
192
                    // FIXME this calculation should not be made here but elsewhere instead (in meta::header?)
193
0
                    y_coordinate: usize_to_i32(index.pixel_position.y(), "pixel index")? + header.own_attributes.layer_position.y(), // TODO sampling??
194
                }),
195
196
753k
                BlockDescription::Tiles(_) => CompressedBlock::Tile(CompressedTileBlock {
197
753k
                    compressed_pixels_le,
198
753k
                    coordinates: tile_coordinates,
199
753k
                }),
200
            }
201
        })
202
753k
    }
Unexecuted instantiation: <exr::block::UncompressedBlock>::compress_to_chunk
Unexecuted instantiation: <exr::block::UncompressedBlock>::compress_to_chunk
<exr::block::UncompressedBlock>::compress_to_chunk
Line
Count
Source
153
753k
    pub fn compress_to_chunk(self, headers: &[Header]) -> Result<Chunk> {
154
753k
        let UncompressedBlock { data, index } = self;
155
156
753k
        let header: &Header = headers.get(index.layer)
157
753k
            .expect("block layer index bug");
158
159
753k
        let expected_byte_size = header.channels.bytes_per_pixel * self.index.pixel_size.area(); // TODO sampling??
160
753k
        if expected_byte_size != data.len() {
161
0
            panic!("get_line byte size should be {} but was {}", expected_byte_size, data.len());
162
753k
        }
163
164
753k
        let tile_coordinates = TileCoordinates {
165
753k
            // FIXME this calculation should not be made here but elsewhere instead (in meta::header?)
166
753k
            tile_index: index.pixel_position / header.max_block_pixel_size(), // TODO sampling??
167
753k
            level_index: index.level,
168
753k
        };
169
170
753k
        let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_coordinates)?;
171
753k
        absolute_indices.validate(Some(header.layer_size))?;
172
173
753k
        if !header.compression.may_loose_data() { debug_assert_eq!(
174
0
            &header.compression.decompress_image_section_from_le(
175
0
                header,
176
0
                header.compression.compress_image_section_to_le(header, data.clone(), absolute_indices)?,
177
0
                absolute_indices,
178
                true
179
0
            ).unwrap(),
180
0
            &data,
181
0
            "compression method not round trippin'"
182
0
        ); }
183
184
753k
        let compressed_pixels_le = header.compression.compress_image_section_to_le(header, data, absolute_indices)?;
185
186
        Ok(Chunk {
187
753k
            layer_index: index.layer,
188
753k
            compressed_block : match header.blocks {
189
                BlockDescription::ScanLines => CompressedBlock::ScanLine(CompressedScanLineBlock {
190
0
                    compressed_pixels_le,
191
192
                    // FIXME this calculation should not be made here but elsewhere instead (in meta::header?)
193
0
                    y_coordinate: usize_to_i32(index.pixel_position.y(), "pixel index")? + header.own_attributes.layer_position.y(), // TODO sampling??
194
                }),
195
196
753k
                BlockDescription::Tiles(_) => CompressedBlock::Tile(CompressedTileBlock {
197
753k
                    compressed_pixels_le,
198
753k
                    coordinates: tile_coordinates,
199
753k
                }),
200
            }
201
        })
202
753k
    }
203
204
    /// Iterate all the lines in this block.
205
    /// Each line contains the all samples for one of the channels.
206
0
    pub fn lines(&self, channels: &ChannelList) -> impl Iterator<Item=LineRef<'_>> {
207
0
        LineIndex::lines_in_block(self.index, channels)
208
0
            .map(move |(bytes, line)| LineSlice { location: line, value: &self.data[bytes] })
209
0
    }
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
0
    pub fn collect_block_data_from_lines(
233
0
        channels: &ChannelList, block_index: BlockIndex,
234
0
        mut extract_line: impl FnMut(LineRefMut<'_>)
235
0
    ) -> Vec<u8>
236
    {
237
0
        let byte_count = block_index.pixel_size.area() * channels.bytes_per_pixel;
238
0
        let mut block_bytes = vec![0_u8; byte_count];
239
240
0
        for (byte_range, line_index) in LineIndex::lines_in_block(block_index, channels) {
241
0
            extract_line(LineRefMut { // TODO subsampling
242
0
                value: &mut block_bytes[byte_range],
243
0
                location: line_index,
244
0
            });
245
0
        }
246
247
0
        block_bytes
248
0
    }
249
250
    /// Create an uncompressed block by requesting one line of samples after another.
251
0
    pub fn from_lines(
252
0
        channels: &ChannelList, block_index: BlockIndex,
253
0
        extract_line: impl FnMut(LineRefMut<'_>)
254
0
    ) -> Self {
255
0
        Self {
256
0
            index: block_index,
257
0
            data: Self::collect_block_data_from_lines(channels, block_index, extract_line)
258
0
        }
259
0
    }
260
}