pub mod reader;
pub mod writer;
pub mod chunk;
pub mod lines;
pub mod samples;
use std::io::{Read, Seek, Write};
use crate::{
block::{
chunk::{
Chunk, CompressedBlock, CompressedScanLineBlock, CompressedTileBlock, TileCoordinates,
},
lines::{LineIndex, LineRef, LineRefMut, LineSlice},
},
compression::ByteVec,
error::{usize_to_i32, Error, Result, UnitResult},
math::Vec2,
meta::{attribute::ChannelList, header::Header, BlockDescription, Headers, MetaData},
};
#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug)]
pub struct BlockIndex {
pub layer: usize,
pub pixel_position: Vec2<usize>,
pub pixel_size: Vec2<usize>,
pub level: Vec2<usize>,
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct UncompressedBlock {
pub index: BlockIndex,
pub data: ByteVec,
}
pub fn read<R: Read + Seek>(buffered_read: R, pedantic: bool) -> Result<self::reader::Reader<R>> {
self::reader::Reader::read_from_buffered(buffered_read, pedantic)
}
pub fn write<W: Write + Seek>(
buffered_write: W,
headers: Headers,
compatibility_checks: bool,
write_chunks: impl FnOnce(MetaData, &mut self::writer::ChunkWriter<W>) -> UnitResult,
) -> UnitResult {
self::writer::write_chunks_with(buffered_write, headers, compatibility_checks, write_chunks)
}
pub fn enumerate_ordered_header_block_indices(
headers: &[Header],
) -> impl '_ + Iterator<Item = (usize, BlockIndex)> {
headers.iter().enumerate().flat_map(|(layer_index, header)| {
header.enumerate_ordered_blocks().map(move |(index_in_header, tile)| {
let data_indices = header
.get_absolute_block_pixel_coordinates(tile.location)
.expect("tile coordinate bug");
let block = BlockIndex {
layer: layer_index,
level: tile.location.level_index,
pixel_position: data_indices
.position
.to_usize("data indices start")
.expect("data index bug"),
pixel_size: data_indices.size,
};
(index_in_header, block)
})
})
}
impl UncompressedBlock {
#[inline]
#[must_use]
pub fn decompress_chunk(chunk: Chunk, meta_data: &MetaData, pedantic: bool) -> Result<Self> {
let header: &Header = meta_data
.headers
.get(chunk.layer_index)
.ok_or_else(|| Error::invalid("chunk layer index"))?;
let tile_data_indices = header.get_block_data_indices(&chunk.compressed_block)?;
let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_data_indices)?;
absolute_indices.validate(Some(header.layer_size))?;
match chunk.compressed_block {
CompressedBlock::Tile(CompressedTileBlock {
compressed_pixels_le,
..
})
| CompressedBlock::ScanLine(CompressedScanLineBlock {
compressed_pixels_le,
..
}) => Ok(Self {
data: header.compression.decompress_image_section_from_le(
header,
compressed_pixels_le,
absolute_indices,
pedantic,
)?,
index: BlockIndex {
layer: chunk.layer_index,
pixel_position: absolute_indices.position.to_usize("data indices start")?,
level: tile_data_indices.level_index,
pixel_size: absolute_indices.size,
},
}),
_ => Err(Error::unsupported("deep data not supported yet")),
}
}
#[inline]
#[must_use]
pub fn compress_to_chunk(self, headers: &[Header]) -> Result<Chunk> {
let Self {
data,
index,
} = self;
let header: &Header = headers.get(index.layer).expect("block layer index bug");
let expected_byte_size = header.channels.bytes_per_pixel * self.index.pixel_size.area(); if expected_byte_size != data.len() {
return Err(Error::invalid(format!(
"decompressed block byte size mismatch: expected {} bytes but got {} bytes",
expected_byte_size,
data.len()
)));
}
let tile_coordinates = TileCoordinates {
tile_index: index.pixel_position / header.max_block_pixel_size(), level_index: index.level,
};
let absolute_indices = header.get_absolute_block_pixel_coordinates(tile_coordinates)?;
absolute_indices.validate(Some(header.layer_size))?;
if !header.compression.may_loose_data() {
debug_assert_eq!(
&header
.compression
.decompress_image_section_from_le(
header,
header.compression.compress_image_section_to_le(
header,
data.clone(),
absolute_indices
)?,
absolute_indices,
true
)
.unwrap(),
&data,
"compression method not round trippin'"
);
}
let compressed_pixels_le =
header.compression.compress_image_section_to_le(header, data, absolute_indices)?;
Ok(Chunk {
layer_index: index.layer,
compressed_block: match header.blocks {
BlockDescription::ScanLines => CompressedBlock::ScanLine(CompressedScanLineBlock {
compressed_pixels_le,
y_coordinate: usize_to_i32(index.pixel_position.y(), "pixel index")?
+ header.own_attributes.layer_position.y(), }),
BlockDescription::Tiles(_) => CompressedBlock::Tile(CompressedTileBlock {
compressed_pixels_le,
coordinates: tile_coordinates,
}),
},
})
}
pub fn lines(&self, channels: &ChannelList) -> impl Iterator<Item = LineRef<'_>> {
LineIndex::lines_in_block(self.index, channels).map(move |(bytes, line)| LineSlice {
location: line,
value: &self.data[bytes],
})
}
pub fn collect_block_data_from_lines(
channels: &ChannelList,
block_index: BlockIndex,
mut extract_line: impl FnMut(LineRefMut<'_>),
) -> Vec<u8> {
let byte_count = block_index.pixel_size.area() * channels.bytes_per_pixel;
let mut block_bytes = vec![0_u8; byte_count];
for (byte_range, line_index) in LineIndex::lines_in_block(block_index, channels) {
extract_line(LineRefMut {
value: &mut block_bytes[byte_range],
location: line_index,
});
}
block_bytes
}
pub fn from_lines(
channels: &ChannelList,
block_index: BlockIndex,
extract_line: impl FnMut(LineRefMut<'_>),
) -> Self {
Self {
index: block_index,
data: Self::collect_block_data_from_lines(channels, block_index, extract_line),
}
}
}