use std::{
convert::TryFrom,
fmt::Debug,
io::{Read, Seek},
};
use crate::{
block::{
chunk::{Chunk, TileCoordinates},
BlockIndex, UncompressedBlock,
},
error::{u64_to_usize, Error, Result, UnitResult},
io::{PeekRead, Tracking},
meta::{header::Header, MetaData, OffsetTables},
};
#[derive(Debug)]
pub struct Reader<R> {
meta_data: MetaData,
remaining_reader: PeekRead<Tracking<R>>, }
impl<R: Read + Seek> Reader<R> {
pub fn read_from_buffered(read: R, pedantic: bool) -> Result<Self> {
let mut remaining_reader = PeekRead::new(Tracking::new(read));
let meta_data =
MetaData::read_validated_from_buffered_peekable(&mut remaining_reader, pedantic)?;
Ok(Self {
meta_data,
remaining_reader,
})
}
pub const fn meta_data(&self) -> &MetaData {
&self.meta_data
}
pub fn headers(&self) -> &[Header] {
&self.meta_data.headers
}
pub fn into_meta_data(self) -> MetaData {
self.meta_data
}
pub fn all_chunks(mut self, pedantic: bool) -> Result<AllChunksReader<R>> {
let total_chunk_count = {
if pedantic {
let offset_tables = MetaData::read_offset_tables(
&mut self.remaining_reader,
&self.meta_data.headers,
)?;
validate_offset_tables(
self.meta_data.headers.as_slice(),
&offset_tables,
self.remaining_reader.byte_position(),
)?;
offset_tables.iter().map(std::vec::Vec::len).sum()
} else {
MetaData::skip_offset_tables(&mut self.remaining_reader, &self.meta_data.headers)?
}
};
Ok(AllChunksReader {
meta_data: self.meta_data,
remaining_chunks: 0..total_chunk_count,
remaining_bytes: self.remaining_reader,
pedantic,
})
}
pub fn filter_chunks(
mut self,
pedantic: bool,
mut filter: impl FnMut(&MetaData, TileCoordinates, BlockIndex) -> bool,
) -> Result<FilteredChunksReader<R>> {
let offset_tables =
MetaData::read_offset_tables(&mut self.remaining_reader, &self.meta_data.headers)?;
if pedantic {
validate_offset_tables(
self.meta_data.headers.as_slice(),
&offset_tables,
self.remaining_reader.byte_position(),
)?;
}
let mut filtered_offsets =
Vec::with_capacity((self.meta_data.headers.len() * 32).min(2 * 2048));
for (header_index, header) in self.meta_data.headers.iter().enumerate() {
for (block_index, tile) in header.blocks_increasing_y_order().enumerate() {
let data_indices = header.get_absolute_block_pixel_coordinates(tile.location)?;
let block = BlockIndex {
layer: header_index,
level: tile.location.level_index,
pixel_position: data_indices.position.to_usize("data indices start")?,
pixel_size: data_indices.size,
};
if filter(&self.meta_data, tile.location, block) {
filtered_offsets.push(offset_tables[header_index][block_index]);
}
}
}
filtered_offsets.sort_unstable();
if pedantic {
if filtered_offsets.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(Error::invalid("chunk offset table"));
}
}
Ok(FilteredChunksReader {
meta_data: self.meta_data,
expected_filtered_chunk_count: filtered_offsets.len(),
remaining_filtered_chunk_indices: filtered_offsets.into_iter(),
remaining_bytes: self.remaining_reader,
})
}
}
fn validate_offset_tables(
headers: &[Header],
offset_tables: &OffsetTables,
chunks_start_byte: usize,
) -> UnitResult {
let max_pixel_bytes: usize = headers
.iter() .map(|header| header.max_pixel_file_bytes())
.sum();
let end_byte = chunks_start_byte + max_pixel_bytes;
let is_invalid =
offset_tables.iter().flatten().map(|&u64| u64_to_usize(u64, "chunk start")).any(
|maybe_chunk_start| match maybe_chunk_start {
Ok(chunk_start) => chunk_start < chunks_start_byte || chunk_start > end_byte,
Err(_) => true,
},
);
if is_invalid {
Err(Error::invalid("offset table"))
} else {
Ok(())
}
}
#[derive(Debug)]
pub struct FilteredChunksReader<R> {
meta_data: MetaData,
expected_filtered_chunk_count: usize,
remaining_filtered_chunk_indices: std::vec::IntoIter<u64>,
remaining_bytes: PeekRead<Tracking<R>>,
}
#[derive(Debug)]
pub struct AllChunksReader<R> {
meta_data: MetaData,
remaining_chunks: std::ops::Range<usize>,
remaining_bytes: PeekRead<Tracking<R>>,
pedantic: bool,
}
#[derive(Debug)]
pub struct OnProgressChunksReader<R, F> {
chunks_reader: R,
decoded_chunks: usize,
callback: F,
}
pub trait ChunksReader: Sized + Iterator<Item = Result<Chunk>> + ExactSizeIterator {
fn meta_data(&self) -> &MetaData;
fn headers(&self) -> &[Header] {
&self.meta_data().headers
}
fn expected_chunk_count(&self) -> usize;
fn read_next_chunk(&mut self) -> Option<Result<Chunk>> {
self.next()
}
fn on_progress<F>(self, on_progress: F) -> OnProgressChunksReader<Self, F>
where
F: FnMut(f64),
{
OnProgressChunksReader {
chunks_reader: self,
callback: on_progress,
decoded_chunks: 0,
}
}
#[cfg(feature = "rayon")]
fn decompress_parallel(
self,
pedantic: bool,
mut insert_block: impl FnMut(&MetaData, UncompressedBlock) -> UnitResult,
) -> UnitResult {
let mut decompressor = match self.parallel_decompressor(pedantic) {
Err(old_self) => return old_self.decompress_sequential(pedantic, insert_block),
Ok(decompressor) => decompressor,
};
while let Some(block) = decompressor.next() {
insert_block(decompressor.meta_data(), block?)?;
}
debug_assert_eq!(
decompressor.len(),
0,
"compressed blocks left after decompressing all blocks"
);
Ok(())
}
#[cfg(feature = "rayon")]
fn parallel_decompressor(
self,
pedantic: bool,
) -> std::result::Result<ParallelBlockDecompressor<Self>, Self> {
ParallelBlockDecompressor::new(self, pedantic)
}
fn decompress_sequential(
self,
pedantic: bool,
mut insert_block: impl FnMut(&MetaData, UncompressedBlock) -> UnitResult,
) -> UnitResult {
let mut decompressor = self.sequential_decompressor(pedantic);
while let Some(block) = decompressor.next() {
insert_block(decompressor.meta_data(), block?)?;
}
debug_assert_eq!(
decompressor.len(),
0,
"compressed blocks left after decompressing all blocks"
);
Ok(())
}
fn sequential_decompressor(self, pedantic: bool) -> SequentialBlockDecompressor<Self> {
SequentialBlockDecompressor {
remaining_chunks_reader: self,
pedantic,
}
}
}
impl<R, F> ChunksReader for OnProgressChunksReader<R, F>
where
R: ChunksReader,
F: FnMut(f64),
{
fn meta_data(&self) -> &MetaData {
self.chunks_reader.meta_data()
}
fn expected_chunk_count(&self) -> usize {
self.chunks_reader.expected_chunk_count()
}
}
impl<R, F> ExactSizeIterator for OnProgressChunksReader<R, F>
where
R: ChunksReader,
F: FnMut(f64),
{
}
impl<R, F> Iterator for OnProgressChunksReader<R, F>
where
R: ChunksReader,
F: FnMut(f64),
{
type Item = Result<Chunk>;
fn next(&mut self) -> Option<Self::Item> {
self.chunks_reader
.next()
.map(|item| {
{
let total_chunks = self.expected_chunk_count() as f64;
let callback = &mut self.callback;
callback(self.decoded_chunks as f64 / total_chunks);
}
self.decoded_chunks += 1;
item
})
.or_else(|| {
debug_assert_eq!(
self.decoded_chunks,
self.expected_chunk_count(),
"chunks reader finished but not all chunks are decompressed"
);
let callback = &mut self.callback;
callback(1.0);
None
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.chunks_reader.size_hint()
}
}
impl<R: Read + Seek> ChunksReader for AllChunksReader<R> {
fn meta_data(&self) -> &MetaData {
&self.meta_data
}
fn expected_chunk_count(&self) -> usize {
self.remaining_chunks.end
}
}
impl<R: Read + Seek> ExactSizeIterator for AllChunksReader<R> {}
impl<R: Read + Seek> Iterator for AllChunksReader<R> {
type Item = Result<Chunk>;
fn next(&mut self) -> Option<Self::Item> {
let next_chunk = self
.remaining_chunks
.next()
.map(|_| Chunk::read(&mut self.remaining_bytes, &self.meta_data));
if self.pedantic && next_chunk.is_none() && self.remaining_bytes.peek_u8().is_ok() {
return Some(Err(Error::invalid("end of file expected")));
}
next_chunk
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining_chunks.len(), Some(self.remaining_chunks.len()))
}
}
impl<R: Read + Seek> ChunksReader for FilteredChunksReader<R> {
fn meta_data(&self) -> &MetaData {
&self.meta_data
}
fn expected_chunk_count(&self) -> usize {
self.expected_filtered_chunk_count
}
}
impl<R: Read + Seek> ExactSizeIterator for FilteredChunksReader<R> {}
impl<R: Read + Seek> Iterator for FilteredChunksReader<R> {
type Item = Result<Chunk>;
fn next(&mut self) -> Option<Self::Item> {
self.remaining_filtered_chunk_indices.next().map(|next_chunk_location| {
self.remaining_bytes.skip_to(
usize::try_from(next_chunk_location)?,
)?;
let meta_data = &self.meta_data;
Chunk::read(&mut self.remaining_bytes, meta_data)
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
(
self.remaining_filtered_chunk_indices.len(),
Some(self.remaining_filtered_chunk_indices.len()),
)
}
}
#[derive(Debug)]
pub struct SequentialBlockDecompressor<R: ChunksReader> {
remaining_chunks_reader: R,
pedantic: bool,
}
impl<R: ChunksReader> SequentialBlockDecompressor<R> {
pub fn meta_data(&self) -> &MetaData {
self.remaining_chunks_reader.meta_data()
}
pub fn decompress_next_block(&mut self) -> Option<Result<UncompressedBlock>> {
self.remaining_chunks_reader.read_next_chunk().map(|compressed_chunk| {
UncompressedBlock::decompress_chunk(
compressed_chunk?,
self.remaining_chunks_reader.meta_data(),
self.pedantic,
)
})
}
}
#[cfg(feature = "rayon")]
#[derive(Debug)]
pub struct ParallelBlockDecompressor<R: ChunksReader> {
remaining_chunks: R,
sender: std::sync::mpsc::Sender<Result<UncompressedBlock>>,
receiver: std::sync::mpsc::Receiver<Result<UncompressedBlock>>,
currently_decompressing_count: usize,
max_threads: usize,
shared_meta_data_ref: std::sync::Arc<MetaData>,
pedantic: bool,
pool: rayon_core::ThreadPool,
}
#[cfg(feature = "rayon")]
impl<R: ChunksReader> ParallelBlockDecompressor<R> {
pub fn new(chunks: R, pedantic: bool) -> std::result::Result<Self, R> {
Self::new_with_thread_pool(chunks, pedantic, || {
rayon_core::ThreadPoolBuilder::new()
.thread_name(|index| format!("OpenEXR Block Decompressor Thread #{index}"))
.build()
})
}
pub fn new_with_thread_pool<CreatePool>(
chunks: R,
pedantic: bool,
try_create_thread_pool: CreatePool,
) -> std::result::Result<Self, R>
where
CreatePool:
FnOnce()
-> std::result::Result<rayon_core::ThreadPool, rayon_core::ThreadPoolBuildError>,
{
use crate::compression::Compression;
let is_entirely_uncompressed = chunks
.meta_data()
.headers
.iter()
.all(|head| head.compression == Compression::Uncompressed);
if is_entirely_uncompressed {
return Err(chunks);
}
let pool = match try_create_thread_pool() {
Ok(pool) => pool,
Err(_) => return Err(chunks),
};
let max_threads = pool.current_num_threads().max(1).min(chunks.len()) + 2;
let (send, recv) = std::sync::mpsc::channel();
Ok(Self {
shared_meta_data_ref: std::sync::Arc::new(chunks.meta_data().clone()),
currently_decompressing_count: 0,
remaining_chunks: chunks,
sender: send,
receiver: recv,
pedantic,
max_threads,
pool,
})
}
pub fn decompress_next_block(&mut self) -> Option<Result<UncompressedBlock>> {
while self.currently_decompressing_count < self.max_threads {
let block = self.remaining_chunks.next();
if let Some(block) = block {
let block = match block {
Ok(block) => block,
Err(error) => return Some(Err(error)),
};
let sender = self.sender.clone();
let meta = self.shared_meta_data_ref.clone();
let pedantic = self.pedantic;
self.currently_decompressing_count += 1;
self.pool.spawn(move || {
let decompressed_or_err =
UncompressedBlock::decompress_chunk(block, &meta, pedantic);
let _ = sender.send(decompressed_or_err);
});
} else {
break;
}
}
if self.currently_decompressing_count > 0 {
let next = self
.receiver
.recv()
.expect("all decompressing senders hung up but more messages were expected");
self.currently_decompressing_count -= 1;
Some(next)
} else {
debug_assert!(
self.receiver.try_recv().is_err(),
"uncompressed chunks left in channel after decompressing all chunks"
); debug_assert_eq!(
self.len(),
0,
"compressed chunks left after decompressing all chunks"
);
None
}
}
pub fn meta_data(&self) -> &MetaData {
self.remaining_chunks.meta_data()
}
}
impl<R: ChunksReader> ExactSizeIterator for SequentialBlockDecompressor<R> {}
impl<R: ChunksReader> Iterator for SequentialBlockDecompressor<R> {
type Item = Result<UncompressedBlock>;
fn next(&mut self) -> Option<Self::Item> {
self.decompress_next_block()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.remaining_chunks_reader.size_hint()
}
}
#[cfg(feature = "rayon")]
impl<R: ChunksReader> ExactSizeIterator for ParallelBlockDecompressor<R> {}
#[cfg(feature = "rayon")]
impl<R: ChunksReader> Iterator for ParallelBlockDecompressor<R> {
type Item = Result<UncompressedBlock>;
fn next(&mut self) -> Option<Self::Item> {
self.decompress_next_block()
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.remaining_chunks.len() + self.currently_decompressing_count;
(remaining, Some(remaining))
}
}