Compare commits

...
Author SHA1 Message Date
A. Molzer bf79af8de4 Add Decoder::open, deprecate new 2026-03-06 09:31:23 +01:00
A. Molzer be9b3a04b9 Expand documentation with TiffHeader 2026-03-06 06:05:32 +01:00
A. Molzer 08f1b25fac Drafting release notes 2026-03-06 05:35:45 +01:00
A. Molzer c7b97519a0 Error guarantee on decoder IFD iteration 2026-03-06 04:42:02 +01:00
A. Molzer e093e15bbe Remove redundant next_ifd attribute 2026-03-06 03:44:08 +01:00
A. Molzer 647e9b4a81 Add TiffHeader to open a file without seeking 2026-03-06 02:59:45 +01:00
A. Molzer cf8f96568e Expose Decoder::seek_to_directory 2026-03-04 08:59:41 +01:00
A. Molzer 4ff88e658d Rewrite Decoder::seek_to_image 2026-03-04 05:12:31 +01:00
A. Molzer 14ebf376f3 Move Directory from image into decoder proper
This prepares allowing to iterate over a TIFF file with invalid images
while `Image` maintains invariants. For instance we precalculate the
amount of buffer space and verify that the count of strips makes sense
in the constructor—so we do not have to redo that for every other entry
point.
2026-03-04 03:37:42 +01:00
Aurelia Molzer 37578add53 Merge pull request #344 from RunDevelopment/fix-libtiffpic-test-for-windows
Fix libtiffpic test for Windows
2026-03-03 20:42:20 +00:00
Aurelia Molzer 2b9476566b Merge pull request #346 from RunDevelopment/invert-color-as-chunks
More optimal code gen for `invert_colors`
2026-03-03 20:30:20 +00:00
Aurelia Molzer 448a0e61d1 Merge pull request #345 from mpospelova/increase-group4reader-height-size
Change Group4Reader height dimension from u16 to u32
2026-03-03 20:23:52 +00:00
RunDevelopment d872ff8751 Unify grayscale integer code paths 2026-03-03 14:54:32 +01:00
RunDevelopment 0a878888db More optimal code gen for invert_colors 2026-03-03 14:36:16 +01:00
Maria Pospelova a0a95ef86e Fix formatting 2026-03-03 13:26:09 +00:00
RunDevelopment bbb5e5a41b Use std::path::MAIN_SEPARATOR 2026-03-03 14:13:00 +01:00
RunDevelopment f74169d4af Fix libtiffpic test for Windows 2026-03-03 14:11:24 +01:00
Maria Pospelova c304a3cc13 Upgrade Group4Reader dimensions from u16 to u32 2026-03-03 10:50:27 +00:00
Sergey "Shnatsel" Davidoff 9a9193345c Merge pull request #343 from Shnatsel/float-predictor-opt
Optimize floating-point predictor encoding
2026-02-27 01:14:36 +00:00
Sergey "Shnatsel" DavidoffandClaude 22046d4272 Simplify byte shuffle to direct scatter writes
Replace chunked scratch-buffer approach with direct indexed writes
into the pre-allocated output. Simpler and faster in practice.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-27 01:02:41 +00:00
Sergey "Shnatsel" DavidoffandClaude 2c5fe2c2f5 Use chunked writes for byte shuffle in fp_predict
Replace single-byte pushes with chunk-based processing: pre-allocate
zeroed output, shuffle 4 values at a time into a stack buffer, then
copy_from_slice each byte lane into the output.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-27 00:47:13 +00:00
Sergey "Shnatsel" DavidoffandClaude eceaa1f14d Add encoding benchmark for RGB32Float with float predictor and DEFLATE
Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-27 00:27:40 +00:00
Aurelia Molzer d42db25c27 Merge pull request #342 from Shnatsel/float-predictor-encoding
Implement floating-point predictor (Predictor 3) encoding
2026-02-26 20:30:08 +00:00
Sergey "Shnatsel" DavidoffandClaude c74f23784c Vectorize byte-level horizontal differencing in fp_predict
Process the difference step in reverse 16-byte chunks, reading both
curr and prev into local arrays before writing. This makes all 16
subtractions independent, enabling autovectorization (confirmed with
SSE2: movdqu + psubb).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-26 15:45:21 +00:00
Sergey "Shnatsel" DavidoffandClaude 8b0da5939a Deduplicate fp_predict_f32 and fp_predict_f64 with a declarative macro
Replace the two near-identical floating-point predictor encoding
functions with a single fp_predict! macro parameterized on the float
type, using core::mem::size_of to derive the byte size.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-26 15:29:29 +00:00
Sergey "Shnatsel" Davidoff fdde3467fc Apply clippy lint 2026-02-26 15:00:21 +00:00
Sergey "Shnatsel" DavidoffandClaude Opus 4.5 3f2066bb20 Implement floating-point predictor (Predictor 3) encoding
Add support for encoding TIFF images with the floating-point predictor,
which improves compression ratios for floating-point image data.

The floating-point predictor works by:
1. Converting floats to big-endian bytes
2. Shuffling bytes so all first bytes are together, all second bytes, etc.
3. Applying byte-level horizontal differencing across the buffer

This complements the existing decoding support for Predictor 3.

Supported types: Gray32Float, Gray64Float, RGB32Float, RGB64Float,
RGBA32Float, RGBA64Float, CMYK32Float, CMYK64Float.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-25 23:05:35 +00:00
Aurelia Molzer 2ac8be1177 Merge pull request #337 from image-rs/cieluv
The whole libtiffpic test suite and SgiLuv compression
2026-02-20 06:47:57 +00:00
25 changed files with 895 additions and 266 deletions

No files matched your search

