Allow arbitrary number of samples as long as all have the same bit depth (#215)
This commit is contained in:
1 parent
a67f50568c
commit
4a2e76858b
4 files changed
+51
-48
No files matched your search
+40
-28
@@ -7,7 +7,7 @@ use crate::tags::{
|
|||||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
|
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
|
||||||
};
|
};
|
||||||
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::TryFrom;
|
||||||
use std::io::{self, Cursor, Read, Seek};
|
use std::io::{self, Cursor, Read, Seek};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -64,9 +64,9 @@ pub(crate) struct Image {
|
|||||||
pub ifd: Option<Directory>,
|
pub ifd: Option<Directory>,
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub bits_per_sample: Vec<u8>,
|
pub bits_per_sample: u8,
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub samples: u8,
|
pub samples: u16,
|
||||||
pub sample_format: Vec<SampleFormat>,
|
pub sample_format: Vec<SampleFormat>,
|
||||||
pub photometric_interpretation: PhotometricInterpretation,
|
pub photometric_interpretation: PhotometricInterpretation,
|
||||||
pub compression_method: CompressionMethod,
|
pub compression_method: CompressionMethod,
|
||||||
@@ -134,12 +134,14 @@ impl Image {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let samples = tag_reader
|
let samples: u16 = tag_reader
|
||||||
.find_tag(Tag::SamplesPerPixel)?
|
.find_tag(Tag::SamplesPerPixel)?
|
||||||
.map(Value::into_u16)
|
.map(Value::into_u16)
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(1)
|
.unwrap_or(1);
|
||||||
.try_into()?;
|
if samples == 0 {
|
||||||
|
return Err(TiffFormatError::SamplesPerPixelIsZero.into());
|
||||||
|
}
|
||||||
|
|
||||||
let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
|
let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
|
||||||
Some(vals) => {
|
Some(vals) => {
|
||||||
@@ -158,12 +160,22 @@ impl Image {
|
|||||||
None => vec![SampleFormat::Uint],
|
None => vec![SampleFormat::Uint],
|
||||||
};
|
};
|
||||||
|
|
||||||
let bits_per_sample = match samples {
|
let bits_per_sample: Vec<u8> = tag_reader
|
||||||
1 | 3 | 4 => tag_reader
|
.find_tag_uint_vec(Tag::BitsPerSample)?
|
||||||
.find_tag_uint_vec(Tag::BitsPerSample)?
|
.unwrap_or_else(|| vec![1]);
|
||||||
.unwrap_or_else(|| vec![1]),
|
|
||||||
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(samples).into()),
|
// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows
|
||||||
};
|
// it to be a single value that applies to all samples.
|
||||||
|
if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This library (and libtiff) do not support mixed sample formats.
|
||||||
|
if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) {
|
||||||
|
return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into());
|
||||||
|
}
|
||||||
|
|
||||||
let predictor = tag_reader
|
let predictor = tag_reader
|
||||||
.find_tag(Tag::Predictor)?
|
.find_tag(Tag::Predictor)?
|
||||||
@@ -284,7 +296,7 @@ impl Image {
|
|||||||
ifd: Some(ifd),
|
ifd: Some(ifd),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
bits_per_sample,
|
bits_per_sample: bits_per_sample[0],
|
||||||
samples,
|
samples,
|
||||||
sample_format,
|
sample_format,
|
||||||
photometric_interpretation,
|
photometric_interpretation,
|
||||||
@@ -302,9 +314,9 @@ impl Image {
|
|||||||
|
|
||||||
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
|
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
|
||||||
match self.photometric_interpretation {
|
match self.photometric_interpretation {
|
||||||
PhotometricInterpretation::RGB => match self.bits_per_sample[..] {
|
PhotometricInterpretation::RGB => match self.samples {
|
||||||
[r, g, b] if [r, r] == [g, b] => Ok(ColorType::RGB(r)),
|
3 => Ok(ColorType::RGB(self.bits_per_sample)),
|
||||||
[r, g, b, a] if [r, r, r] == [g, b, a] => Ok(ColorType::RGBA(r)),
|
4 => Ok(ColorType::RGBA(self.bits_per_sample)),
|
||||||
// FIXME: We should _ignore_ other components. In particular:
|
// FIXME: We should _ignore_ other components. In particular:
|
||||||
// > Beware of extra components. Some TIFF files may have more components per pixel
|
// > 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
|
// than you think. A Baseline TIFF reader must skip over them gracefully,using the
|
||||||
@@ -313,39 +325,39 @@ impl Image {
|
|||||||
_ => Err(TiffError::UnsupportedError(
|
_ => Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::InterpretationWithBits(
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
self.photometric_interpretation,
|
self.photometric_interpretation,
|
||||||
self.bits_per_sample.clone(),
|
vec![self.bits_per_sample; self.samples as usize],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
PhotometricInterpretation::CMYK => match self.bits_per_sample[..] {
|
PhotometricInterpretation::CMYK => match self.samples {
|
||||||
[c, m, y, k] if [c, c, c] == [m, y, k] => Ok(ColorType::CMYK(c)),
|
4 => Ok(ColorType::CMYK(self.bits_per_sample)),
|
||||||
_ => Err(TiffError::UnsupportedError(
|
_ => Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::InterpretationWithBits(
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
self.photometric_interpretation,
|
self.photometric_interpretation,
|
||||||
self.bits_per_sample.clone(),
|
vec![self.bits_per_sample; self.samples as usize],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
PhotometricInterpretation::YCbCr => match self.bits_per_sample[..] {
|
PhotometricInterpretation::YCbCr => match self.samples {
|
||||||
[y, cb, cr] if [y, y] == [cb, cr] => Ok(ColorType::YCbCr(y)),
|
3 => Ok(ColorType::YCbCr(self.bits_per_sample)),
|
||||||
_ => Err(TiffError::UnsupportedError(
|
_ => Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::InterpretationWithBits(
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
self.photometric_interpretation,
|
self.photometric_interpretation,
|
||||||
self.bits_per_sample.clone(),
|
vec![self.bits_per_sample; self.samples as usize],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
|
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
|
||||||
if self.bits_per_sample.len() == 1 =>
|
if self.samples == 1 =>
|
||||||
{
|
{
|
||||||
Ok(ColorType::Gray(self.bits_per_sample[0]))
|
Ok(ColorType::Gray(self.bits_per_sample))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this is bad we should not fail at this point
|
// TODO: this is bad we should not fail at this point
|
||||||
_ => Err(TiffError::UnsupportedError(
|
_ => Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::InterpretationWithBits(
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
self.photometric_interpretation,
|
self.photometric_interpretation,
|
||||||
self.bits_per_sample.clone(),
|
vec![self.bits_per_sample; self.samples as usize],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
@@ -448,7 +460,7 @@ impl Image {
|
|||||||
/// * `PlanarConfiguration::Planar` -> 1 (RRR...) (GGG...) (BBB...)
|
/// * `PlanarConfiguration::Planar` -> 1 (RRR...) (GGG...) (BBB...)
|
||||||
pub(crate) fn samples_per_pixel(&self) -> usize {
|
pub(crate) fn samples_per_pixel(&self) -> usize {
|
||||||
match self.planar_config {
|
match self.planar_config {
|
||||||
PlanarConfiguration::Chunky => self.bits_per_sample.len(),
|
PlanarConfiguration::Chunky => self.samples.into(),
|
||||||
PlanarConfiguration::Planar => 1,
|
PlanarConfiguration::Planar => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -457,7 +469,7 @@ impl Image {
|
|||||||
pub(crate) fn strips_per_pixel(&self) -> usize {
|
pub(crate) fn strips_per_pixel(&self) -> usize {
|
||||||
match self.planar_config {
|
match self.planar_config {
|
||||||
PlanarConfiguration::Chunky => 1,
|
PlanarConfiguration::Chunky => 1,
|
||||||
PlanarConfiguration::Planar => self.bits_per_sample.len(),
|
PlanarConfiguration::Planar => self.samples.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-8
@@ -572,7 +572,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
ifd: None,
|
ifd: None,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
bits_per_sample: vec![1],
|
bits_per_sample: 1,
|
||||||
samples: 1,
|
samples: 1,
|
||||||
sample_format: vec![SampleFormat::Uint],
|
sample_format: vec![SampleFormat::Uint],
|
||||||
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
|
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
|
||||||
@@ -1064,13 +1064,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
None => return Err(TiffError::LimitsExceeded),
|
None => return Err(TiffError::LimitsExceeded),
|
||||||
};
|
};
|
||||||
|
|
||||||
let max_sample_bits = self
|
let max_sample_bits = self.image().bits_per_sample;
|
||||||
.image()
|
|
||||||
.bits_per_sample
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.max()
|
|
||||||
.unwrap_or(8);
|
|
||||||
match self
|
match self
|
||||||
.image()
|
.image()
|
||||||
.sample_format
|
.sample_format
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ pub enum TiffFormatError {
|
|||||||
StripTileTagConflict,
|
StripTileTagConflict,
|
||||||
CycleInOffsets,
|
CycleInOffsets,
|
||||||
JpegDecoder(JpegDecoderError),
|
JpegDecoder(JpegDecoderError),
|
||||||
|
SamplesPerPixelIsZero,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TiffFormatError {
|
impl fmt::Display for TiffFormatError {
|
||||||
@@ -129,6 +130,7 @@ impl fmt::Display for TiffFormatError {
|
|||||||
StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
|
StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
|
||||||
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
|
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
|
||||||
JpegDecoder(ref error) => write!(fmt, "{}", error),
|
JpegDecoder(ref error) => write!(fmt, "{}", error),
|
||||||
|
SamplesPerPixelIsZero => write!(fmt, "Samples per pixel is zero"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +148,7 @@ impl fmt::Display for TiffFormatError {
|
|||||||
pub enum TiffUnsupportedError {
|
pub enum TiffUnsupportedError {
|
||||||
FloatingPointPredictor(ColorType),
|
FloatingPointPredictor(ColorType),
|
||||||
HorizontalPredictor(ColorType),
|
HorizontalPredictor(ColorType),
|
||||||
|
InconsistentBitsPerSample(Vec<u8>),
|
||||||
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
|
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
|
||||||
UnknownInterpretation,
|
UnknownInterpretation,
|
||||||
UnknownCompressionMethod,
|
UnknownCompressionMethod,
|
||||||
@@ -174,6 +177,9 @@ impl fmt::Display for TiffUnsupportedError {
|
|||||||
"Horizontal predictor for {:?} is unsupported.",
|
"Horizontal predictor for {:?} is unsupported.",
|
||||||
color_type
|
color_type
|
||||||
),
|
),
|
||||||
|
InconsistentBitsPerSample(ref bits_per_sample) => {
|
||||||
|
write!(fmt, "Inconsistent bits per sample: {:?}.", bits_per_sample)
|
||||||
|
}
|
||||||
InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => write!(
|
InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => write!(
|
||||||
fmt,
|
fmt,
|
||||||
"{:?} with {:?} bits per sample is unsupported",
|
"{:?} with {:?} bits per sample is unsupported",
|
||||||
|
|||||||
+3
-12
@@ -375,9 +375,7 @@ fn fuzzer_testcase5() {
|
|||||||
178, 178, 178,
|
178, 178, 178,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
let _ = decoder.read_image().unwrap_err();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -394,7 +392,7 @@ fn fuzzer_testcase1() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stripped_image_overflow() {
|
fn fuzzer_testcase6() {
|
||||||
let image = [
|
let image = [
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0,
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0,
|
||||||
0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0,
|
0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0,
|
||||||
@@ -403,14 +401,7 @@ fn test_stripped_image_overflow() {
|
|||||||
178, 178,
|
178, 178,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
let err = decoder.read_image().unwrap_err();
|
|
||||||
|
|
||||||
match err {
|
|
||||||
tiff::TiffError::LimitsExceeded => {}
|
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user