Merge pull request #397 from soyosaki/lenient
Fix: add a lenient mode for image decoder
This commit is contained in:
4 files changed
+43
-12
No files matched your search
+4
-2
@@ -2,13 +2,15 @@ use tiff::decoder::{BufferLayoutPreference, Decoder, DecodingSampleBuffer, Decod
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let Some(image) = std::env::args_os().nth(1) else {
|
||||
eprintln!("Usage: decode FILE");
|
||||
eprintln!("Usage: decode FILE [--lenient]");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let lenient = std::env::args().nth(2).as_deref() == Some("--lenient");
|
||||
|
||||
let file = std::fs::File::open(image)?;
|
||||
let io = std::io::BufReader::new(file);
|
||||
let mut reader = Decoder::open(io)?;
|
||||
let mut reader = Decoder::open(io)?.with_lenient(lenient);
|
||||
|
||||
let mut data = DecodingSampleBuffer::I8(vec![]);
|
||||
|
||||
|
||||
+11
-5
@@ -97,6 +97,7 @@ pub(crate) struct Image {
|
||||
/// run on samples directly and thus skip the outer byte order entirely, except potentially
|
||||
/// they have interior byte order treatment.
|
||||
pub decompression_to_host_endian: bool,
|
||||
pub lenient: bool,
|
||||
}
|
||||
|
||||
/// Describes how to read a tile-aligned portion of the image.
|
||||
@@ -158,6 +159,7 @@ impl Image {
|
||||
pub fn from_reader<R: Read + Seek>(
|
||||
decoder: &mut ValueReader<R>,
|
||||
ifd: &Directory,
|
||||
lenient: bool,
|
||||
) -> TiffResult<Image> {
|
||||
let mut tag_reader = TagReader { decoder, ifd };
|
||||
|
||||
@@ -500,6 +502,7 @@ impl Image {
|
||||
compression_method,
|
||||
CompressionMethod::SgiLog | CompressionMethod::SgiLog24
|
||||
),
|
||||
lenient,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -515,10 +518,13 @@ impl Image {
|
||||
}
|
||||
|
||||
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
|
||||
let is_alpha_extra_samples = matches!(
|
||||
self.extra_samples.as_slice(),
|
||||
[ExtraSamples::AssociatedAlpha, ..] | [ExtraSamples::UnassociatedAlpha, ..]
|
||||
);
|
||||
let is_alpha_extra_samples = match self.extra_samples.as_slice().first() {
|
||||
Some(ExtraSamples::AssociatedAlpha) | Some(ExtraSamples::UnassociatedAlpha) => true,
|
||||
// In lenient mode, keep unspecified alpha interpretation logic compatible with libtiff
|
||||
// See Issue #341
|
||||
Some(ExtraSamples::Unspecified) if self.lenient && self.samples > 3 => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
match self.photometric_interpretation {
|
||||
PhotometricInterpretation::RGB => match self.photometric_samples {
|
||||
@@ -1192,7 +1198,7 @@ impl Image {
|
||||
super::fix_endianness_and_predict(
|
||||
out_row,
|
||||
color_type.bit_depth(),
|
||||
samples,
|
||||
data_samples,
|
||||
ByteOrder::native(),
|
||||
predictor,
|
||||
scratch,
|
||||
|
||||
+24
-3
@@ -257,6 +257,8 @@ pub struct Decoder<R> {
|
||||
/// Reusable scratch buffer for the floating-point predictor. Stored here so that the
|
||||
/// allocation persists across strips/tiles and images, avoiding per-row heap allocation.
|
||||
scratch: Vec<u8>,
|
||||
/// Leniency flag that allows libtiff-like TIFF color interpretation behavior
|
||||
lenient: bool,
|
||||
}
|
||||
|
||||
/// All the information needed to read and interpret byte slices from the underlying file, i.e. to
|
||||
@@ -750,6 +752,7 @@ impl TiffHeader {
|
||||
seen_ifds: cycles::IfdCycles::new(),
|
||||
image: None,
|
||||
scratch: Vec::new(),
|
||||
lenient: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -832,6 +835,15 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Use a lenient decoder.
|
||||
///
|
||||
/// Configuring this make the parser respect libtiff's behavior to maintain compatibility with
|
||||
/// some older softwares (e.g. some old Adobe suite products).
|
||||
pub fn with_lenient(mut self, lenient: bool) -> Decoder<R> {
|
||||
self.lenient = lenient;
|
||||
self
|
||||
}
|
||||
|
||||
// FIXME: this tuple would be a good candidate for a proxy, like IfdDecoder. That has been
|
||||
// requested in some form or another in a number of issues.
|
||||
#[expect(clippy::unnecessary_unwrap)] // Lifetimes would overlap due to return.
|
||||
@@ -846,7 +858,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
));
|
||||
}
|
||||
|
||||
let image = Image::from_reader(&mut self.value_reader, &self.directory)?;
|
||||
let image = Image::from_reader(&mut self.value_reader, &self.directory, self.lenient)?;
|
||||
Ok((
|
||||
self.image.insert(image),
|
||||
&mut self.value_reader,
|
||||
@@ -982,6 +994,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
current_ifd_pointer: self.current_ifd_pointer,
|
||||
image: core::mem::take(&mut self.image),
|
||||
scratch: core::mem::take(&mut self.scratch),
|
||||
lenient: self.lenient,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,7 +1043,11 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
/// error is returned.
|
||||
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
|
||||
let (offset, directory) = self.find_nth_ifd(ifd_index)?;
|
||||
self.image = Some(Image::from_reader(&mut self.value_reader, &directory)?);
|
||||
self.image = Some(Image::from_reader(
|
||||
&mut self.value_reader,
|
||||
&directory,
|
||||
self.lenient,
|
||||
)?);
|
||||
self.current_ifd_pointer = Some(offset);
|
||||
self.directory = directory;
|
||||
Ok(())
|
||||
@@ -1123,7 +1140,11 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
pub fn next_image(&mut self) -> TiffResult<()> {
|
||||
let next_ifd = self.directory.next();
|
||||
let (offset, directory) = self.read_chained_ifd(self.current_ifd_pointer, next_ifd)?;
|
||||
self.image = Some(Image::from_reader(&mut self.value_reader, &directory)?);
|
||||
self.image = Some(Image::from_reader(
|
||||
&mut self.value_reader,
|
||||
&directory,
|
||||
self.lenient,
|
||||
)?);
|
||||
self.current_ifd_pointer = Some(offset);
|
||||
self.directory = directory;
|
||||
Ok(())
|
||||
|
||||
@@ -16,7 +16,9 @@ macro_rules! test_image_sum {
|
||||
fn $name(file: &str, expected_type: ColorType, expected_sum: $sum_ty) {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file)
|
||||
.expect("Cannot create decoder")
|
||||
.with_lenient(true);
|
||||
|
||||
decoder.next_image().expect("Cannot read image IFD");
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
@@ -975,7 +977,7 @@ fn extra_bits_gray() {
|
||||
|
||||
#[test]
|
||||
fn extra_bits_rgb() {
|
||||
test_image_sum_u8("extra_bits_rgb_8b.tiff", ColorType::RGB(8), 0);
|
||||
test_image_sum_u8("extra_bits_rgb_8b.tiff", ColorType::RGBA(8), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user