+13
View File
@@ -1,3 +1,16 @@
# Version 0.12.0 (unreleased)
Additions:
- Added the `TiffHeader` type. It can be used to create a `Decoder` in a state
before the first IFD and allows opening files at an arbitrary first IFD
offset rather than one indicated by an encoded header at the start.
Changes:
- The IFD iteration methods on `Decoder` (`next_image`, `seek_to_image`, etc)
now come with a strong error guarantee. They will not modify the current
directory state if they fail. This includes the failure to interpret an IFD
as a supported image in the case of `*_image` variants.
# Version 0.11.3
Additions:
+4
View File
@@ -50,6 +50,10 @@ zstd = ["dep:zstd"]
name = "lzw"
harness = false
[[bench]]
name = "encode"
harness = false
[[test]]
name = "decode_libtiffpic"
path = "tests/decode_libtiffpic.rs"
+44
View File
@@ -0,0 +1,44 @@
extern crate criterion;
extern crate tiff;
use criterion::{black_box, Criterion, Throughput};
use std::io::Cursor;
use tiff::encoder::colortype::RGB32Float;
use tiff::encoder::{Compression, DeflateLevel, Predictor, TiffEncoder};
const WIDTH: u32 = 500;
const HEIGHT: u32 = 500;
fn encode_image_f32_float_predictor_deflate(data: &[f32]) {
let mut buf = Cursor::new(Vec::with_capacity(WIDTH as usize * HEIGHT as usize * 4));
TiffEncoder::new(&mut buf)
.unwrap()
.with_predictor(Predictor::FloatingPoint)
.with_compression(Compression::Deflate(DeflateLevel::Fast))
.write_image::<RGB32Float>(WIDTH, HEIGHT, black_box(data))
.unwrap();
}
fn main() {
// Generate synthetic f32 image data with a gradient pattern
let data: Vec<f32> = (0..WIDTH as usize * HEIGHT as usize * 3)
.map(|i| {
let pixel = i / 3;
let x = (pixel % WIDTH as usize) as f32 / WIDTH as f32;
let y = (pixel / WIDTH as usize) as f32 / HEIGHT as f32;
x * 0.5 + y * 0.5
})
.collect();
let mut c = Criterion::default().configure_from_args();
let mut group = c.benchmark_group("tiff-encode");
group
.sample_size(20)
.throughput(Throughput::Bytes(
(WIDTH as u64) * (HEIGHT as u64) * 3 * std::mem::size_of::<f32>() as u64,
))
.bench_function("f32-float-predictor-deflate-fast", |b| {
b.iter(|| encode_image_f32_float_predictor_deflate(&data))
});
}
+4 -5
View File
@@ -8,13 +8,12 @@ use tiff::decoder::Decoder;
fn read_image(image: &[u8]) {
let image = std::io::Cursor::new(image);
let decoder = Decoder::new(black_box(image));
let mut reader = decoder.unwrap();
let decoder = Decoder::open(black_box(image));
while {
let mut reader = decoder.unwrap();
while reader.more_images() {
reader.read_image().unwrap();
reader.more_images()
} {}
}
}
fn main() {
+1 -1
View File
@@ -8,7 +8,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = std::fs::File::open(image)?;
let io = std::io::BufReader::new(file);
let mut reader = Decoder::new(io)?;
let mut reader = Decoder::open(io)?;
let mut data = DecodingResult::I8(vec![]);
+4 -7
View File
@@ -10,14 +10,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = std::fs::File::open(image)?;
let io = std::io::BufReader::new(file);
let mut reader = Decoder::new(io)?;
let mut reader = Decoder::open(io)?;
loop {
while reader.more_images() {
reader.next_directory()?;
ls_dir(&mut reader);
if !reader.more_images() {
break;
}
}
Ok(())
@@ -29,7 +26,7 @@ fn ls_dir<R: std::io::Read + std::io::Seek>(reader: &mut Decoder<R>) {
}
println!("Name\tHex\tType\tCount");
let ifd = reader.image_ifd();
let ifd = reader.current_ifd();
for (tag, entry) in ifd.directory().iter() {
let name: &'static str = match tag {
+26 -4
View File
@@ -64,7 +64,6 @@ impl TileAttributes {
#[derive(Debug)]
pub(crate) struct Image {
pub ifd: Option<Directory>,
pub width: u32,
pub height: u32,
pub bits_per_sample: u8,
@@ -147,11 +146,35 @@ pub(crate) struct ReadoutLayout {
}
impl Image {
pub(crate) fn no_image() -> Image {
Image {
width: 0,
height: 0,
bits_per_sample: 1,
samples: 1,
extra_samples: vec![],
photometric_samples: 1,
sample_format: SampleFormat::Uint,
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
compression_method: CompressionMethod::None,
jpeg_tables: None,
predictor: Predictor::None,
chunk_type: ChunkType::Strip,
planar_config: PlanarConfiguration::Chunky,
strip_decoder: None,
tile_attributes: None,
chunk_offsets: Vec::new(),
chunk_bytes: Vec::new(),
chroma_subsampling: (2, 2),
decompression_to_host_endian: false,
}
}
pub fn from_reader<R: Read + Seek>(
decoder: &mut ValueReader<R>,
ifd: Directory,
ifd: &Directory,
) -> TiffResult<Image> {
let mut tag_reader = TagReader { decoder, ifd: &ifd };
let mut tag_reader = TagReader { decoder, ifd };
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
@@ -435,7 +458,6 @@ impl Image {
}
Ok(Image {
ifd: Some(ifd),
width,
height,
bits_per_sample,
+337 -166
View File
@@ -4,8 +4,8 @@ use std::io::{self, Read, Seek};
use std::num::NonZeroUsize;
use crate::tags::{
CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor,
SampleFormat, Tag, Type, ValueBuffer,
ByteOrder, IfdPointer, PlanarConfiguration, Predictor, SampleFormat, Tag, TiffVariant, Type,
ValueBuffer,
};
use crate::{
bytecast, ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError,
@@ -14,7 +14,7 @@ use crate::{
use half::f16;
use self::image::Image;
use self::stream::{ByteOrder, EndianReader};
use self::stream::EndianReader;
mod cycles;
pub mod ifd;
@@ -505,6 +505,14 @@ impl Default for Limits {
}
}
/// Describe the header of TIFF file, including where to find the first image directory.
#[derive(Debug)]
pub struct TiffHeader {
pub byte_order: ByteOrder,
pub variant: TiffVariant,
pub first_ifd: IfdPointer,
}
/// The representation of a TIFF decoder
///
/// Currently does not support decoding of interlaced images
@@ -516,8 +524,8 @@ where
/// There are grouped for borrow checker reasons. This allows us to implement methods that
/// borrow the stream access and the other fields mutably at the same time.
value_reader: ValueReader<R>,
current_ifd: Option<IfdPointer>,
next_ifd: Option<IfdPointer>,
current_ifd_pointer: Option<IfdPointer>,
directory: Directory,
/// The IFDs we visited already in this chain of IFDs.
ifd_offsets: Vec<IfdPointer>,
/// Map from the ifd into the `ifd_offsets` ordered list.
@@ -536,6 +544,8 @@ struct ValueReader<R> {
}
/// Reads a directory's tag values from an underlying stream.
///
/// Created by [`Decoder::current_ifd`] and [`Decoder::read_directory_tags`].
pub struct IfdDecoder<'lt> {
inner: tag_reader::TagReader<'lt, dyn tag_reader::EntryDecoder + 'lt>,
}
@@ -717,46 +727,29 @@ fn invert_colors(
sample_format: SampleFormat,
) -> TiffResult<()> {
match (color_type, sample_format) {
// Where pixels do not cross a byte boundary
(ColorType::Gray(1 | 2 | 4 | 8), SampleFormat::Uint) => {
(ColorType::Gray(1 | 2 | 4 | 8 | 16 | 32 | 64), SampleFormat::Uint) => {
// It's guaranteed that pixels do not cross a byte boundary.
// To invert a single N-bit pixel, we can calculate:
// (2^N - 1) - x = (2^N - 1) + !x + 1 (because -x = !x + 1)
// = 2^n + !x
// = !x (because modular arithmetic)
//
// Further, since !x operates on bits independently, negating bytes
// also works for multi-byte pixels.
for x in buf {
// Equivalent to both of the following:
//
// *x = 0xff - *x
// *x = !*x
//
// since -x = !x+1
*x = !*x;
}
}
(ColorType::Gray(16), SampleFormat::Uint) => {
for x in buf.chunks_mut(2) {
let v = u16::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(0xffff - v).to_ne_bytes());
}
}
(ColorType::Gray(32), SampleFormat::Uint) => {
for x in buf.chunks_mut(4) {
let v = u32::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(0xffff_ffff - v).to_ne_bytes());
}
}
(ColorType::Gray(64), SampleFormat::Uint) => {
for x in buf.chunks_mut(8) {
let v = u64::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(0xffff_ffff_ffff_ffff - v).to_ne_bytes());
}
}
(ColorType::Gray(32), SampleFormat::IEEEFP) => {
for x in buf.chunks_mut(4) {
let v = f32::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
for x in buf.as_chunks_mut::<4>().0 {
let v = f32::from_ne_bytes(*x);
*x = (1.0 - v).to_ne_bytes();
}
}
(ColorType::Gray(64), SampleFormat::IEEEFP) => {
for x in buf.chunks_mut(8) {
let v = f64::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
for x in buf.as_chunks_mut::<8>().0 {
let v = f64::from_ne_bytes(*x);
*x = (1.0 - v).to_ne_bytes();
}
}
_ => {
@@ -783,8 +776,8 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
host.convert_endian_bytes(class, buf, byte_order);
}
impl<R: Read + Seek> Decoder<R> {
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
impl TiffHeader {
pub fn parse<R: Read + Seek>(mut r: R) -> TiffResult<Self> {
let mut endianess = Vec::with_capacity(2);
(&mut r).take(2).read_to_end(&mut endianess)?;
let byte_order = match &*endianess {
@@ -796,6 +789,7 @@ impl<R: Read + Seek> Decoder<R> {
))
}
};
let mut reader = EndianReader::new(r, byte_order);
let bigtiff = match reader.read_u16()? {
@@ -822,119 +816,155 @@ impl<R: Read + Seek> Decoder<R> {
}
};
let next_ifd = if bigtiff {
Some(reader.read_u64()?)
let next_ifd = IfdPointer(if bigtiff {
reader.read_u64()?
} else {
Some(u64::from(reader.read_u32()?))
u64::from(reader.read_u32()?)
});
Ok(TiffHeader {
byte_order,
variant: if bigtiff {
TiffVariant::BigTiff
} else {
TiffVariant::Standard
},
first_ifd: next_ifd,
})
}
.map(IfdPointer);
let current_ifd = *next_ifd.as_ref().unwrap();
let ifd_offsets = vec![current_ifd];
/// Open a file, without seeking to any IFD yet.
pub fn open<R: Read + Seek>(&self, r: R) -> Decoder<R> {
let reader = EndianReader::new(r, self.byte_order);
let mut decoder = Decoder {
Decoder {
value_reader: ValueReader {
reader,
bigtiff,
bigtiff: matches!(self.variant, TiffVariant::BigTiff),
limits: Default::default(),
},
next_ifd,
ifd_offsets,
current_ifd: None,
seen_ifds: cycles::IfdCycles::new(),
image: Image {
ifd: None,
width: 0,
height: 0,
bits_per_sample: 1,
samples: 1,
extra_samples: vec![],
photometric_samples: 1,
sample_format: SampleFormat::Uint,
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
compression_method: CompressionMethod::None,
jpeg_tables: None,
predictor: Predictor::None,
chunk_type: ChunkType::Strip,
planar_config: PlanarConfiguration::Chunky,
strip_decoder: None,
tile_attributes: None,
chunk_offsets: Vec::new(),
chunk_bytes: Vec::new(),
chroma_subsampling: (2, 2),
decompression_to_host_endian: false,
ifd_offsets: vec![],
current_ifd_pointer: None,
directory: {
let mut dir = Directory::empty();
dir.set_next(Some(self.first_ifd));
dir
},
};
seen_ifds: cycles::IfdCycles::new(),
image: Image::no_image(),
}
}
}
impl<R: Read + Seek> Decoder<R> {
/// 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
/// errors in the directory), use [`TiffHeader::parse`] and [`TiffHeader::open`] instead..
#[deprecated = "Prefer `Decoder::open`. These methods will become aliases in a future major release."]
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
let header = TiffHeader::parse(&mut r)?;
let mut decoder = header.open(r);
decoder.next_image()?;
Ok(decoder)
}
/// Parse the image header from the underlying file and create a decoder positioned before its
/// first directory.
///
/// The [`TiffHeader`] type allows opening a file with a custom.
pub fn open(mut r: R) -> TiffResult<Decoder<R>> {
let header = TiffHeader::parse(&mut r)?;
Ok(header.open(r))
}
/// Configure allocation limits for all subsequent operations.
///
/// Limits apply to allocated return values such as from [`Decoder::read_image`] and
/// reallocations on [`ValueBuffer`] and [`DecodingResult`] arguments when they are passed as
/// mutable references to methods such as [`Decoder::read_image_to_buffer`]. They carry over to
/// the decoder returned form [`Self::current_ifd`].
pub fn with_limits(mut self, limits: Limits) -> Decoder<R> {
self.value_reader.limits = limits;
self
}
pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> {
Ok((self.image().width, self.image().height))
Ok((self.image.width, self.image.height))
}
pub fn colortype(&mut self) -> TiffResult<ColorType> {
self.image().colortype()
self.image.colortype()
}
/// The offset of the directory representing the current image.
pub fn ifd_pointer(&mut self) -> Option<IfdPointer> {
self.current_ifd
}
fn image(&self) -> &Image {
&self.image
self.current_ifd_pointer
}
/// Loads the IFD at the specified index in the list, if one exists
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
// Check whether we have seen this IFD before, if so then the index will be less than the length of the list of ifd offsets
if ifd_index >= self.ifd_offsets.len() {
// We possibly need to load in the next IFD
if self.next_ifd.is_none() {
self.current_ifd = None;
return Err(TiffError::FormatError(
TiffFormatError::ImageFileDirectoryNotFound,
));
///
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an
/// error is returned.
pub fn seek_to_directory(&mut self, ifd_index: usize) -> TiffResult<()> {
let (offset, directory) = self.find_nth_ifd(ifd_index)?;
self.image = Image::no_image();
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
}
/// Loads the IFD at the specified index in the list, if one exists
///
/// The directory is then decoded as an image.
///
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an
/// 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 = Image::from_reader(&mut self.value_reader, &directory)?;
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
}
/// Change the directory to the nth in the linked-list chain of IFDs.
fn find_nth_ifd(&mut self, ifd_index: usize) -> TiffResult<(IfdPointer, Directory)> {
// Check whether we have seen this IFD before, if so then the index will be less than the
// length of the list of ifd offsets If the index is within the list of ifds then we can
// load the selected image/IFD. Otherwise we iterate the chain until the selected index.
if let Some(&ifd_offset) = self.ifd_offsets.get(ifd_index) {
let directory = self.value_reader.read_directory(ifd_offset)?;
Ok((ifd_offset, directory))
} else {
// We do not know if we are at the last IFD in the chain if we previous did seek back.
// The `read_next_ifd_pointer` correctly handles this.
let mut chained_ifd = self.current_ifd_pointer;
let mut next_ifd = self.directory.next();
loop {
// Follow the list until we find the one we want, or we reach the end, whichever happens first
let ifd = self.next_ifd()?;
if ifd.next().is_none() {
break;
}
let (ifd_pointer, ifd) = self.read_chained_ifd(chained_ifd, next_ifd)?;
chained_ifd = Some(ifd_pointer);
next_ifd = ifd.next();
if ifd_index < self.ifd_offsets.len() {
break;
// We only end up here if initially `ifd_index >= self.ifd_offsets.len()` and
// each loop iteration increases the length of `self.ifd_offsets` by (at most)
// one.
debug_assert!(ifd_index + 1 == self.ifd_offsets.len());
return Ok((ifd_pointer, ifd));
}
}
}
}
// If the index is within the list of ifds then we can load the selected image/IFD
if let Some(ifd_offset) = self.ifd_offsets.get(ifd_index) {
let ifd = self.value_reader.read_directory(*ifd_offset)?;
self.next_ifd = ifd.next();
self.current_ifd = Some(*ifd_offset);
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
Ok(())
} else {
Err(TiffError::FormatError(
TiffFormatError::ImageFileDirectoryNotFound,
))
}
}
fn next_ifd(&mut self) -> TiffResult<Directory> {
let Some(next_ifd) = self.next_ifd.take() else {
fn read_chained_ifd(
&mut self,
current_ifd_pointer: Option<IfdPointer>,
next_ifd: Option<IfdPointer>,
) -> TiffResult<(IfdPointer, Directory)> {
let Some(next_ifd) = next_ifd else {
return Err(TiffError::FormatError(
TiffFormatError::ImageFileDirectoryNotFound,
));
@@ -946,28 +976,48 @@ impl<R: Read + Seek> Decoder<R> {
self.seen_ifds.insert_next(next_ifd, ifd.next())?;
// Extend the list of known IFD offsets in this chain, if needed.
if self.ifd_offsets.last().copied() == self.current_ifd {
if self.ifd_offsets.last().copied() == current_ifd_pointer {
self.ifd_offsets.push(next_ifd);
}
self.current_ifd = Some(next_ifd);
self.next_ifd = ifd.next();
Ok((next_ifd, ifd))
}
Ok(ifd)
/// Reads the next IFD.
///
/// If there is no further image in the TIFF file a format error is returned.
/// To determine whether there are more images call `TIFFDecoder::more_images` instead.
///
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an error is
/// returned.
pub fn next_directory(&mut self) -> TiffResult<()> {
let next_ifd = self.directory.next();
let (offset, directory) = self.read_chained_ifd(self.current_ifd_pointer, next_ifd)?;
self.image = Image::no_image();
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
}
/// Reads in the next image.
///
/// If there is no further image in the TIFF file a format error is returned.
/// To determine whether there are more images call `TIFFDecoder::more_images` instead.
///
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an error is
/// returned.
pub fn next_image(&mut self) -> TiffResult<()> {
let ifd = self.next_ifd()?;
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
let next_ifd = self.directory.next();
let (offset, directory) = self.read_chained_ifd(self.current_ifd_pointer, next_ifd)?;
self.image = Image::from_reader(&mut self.value_reader, &directory)?;
self.current_ifd_pointer = Some(offset);
self.directory = directory;
Ok(())
}
/// Returns `true` if there is at least one more image available.
pub fn more_images(&self) -> bool {
self.next_ifd.is_some()
self.directory.next().is_some()
}
/// Returns the byte_order of the file.
@@ -1116,10 +1166,10 @@ impl<R: Read + Seek> Decoder<R> {
}
fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> {
if expected != self.image().chunk_type {
if expected != self.image.chunk_type {
return Err(TiffError::UsageError(UsageError::InvalidChunkType(
expected,
self.image().chunk_type,
self.image.chunk_type,
)));
}
@@ -1128,27 +1178,27 @@ impl<R: Read + Seek> Decoder<R> {
/// The chunk type (Strips / Tiles) of the image
pub fn get_chunk_type(&self) -> ChunkType {
self.image().chunk_type
self.image.chunk_type
}
/// Number of strips in image
pub fn strip_count(&mut self) -> TiffResult<u32> {
self.check_chunk_type(ChunkType::Strip)?;
let rows_per_strip = self.image().strip_decoder.as_ref().unwrap().rows_per_strip;
let rows_per_strip = self.image.strip_decoder.as_ref().unwrap().rows_per_strip;
if rows_per_strip == 0 {
return Ok(0);
}
// rows_per_strip - 1 can never fail since we know it's at least 1
let height = match self.image().height.checked_add(rows_per_strip - 1) {
let height = match self.image.height.checked_add(rows_per_strip - 1) {
Some(h) => h,
None => return Err(TiffError::IntSizeError),
};
let strips = match self.image().planar_config {
let strips = match self.image.planar_config {
PlanarConfiguration::Chunky => height / rows_per_strip,
PlanarConfiguration::Planar => height / rows_per_strip * self.image().samples as u32,
PlanarConfiguration::Planar => height / rows_per_strip * self.image.samples as u32,
};
Ok(strips)
@@ -1157,7 +1207,7 @@ impl<R: Read + Seek> Decoder<R> {
/// Number of tiles in image
pub fn tile_count(&mut self) -> TiffResult<u32> {
self.check_chunk_type(ChunkType::Tile)?;
Ok(u32::try_from(self.image().chunk_offsets.len())?)
Ok(u32::try_from(self.image.chunk_offsets.len())?)
}
fn read_chunk_to_bytes(
@@ -1186,8 +1236,8 @@ impl<R: Read + Seek> Decoder<R> {
&mut self,
chunk_index: u32,
) -> TiffResult<BufferLayoutPreference> {
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
let readout = self.image().readout_for_size(data_dims.0, data_dims.1)?;
let data_dims = self.image.chunk_data_dimensions(chunk_index)?;
let readout = self.image.readout_for_size(data_dims.0, data_dims.1)?;
let extent = readout.result_extent_for_planes(0..1)?;
let sample_type = extent.sample_type();
@@ -1202,7 +1252,7 @@ impl<R: Read + Seek> Decoder<R> {
planes: 1,
plane_stride,
complete_len: layout.size(),
sample_format: self.image().sample_format,
sample_format: self.image.sample_format,
sample_type: Some(sample_type),
})
}
@@ -1219,15 +1269,15 @@ impl<R: Read + Seek> Decoder<R> {
&mut self,
code_unit: TiffCodingUnit,
) -> TiffResult<BufferLayoutPreference> {
match self.image().planar_config {
match self.image.planar_config {
PlanarConfiguration::Chunky => return self.image_chunk_buffer_layout(code_unit.0),
PlanarConfiguration::Planar => {}
}
let (width, height) = self.image().chunk_data_dimensions(code_unit.0)?;
let (width, height) = self.image.chunk_data_dimensions(code_unit.0)?;
let layout = self
.image()
.image
.readout_for_size(width, height)?
.to_plane_layout()?;
@@ -1245,9 +1295,9 @@ impl<R: Read + Seek> Decoder<R> {
///
/// Note that for planar images each chunk contains only one sample of the underlying data.
pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<DecodingResult> {
let (width, height) = self.image().chunk_data_dimensions(chunk_index)?;
let (width, height) = self.image.chunk_data_dimensions(chunk_index)?;
let readout = self.image().readout_for_size(width, height)?;
let readout = self.image.readout_for_size(width, height)?;
let mut result = readout
.result_extent_for_planes(0..1)?
@@ -1266,9 +1316,9 @@ impl<R: Read + Seek> Decoder<R> {
///
/// Note that for planar images each chunk contains only one sample of the underlying data.
pub fn read_chunk_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> {
let (width, height) = self.image().chunk_data_dimensions(chunk_index)?;
let (width, height) = self.image.chunk_data_dimensions(chunk_index)?;
let layout = self.image().readout_for_size(width, height)?;
let layout = self.image.readout_for_size(width, height)?;
layout.assert_min_layout(buffer)?;
self.read_chunk_to_bytes(buffer, chunk_index, &layout)?;
@@ -1289,9 +1339,9 @@ impl<R: Read + Seek> Decoder<R> {
chunk_index: u32,
output_width: usize,
) -> TiffResult<()> {
let (width, height) = self.image().chunk_data_dimensions(chunk_index)?;
let (width, height) = self.image.chunk_data_dimensions(chunk_index)?;
let mut layout = self.image().readout_for_size(width, height)?;
let mut layout = self.image.readout_for_size(width, height)?;
layout.set_row_stride(output_width)?;
let extent = layout.result_extent_for_planes(0..1)?;
@@ -1321,8 +1371,8 @@ impl<R: Read + Seek> Decoder<R> {
slice: TiffCodingUnit,
buffer: &mut [u8],
) -> TiffResult<()> {
let (width, height) = self.image().chunk_data_dimensions(slice.0)?;
let readout = self.image().readout_for_size(width, height)?;
let (width, height) = self.image.chunk_data_dimensions(slice.0)?;
let readout = self.image.readout_for_size(width, height)?;
let ref layout @ image::PlaneLayout {
ref plane_offsets,
@@ -1344,7 +1394,7 @@ impl<R: Read + Seek> Decoder<R> {
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
let chunk = slice.0 + idx as u32 * readout.chunks_per_plane;
self.goto_offset_u64(self.image().chunk_offsets[chunk as usize])?;
self.goto_offset_u64(self.image.chunk_offsets[chunk as usize])?;
self.image.expand_chunk(
&mut self.value_reader,
@@ -1360,13 +1410,13 @@ impl<R: Read + Seek> Decoder<R> {
/// Returns the default chunk size for the current image. Any given chunk in the image is at most as large as
/// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`.
pub fn chunk_dimensions(&self) -> (u32, u32) {
self.image().chunk_dimensions().unwrap()
self.image.chunk_dimensions().unwrap()
}
/// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk,
/// minus any padding.
pub fn chunk_data_dimensions(&self, chunk_index: u32) -> (u32, u32) {
self.image()
self.image
.chunk_data_dimensions(chunk_index)
.expect("invalid chunk_index")
}
@@ -1385,7 +1435,7 @@ impl<R: Read + Seek> Decoder<R> {
/// indicate the layout needed to read the first data plane. This will be fixed in a future
/// major version of `tiff`.
pub fn image_buffer_layout(&mut self) -> TiffResult<BufferLayoutPreference> {
let layout = self.image().readout_for_image()?.to_plane_layout()?;
let layout = self.image.readout_for_image()?.to_plane_layout()?;
Ok(BufferLayoutPreference::from_planes(&layout))
}
@@ -1409,7 +1459,7 @@ impl<R: Read + Seek> Decoder<R> {
/// replaced, to ensure that existing code will not run into unexpectedly large allocations
/// that will error on limits instead.
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
let readout = self.image().readout_for_image()?;
let readout = self.image.readout_for_image()?;
let mut result = readout
.result_extent_for_planes(0..1)?
@@ -1438,9 +1488,10 @@ impl<R: Read + Seek> Decoder<R> {
/// let mut result = DecodingResult::U8(vec![]);
///
/// let mut reader = /* */
/// # Decoder::new(std::io::Cursor::new(include_bytes!(concat!(
/// # Decoder::open(std::io::Cursor::new(include_bytes!(concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # )))).unwrap();
/// # reader.next_image().unwrap();
/// let layout = reader.read_image_to_buffer(&mut result)?;
///
/// if result.as_buffer(0).as_bytes().len() < layout.complete_len {
@@ -1458,10 +1509,10 @@ impl<R: Read + Seek> Decoder<R> {
/// let mut result = DecodingResult::I8(vec![]);
///
/// let mut reader = /* */
/// # Decoder::new(std::io::Cursor::new(include_bytes!(concat!(
/// # Decoder::open(std::io::Cursor::new(include_bytes!(concat!(
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
/// # )))).unwrap();
///
/// # reader.next_image().unwrap();
/// reader.read_image_to_buffer(&mut result)?;
///
/// # Ok::<_, tiff::TiffError>(())
@@ -1470,7 +1521,7 @@ impl<R: Read + Seek> Decoder<R> {
&mut self,
result: &mut DecodingResult,
) -> TiffResult<BufferLayoutPreference> {
let readout = self.image().readout_for_image()?;
let readout = self.image.readout_for_image()?;
let planes = readout.to_plane_layout()?;
let num_planes = if planes.total_bytes <= self.value_reader.limits.decoding_buffer_size {
@@ -1501,7 +1552,7 @@ impl<R: Read + Seek> Decoder<R> {
/// Returns an error if the buffer fits less than one plane. In particular, for non-planar
/// images returns an error if the buffer does not fit the required size.
pub fn read_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
let readout = self.image().readout_for_image()?;
let readout = self.image.readout_for_image()?;
let ref layout @ image::PlaneLayout {
ref plane_offsets,
@@ -1525,7 +1576,7 @@ impl<R: Read + Seek> Decoder<R> {
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
let chunk = chunk + idx as u32 * readout.chunks_per_plane;
self.goto_offset_u64(self.image().chunk_offsets[chunk as usize])?;
self.goto_offset_u64(self.image.chunk_offsets[chunk as usize])?;
self.image.expand_chunk(
&mut self.value_reader,
@@ -1539,12 +1590,16 @@ impl<R: Read + Seek> Decoder<R> {
Ok(())
}
/// Get the IFD decoder for our current image IFD.
pub fn image_ifd(&mut self) -> IfdDecoder<'_> {
/// Get the IFD decoder for our current IFD.
///
/// This may be used with any directory, valid image or otherwise. When the decoder has not
/// read a directory yet (a state after [`TiffHeader::open`]) the decoder will represent an
/// empty directory with only the next pointer set.
pub fn current_ifd(&mut self) -> IfdDecoder<'_> {
IfdDecoder {
inner: tag_reader::TagReader {
decoder: &mut self.value_reader,
ifd: self.image.ifd.as_ref().unwrap(),
ifd: &self.directory,
},
}
}
@@ -1562,7 +1617,9 @@ impl<R: Read + Seek> Decoder<R> {
///
/// # use std::io::Cursor;
/// # let mut data = Cursor::new(vec![0]);
/// let mut decoder = Decoder::new(&mut data).unwrap();
/// let mut decoder = Decoder::open(&mut data)?;
///
/// decoder.next_directory()?;
/// let sub_ifds = decoder.get_tag(Tag::SubIfd)?.into_ifd_vec()?;
///
/// for ifd in sub_ifds {
@@ -1585,13 +1642,13 @@ impl<R: Read + Seek> Decoder<R> {
/// Tries to retrieve a tag from the current image directory.
/// Return `Ok(None)` if the tag is not present.
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
self.image_ifd().find_tag(tag)
self.current_ifd().find_tag(tag)
}
/// Tries to retrieve a tag in the current image directory and convert it to the desired
/// unsigned type.
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
self.image_ifd().find_tag_unsigned(tag)
self.current_ifd().find_tag_unsigned(tag)
}
/// Tries to retrieve a vector of all a tag's values and convert them to the desired unsigned
@@ -1600,19 +1657,19 @@ impl<R: Read + Seek> Decoder<R> {
&mut self,
tag: Tag,
) -> TiffResult<Option<Vec<T>>> {
self.image_ifd().find_tag_unsigned_vec(tag)
self.current_ifd().find_tag_unsigned_vec(tag)
}
/// Tries to retrieve a tag from the current image directory and convert it to the desired
/// unsigned type. Returns an error if the tag is not present.
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
self.image_ifd().get_tag_unsigned(tag)
self.current_ifd().get_tag_unsigned(tag)
}
/// Tries to retrieve a tag from the current image directory.
/// Returns an error if the tag is not present
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
self.image_ifd().get_tag(tag)
self.current_ifd().get_tag(tag)
}
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
@@ -1667,7 +1724,7 @@ impl<R: Read + Seek> Decoder<R> {
}
pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
self.image_ifd().tag_iter()
self.current_ifd().tag_iter()
}
}
@@ -1911,7 +1968,7 @@ impl<'l> IfdDecoder<'l> {
mod tests {
use super::Decoder;
use crate::{
bytecast,
bytecast, encoder,
tags::{ByteOrder, Tag, ValueBuffer},
};
@@ -1923,9 +1980,11 @@ mod tests {
))
.unwrap();
let mut decoder = Decoder::new(file).unwrap();
let mut decoder = Decoder::open(file).unwrap();
let file_bo = decoder.byte_order();
let mut ifd = decoder.image_ifd();
decoder.next_image().unwrap();
let mut ifd = decoder.current_ifd();
{
let value = ifd
@@ -1972,4 +2031,116 @@ mod tests {
assert_eq!(&by_bytes[..3], &[8, 8, 8]);
}
}
#[test]
fn non_image_directory_between() {
let mut cursor = std::io::Cursor::new(vec![]);
{
let mut encoder = encoder::TiffEncoder::new(&mut cursor).unwrap();
encoder
.write_image::<encoder::colortype::Gray8>(1, 1, &[0])
.unwrap();
encoder.image_directory().unwrap().finish().unwrap();
encoder
.write_image::<encoder::colortype::Gray8>(1, 1, &[0])
.unwrap();
}
cursor.set_position(0);
let (first_ptr, second_ptr, third_ptr);
{
let mut decoder = Decoder::open(&mut cursor).unwrap();
decoder.next_image().unwrap();
first_ptr = decoder.ifd_pointer();
let first_ifd = decoder.current_ifd();
assert!(first_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(decoder.more_images());
assert!(decoder.next_image().is_err());
let broken_ifd = decoder.current_ifd();
assert!(broken_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(decoder.next_directory().is_ok());
second_ptr = decoder.ifd_pointer();
let second_ifd = decoder.current_ifd();
assert!(second_ifd.directory().get(Tag::ImageWidth).is_none());
assert!(decoder.more_images());
assert!(decoder.next_image().is_ok());
third_ptr = decoder.ifd_pointer();
let third_ifd = decoder.current_ifd();
assert!(third_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(!decoder.more_images());
assert!(decoder.seek_to_image(1).is_err());
assert_eq!(decoder.ifd_pointer(), third_ptr);
assert!(decoder.seek_to_directory(1).is_ok());
assert_eq!(decoder.ifd_pointer(), second_ptr);
}
cursor.set_position(0);
{
let mut seek_immediately = Decoder::open(&mut cursor).unwrap();
assert!(seek_immediately.seek_to_image(0).is_ok());
assert_eq!(seek_immediately.ifd_pointer(), first_ptr);
assert!(seek_immediately.seek_to_image(1).is_err());
assert!(seek_immediately.seek_to_image(2).is_ok());
assert_eq!(seek_immediately.ifd_pointer(), third_ptr);
assert!(seek_immediately
.current_ifd()
.directory()
.get(Tag::ImageWidth)
.is_some());
}
cursor.set_position(0);
{
let mut seek_immediately = Decoder::open(&mut cursor).unwrap();
assert!(seek_immediately.seek_to_directory(1).is_ok());
assert_eq!(seek_immediately.ifd_pointer(), second_ptr);
assert!(seek_immediately
.current_ifd()
.directory()
.get(Tag::ImageWidth)
.is_none());
}
}
#[test]
fn non_image_directory_start() {
let mut cursor = std::io::Cursor::new(vec![]);
{
let mut encoder = encoder::TiffEncoder::new(&mut cursor).unwrap();
encoder.image_directory().unwrap().finish().unwrap();
encoder
.write_image::<encoder::colortype::Gray8>(1, 1, &[0])
.unwrap();
}
cursor.set_position(0);
let header = super::TiffHeader::parse(&mut cursor).unwrap();
let mut decoder = header.open(cursor);
let indicated_next = decoder.current_ifd().directory().next();
assert!(decoder.more_images());
assert_eq!(decoder.ifd_pointer(), None);
assert!(decoder.next_directory().is_ok());
let first_ifd = decoder.current_ifd();
assert!(first_ifd.directory().get(Tag::ImageWidth).is_none());
assert!(decoder.more_images());
assert_eq!(decoder.ifd_pointer(), indicated_next);
assert!(decoder.next_image().is_ok());
let second_ifd = decoder.current_ifd();
assert!(second_ifd.directory().get(Tag::ImageWidth).is_some());
assert!(!decoder.more_images());
}
}
+3 -3
View File
@@ -260,9 +260,9 @@ impl<R: Read> Read for PackBitsReader<R> {
pub struct Group4Reader<R: Read> {
decoder: fax34::decoder::Group4Decoder<io::Bytes<io::BufReader<io::Take<R>>>>,
line_buf: io::Cursor<Vec<u8>>,
height: u16,
height: u32,
width: u16,
y: u16,
y: u32,
}
#[cfg(feature = "fax")]
@@ -273,7 +273,7 @@ impl<R: Read> Group4Reader<R> {
compressed_length: u64,
) -> crate::TiffResult<Self> {
let width = u16::try_from(dimensions.0)?;
let height = u16::try_from(dimensions.1)?;
let height = dimensions.1;
Ok(Self {
decoder: fax34::decoder::Group4Decoder::new(
+144 -8
View File
@@ -1,5 +1,97 @@
use crate::tags::{PhotometricInterpretation, SampleFormat};
/// Apply floating-point predictor encoding to a row of floating-point samples.
///
/// The floating-point predictor works by:
/// 1. Converting each value to big-endian bytes
/// 2. Shuffling bytes so all first bytes are together, all second bytes, etc.
/// 3. Applying byte-level horizontal difference (wrapping_sub) across the entire buffer
///
/// The decoder reverses this by:
/// 1. Applying cumulative sum (wrapping_add) on the entire byte stream
/// 2. De-shuffling bytes back into float values
macro_rules! fp_predict {
($name:ident, $float_type:ty) => {
fn $name(row: &[$float_type], samples: usize, result: &mut Vec<u8>) {
const BYTE_SIZE: usize = core::mem::size_of::<$float_type>();
let num_values = row.len();
let byte_count = num_values * BYTE_SIZE;
let start_len = result.len();
// Allocate zeroed space so we can write to arbitrary positions
result.resize(start_len + byte_count, 0);
let output = &mut result[start_len..];
// Step 1 & 2: Convert to big-endian and scatter bytes
for (i, &value) in row.iter().enumerate() {
let bytes = value.to_be_bytes();
for b in 0..BYTE_SIZE {
output[b * num_values + i] = bytes[b];
}
}
// Step 3: Apply byte-level horizontal difference across the entire buffer.
// The prediction spans across quarter boundaries, just like the decoder's cumulative sum.
//
// We process elements in reverse in batches of 16 to enable autovectorization.
// Reverse order ensures that when we compute output[i] - output[i-samples],
// the value at output[i-samples] hasn't been overwritten yet (critical when
// samples < 16, which is the common case: 1, 3, 4, 5).
//
// 8 bits * 16 = 128 bits, the width of a typical SIMD register.
// Even if the target has no vector instructions, this still benefits from
// instruction-level parallelism, since unlike in decoding, all subtractions
// within a chunk are truly independent of each other.
let output = &mut result[start_len..byte_count + start_len];
let diff_len = byte_count - samples;
let remainder_len = diff_len % 16;
// Process full 16-byte chunks in reverse.
// We iterate from the end so that predecessor values (at lower indices)
// are still in their original state when we read them.
let mut chunk_end = byte_count;
while chunk_end >= samples + remainder_len + 16 {
let chunk_start = chunk_end - 16;
let prev_start = chunk_start - samples;
// Read all values in the chunk and their predecessors first.
// This makes all subtractions below independent, enabling vectorization.
let curr: [u8; 16] = output[chunk_start..chunk_end].try_into().unwrap();
let prev: [u8; 16] = output[prev_start..prev_start + 16].try_into().unwrap();
let chunk = &mut output[chunk_start..chunk_end];
chunk[0] = curr[0].wrapping_sub(prev[0]);
chunk[1] = curr[1].wrapping_sub(prev[1]);
chunk[2] = curr[2].wrapping_sub(prev[2]);
chunk[3] = curr[3].wrapping_sub(prev[3]);
chunk[4] = curr[4].wrapping_sub(prev[4]);
chunk[5] = curr[5].wrapping_sub(prev[5]);
chunk[6] = curr[6].wrapping_sub(prev[6]);
chunk[7] = curr[7].wrapping_sub(prev[7]);
chunk[8] = curr[8].wrapping_sub(prev[8]);
chunk[9] = curr[9].wrapping_sub(prev[9]);
chunk[10] = curr[10].wrapping_sub(prev[10]);
chunk[11] = curr[11].wrapping_sub(prev[11]);
chunk[12] = curr[12].wrapping_sub(prev[12]);
chunk[13] = curr[13].wrapping_sub(prev[13]);
chunk[14] = curr[14].wrapping_sub(prev[14]);
chunk[15] = curr[15].wrapping_sub(prev[15]);
chunk_end = chunk_start;
}
// Handle the leading remainder that doesn't fill a complete chunk of 16.
// This must be done last (after the reverse chunk loop) because these are
// the lowest indices, and the chunks above may read predecessor values
// from this range.
for i in (samples..samples + remainder_len).rev() {
output[i] = output[i].wrapping_sub(output[i - samples]);
}
}
};
}
fp_predict!(fp_predict_f32, f32);
fp_predict!(fp_predict_f64, f64);
macro_rules! integer_horizontal_predict {
() => {
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
@@ -23,6 +115,10 @@ macro_rules! integer_horizontal_predict {
.map(|(prev, current)| current.wrapping_sub(*prev)),
);
}
fn floating_point_predict(_: &[Self::Inner], _: &mut Vec<u8>) {
unreachable!("floating-point predictor is only valid for floating-point sample types")
}
};
}
@@ -38,6 +134,10 @@ pub trait ColorType {
const SAMPLE_FORMAT: &'static [SampleFormat];
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
/// Apply floating-point predictor encoding to a row of samples.
/// This is only implemented for floating-point types; integer types will panic.
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>);
}
pub struct Gray8;
@@ -108,7 +208,11 @@ impl ColorType for Gray32Float {
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
unreachable!("horizontal predictor is not valid for floating-point sample types")
}
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
}
}
@@ -140,7 +244,11 @@ impl ColorType for Gray64Float {
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
unreachable!("horizontal predictor is not valid for floating-point sample types")
}
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
}
}
@@ -180,8 +288,13 @@ impl ColorType for RGB32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
unreachable!("horizontal predictor is not valid for floating-point sample types")
}
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
}
}
@@ -201,8 +314,13 @@ impl ColorType for RGB64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
unreachable!("horizontal predictor is not valid for floating-point sample types")
}
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
}
}
@@ -242,8 +360,13 @@ impl ColorType for RGBA32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
unreachable!("horizontal predictor is not valid for floating-point sample types")
}
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
}
}
@@ -263,8 +386,13 @@ impl ColorType for RGBA64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
unreachable!("horizontal predictor is not valid for floating-point sample types")
}
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
}
}
@@ -306,7 +434,11 @@ impl ColorType for CMYK32Float {
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
unreachable!("horizontal predictor is not valid for floating-point sample types")
}
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
}
}
@@ -328,7 +460,11 @@ impl ColorType for CMYK64Float {
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
unreachable!("horizontal predictor is not valid for floating-point sample types")
}
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
}
}
+13 -3
View File
@@ -35,7 +35,8 @@ use self::writer::*;
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
/// ratio over the unpredicted compression!
///
/// [Predictor::FloatingPoint] is currently not supported.
/// [Predictor::Horizontal] is for integer sample types.
/// [Predictor::FloatingPoint] is for floating-point sample types (f32, f64).
pub type Predictor = crate::tags::Predictor;
#[cfg(feature = "deflate")]
pub type DeflateLevel = compression::DeflateLevel;
@@ -635,11 +636,14 @@ pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
match (predictor, compression, T::SAMPLE_FORMAT[0]) {
// Horizontal predictor is not valid for floating-point types
(Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
}
// Floating-point predictor is only valid for IEEEFP sample format
(Predictor::FloatingPoint, _, SampleFormat::IEEEFP) => Ok(()),
(Predictor::FloatingPoint, _, _) => {
Err(TiffError::UsageError(UsageError::PredictorUnavailable))
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
}
_ => Ok(()),
}
@@ -773,7 +777,13 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
}
self.encoder.write_data(row_result.as_slice())?
}
_ => unimplemented!(),
Predictor::FloatingPoint => {
let mut row_result = Vec::with_capacity(std::mem::size_of_val(value));
for row in value.chunks_exact(self.row_samples as usize) {
T::floating_point_predict(row, &mut row_result);
}
self.encoder.write_data(row_result.as_slice())?
}
};
let byte_count = self.encoder.last_written() as usize;
+3 -1
View File
@@ -241,7 +241,9 @@ fn encoder_on_short_writes() {
}
write.inner.set_position(0);
let mut tiff_reader = crate::decoder::Decoder::new(&mut write.inner).unwrap();
let mut tiff_reader = crate::decoder::Decoder::open(&mut write.inner).unwrap();
tiff_reader.next_image().unwrap();
let _layout = tiff_reader.read_image_to_buffer(&mut data).unwrap();
assert!(
+12 -1
View File
@@ -357,7 +357,9 @@ pub enum Predictor(u16) {
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
Horizontal = 2,
/// Not currently supported
/// Floating-point predictor for IEEE floating-point sample types.
///
/// This predictor byte-shuffles the data and applies byte-level horizontal differencing.
FloatingPoint = 3,
}
}
@@ -587,3 +589,12 @@ pub(crate) enum EndianBytes {
Four,
Eight,
}
/// Defines how directories are laid out in the file.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TiffVariant {
/// TIFF-V6.0 with 32-bit offsets and counts.
Standard,
/// Extended TIFF with 64-bit offsets and counts and long directories.
BigTiff,
}
+3 -1
View File
@@ -15,7 +15,9 @@ fn test_big_tiff() {
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(64, 64)
+20 -5
View File
@@ -16,7 +16,9 @@ fn test_white_ieee_fp16() {
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
@@ -25,6 +27,7 @@ fn test_white_ieee_fp16() {
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for p in img {
assert!(p == half::f16::from_f32_const(1.0));
@@ -43,7 +46,9 @@ fn test_one_black_pixel_ieee_fp16() {
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
@@ -52,6 +57,7 @@ fn test_one_black_pixel_ieee_fp16() {
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for (i, p) in img.iter().enumerate() {
if i == 0 {
@@ -74,7 +80,9 @@ fn test_pattern_horizontal_differencing_ieee_fp16() {
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
@@ -83,6 +91,7 @@ fn test_pattern_horizontal_differencing_ieee_fp16() {
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
// 0, 2, 5, 8, 12, 16, 255 are black
let black = [0, 2, 5, 8, 12, 16, 255];
@@ -107,7 +116,9 @@ fn test_pattern_predictor_ieee_fp16() {
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
@@ -116,6 +127,7 @@ fn test_pattern_predictor_ieee_fp16() {
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
// 0, 2, 5, 8, 12, 16, 255 are black
let black = [0, 2, 5, 8, 12, 16, 255];
@@ -175,7 +187,9 @@ fn test_predictor_ieee_fp16() {
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(16, 16)
@@ -184,6 +198,7 @@ fn test_predictor_ieee_fp16() {
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) {
assert!((exp - found.to_f32()).abs() < 0.0001);
+5 -9
View File
@@ -15,8 +15,9 @@ fn test_geo_tiff() {
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder = decoder.with_limits(tiff::decoder::Limits::unlimited());
decoder.next_image().expect("Cannot read image");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
@@ -71,16 +72,11 @@ fn test_webp_tiff() {
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
loop {
decoder.read_image_to_buffer(&mut buffer).unwrap();
if !decoder.more_images() {
break;
}
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
while decoder.more_images() {
decoder.next_image().unwrap();
decoder.read_image_to_buffer(&mut buffer).unwrap();
}
}
}
+67 -19
View File
@@ -14,7 +14,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::new(img_file).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image IFD");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let img_res = decoder.read_image().unwrap();
@@ -44,7 +46,9 @@ test_image_sum!(test_image_sum_f64, F64, f64);
fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
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");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image IFD");
assert_eq!(decoder.colortype().unwrap(), expected_type);
assert!(matches!(
decoder.read_image(),
@@ -166,7 +170,8 @@ fn test_string_tags() {
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image IFD");
let software = decoder.get_tag(tiff::tags::Tag::Software).unwrap();
match software {
ifd::Value::Ascii(s) => assert_eq!(
@@ -190,7 +195,8 @@ fn test_decode_data() {
}
}
let file = File::open("./tests/decodedata-rgb-3c-8b.tiff").unwrap();
let mut decoder = Decoder::new(file).unwrap();
let mut decoder = Decoder::open(file).unwrap();
decoder.next_image().expect("Cannot read image IFD");
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
@@ -236,7 +242,8 @@ fn test_tiled_rect_rgb_u8() {
fn test_inner_access() {
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image IFD");
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
let c = decoder
@@ -322,7 +329,8 @@ fn test_tiled_incremental() {
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");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image IFD");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let tiles = decoder.tile_count().unwrap();
@@ -352,7 +360,8 @@ fn test_read_planar_bands() {
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");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().expect("Cannot read image IFD");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let chunks = decoder.strip_count().unwrap();
@@ -396,7 +405,8 @@ fn test_div_zero() {
178, 178, 178,
];
let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let err = decoder.next_image().unwrap_err();
match err {
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
@@ -414,7 +424,8 @@ fn test_too_many_value_bytes() {
0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18,
];
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let error = decoder.next_image().unwrap_err();
match error {
tiff::TiffError::LimitsExceeded => {}
@@ -432,7 +443,8 @@ fn fuzzer_testcase5() {
178, 178, 178,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let _ = decoder.next_image().unwrap_err();
}
#[test]
@@ -445,7 +457,8 @@ fn fuzzer_testcase1() {
178,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let _ = decoder.next_image().unwrap_err();
}
#[test]
@@ -458,7 +471,8 @@ fn fuzzer_testcase6() {
178, 178,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let _ = decoder.next_image().unwrap_err();
}
#[test]
@@ -470,7 +484,8 @@ fn oom() {
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let _ = decoder.next_image().unwrap_err();
}
#[test]
@@ -482,7 +497,8 @@ fn fuzzer_testcase4() {
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let _ = decoder.next_image().unwrap_err();
}
#[test]
@@ -498,7 +514,8 @@ fn fuzzer_testcase2() {
73,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let _ = decoder.next_image().unwrap_err();
}
#[test]
@@ -514,7 +531,8 @@ fn invalid_jpeg_tag_2() {
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let _ = decoder.next_image().unwrap_err();
}
#[test]
@@ -527,7 +545,8 @@ fn fuzzer_testcase3() {
255, 255,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let _ = decoder.next_image().unwrap_err();
}
#[test]
@@ -545,7 +564,8 @@ fn timeout() {
0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32,
];
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
let error = decoder.next_image().unwrap_err();
match error {
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
@@ -583,7 +603,8 @@ fn test_image_bytes(
) {
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");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().unwrap();
let mut buffer = vec![0u8; 0];
for (idx, &(expected_type, expected_crc)) in expected.iter().enumerate() {
@@ -720,3 +741,30 @@ fn extra_bits_gray() {
fn extra_bits_rgb() {
test_image_sum_u8("extra_bits_rgb_8b.tiff", ColorType::RGB(8), 0);
}
#[test]
#[cfg(feature = "fax")]
fn test_decode_huge_g4() {
let path = PathBuf::from(TEST_IMAGE_DIR).join("big_g4.tif");
let img_file = File::open(path).expect("failed to open large G4 test image");
let mut decoder = Decoder::open(img_file).expect("failed to create decoder");
decoder.next_image().expect("failed to read image IFD");
let (width, height) = decoder.dimensions().expect("failed to get dimensions");
assert!(
height > u16::MAX as u32,
"Test image height {} is not large enough to test u16 overflow",
height
);
let result = decoder
.read_image()
.expect("failed to decode huge G4 image");
if let tiff::decoder::DecodingResult::U8(data) = result {
assert_eq!(data.len(), (width as usize * height as usize));
} else {
panic!("Unexpected decoding result type");
}
}
+6 -10
View File
@@ -115,9 +115,10 @@ fn main() {
let Some(path) = entry.path().to_str() else {
continue;
};
let path = path.replace(std::path::MAIN_SEPARATOR, "/"); // always use UNIX path separators
// Mark this path as consumed in either case by removing it.
let Some(should_decode) = candidates.remove(path) else {
let Some(should_decode) = candidates.remove(path.as_str()) else {
panic!("File {} not in reference list", path);
};
@@ -152,17 +153,12 @@ fn decode_libtiffpic(path: &str) -> Result<(), tiff::TiffError> {
let path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path);
let img_file = File::open(path)?;
let mut decoder = Decoder::new(img_file)?;
let mut decoder = Decoder::open(img_file)?;
let mut buffer = DecodingResult::U8(vec![]);
loop {
let _layout = decoder.read_image_to_buffer(&mut buffer);
if !decoder.more_images() {
break;
}
while decoder.more_images() {
decoder.next_image()?;
let _layout = decoder.read_image_to_buffer(&mut buffer);
}
Ok(())
+6 -3
View File
@@ -10,8 +10,9 @@ fn read_all_planes() {
let path = PathBuf::from(TEST_IMAGE_DIR).join("planar-rgb-u8.tif");
let file = File::open(path).unwrap();
let io = std::io::BufReader::new(file);
let mut tif = Decoder::open(io).unwrap();
let mut tif = Decoder::new(io).unwrap();
tif.next_image().unwrap();
let layout = tif.image_buffer_layout().unwrap();
assert_eq!(layout.planes, 3);
let plane_stride = layout.plane_stride.map_or(0, |x| x.get());
@@ -36,8 +37,9 @@ fn read_first_coding_unit() {
let path = PathBuf::from(TEST_IMAGE_DIR).join("planar-rgb-u8.tif");
let file = File::open(path).unwrap();
let io = std::io::BufReader::new(file);
let mut tif = Decoder::open(io).unwrap();
let mut tif = Decoder::new(io).unwrap();
tif.next_image().unwrap();
let layout = tif.image_coding_unit_layout(TiffCodingUnit(0)).unwrap();
assert_eq!(layout.planes, 3);
@@ -85,12 +87,13 @@ fn read_planes_by_limits() {
let mut buffer = DecodingResult::U8(vec![]);
// Make room for 1 plane.
let mut tif = Decoder::new(io).unwrap();
let mut tif = Decoder::open(io).unwrap();
tif = tif.with_limits({
limits.decoding_buffer_size = 374 * 499;
limits.clone()
});
tif.next_image().unwrap();
tif.read_image_to_buffer(&mut buffer).unwrap();
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499);
+166 -14
View File
@@ -1,7 +1,7 @@
extern crate tiff;
use tiff::decoder::{ifd, Decoder, DecodingResult};
use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder};
use tiff::encoder::{colortype, Ifd, Ifd8, Predictor, SRational, TiffEncoder};
use tiff::tags::Tag;
use tiff::ColorType;
@@ -33,7 +33,9 @@ fn encode_decode() {
}
{
file.seek(SeekFrom::Start(0)).unwrap();
let mut decoder = Decoder::new(&mut file).unwrap();
let mut decoder = Decoder::open(&mut file).unwrap();
decoder.next_image().unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
@@ -116,7 +118,9 @@ fn encode_decode_big() {
}
{
file.seek(SeekFrom::Start(0)).unwrap();
let mut decoder = Decoder::new(&mut file).unwrap();
let mut decoder = Decoder::open(&mut file).unwrap();
decoder.next_image().unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
@@ -207,8 +211,10 @@ fn test_encode_ifd() {
// Rewind the cursor for reading
data.set_position(0);
{
let mut decoder = Decoder::new(&mut data).unwrap();
let mut decoder = Decoder::open(&mut data).unwrap();
decoder.next_image().unwrap();
assert_eq!(decoder.assert_tag_u32(65000), 42);
assert_eq!(decoder.assert_tag_u32_vec(65000), [42]);
@@ -247,7 +253,9 @@ macro_rules! test_roundtrip {
) {
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");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().unwrap();
assert_eq!(decoder.colortype().unwrap(), expected_type);
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
@@ -268,7 +276,8 @@ macro_rules! test_roundtrip {
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
let mut decoder = Decoder::new(&mut file).unwrap();
let mut decoder = Decoder::open(&mut file).unwrap();
decoder.next_image().unwrap();
decoder.read_image_to_buffer(&mut buffer).unwrap();
if let DecodingResult::$buffer(img_res) = buffer {
assert_eq!(*image_data, img_res);
@@ -379,6 +388,144 @@ fn test_ycbcr_u8_roundtrip() {
test_u8_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}
/// Test that encoding with the floating-point predictor works correctly
/// and can be decoded back to the original data.
#[test]
fn test_gray_f32_floating_point_predictor() {
// Generate test data: a gradient of f32 values
let width = 32u32;
let height = 32u32;
let mut image_data: Vec<f32> = Vec::with_capacity((width * height) as usize);
for y in 0..height {
for x in 0..width {
// Create a gradient pattern
image_data.push((x as f32 + y as f32 * 0.1) / 100.0);
}
}
let mut file = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new(&mut file)
.unwrap()
.with_predictor(Predictor::FloatingPoint);
tiff.write_image::<colortype::Gray32Float>(width, height, &image_data)
.unwrap();
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::open(&mut file).unwrap();
decoder.next_image().unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(32));
let mut buffer = DecodingResult::F32(vec![]);
decoder.read_image_to_buffer(&mut buffer).unwrap();
if let DecodingResult::F32(img_res) = buffer {
assert_eq!(image_data.len(), img_res.len());
for (orig, decoded) in image_data.iter().zip(img_res.iter()) {
assert_eq!(
*orig, *decoded,
"Mismatch at value: orig={}, decoded={}",
orig, decoded
);
}
} else {
panic!("Wrong data type");
}
}
}
/// Test floating-point predictor with RGB f32 data
#[test]
fn test_rgb_f32_floating_point_predictor() {
let width = 16u32;
let height = 16u32;
let mut image_data: Vec<f32> = Vec::with_capacity((width * height * 3) as usize);
for y in 0..height {
for x in 0..width {
// RGB pattern
image_data.push(x as f32 / width as f32); // R
image_data.push(y as f32 / height as f32); // G
image_data.push((x + y) as f32 / (width + height) as f32); // B
}
}
let mut file = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new(&mut file)
.unwrap()
.with_predictor(Predictor::FloatingPoint);
tiff.write_image::<colortype::RGB32Float>(width, height, &image_data)
.unwrap();
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::open(&mut file).unwrap();
decoder.next_image().unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(32));
let mut buffer = DecodingResult::F32(vec![]);
decoder.read_image_to_buffer(&mut buffer).unwrap();
if let DecodingResult::F32(img_res) = buffer {
assert_eq!(image_data.len(), img_res.len());
for (i, (orig, decoded)) in image_data.iter().zip(img_res.iter()).enumerate() {
assert_eq!(
*orig, *decoded,
"Mismatch at index {}: orig={}, decoded={}",
i, orig, decoded
);
}
} else {
panic!("Wrong data type");
}
}
}
/// Test floating-point predictor with f64 data
#[test]
fn test_gray_f64_floating_point_predictor() {
let width = 16u32;
let height = 16u32;
let mut image_data: Vec<f64> = Vec::with_capacity((width * height) as usize);
for y in 0..height {
for x in 0..width {
image_data.push((x as f64 + y as f64 * 0.1) / 100.0);
}
}
let mut file = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new(&mut file)
.unwrap()
.with_predictor(Predictor::FloatingPoint);
tiff.write_image::<colortype::Gray64Float>(width, height, &image_data)
.unwrap();
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::open(&mut file).unwrap();
decoder.next_image().unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(64));
let mut buffer = DecodingResult::F64(vec![]);
decoder.read_image_to_buffer(&mut buffer).unwrap();
if let DecodingResult::F64(img_res) = buffer {
assert_eq!(image_data.len(), img_res.len());
for (orig, decoded) in image_data.iter().zip(img_res.iter()) {
assert_eq!(
*orig, *decoded,
"Mismatch: orig={}, decoded={}",
orig, decoded
);
}
} else {
panic!("Wrong data type");
}
}
}
trait AssertDecode {
fn assert_tag_u32(&mut self, tag: u16) -> u32;
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
@@ -456,7 +603,8 @@ fn test_multiple_byte() {
data.set_position(0);
{
let mut decoder = Decoder::new(&mut data).unwrap();
let mut decoder = Decoder::open(&mut data).unwrap();
decoder.next_image().unwrap();
assert_eq!(decoder.assert_tag_u32_vec(65000), [1]);
assert_eq!(decoder.assert_tag_u32_vec(65001), [1, 2]);
@@ -537,7 +685,8 @@ fn test_signed() {
//Rewind the cursor for reading
data.set_position(0);
{
let mut decoder = Decoder::new(&mut data).unwrap();
let mut decoder = Decoder::open(&mut data).unwrap();
decoder.next_image().unwrap();
assert_eq!(decoder.assert_tag_i32(65000), -1);
assert_eq!(decoder.assert_tag_i32_vec(65001), [-1]);
@@ -589,13 +738,14 @@ fn test_multipage_image() {
img_file.seek(SeekFrom::Start(0)).unwrap();
{
let mut img_decoder = Decoder::new(&mut img_file).unwrap();
let mut decoder = Decoder::open(&mut img_file).unwrap();
decoder.next_image().unwrap();
// check the dimensions of the image in the first page
assert_eq!(img_decoder.dimensions().unwrap(), (2, 2));
img_decoder.next_image().unwrap();
assert_eq!(decoder.dimensions().unwrap(), (2, 2));
decoder.next_image().unwrap();
// check the dimensions of the image in the second page
assert_eq!(img_decoder.dimensions().unwrap(), (3, 3));
assert_eq!(decoder.dimensions().unwrap(), (3, 3));
}
}
@@ -624,7 +774,8 @@ fn test_rows_per_strip() {
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::new(&mut file).unwrap();
let mut decoder = Decoder::open(&mut file).unwrap();
decoder.next_image().unwrap();
assert_eq!(decoder.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2);
assert_eq!(decoder.strip_count().unwrap(), 50);
@@ -664,9 +815,10 @@ fn test_auxiliary_directory() {
}
data.set_position(0);
let mut decoder = Decoder::new(&mut data).unwrap();
let mut decoder = Decoder::open(&mut data).unwrap();
{
decoder.next_image().unwrap();
assert_eq!(decoder.dimensions().unwrap(), (1, 1));
let _ = decoder.read_image().unwrap();
}
+2 -1
View File
@@ -110,7 +110,8 @@ fn encode_decode_with_compression(compression: Compression) {
// Decode tiff
data.set_position(0);
{
let mut decoder = Decoder::new(data).unwrap();
let mut decoder = Decoder::open(data).unwrap();
decoder.next_image().unwrap();
// Check the RGB image
assert_eq!(
+2 -1
View File
@@ -13,7 +13,8 @@ fn test_directory<F: Fn(File) -> bool>(path: &str, f: F) {
}
fn decode_tiff(file: File) -> TiffResult<()> {
let mut decoder = Decoder::new(file)?;
let mut decoder = Decoder::open(file)?;
decoder.next_image()?;
decoder.read_image()?;
Ok(())
}
Binary file not shown.
+7 -3
View File
@@ -18,8 +18,9 @@ macro_rules! test_predict {
) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(file).expect("Cannot create decoder!");
let mut decoder = Decoder::open(file).expect("Cannot create decoder!");
decoder.next_image().unwrap();
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
DecodingResult::$buffer(res) => res,
@@ -123,7 +124,9 @@ macro_rules! test_predict_roundtrip {
) {
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");
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
decoder.next_image().unwrap();
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
@@ -142,7 +145,8 @@ macro_rules! test_predict_roundtrip {
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::new(&mut file).unwrap();
let mut decoder = Decoder::open(&mut file).unwrap();
decoder.next_image().unwrap();
if let DecodingResult::$buffer(img_res) =
decoder.read_image().expect("Decoding image failed")
{
+3 -1
View File
@@ -18,8 +18,10 @@ use std::fs::File;
#[test]
fn decode_seek_chain() {
let file = File::open(TEST_IMAGE_SUBIFD).expect("Cannot open test image");
let mut decoder = Decoder::new(file).expect("Invalid format to create decoder");
let mut decoder = Decoder::open(file).expect("Invalid format to create decoder");
assert!(decoder.ifd_pointer().is_none());
decoder.next_image().unwrap();
// Remember the offsets in the first iteration.
let offset0 = decoder.ifd_pointer().expect("First IFD pointer not found");
decoder.next_image().unwrap();