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