fix: add lenient mode for tiff image decoder

This commit is contained in:
陈启 committed 2026-06-22 10:59:36 +08:00
1 parent 279851a9a9
commit 0309349e1b
2 files changed
+40 -10

No files matched your search

+12 -6
View File
@@ -97,6 +97,7 @@ pub(crate) struct Image {
/// run on samples directly and thus skip the outer byte order entirely, except potentially /// run on samples directly and thus skip the outer byte order entirely, except potentially
/// they have interior byte order treatment. /// they have interior byte order treatment.
pub decompression_to_host_endian: bool, pub decompression_to_host_endian: bool,
pub lenient: bool,
} }
/// Describes how to read a tile-aligned portion of the image. /// Describes how to read a tile-aligned portion of the image.
@@ -155,9 +156,10 @@ pub(crate) struct ReadoutLayout {
} }
impl Image { impl Image {
pub fn from_reader<R: Read + Seek>( pub fn from_reader_with_options<R: Read + Seek>(
decoder: &mut ValueReader<R>, decoder: &mut ValueReader<R>,
ifd: &Directory, ifd: &Directory,
lenient: bool,
) -> TiffResult<Image> { ) -> TiffResult<Image> {
let mut tag_reader = TagReader { decoder, ifd }; let mut tag_reader = TagReader { decoder, ifd };
@@ -500,6 +502,7 @@ impl Image {
compression_method, compression_method,
CompressionMethod::SgiLog | CompressionMethod::SgiLog24 CompressionMethod::SgiLog | CompressionMethod::SgiLog24
), ),
lenient,
}) })
} }
@@ -515,10 +518,13 @@ impl Image {
} }
pub(crate) fn colortype(&self) -> TiffResult<ColorType> { pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
let is_alpha_extra_samples = matches!( let is_alpha_extra_samples = match self.extra_samples.as_slice().get(0) {
self.extra_samples.as_slice(), Some(ExtraSamples::AssociatedAlpha) | Some(ExtraSamples::UnassociatedAlpha) => true,
[ExtraSamples::AssociatedAlpha, ..] | [ExtraSamples::UnassociatedAlpha, ..] // 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 { match self.photometric_interpretation {
PhotometricInterpretation::RGB => match self.photometric_samples { PhotometricInterpretation::RGB => match self.photometric_samples {
@@ -1192,7 +1198,7 @@ impl Image {
super::fix_endianness_and_predict( super::fix_endianness_and_predict(
out_row, out_row,
color_type.bit_depth(), color_type.bit_depth(),
samples, data_samples,
ByteOrder::native(), ByteOrder::native(),
predictor, predictor,
scratch, scratch,
+28 -4
View File
@@ -227,6 +227,7 @@ pub struct TiffHeader {
pub byte_order: ByteOrder, pub byte_order: ByteOrder,
pub variant: TiffVariant, pub variant: TiffVariant,
pub first_ifd: IfdPointer, pub first_ifd: IfdPointer,
lenient: bool,
} }
/// The representation of a TIFF decoder /// The representation of a TIFF decoder
@@ -257,6 +258,8 @@ pub struct Decoder<R> {
/// Reusable scratch buffer for the floating-point predictor. Stored here so that the /// 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. /// allocation persists across strips/tiles and images, avoiding per-row heap allocation.
scratch: Vec<u8>, 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 /// All the information needed to read and interpret byte slices from the underlying file, i.e. to
@@ -674,7 +677,11 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
} }
impl TiffHeader { impl TiffHeader {
pub fn parse<R: Read + Seek>(mut r: R) -> TiffResult<Self> { pub fn parse<R: Read + Seek>(r: R) -> TiffResult<Self> {
Self::parse_with_options(r, false)
}
pub fn parse_with_options<R: Read + Seek>(mut r: R, lenient: bool) -> TiffResult<Self> {
let mut endianess = Vec::with_capacity(2); let mut endianess = Vec::with_capacity(2);
(&mut r).take(2).read_to_end(&mut endianess)?; (&mut r).take(2).read_to_end(&mut endianess)?;
let byte_order = match &*endianess { let byte_order = match &*endianess {
@@ -727,6 +734,7 @@ impl TiffHeader {
TiffVariant::Standard TiffVariant::Standard
}, },
first_ifd: next_ifd, first_ifd: next_ifd,
lenient,
}) })
} }
@@ -750,6 +758,7 @@ impl TiffHeader {
seen_ifds: cycles::IfdCycles::new(), seen_ifds: cycles::IfdCycles::new(),
image: None, image: None,
scratch: Vec::new(), scratch: Vec::new(),
lenient: self.lenient,
} }
} }
} }
@@ -792,6 +801,11 @@ impl<R: Read + Seek> Decoder<R> {
Ok(header.open(r)) Ok(header.open(r))
} }
pub fn open_with_options(mut r: R, lenient: bool) -> TiffResult<Decoder<R>> {
let header = TiffHeader::parse_with_options(&mut r, lenient)?;
Ok(header.open(r))
}
/// Create a decoder and attempt to parse the first image directory. /// Create a decoder and attempt to parse the first image directory.
/// ///
/// To open a file without parsing any directory yet (for instance to retain a [`Decoder`] on /// To open a file without parsing any directory yet (for instance to retain a [`Decoder`] on
@@ -846,7 +860,8 @@ impl<R: Read + Seek> Decoder<R> {
)); ));
} }
let image = Image::from_reader(&mut self.value_reader, &self.directory)?; let image =
Image::from_reader_with_options(&mut self.value_reader, &self.directory, self.lenient)?;
Ok(( Ok((
self.image.insert(image), self.image.insert(image),
&mut self.value_reader, &mut self.value_reader,
@@ -982,6 +997,7 @@ impl<R: Read + Seek> Decoder<R> {
current_ifd_pointer: self.current_ifd_pointer, current_ifd_pointer: self.current_ifd_pointer,
image: core::mem::take(&mut self.image), image: core::mem::take(&mut self.image),
scratch: core::mem::take(&mut self.scratch), scratch: core::mem::take(&mut self.scratch),
lenient: self.lenient,
} }
} }
@@ -1030,7 +1046,11 @@ impl<R: Read + Seek> Decoder<R> {
/// error is returned. /// error is returned.
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> { pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
let (offset, directory) = self.find_nth_ifd(ifd_index)?; 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_with_options(
&mut self.value_reader,
&directory,
self.lenient,
)?);
self.current_ifd_pointer = Some(offset); self.current_ifd_pointer = Some(offset);
self.directory = directory; self.directory = directory;
Ok(()) Ok(())
@@ -1123,7 +1143,11 @@ impl<R: Read + Seek> Decoder<R> {
pub fn next_image(&mut self) -> TiffResult<()> { pub fn next_image(&mut self) -> TiffResult<()> {
let next_ifd = self.directory.next(); let next_ifd = self.directory.next();
let (offset, directory) = self.read_chained_ifd(self.current_ifd_pointer, next_ifd)?; 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_with_options(
&mut self.value_reader,
&directory,
self.lenient,
)?);
self.current_ifd_pointer = Some(offset); self.current_ifd_pointer = Some(offset);
self.directory = directory; self.directory = directory;
Ok(()) Ok(())