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)]
54pub struct UncompressedBlock {
55
56 pub index: BlockIndex,
58
59 pub data: ByteVec,
66}
67
68pub 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
77pub 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
92pub 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 #[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 #[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(); 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 tile_index: index.pixel_position / header.max_block_pixel_size(), 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 y_coordinate: usize_to_i32(index.pixel_position.y(), "pixel index")? + header.own_attributes.layer_position.y(), }),
195
196 BlockDescription::Tiles(_) => CompressedBlock::Tile(CompressedTileBlock {
197 compressed_pixels_le,
198 coordinates: tile_coordinates,
199 }),
200 }
201 })
202 }
203
204 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 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 { value: &mut block_bytes[byte_range],
243 location: line_index,
244 });
245 }
246
247 block_bytes
248 }
249
250 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}