1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
//! This is the low-level interface for the raw blocks of an image.
//! See `exr::image` module for a high-level interface.
//!
//! Handle compressed and uncompressed pixel byte blocks. Includes compression and decompression,
//! and reading a complete image into blocks.
//!
//! Start with the `block::read(...)`
//! and `block::write(...)` functions.
pub mod writer;
pub mod reader;
pub mod lines;
pub mod samples;
pub mod chunk;
use std::io::{Read, Seek, Write};
use crate::error::{Result, UnitResult, Error, usize_to_i32};
use crate::meta::{Headers, MetaData, BlockDescription};
use crate::math::Vec2;
use crate::compression::ByteVec;
use crate::block::chunk::{CompressedBlock, CompressedTileBlock, CompressedScanLineBlock, Chunk, TileCoordinates};
use crate::meta::header::Header;
use crate::block::lines::{LineIndex, LineRef, LineSlice, LineRefMut};
use crate::meta::attribute::ChannelList;
/// Specifies where a block of pixel data should be placed in the actual image.
/// This is a globally unique identifier which
/// includes the layer, level index, and pixel location.
#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug)]
pub struct BlockIndex {
/// Index of the layer.
pub layer: usize,
/// Index of the top left pixel from the block within the data window.
pub pixel_position: Vec2<usize>,
/// Number of pixels in this block, extending to the right and downwards.
/// Stays the same across all resolution levels.
pub pixel_size: Vec2<usize>,
/// Index of the mip or rip level in the image.
pub level: Vec2<usize>,
}
/// Contains a block of pixel data and where that data should be placed in the actual image.
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct UncompressedBlock {
/// Location of the data inside the image.
pub index: BlockIndex,
/// Uncompressed pixel values of the whole block.
/// One or more scan lines may be stored together as a scan line block.
/// This byte vector contains all pixel rows, one after another.
/// For each line in the tile, for each channel, the row values are contiguous.
/// Stores all samples of the first channel, then all samples of the second channel, and so on.
pub data: ByteVec,
}
/// Immediately reads the meta data from the file.
/// Then, returns a reader that can be used to read all pixel blocks.
/// From the reader, you can pull each compressed chunk from the file.
/// Alternatively, you can create a decompressor, and pull the uncompressed data from it.
/// The reader is assumed to be buffered.
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)
}
/// Immediately writes the meta data to the file.
/// Then, calls a closure with a writer that can be used to write all pixel blocks.
/// In the closure, you can push compressed chunks directly into the writer.
/// Alternatively, you can create a compressor, wrapping the writer, and push the uncompressed data to it.
/// The writer is assumed to be buffered.
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)
}
/// This iterator tells you the block indices of all blocks that must be in the image.
/// The order of the blocks depends on the `LineOrder` attribute
/// (unspecified line order is treated the same as increasing line order).
/// The blocks written to the file must be exactly in this order,
/// except for when the `LineOrder` is unspecified.
/// The index represents the block index, in increasing line order, within the header.
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 {
/// Decompress the possibly compressed chunk and returns an `UncompressedBlock`.
// for uncompressed data, the ByteVec in the chunk is moved all the way
#[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(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, .. }) |
CompressedBlock::ScanLine(CompressedScanLineBlock { compressed_pixels, .. }) => {
Ok(UncompressedBlock {
data: header.compression.decompress_image_section(header, compressed_pixels, 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,
}
})
},
_ => return Err(Error::unsupported("deep data not supported yet"))
}
}
/// Consume this block by compressing it, returning a `Chunk`.
// for uncompressed data, the ByteVec in the chunk is moved all the way
#[inline]
#[must_use]
pub fn compress_to_chunk(self, headers: &[Header]) -> Result<Chunk> {
let UncompressedBlock { 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(); // TODO sampling??
if expected_byte_size != data.len() {
panic!("get_line byte size should be {} but was {}", expected_byte_size, data.len());
}
let tile_coordinates = TileCoordinates {
// FIXME this calculation should not be made here but elsewhere instead (in meta::header?)
tile_index: index.pixel_position / header.max_block_pixel_size(), // TODO sampling??
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(
header,
header.compression.compress_image_section(header, data.clone(), absolute_indices)?,
absolute_indices,
true
).unwrap(),
&data,
"compression method not round trippin'"
); }
let compressed_data = header.compression.compress_image_section(header, data, absolute_indices)?;
Ok(Chunk {
layer_index: index.layer,
compressed_block : match header.blocks {
BlockDescription::ScanLines => CompressedBlock::ScanLine(CompressedScanLineBlock {
compressed_pixels: compressed_data,
// FIXME this calculation should not be made here but elsewhere instead (in meta::header?)
y_coordinate: usize_to_i32(index.pixel_position.y()) + header.own_attributes.layer_position.y(), // TODO sampling??
}),
BlockDescription::Tiles(_) => CompressedBlock::Tile(CompressedTileBlock {
compressed_pixels: compressed_data,
coordinates: tile_coordinates,
}),
}
})
}
/// Iterate all the lines in this block.
/// Each line contains the all samples for one of the channels.
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] })
}
/* TODO pub fn lines_mut<'s>(&'s mut self, header: &Header) -> impl 's + Iterator<Item=LineRefMut<'s>> {
LineIndex::lines_in_block(self.index, &header.channels)
.map(move |(bytes, line)| LineSlice { location: line, value: &mut self.data[bytes] })
}*/
/*// TODO make iterator
/// Call a closure for each line of samples in this uncompressed block.
pub fn for_lines(
&self, header: &Header,
mut accept_line: impl FnMut(LineRef<'_>) -> UnitResult
) -> UnitResult {
for (bytes, line) in LineIndex::lines_in_block(self.index, &header.channels) {
let line_ref = LineSlice { location: line, value: &self.data[bytes] };
accept_line(line_ref)?;
}
Ok(())
}*/
// TODO from iterator??
/// Create an uncompressed block byte vector by requesting one line of samples after another.
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 { // TODO subsampling
value: &mut block_bytes[byte_range],
location: line_index,
});
}
block_bytes
}
/// Create an uncompressed block by requesting one line of samples after another.
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)
}
}
}