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