incremental tile decoding, refactoring

This commit is contained in:
Tomáš Král committed 2021-04-12 15:09:39 +02:00
1 parent ebd0e266f7
commit 00e0eb9006
5 files changed
+362 -226

No files matched your search

+286 -224
View File
@@ -3,7 +3,7 @@ use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::io::{self, Read, Seek}; use std::io::{self, Read, Seek};
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
use self::ifd::Directory; use self::ifd::Directory;
use crate::tags::{ use crate::tags::{
@@ -221,10 +221,8 @@ struct StripDecodeState {
} }
#[derive(Debug)] #[derive(Debug)]
/// Computed values useful for tile decoding
struct TileAttributes { struct TileAttributes {
// current_tile: usize, // TODO(Tomáš Král): incremental tile decoding
tile_offsets: Vec<u64>,
tile_bytes: Vec<u64>,
tile_width: usize, tile_width: usize,
tile_length: usize, tile_length: usize,
tiles_down: usize, tiles_down: usize,
@@ -234,18 +232,57 @@ struct TileAttributes {
/// length of padding for bottommost tile in pixels /// length of padding for bottommost tile in pixels
padding_down: usize, padding_down: usize,
/// A simple buffer right paddding is read into /// A simple buffer right paddding is read into
padding_buffer: Vec<u8>,
tile_samples: usize, tile_samples: usize,
/// Sample count of one row of the image
line_samples: usize,
/// Sample count of one row of one tile /// Sample count of one row of one tile
row_samples: usize, row_samples: usize,
/// Sample count of one row of tiles /// Sample count of one row of tiles
tile_strip_samples: usize, tile_strip_samples: usize,
} }
#[derive(Debug, Copy, Clone)] impl TileAttributes {
enum ChunkType { /// Returns the tile offset in the result buffer, counted in samples
fn get_offset(&self, tile: usize) -> usize {
let row = tile / self.tiles_across;
let column = tile % self.tiles_across;
(row * self.tile_strip_samples) + (column * self.row_samples)
}
fn get_padding(&self, tile: usize) -> (usize, usize) {
let row = tile / self.tiles_across;
let column = tile % self.tiles_across;
let padding_right = if column == self.tiles_across - 1 {
self.padding_right
} else {
0
};
let padding_down = if row == self.tiles_down - 1 {
self.padding_down
} else {
0
};
(padding_right, padding_down)
}
}
#[derive(Debug)]
/// Stateful variables for tile decoding
struct TileDecodeState {
current_tile: usize,
tile_offsets: Vec<u64>,
tile_bytes: Vec<u64>,
/// Buffer used for skipping horizontal padding
padding_buffer: Vec<u8>,
/// Pixel width of one row of the decoding result (tile / whole image)
result_width: usize,
}
#[derive(Debug, Copy, Clone, PartialEq)]
/// Chunk type of the internal representation
pub enum ChunkType {
Strip, Strip,
Tile, Tile,
} }
@@ -322,6 +359,7 @@ where
compression_method: CompressionMethod, compression_method: CompressionMethod,
chunk_type: ChunkType, chunk_type: ChunkType,
strip_decoder: Option<StripDecodeState>, strip_decoder: Option<StripDecodeState>,
tile_decoder: Option<TileDecodeState>,
tile_attributes: Option<TileAttributes>, tile_attributes: Option<TileAttributes>,
} }
@@ -404,6 +442,7 @@ fn rev_hpredict(
img_width: usize, img_width: usize,
color_type: ColorType, color_type: ColorType,
) -> TiffResult<()> { ) -> TiffResult<()> {
// TODO: use bits_per_sample.len() after implementing type 3 predictor
let samples = match color_type { let samples = match color_type {
ColorType::Gray(8) | ColorType::Gray(16) | ColorType::Gray(32) | ColorType::Gray(64) => 1, ColorType::Gray(8) | ColorType::Gray(16) | ColorType::Gray(32) | ColorType::Gray(64) => 1,
ColorType::RGB(8) | ColorType::RGB(16) | ColorType::RGB(32) | ColorType::RGB(64) => 3, ColorType::RGB(8) | ColorType::RGB(16) | ColorType::RGB(32) | ColorType::RGB(64) => 3,
@@ -421,6 +460,7 @@ fn rev_hpredict(
)) ))
} }
}; };
match image { match image {
DecodingBuffer::U8(buf) => { DecodingBuffer::U8(buf) => {
rev_hpredict_nsamp(buf, size, img_width, samples)?; rev_hpredict_nsamp(buf, size, img_width, samples)?;
@@ -535,6 +575,7 @@ impl<R: Read + Seek> Decoder<R> {
compression_method: CompressionMethod::None, compression_method: CompressionMethod::None,
chunk_type: ChunkType::Strip, chunk_type: ChunkType::Strip,
strip_decoder: None, strip_decoder: None,
tile_decoder: None,
tile_attributes: None, tile_attributes: None,
} }
.init() .init()
@@ -698,12 +739,11 @@ impl<R: Read + Seek> Decoder<R> {
) { ) {
(Ok(_), Err(_), Err(_)) => ChunkType::Strip, (Ok(_), Err(_), Err(_)) => ChunkType::Strip,
(Err(_), Ok(_), Ok(_)) => ChunkType::Tile, (Err(_), Ok(_), Ok(_)) => ChunkType::Tile,
// TODO(Tomáš Král) - Should it be a hard error if both are used ? // TODO: The spec says not to use both strip-oriented fields and tile-oriented fields.
// The spec says not to use both strip-oriented fields and tile-oriented fields. // We can relax this later if it becomes a problem
// ...and other things like tiles not being x * 16 wide ?
_ => return Err(TiffError::FormatError(TiffFormatError::Format( _ => return Err(TiffError::FormatError(TiffFormatError::Format(
String::from( String::from(
"Either no strips or tiles were found or both were used in the same file", "Neither strips nor tiles were found or both were used in the same file",
), ),
))), ))),
}; };
@@ -1008,49 +1048,14 @@ impl<R: Read + Seek> Decoder<R> {
) -> TiffResult<usize> { ) -> TiffResult<usize> {
let color_type = self.colortype()?; let color_type = self.colortype()?;
self.goto_offset_u64(offset)?; self.goto_offset_u64(offset)?;
let (bytes, mut reader): (usize, Box<dyn EndianReader>) = match self.compression_method {
CompressionMethod::None => {
let order = self.reader.byte_order;
(
usize::try_from(length)?,
Box::new(SmartReader::wrap(&mut self.reader, order)),
)
}
CompressionMethod::LZW => {
let (bytes, reader) = LZWReader::new(
&mut self.reader,
usize::try_from(length)?,
strip_sample_count * buffer.byte_len(),
)?;
(bytes, Box::new(reader))
}
CompressionMethod::PackBits => {
let order = self.reader.byte_order;
let (bytes, reader) =
PackBitsReader::new(&mut self.reader, order, usize::try_from(length)?)?;
(bytes, Box::new(reader))
}
CompressionMethod::OldDeflate => {
let (bytes, reader) = DeflateReader::new(&mut self.reader, strip_sample_count)?;
(bytes, Box::new(reader))
}
method => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedCompressionMethod(method),
))
}
};
// FIXME: this might be suboptimal. We might default remaining bits to ´0`, which some let (bytes, mut reader) = Self::create_reader(
// other decoders might do. &mut self.reader,
if bytes / buffer.byte_len() > strip_sample_count { self.compression_method,
return Err(TiffError::FormatError( length,
TiffFormatError::UnexpectedCompressedData { strip_sample_count,
actual_bytes: bytes, buffer.byte_len(),
required_bytes: strip_sample_count * buffer.byte_len(), )?;
},
));
}
Ok(match (color_type, buffer) { Ok(match (color_type, buffer) {
(ColorType::RGB(8), DecodingBuffer::U8(ref mut buffer)) (ColorType::RGB(8), DecodingBuffer::U8(ref mut buffer))
@@ -1188,61 +1193,24 @@ impl<R: Read + Seek> Decoder<R> {
let color_type = self.colortype()?; let color_type = self.colortype()?;
self.goto_offset_u64(offset)?; self.goto_offset_u64(offset)?;
let (padding_right, padding_down) = self.get_tile_padding(tile); let tile_attrs = self.tile_attributes.as_mut().unwrap();
let tile_attributes = self.tile_attributes.as_mut().unwrap(); let (padding_right, padding_down) = tile_attrs.get_padding(tile);
let tile_samples = tile_attributes.tile_samples; let tile_samples = tile_attrs.tile_samples;
let tile_length = tile_attributes.tile_length; let tile_length = tile_attrs.tile_length;
let line_samples = tile_attributes.line_samples; let row_samples = tile_attrs.row_samples;
let row_samples = tile_attributes.row_samples;
let padding_buffer = &mut tile_attributes.padding_buffer;
let padding_right_samples = padding_right * self.bits_per_sample.len(); let padding_right_samples = padding_right * self.bits_per_sample.len();
let (bytes, mut reader): (usize, Box<dyn EndianReader>) = match self.compression_method { let (bytes, mut reader) = Self::create_reader(
CompressionMethod::None => { &mut self.reader,
let order = self.reader.byte_order; self.compression_method,
( compressed_length,
usize::try_from(compressed_length)?, tile_samples,
Box::new(SmartReader::wrap(&mut self.reader, order)), buffer.byte_len(),
) )?;
}
CompressionMethod::LZW => {
let (bytes, reader) = LZWReader::new(
&mut self.reader,
usize::try_from(compressed_length)?,
tile_samples * buffer.byte_len(),
)?;
(bytes, Box::new(reader))
}
CompressionMethod::PackBits => {
let order = self.reader.byte_order;
let (bytes, reader) = PackBitsReader::new(
&mut self.reader,
order,
usize::try_from(compressed_length)?,
)?;
(bytes, Box::new(reader))
}
CompressionMethod::OldDeflate => {
let (bytes, reader) = DeflateReader::new(&mut self.reader, tile_samples)?;
(bytes, Box::new(reader))
}
method => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedCompressionMethod(method),
))
}
};
// FIXME: this might be suboptimal. We might default remaining bits to ´0`, which some let tile_decoder = self.tile_decoder.as_mut().unwrap();
// other decoders might do. let line_samples = tile_decoder.result_width * self.bits_per_sample.len();
if bytes / buffer.byte_len() > tile_samples { let padding_buffer = &mut tile_decoder.padding_buffer;
return Err(TiffError::FormatError(
TiffFormatError::UnexpectedCompressedData {
actual_bytes: bytes,
required_bytes: tile_samples * buffer.byte_len(),
},
));
}
macro_rules! fill_tile_buffer { macro_rules! fill_tile_buffer {
($img_buf:ident, $byte_len:expr, $read_fn:ident) => {{ ($img_buf:ident, $byte_len:expr, $read_fn:ident) => {{
@@ -1253,7 +1221,7 @@ impl<R: Read + Seek> Decoder<R> {
let src = &mut $img_buf[(row_start / $byte_len)..(row_end / $byte_len)]; let src = &mut $img_buf[(row_start / $byte_len)..(row_end / $byte_len)];
reader.$read_fn(src)?; reader.$read_fn(src)?;
// Skip horizontal padding // Skip horizontal padding
// TODO(Tomáš Král): is there a better way of skipping the padding ? // TODO: is there a better way of skipping the padding ?
if padding_right > 0 { if padding_right > 0 {
reader.read_exact(padding_buffer)? reader.read_exact(padding_buffer)?
} }
@@ -1376,8 +1344,79 @@ impl<R: Read + Seek> Decoder<R> {
} }
} }
fn create_reader<'r>(
reader: &'r mut SmartReader<R>,
compression_method: CompressionMethod,
compressed_length: u64,
tile_samples: usize,
byte_len: usize,
) -> TiffResult<(usize, Box<dyn EndianReader + 'r>)> {
let (bytes, reader): (usize, Box<dyn EndianReader>) = match compression_method {
CompressionMethod::None => {
let order = reader.byte_order;
(
usize::try_from(compressed_length)?,
Box::new(SmartReader::wrap(reader, order)),
)
}
CompressionMethod::LZW => {
let (bytes, reader) = LZWReader::new(
reader,
usize::try_from(compressed_length)?,
tile_samples * byte_len,
)?;
(bytes, Box::new(reader))
}
CompressionMethod::PackBits => {
let order = reader.byte_order;
let (bytes, reader) =
PackBitsReader::new(reader, order, usize::try_from(compressed_length)?)?;
(bytes, Box::new(reader))
}
CompressionMethod::OldDeflate => {
let (bytes, reader) = DeflateReader::new(reader, tile_samples)?;
(bytes, Box::new(reader))
}
method => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedCompressionMethod(method),
))
}
};
// FIXME: this might be suboptimal. We might default remaining bits to ´0`, which some
// other decoders might do.
if bytes / byte_len > tile_samples {
return Err(TiffError::FormatError(
TiffFormatError::UnexpectedCompressedData {
actual_bytes: bytes,
required_bytes: tile_samples * byte_len,
},
));
}
Ok((bytes, reader))
}
fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> {
if expected != self.chunk_type {
return Err(TiffError::UsageError(UsageError::InvalidChunkType(
expected,
self.chunk_type,
)));
}
Ok(())
}
/// The chunk type (Strips / Tiles) of the image
pub fn get_chunk_type(&self) -> ChunkType {
self.chunk_type
}
/// Number of strips in image /// Number of strips in image
pub fn strip_count(&mut self) -> TiffResult<u32> { pub fn strip_count(&mut self) -> TiffResult<u32> {
self.check_chunk_type(ChunkType::Strip)?;
let rows_per_strip = self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height); let rows_per_strip = self.get_tag_u32(Tag::RowsPerStrip).unwrap_or(self.height);
if rows_per_strip == 0 { if rows_per_strip == 0 {
@@ -1387,6 +1426,16 @@ impl<R: Read + Seek> Decoder<R> {
Ok((self.height + rows_per_strip - 1) / rows_per_strip) Ok((self.height + rows_per_strip - 1) / rows_per_strip)
} }
/// Number of tiles in image
pub fn tile_count(&mut self) -> TiffResult<u32> {
self.check_chunk_type(ChunkType::Tile)?;
self.init_tile_attributes()?;
let tile_attrs = self.tile_attributes.as_ref().unwrap();
Ok(u32::try_from(
tile_attrs.tiles_across * tile_attrs.tiles_down,
)?)
}
fn initialize_strip_decoder(&mut self) -> TiffResult<()> { fn initialize_strip_decoder(&mut self) -> TiffResult<()> {
if self.strip_decoder.is_none() { if self.strip_decoder.is_none() {
let strip_offsets = self.get_tag_u64_vec(Tag::StripOffsets)?; let strip_offsets = self.get_tag_u64_vec(Tag::StripOffsets)?;
@@ -1401,7 +1450,7 @@ impl<R: Read + Seek> Decoder<R> {
Ok(()) Ok(())
} }
fn init_tile_attributes(&mut self, buffer_byte_len: usize) -> TiffResult<()> { fn init_tile_attributes(&mut self) -> TiffResult<()> {
if self.tile_attributes.is_none() { if self.tile_attributes.is_none() {
let tile_width = usize::try_from(self.get_tag_u32(Tag::TileWidth)?)?; let tile_width = usize::try_from(self.get_tag_u32(Tag::TileWidth)?)?;
let tile_length = usize::try_from(self.get_tag_u32(Tag::TileLength)?)?; let tile_length = usize::try_from(self.get_tag_u32(Tag::TileLength)?)?;
@@ -1411,36 +1460,57 @@ impl<R: Read + Seek> Decoder<R> {
let samples_per_pixel = self.bits_per_sample.len(); let samples_per_pixel = self.bits_per_sample.len();
let tile_sample_count = tile_length * tile_width * samples_per_pixel; let tile_samples = tile_length * tile_width * samples_per_pixel;
let padding_right = (tiles_across * tile_width) - usize::try_from(self.width)?; let padding_right = (tiles_across * tile_width) - usize::try_from(self.width)?;
let tile_strip_sample_count = (tile_sample_count * tiles_across) let tile_strip_samples =
- (padding_right * tile_length * samples_per_pixel); (tile_samples * tiles_across) - (padding_right * tile_length * samples_per_pixel);
let padding_buffer_size = padding_right * samples_per_pixel * buffer_byte_len;
if padding_buffer_size > self.limits.intermediate_buffer_size {
return Err(TiffError::LimitsExceeded);
}
self.tile_attributes = Some(TileAttributes { self.tile_attributes = Some(TileAttributes {
tile_offsets: self.get_tag_u64_vec(Tag::TileOffsets)?,
tile_bytes: self.get_tag_u64_vec(Tag::TileByteCounts)?,
tile_width, tile_width,
tile_length, tile_length,
tiles_across, tiles_across,
tiles_down, tiles_down,
tile_samples: tile_sample_count, tile_samples,
padding_right, padding_right,
padding_down: (tiles_down * tile_length) - usize::try_from(self.height)?, padding_down: (tiles_down * tile_length) - usize::try_from(self.height)?,
padding_buffer: vec![0; padding_buffer_size],
line_samples: usize::try_from(self.width)? * samples_per_pixel,
row_samples: (tile_width * samples_per_pixel), row_samples: (tile_width * samples_per_pixel),
tile_strip_samples: tile_strip_sample_count, tile_strip_samples,
}); });
} }
Ok(()) Ok(())
} }
fn update_tile_decoder(
&mut self,
result_width: usize,
buffer_byte_len: usize,
) -> TiffResult<()> {
let samples_per_pixel = self.bits_per_sample.len();
if self.tile_decoder.is_none() {
let tile_attrs = self.tile_attributes.as_ref().unwrap();
let padding_buffer_size =
tile_attrs.padding_right * samples_per_pixel * buffer_byte_len;
if padding_buffer_size > self.limits.intermediate_buffer_size {
return Err(TiffError::LimitsExceeded);
}
self.tile_decoder = Some(TileDecodeState {
current_tile: 0,
tile_offsets: self.get_tag_u64_vec(Tag::TileOffsets)?,
tile_bytes: self.get_tag_u64_vec(Tag::TileByteCounts)?,
padding_buffer: vec![0; padding_buffer_size],
result_width: 0, // needs to be updated for differently padded_tiles, see below
})
}
self.tile_decoder.as_mut().unwrap().result_width = result_width;
Ok(())
}
pub fn read_jpeg(&mut self) -> TiffResult<DecodingResult> { pub fn read_jpeg(&mut self) -> TiffResult<DecodingResult> {
let offsets = self.get_tag_u32_vec(Tag::StripOffsets)?; let offsets = self.get_tag_u32_vec(Tag::StripOffsets)?;
let bytes = self.get_tag_u32_vec(Tag::StripByteCounts)?; let bytes = self.get_tag_u32_vec(Tag::StripByteCounts)?;
@@ -1565,8 +1635,72 @@ impl<R: Read + Seek> Decoder<R> {
Ok(()) Ok(())
} }
fn result_buffer(&self, height: usize) -> TiffResult<DecodingResult> { fn read_tile_to_buffer(&mut self, result: &mut DecodingBuffer, tile: usize) -> TiffResult<()> {
let buffer_size = usize::try_from(self.width)? * height * self.bits_per_sample.len(); let file_offset = *self
.tile_decoder
.as_ref()
.unwrap()
.tile_offsets
.get(tile)
.ok_or(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
))?;
let compressed_bytes = *self
.tile_decoder
.as_ref()
.unwrap()
.tile_bytes
.get(tile)
.ok_or(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
))?;
let tile_attrs = self.tile_attributes.as_ref().unwrap();
let tile_samples = tile_attrs.tile_samples;
let tile_width = tile_attrs.tile_width;
let tile_length = tile_attrs.tile_length;
let (padding_right, padding_down) = tile_attrs.get_padding(tile);
let units_read = self.expand_tile(result.copy(), file_offset, compressed_bytes, tile)?;
if units_read < tile_samples {
return Err(TiffError::FormatError(
TiffFormatError::InconsistentStripSamples {
actual_samples: units_read,
required_samples: tile_samples,
},
));
}
if let Ok(predictor) = self.get_tag_unsigned(Tag::Predictor) {
match Predictor::from_u16(predictor) {
Some(Predictor::None) => (),
Some(Predictor::Horizontal) => {
rev_hpredict(
result.copy(),
(
u32::try_from(tile_width - padding_right)?,
u32::try_from(tile_length - padding_down)?,
),
self.tile_decoder.as_ref().unwrap().result_width,
self.colortype()?,
)?;
}
None => {
return Err(TiffError::FormatError(TiffFormatError::UnknownPredictor(
predictor,
)))
}
Some(Predictor::__NonExhaustive) => unreachable!(),
}
}
Ok(())
}
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
let buffer_size = width * height * self.bits_per_sample.len();
let max_sample_bits = self.bits_per_sample.iter().cloned().max().unwrap_or(8); let max_sample_bits = self.bits_per_sample.iter().cloned().max().unwrap_or(8);
match self.sample_format.first().unwrap_or(&SampleFormat::Uint) { match self.sample_format.first().unwrap_or(&SampleFormat::Uint) {
@@ -1603,6 +1737,7 @@ impl<R: Read + Seek> Decoder<R> {
/// Read a single strip from the image and return it as a Vector /// Read a single strip from the image and return it as a Vector
pub fn read_strip(&mut self) -> TiffResult<DecodingResult> { pub fn read_strip(&mut self) -> TiffResult<DecodingResult> {
self.check_chunk_type(ChunkType::Strip)?;
self.initialize_strip_decoder()?; self.initialize_strip_decoder()?;
let index = self.strip_decoder.as_ref().unwrap().strip_index; let index = self.strip_decoder.as_ref().unwrap().strip_index;
@@ -1614,123 +1749,49 @@ impl<R: Read + Seek> Decoder<R> {
usize::try_from(self.height)? - index * rows_per_strip, usize::try_from(self.height)? - index * rows_per_strip,
); );
let mut result = self.result_buffer(strip_height)?; let mut result = self.result_buffer(usize::try_from(self.width)?, strip_height)?;
self.read_strip_to_buffer(result.as_buffer(0))?; self.read_strip_to_buffer(result.as_buffer(0))?;
Ok(result) Ok(result)
} }
// Tile offset in the result buffer, counted in samples /// Read a single tile from the image and return it as a Vector
fn get_tile_offset(&self, tile: usize) -> usize { pub fn read_tile(&mut self) -> TiffResult<DecodingResult> {
let tile_attrs = self.tile_attributes.as_ref().unwrap(); self.check_chunk_type(ChunkType::Tile)?;
self.init_tile_attributes()?;
let row = tile / tile_attrs.tiles_across; let tile = self.tile_decoder.as_ref().map_or(0, |d| d.current_tile);
let column = tile % tile_attrs.tiles_across;
(row * tile_attrs.tile_strip_samples) + (column * tile_attrs.row_samples)
}
fn get_tile_padding(&self, tile: usize) -> (usize, usize) {
let tile_attrs = self.tile_attributes.as_ref().unwrap();
let row = tile / tile_attrs.tiles_across;
let column = tile % tile_attrs.tiles_across;
let padding_right = if column == tile_attrs.tiles_across - 1 {
tile_attrs.padding_right
} else {
0
};
let padding_down = if row == tile_attrs.tiles_down - 1 {
tile_attrs.padding_down
} else {
0
};
(padding_right, padding_down)
}
fn read_tile_to_buffer(&mut self, result: &mut DecodingResult, tile: usize) -> TiffResult<()> {
let file_offset = *self
.tile_attributes
.as_ref()
.unwrap()
.tile_offsets
.get(tile)
.ok_or(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
))?;
let compressed_bytes = *self
.tile_attributes
.as_ref()
.unwrap()
.tile_bytes
.get(tile)
.ok_or(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
))?;
let tile_attrs = self.tile_attributes.as_ref().unwrap(); let tile_attrs = self.tile_attributes.as_ref().unwrap();
let tile_samples = tile_attrs.tile_samples; let (padding_right, padding_down) = tile_attrs.get_padding(tile);
let tile_width = tile_attrs.tile_width;
let tile_length = tile_attrs.tile_length;
let buffer_offset = self.get_tile_offset(tile); let tile_width = tile_attrs.tile_width - padding_right;
let (padding_right, padding_down) = self.get_tile_padding(tile); let tile_length = tile_attrs.tile_length - padding_down;
let units_read = self.expand_tile( let mut result = self.result_buffer(tile_width, tile_length)?;
result.as_buffer(buffer_offset), self.update_tile_decoder(tile_width, result.as_buffer(0).byte_len())?;
file_offset,
compressed_bytes,
tile,
)?;
if units_read < tile_samples { self.read_tile_to_buffer(&mut result.as_buffer(0), tile)?;
return Err(TiffError::FormatError(
TiffFormatError::InconsistentStripSamples {
actual_samples: units_read,
required_samples: tile_samples,
},
));
}
if let Ok(predictor) = self.get_tag_unsigned(Tag::Predictor) { self.tile_decoder.as_mut().unwrap().current_tile += 1;
match Predictor::from_u16(predictor) {
Some(Predictor::None) => (), Ok(result)
Some(Predictor::Horizontal) => {
rev_hpredict(
result.as_buffer(buffer_offset).copy(),
(
u32::try_from(tile_width - padding_right)?,
u32::try_from(tile_length - padding_down)?,
),
usize::try_from(self.width)?,
self.colortype()?,
)?;
}
None => {
return Err(TiffError::FormatError(TiffFormatError::UnknownPredictor(
predictor,
)))
}
Some(Predictor::__NonExhaustive) => unreachable!(),
}
}
Ok(())
} }
fn read_tiled_image(&mut self) -> TiffResult<DecodingResult> { fn read_tiled_image(&mut self) -> TiffResult<DecodingResult> {
let mut result = self.result_buffer(usize::try_from(self.height)?)?; let width = usize::try_from(self.width)?;
let mut result = self.result_buffer(width, usize::try_from(self.height)?)?;
self.init_tile_attributes(result.as_buffer(0).byte_len())?; self.init_tile_attributes()?;
self.update_tile_decoder(width, result.as_buffer(0).byte_len())?;
let tiles_across = self.tile_attributes.as_ref().unwrap().tiles_across; let tile_attrs = self.tile_attributes.as_ref().unwrap();
let tiles_down = self.tile_attributes.as_ref().unwrap().tiles_down; let tiles_across = tile_attrs.tiles_across;
let tiles_down = tile_attrs.tiles_down;
for tile in 0..(tiles_across * tiles_down) { for tile in 0..(tiles_across * tiles_down) {
self.read_tile_to_buffer(&mut result, tile)?; let buffer_offset = self.tile_attributes.as_ref().unwrap().get_offset(tile);
self.read_tile_to_buffer(&mut result.as_buffer(buffer_offset), tile)?;
} }
Ok(result) Ok(result)
@@ -1744,7 +1805,8 @@ impl<R: Read + Seek> Decoder<R> {
let samples_per_strip = let samples_per_strip =
usize::try_from(self.width)? * rows_per_strip * self.bits_per_sample.len(); usize::try_from(self.width)? * rows_per_strip * self.bits_per_sample.len();
let mut result = self.result_buffer(usize::try_from(self.height)?)?; let mut result =
self.result_buffer(usize::try_from(self.width)?, usize::try_from(self.height)?)?;
for i in 0..usize::try_from(self.strip_count()?)? { for i in 0..usize::try_from(self.strip_count()?)? {
let r = result.as_buffer(samples_per_strip * i); let r = result.as_buffer(samples_per_strip * i);
+29 -1
View File
@@ -4,7 +4,7 @@ use std::io;
use std::str; use std::str;
use std::string; use std::string;
use crate::decoder::ifd::Value; use crate::decoder::{ifd::Value, ChunkType};
use crate::tags::{ use crate::tags::{
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag, CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
}; };
@@ -29,6 +29,9 @@ pub enum TiffError {
/// An integer conversion to or from a platform size failed, either due to /// An integer conversion to or from a platform size failed, either due to
/// limits of the platform size or limits of the format. /// limits of the platform size or limits of the format.
IntSizeError, IntSizeError,
/// The image does not support the requested operation
UsageError(UsageError),
} }
/// The image is not formatted properly. /// The image is not formatted properly.
@@ -205,6 +208,29 @@ impl fmt::Display for TiffUnsupportedError {
} }
} }
/// User attempted to use the Decoder in a way that is incompatible with a specific image.
///
/// For example: attempting to read a tile from a stripped image.
#[derive(Debug)]
pub enum UsageError {
InvalidChunkType(ChunkType, ChunkType),
}
impl fmt::Display for UsageError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::UsageError::*;
match *self {
InvalidChunkType(expected, actual) => {
write!(
fmt,
"Requested operation is only valid for images with chunk encoding of type: {:?}, got {:?}.",
expected, actual
)
}
}
}
}
impl fmt::Display for TiffError { impl fmt::Display for TiffError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self { match *self {
@@ -218,6 +244,7 @@ impl fmt::Display for TiffError {
TiffError::IoError(ref e) => e.fmt(fmt), TiffError::IoError(ref e) => e.fmt(fmt),
TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"), TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"),
TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"), TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"),
TiffError::UsageError(ref e) => write!(fmt, "Usage error: {}", e),
} }
} }
} }
@@ -230,6 +257,7 @@ impl Error for TiffError {
TiffError::IoError(..) => "IO error", TiffError::IoError(..) => "IO error",
TiffError::LimitsExceeded => "Decoder limits exceeded", TiffError::LimitsExceeded => "Decoder limits exceeded",
TiffError::IntSizeError => "Platform or format size limits exceeded", TiffError::IntSizeError => "Platform or format size limits exceeded",
TiffError::UsageError(..) => "Invalid usage",
} }
} }
+3 -1
View File
@@ -16,7 +16,9 @@ pub mod encoder;
mod error; mod error;
pub mod tags; pub mod tags;
pub use self::error::{InflateError, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; pub use self::error::{
InflateError, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
};
/// An enumeration over supported color types and their bit depths /// An enumeration over supported color types and their bit depths
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
+44
View File
@@ -245,3 +245,47 @@ fn test_tiled_whiteiszero_gray_i8() {
fn test_tiled_cmyk_i8() { fn test_tiled_cmyk_i8() {
test_image_sum_i8("tiled-cmyk-i8.tif", ColorType::CMYK(8), 1759101); test_image_sum_i8("tiled-cmyk-i8.tif", ColorType::CMYK(8), 1759101);
} }
#[test]
fn test_tiled_incremental() {
let file = "tiled-rgb-u8.tif";
let expected_type = ColorType::RGB(8);
let sums = [
188760, 195639, 108148, 81986, 665088, 366140, 705317, 423366, 172033, 324455, 244102,
81853, 181258, 247971, 129486, 55600, 565625, 422102, 730888, 379271, 232142, 292549,
244045, 86866, 188141, 115036, 150785, 84389, 353170, 459325, 719619, 329594, 278663,
220474, 243048, 113563, 189152, 109684, 179391, 122188, 279651, 622093, 724682, 302459,
268428, 204499, 224255, 124674, 170668, 121868, 192768, 183367, 378029, 585651, 657712,
296790, 241444, 197083, 198429, 134869, 182318, 86034, 203655, 182338, 297255, 601284,
633813, 242531, 228578, 206441, 193552, 125412, 181527, 165439, 202531, 159538, 268388,
565790, 611382, 272967, 236497, 215154, 158881, 90806, 106114, 182342, 191824, 186138,
215174, 393193, 701228, 198866, 227944, 193830, 166330, 49008, 55719, 122820, 197316,
161969, 203152, 170986, 624427, 188605, 186187, 111064, 115192, 39538, 48626, 163929,
144682, 135796, 194141, 154198, 584125, 180255, 153524, 121433, 132641, 35743, 47798,
152343, 162874, 167664, 160175, 133038, 659882, 138339, 166470, 124173, 118929, 51317,
45267, 155776, 161331, 161006, 130052, 137618, 337291, 106481, 161999, 127343, 87724,
59540, 63907, 155677, 140668, 141523, 108061, 168657, 186482, 98599, 147614, 139963, 90444,
56602, 92547, 125644, 134212, 126569, 144153, 179800, 174516, 133969, 129399, 117681,
83305, 55075, 110737, 115108, 128572, 128911, 130922, 179986, 143288, 145884, 155856,
96683, 94057, 56238, 79649, 71651, 70182, 75010, 77009, 98855, 78979, 74341, 83482, 53403,
59842, 30305,
];
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let tiles = decoder.tile_count().unwrap();
assert_eq!(tiles as usize, sums.len());
for tile in 0..tiles {
match decoder.read_tile().unwrap() {
DecodingResult::U8(res) => {
let sum: u64 = res.into_iter().map(<u64>::from).sum();
assert_eq!(sum, sums[tile as usize]);
}
_ => panic!("Wrong bit depth"),
}
}
}
Binary file not shown.