1pub 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#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug)]
34pub struct BlockIndex {
35
36 pub layer: usize,
38
39 pub pixel_position: Vec2<usize>,
41
42 pub pixel_size: Vec2<usize>,
45
46 pub level: Vec2<usize>,
48}
49
50#[derive(Clone, Eq, PartialEq, Debug)]
52pub struct UncompressedBlock {
53
54 pub index: BlockIndex,
56
57 pub data: ByteVec,
63}
64
65pub 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
74pub 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, compatibility_checks, write_chunks)
84}
85
86
87
88
89pub fn enumerate_ordered_header_block_indices(headers: &[Header]) -> impl '_ + Iterator<Item=(usize, BlockIndex)> {
96 headers.iter().enumerate().flat_map(|(layer_index, header)|{
97 header.enumerate_ordered_blocks().map(move |(index_in_header, tile)|{
98 let data_indices = header.get_absolute_block_pixel_coordinates(tile.location).expect("tile coordinate bug");
99
100 let block = BlockIndex {
101 layer: layer_index,
102 level: tile.location.level_index,
103 pixel_position: data_indices.position.to_usize("data indices start").expect("data index bug"),
104 pixel_size: data_indices.size,
105 };
106
107 (index_in_header, block)
108 })
109 })
110}
111
112
113impl UncompressedBlock {
114
115 #[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 #[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(); 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 tile_index: index.pixel_position / header.max_block_pixel_size(), 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 y_coordinate: usize_to_i32(index.pixel_position.y()) + header.own_attributes.layer_position.y(), }),
192
193 BlockDescription::Tiles(_) => CompressedBlock::Tile(CompressedTileBlock {
194 compressed_pixels: compressed_data,
195 coordinates: tile_coordinates,
196 }),
197 }
198 })
199 }
200
201 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 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 { value: &mut block_bytes[byte_range],
240 location: line_index,
241 });
242 }
243
244 block_bytes
245 }
246
247 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}