diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/tiff/src/decoder/image.rs | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/tiff/src/decoder/image.rs')
-rw-r--r-- | vendor/tiff/src/decoder/image.rs | 601 |
1 files changed, 601 insertions, 0 deletions
diff --git a/vendor/tiff/src/decoder/image.rs b/vendor/tiff/src/decoder/image.rs new file mode 100644 index 0000000..c037e31 --- /dev/null +++ b/vendor/tiff/src/decoder/image.rs @@ -0,0 +1,601 @@ +use super::ifd::{Directory, Value}; +use super::stream::{ByteOrder, DeflateReader, JpegReader, LZWReader, PackBitsReader}; +use super::tag_reader::TagReader; +use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits}; +use super::{stream::SmartReader, ChunkType}; +use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag}; +use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError}; +use std::convert::{TryFrom, TryInto}; +use std::io::{self, Cursor, Read, Seek}; +use std::sync::Arc; + +#[derive(Debug)] +pub(crate) struct StripDecodeState { + pub rows_per_strip: u32, +} + +#[derive(Debug)] +/// Computed values useful for tile decoding +pub(crate) struct TileAttributes { + pub image_width: usize, + pub image_height: usize, + + pub tile_width: usize, + pub tile_length: usize, +} + +impl TileAttributes { + pub fn tiles_across(&self) -> usize { + (self.image_width + self.tile_width - 1) / self.tile_width + } + pub fn tiles_down(&self) -> usize { + (self.image_height + self.tile_length - 1) / self.tile_length + } + fn padding_right(&self) -> usize { + (self.tile_width - self.image_width % self.tile_width) % self.tile_width + } + fn padding_down(&self) -> usize { + (self.tile_length - self.image_height % self.tile_length) % self.tile_length + } + pub 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)] +pub(crate) struct Image { + pub ifd: Option<Directory>, + pub width: u32, + pub height: u32, + pub bits_per_sample: Vec<u8>, + #[allow(unused)] + pub samples: u8, + pub sample_format: Vec<SampleFormat>, + pub photometric_interpretation: PhotometricInterpretation, + pub compression_method: CompressionMethod, + pub predictor: Predictor, + pub jpeg_tables: Option<Arc<Vec<u8>>>, + pub chunk_type: ChunkType, + pub strip_decoder: Option<StripDecodeState>, + pub tile_attributes: Option<TileAttributes>, + pub chunk_offsets: Vec<u64>, + pub chunk_bytes: Vec<u64>, +} + +impl Image { + pub fn from_reader<R: Read + Seek>( + reader: &mut SmartReader<R>, + ifd: Directory, + limits: &Limits, + bigtiff: bool, + ) -> TiffResult<Image> { + let mut tag_reader = TagReader { + reader, + limits, + ifd: &ifd, + bigtiff, + }; + + let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?; + let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?; + if width == 0 || height == 0 { + return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions( + width, height, + ))); + } + + let photometric_interpretation = tag_reader + .find_tag(Tag::PhotometricInterpretation)? + .map(Value::into_u16) + .transpose()? + .and_then(PhotometricInterpretation::from_u16) + .ok_or(TiffUnsupportedError::UnknownInterpretation)?; + + // Try to parse both the compression method and the number, format, and bits of the included samples. + // If they are not explicitly specified, those tags are reset to their default values and not carried from previous images. + let compression_method = match tag_reader.find_tag(Tag::Compression)? { + Some(val) => CompressionMethod::from_u16(val.into_u16()?) + .ok_or(TiffUnsupportedError::UnknownCompressionMethod)?, + None => CompressionMethod::None, + }; + + let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG + && ifd.contains_key(&Tag::JPEGTables) + { + let vec = tag_reader + .find_tag(Tag::JPEGTables)? + .unwrap() + .into_u8_vec()?; + if vec.len() < 2 { + return Err(TiffError::FormatError( + TiffFormatError::InvalidTagValueType(Tag::JPEGTables), + )); + } + + Some(Arc::new(vec)) + } else { + None + }; + + let samples = tag_reader + .find_tag(Tag::SamplesPerPixel)? + .map(Value::into_u16) + .transpose()? + .unwrap_or(1) + .try_into()?; + + let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? { + Some(vals) => { + let sample_format: Vec<_> = vals + .into_iter() + .map(SampleFormat::from_u16_exhaustive) + .collect(); + + // TODO: for now, only homogenous formats across samples are supported. + if !sample_format.windows(2).all(|s| s[0] == s[1]) { + return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into()); + } + + sample_format + } + None => vec![SampleFormat::Uint], + }; + + let bits_per_sample = match samples { + 1 | 3 | 4 => tag_reader + .find_tag_uint_vec(Tag::BitsPerSample)? + .unwrap_or_else(|| vec![1]), + _ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(samples).into()), + }; + + let predictor = tag_reader + .find_tag(Tag::Predictor)? + .map(Value::into_u16) + .transpose()? + .map(|p| { + Predictor::from_u16(p) + .ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p))) + }) + .transpose()? + .unwrap_or(Predictor::None); + + let chunk_type; + let chunk_offsets; + let chunk_bytes; + let strip_decoder; + let tile_attributes; + match ( + ifd.contains_key(&Tag::StripByteCounts), + ifd.contains_key(&Tag::StripOffsets), + ifd.contains_key(&Tag::TileByteCounts), + ifd.contains_key(&Tag::TileOffsets), + ) { + (true, true, false, false) => { + chunk_type = ChunkType::Strip; + + chunk_offsets = tag_reader + .find_tag(Tag::StripOffsets)? + .unwrap() + .into_u64_vec()?; + chunk_bytes = tag_reader + .find_tag(Tag::StripByteCounts)? + .unwrap() + .into_u64_vec()?; + let rows_per_strip = tag_reader + .find_tag(Tag::RowsPerStrip)? + .map(Value::into_u32) + .transpose()? + .unwrap_or(height); + strip_decoder = Some(StripDecodeState { rows_per_strip }); + tile_attributes = None; + + if chunk_offsets.len() != chunk_bytes.len() + || rows_per_strip == 0 + || u32::try_from(chunk_offsets.len())? + != height.saturating_sub(1) / rows_per_strip + 1 + { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } + } + (false, false, true, true) => { + chunk_type = ChunkType::Tile; + + let tile_width = + usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?; + let tile_length = + usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?; + + if tile_width == 0 { + return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into()); + } else if tile_length == 0 { + return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into()); + } + + strip_decoder = None; + tile_attributes = Some(TileAttributes { + image_width: usize::try_from(width)?, + image_height: usize::try_from(height)?, + tile_width, + tile_length, + }); + chunk_offsets = tag_reader + .find_tag(Tag::TileOffsets)? + .unwrap() + .into_u64_vec()?; + chunk_bytes = tag_reader + .find_tag(Tag::TileByteCounts)? + .unwrap() + .into_u64_vec()?; + + let tile = tile_attributes.as_ref().unwrap(); + if chunk_offsets.len() != chunk_bytes.len() + || chunk_offsets.len() != tile.tiles_down() * tile.tiles_across() + { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } + } + (_, _, _, _) => { + return Err(TiffError::FormatError( + TiffFormatError::StripTileTagConflict, + )) + } + }; + + Ok(Image { + ifd: Some(ifd), + width, + height, + bits_per_sample, + samples, + sample_format, + photometric_interpretation, + compression_method, + jpeg_tables, + predictor, + chunk_type, + strip_decoder, + tile_attributes, + chunk_offsets, + chunk_bytes, + }) + } + + pub(crate) fn colortype(&self) -> TiffResult<ColorType> { + match self.photometric_interpretation { + PhotometricInterpretation::RGB => match self.bits_per_sample[..] { + [r, g, b] if [r, r] == [g, b] => Ok(ColorType::RGB(r)), + [r, g, b, a] if [r, r, r] == [g, b, a] => Ok(ColorType::RGBA(r)), + // FIXME: We should _ignore_ other components. In particular: + // > Beware of extra components. Some TIFF files may have more components per pixel + // than you think. A Baseline TIFF reader must skip over them gracefully,using the + // values of the SamplesPerPixel and BitsPerSample fields. + // > -- TIFF 6.0 Specification, Section 7, Additional Baseline requirements. + _ => Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + self.photometric_interpretation, + self.bits_per_sample.clone(), + ), + )), + }, + PhotometricInterpretation::CMYK => match self.bits_per_sample[..] { + [c, m, y, k] if [c, c, c] == [m, y, k] => Ok(ColorType::CMYK(c)), + _ => Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + self.photometric_interpretation, + self.bits_per_sample.clone(), + ), + )), + }, + PhotometricInterpretation::YCbCr => match self.bits_per_sample[..] { + [y, cb, cr] if [y, y] == [cb, cr] => Ok(ColorType::YCbCr(y)), + _ => Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + self.photometric_interpretation, + self.bits_per_sample.clone(), + ), + )), + }, + PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero + if self.bits_per_sample.len() == 1 => + { + Ok(ColorType::Gray(self.bits_per_sample[0])) + } + + // TODO: this is bad we should not fail at this point + _ => Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + self.photometric_interpretation, + self.bits_per_sample.clone(), + ), + )), + } + } + + fn create_reader<'r, R: 'r + Read>( + reader: R, + photometric_interpretation: PhotometricInterpretation, + compression_method: CompressionMethod, + compressed_length: u64, + jpeg_tables: Option<Arc<Vec<u8>>>, + ) -> TiffResult<Box<dyn Read + 'r>> { + Ok(match compression_method { + CompressionMethod::None => Box::new(reader), + CompressionMethod::LZW => { + Box::new(LZWReader::new(reader, usize::try_from(compressed_length)?)) + } + CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)), + CompressionMethod::Deflate | CompressionMethod::OldDeflate => { + Box::new(DeflateReader::new(reader)) + } + CompressionMethod::ModernJPEG => { + if jpeg_tables.is_some() && compressed_length < 2 { + return Err(TiffError::FormatError( + TiffFormatError::InvalidTagValueType(Tag::JPEGTables), + )); + } + + let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?; + let mut decoder = jpeg::Decoder::new(jpeg_reader); + + match photometric_interpretation { + PhotometricInterpretation::RGB => { + decoder.set_color_transform(jpeg::ColorTransform::RGB) + } + PhotometricInterpretation::WhiteIsZero => { + decoder.set_color_transform(jpeg::ColorTransform::None) + } + PhotometricInterpretation::BlackIsZero => { + decoder.set_color_transform(jpeg::ColorTransform::None) + } + PhotometricInterpretation::TransparencyMask => { + decoder.set_color_transform(jpeg::ColorTransform::None) + } + PhotometricInterpretation::CMYK => { + decoder.set_color_transform(jpeg::ColorTransform::CMYK) + } + PhotometricInterpretation::YCbCr => { + decoder.set_color_transform(jpeg::ColorTransform::YCbCr) + } + photometric_interpretation => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedInterpretation( + photometric_interpretation, + ), + )); + } + } + + let data = decoder.decode()?; + + Box::new(Cursor::new(data)) + } + method => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedCompressionMethod(method), + )) + } + }) + } + + pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> { + let file_offset = self + .chunk_offsets + .get(chunk as usize) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; + + let compressed_bytes = + self.chunk_bytes + .get(chunk as usize) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; + + Ok((*file_offset, *compressed_bytes)) + } + + pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> { + match self.chunk_type { + ChunkType::Strip => { + let strip_attrs = self.strip_decoder.as_ref().unwrap(); + Ok((self.width, strip_attrs.rows_per_strip)) + } + ChunkType::Tile => { + let tile_attrs = self.tile_attributes.as_ref().unwrap(); + Ok(( + u32::try_from(tile_attrs.tile_width)?, + u32::try_from(tile_attrs.tile_length)?, + )) + } + } + } + + pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> { + let dims = self.chunk_dimensions()?; + + match self.chunk_type { + ChunkType::Strip => { + let strip_height_without_padding = chunk_index + .checked_mul(dims.1) + .and_then(|x| self.height.checked_sub(x)) + .ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex( + chunk_index, + )))?; + + // Ignore potential vertical padding on the bottommost strip + let strip_height = dims.1.min(strip_height_without_padding); + + Ok((dims.0, strip_height)) + } + ChunkType::Tile => { + let tile_attrs = self.tile_attributes.as_ref().unwrap(); + let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize); + + let tile_width = tile_attrs.tile_width - padding_right; + let tile_length = tile_attrs.tile_length - padding_down; + + Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?)) + } + } + } + + pub(crate) fn expand_chunk( + &self, + reader: impl Read, + mut buffer: DecodingBuffer, + output_width: usize, + byte_order: ByteOrder, + chunk_index: u32, + ) -> TiffResult<()> { + // Validate that the provided buffer is of the expected type. + let color_type = self.colortype()?; + match (color_type, &buffer) { + (ColorType::RGB(n), _) + | (ColorType::RGBA(n), _) + | (ColorType::CMYK(n), _) + | (ColorType::YCbCr(n), _) + | (ColorType::Gray(n), _) + if usize::from(n) == buffer.byte_len() * 8 => {} + (ColorType::Gray(n), DecodingBuffer::U8(_)) if n < 8 => match self.predictor { + Predictor::None => {} + Predictor::Horizontal => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::HorizontalPredictor(color_type), + )) + } + Predictor::FloatingPoint => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::FloatingPointPredictor(color_type), + )); + } + }, + (type_, _) => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedColorType(type_), + )) + } + } + + // Validate that the predictor is supported for the sample type. + match (self.predictor, &buffer) { + (Predictor::Horizontal, DecodingBuffer::F32(_)) + | (Predictor::Horizontal, DecodingBuffer::F64(_)) => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::HorizontalPredictor(color_type), + )); + } + (Predictor::FloatingPoint, DecodingBuffer::F32(_)) + | (Predictor::FloatingPoint, DecodingBuffer::F64(_)) => {} + (Predictor::FloatingPoint, _) => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::FloatingPointPredictor(color_type), + )); + } + _ => {} + } + + let compressed_bytes = + self.chunk_bytes + .get(chunk_index as usize) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; + + let byte_len = buffer.byte_len(); + let compression_method = self.compression_method; + let photometric_interpretation = self.photometric_interpretation; + let predictor = self.predictor; + let samples = self.bits_per_sample.len(); + + let chunk_dims = self.chunk_dimensions()?; + let data_dims = self.chunk_data_dimensions(chunk_index)?; + + let padding_right = chunk_dims.0 - data_dims.0; + + let jpeg_tables = self.jpeg_tables.clone(); + let mut reader = Self::create_reader( + reader, + photometric_interpretation, + compression_method, + *compressed_bytes, + jpeg_tables, + )?; + + if output_width == data_dims.0 as usize && padding_right == 0 { + let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples; + let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len]; + reader.read_exact(tile)?; + + for row in 0..data_dims.1 as usize { + let row_start = row as usize * output_width as usize * samples; + let row_end = (row + 1) * output_width as usize * samples; + let row = buffer.subrange(row_start..row_end); + super::fix_endianness_and_predict(row, samples, byte_order, predictor); + } + if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + super::invert_colors(&mut buffer.subrange(0..total_samples), color_type); + } + } else if padding_right > 0 && self.predictor == Predictor::FloatingPoint { + // The floating point predictor shuffles the padding bytes into the encoded output, so + // this case is handled specially when needed. + let mut encoded = vec![0u8; chunk_dims.0 as usize * samples * byte_len]; + + for row in 0..data_dims.1 as usize { + let row_start = row * output_width as usize * samples; + let row_end = row_start + data_dims.0 as usize * samples; + + reader.read_exact(&mut encoded)?; + match buffer.subrange(row_start..row_end) { + DecodingBuffer::F32(buf) => fp_predict_f32(&mut encoded, buf, samples), + DecodingBuffer::F64(buf) => fp_predict_f64(&mut encoded, buf, samples), + _ => unreachable!(), + } + if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type); + } + } + } else { + for row in 0..data_dims.1 as usize { + let row_start = row * output_width as usize * samples; + let row_end = row_start + data_dims.0 as usize * samples; + + let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)]; + reader.read_exact(row)?; + + // Skip horizontal padding + if padding_right > 0 { + let len = u64::try_from(padding_right as usize * samples * byte_len)?; + io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; + } + + let mut row = buffer.subrange(row_start..row_end); + super::fix_endianness_and_predict(row.copy(), samples, byte_order, predictor); + if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + super::invert_colors(&mut row, color_type); + } + } + } + + Ok(()) + } +} |