Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf79af8de4 | ||
|
|
be9b3a04b9 | ||
|
|
08f1b25fac | ||
|
|
c7b97519a0 | ||
|
|
e093e15bbe | ||
|
|
647e9b4a81 | ||
|
|
cf8f96568e | ||
|
|
4ff88e658d | ||
|
|
14ebf376f3 | ||
|
|
37578add53 | ||
|
|
2b9476566b | ||
|
|
448a0e61d1 | ||
|
|
d872ff8751 | ||
|
|
0a878888db | ||
|
|
a0a95ef86e | ||
|
|
bbb5e5a41b | ||
|
|
f74169d4af | ||
|
|
c304a3cc13 | ||
|
|
9a9193345c | ||
|
|
22046d4272 | ||
|
|
2c5fe2c2f5 | ||
|
|
eceaa1f14d | ||
|
|
d42db25c27 | ||
|
|
c74f23784c | ||
|
|
8b0da5939a | ||
|
|
fdde3467fc | ||
|
|
3f2066bb20 | ||
|
|
2ac8be1177 |
No files matched your search
+13
@@ -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
|
# Version 0.11.3
|
||||||
|
|
||||||
Additions:
|
Additions:
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ zstd = ["dep:zstd"]
|
|||||||
name = "lzw"
|
name = "lzw"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "encode"
|
||||||
|
harness = false
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "decode_libtiffpic"
|
name = "decode_libtiffpic"
|
||||||
path = "tests/decode_libtiffpic.rs"
|
path = "tests/decode_libtiffpic.rs"
|
||||||
|
|||||||
@@ -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
@@ -8,13 +8,12 @@ use tiff::decoder::Decoder;
|
|||||||
|
|
||||||
fn read_image(image: &[u8]) {
|
fn read_image(image: &[u8]) {
|
||||||
let image = std::io::Cursor::new(image);
|
let image = std::io::Cursor::new(image);
|
||||||
let decoder = Decoder::new(black_box(image));
|
let decoder = Decoder::open(black_box(image));
|
||||||
let mut reader = decoder.unwrap();
|
|
||||||
|
|
||||||
while {
|
let mut reader = decoder.unwrap();
|
||||||
|
while reader.more_images() {
|
||||||
reader.read_image().unwrap();
|
reader.read_image().unwrap();
|
||||||
reader.more_images()
|
}
|
||||||
} {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let file = std::fs::File::open(image)?;
|
let file = std::fs::File::open(image)?;
|
||||||
let io = std::io::BufReader::new(file);
|
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![]);
|
let mut data = DecodingResult::I8(vec![]);
|
||||||
|
|
||||||
|
|||||||
+4
-7
@@ -10,14 +10,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
let file = std::fs::File::open(image)?;
|
let file = std::fs::File::open(image)?;
|
||||||
let io = std::io::BufReader::new(file);
|
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);
|
ls_dir(&mut reader);
|
||||||
|
|
||||||
if !reader.more_images() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -29,7 +26,7 @@ fn ls_dir<R: std::io::Read + std::io::Seek>(reader: &mut Decoder<R>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
println!("Name\tHex\tType\tCount");
|
println!("Name\tHex\tType\tCount");
|
||||||
let ifd = reader.image_ifd();
|
let ifd = reader.current_ifd();
|
||||||
|
|
||||||
for (tag, entry) in ifd.directory().iter() {
|
for (tag, entry) in ifd.directory().iter() {
|
||||||
let name: &'static str = match tag {
|
let name: &'static str = match tag {
|
||||||
|
|||||||
+26
-4
@@ -64,7 +64,6 @@ impl TileAttributes {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Image {
|
pub(crate) struct Image {
|
||||||
pub ifd: Option<Directory>,
|
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub bits_per_sample: u8,
|
pub bits_per_sample: u8,
|
||||||
@@ -147,11 +146,35 @@ pub(crate) struct ReadoutLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
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>(
|
pub fn from_reader<R: Read + Seek>(
|
||||||
decoder: &mut ValueReader<R>,
|
decoder: &mut ValueReader<R>,
|
||||||
ifd: Directory,
|
ifd: &Directory,
|
||||||
) -> TiffResult<Image> {
|
) -> 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 width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
||||||
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
||||||
@@ -435,7 +458,6 @@ impl Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(Image {
|
Ok(Image {
|
||||||
ifd: Some(ifd),
|
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
bits_per_sample,
|
bits_per_sample,
|
||||||
|
|||||||
+337
-166
@@ -4,8 +4,8 @@ use std::io::{self, Read, Seek};
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor,
|
ByteOrder, IfdPointer, PlanarConfiguration, Predictor, SampleFormat, Tag, TiffVariant, Type,
|
||||||
SampleFormat, Tag, Type, ValueBuffer,
|
ValueBuffer,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
bytecast, ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError,
|
bytecast, ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError,
|
||||||
@@ -14,7 +14,7 @@ use crate::{
|
|||||||
use half::f16;
|
use half::f16;
|
||||||
|
|
||||||
use self::image::Image;
|
use self::image::Image;
|
||||||
use self::stream::{ByteOrder, EndianReader};
|
use self::stream::EndianReader;
|
||||||
|
|
||||||
mod cycles;
|
mod cycles;
|
||||||
pub mod ifd;
|
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
|
/// The representation of a TIFF decoder
|
||||||
///
|
///
|
||||||
/// Currently does not support decoding of interlaced images
|
/// 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
|
/// 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.
|
/// borrow the stream access and the other fields mutably at the same time.
|
||||||
value_reader: ValueReader<R>,
|
value_reader: ValueReader<R>,
|
||||||
current_ifd: Option<IfdPointer>,
|
current_ifd_pointer: Option<IfdPointer>,
|
||||||
next_ifd: Option<IfdPointer>,
|
directory: Directory,
|
||||||
/// The IFDs we visited already in this chain of IFDs.
|
/// The IFDs we visited already in this chain of IFDs.
|
||||||
ifd_offsets: Vec<IfdPointer>,
|
ifd_offsets: Vec<IfdPointer>,
|
||||||
/// Map from the ifd into the `ifd_offsets` ordered list.
|
/// 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.
|
/// Reads a directory's tag values from an underlying stream.
|
||||||
|
///
|
||||||
|
/// Created by [`Decoder::current_ifd`] and [`Decoder::read_directory_tags`].
|
||||||
pub struct IfdDecoder<'lt> {
|
pub struct IfdDecoder<'lt> {
|
||||||
inner: tag_reader::TagReader<'lt, dyn tag_reader::EntryDecoder + 'lt>,
|
inner: tag_reader::TagReader<'lt, dyn tag_reader::EntryDecoder + 'lt>,
|
||||||
}
|
}
|
||||||
@@ -717,46 +727,29 @@ fn invert_colors(
|
|||||||
sample_format: SampleFormat,
|
sample_format: SampleFormat,
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
match (color_type, sample_format) {
|
match (color_type, sample_format) {
|
||||||
// Where pixels do not cross a byte boundary
|
(ColorType::Gray(1 | 2 | 4 | 8 | 16 | 32 | 64), SampleFormat::Uint) => {
|
||||||
(ColorType::Gray(1 | 2 | 4 | 8), 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 {
|
for x in buf {
|
||||||
// Equivalent to both of the following:
|
|
||||||
//
|
|
||||||
// *x = 0xff - *x
|
|
||||||
// *x = !*x
|
|
||||||
//
|
|
||||||
// since -x = !x+1
|
|
||||||
*x = !*x;
|
*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) => {
|
(ColorType::Gray(32), SampleFormat::IEEEFP) => {
|
||||||
for x in buf.chunks_mut(4) {
|
for x in buf.as_chunks_mut::<4>().0 {
|
||||||
let v = f32::from_ne_bytes(x.try_into().unwrap());
|
let v = f32::from_ne_bytes(*x);
|
||||||
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
|
*x = (1.0 - v).to_ne_bytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(ColorType::Gray(64), SampleFormat::IEEEFP) => {
|
(ColorType::Gray(64), SampleFormat::IEEEFP) => {
|
||||||
for x in buf.chunks_mut(8) {
|
for x in buf.as_chunks_mut::<8>().0 {
|
||||||
let v = f64::from_ne_bytes(x.try_into().unwrap());
|
let v = f64::from_ne_bytes(*x);
|
||||||
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
|
*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);
|
host.convert_endian_bytes(class, buf, byte_order);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read + Seek> Decoder<R> {
|
impl TiffHeader {
|
||||||
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
|
pub fn parse<R: Read + Seek>(mut r: R) -> 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 {
|
||||||
@@ -796,6 +789,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut reader = EndianReader::new(r, byte_order);
|
let mut reader = EndianReader::new(r, byte_order);
|
||||||
|
|
||||||
let bigtiff = match reader.read_u16()? {
|
let bigtiff = match reader.read_u16()? {
|
||||||
@@ -822,119 +816,155 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let next_ifd = if bigtiff {
|
let next_ifd = IfdPointer(if bigtiff {
|
||||||
Some(reader.read_u64()?)
|
reader.read_u64()?
|
||||||
} else {
|
} 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();
|
/// Open a file, without seeking to any IFD yet.
|
||||||
let ifd_offsets = vec![current_ifd];
|
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 {
|
value_reader: ValueReader {
|
||||||
reader,
|
reader,
|
||||||
bigtiff,
|
bigtiff: matches!(self.variant, TiffVariant::BigTiff),
|
||||||
limits: Default::default(),
|
limits: Default::default(),
|
||||||
},
|
},
|
||||||
next_ifd,
|
ifd_offsets: vec![],
|
||||||
ifd_offsets,
|
current_ifd_pointer: None,
|
||||||
current_ifd: None,
|
directory: {
|
||||||
seen_ifds: cycles::IfdCycles::new(),
|
let mut dir = Directory::empty();
|
||||||
image: Image {
|
dir.set_next(Some(self.first_ifd));
|
||||||
ifd: None,
|
dir
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
};
|
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()?;
|
decoder.next_image()?;
|
||||||
Ok(decoder)
|
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> {
|
pub fn with_limits(mut self, limits: Limits) -> Decoder<R> {
|
||||||
self.value_reader.limits = limits;
|
self.value_reader.limits = limits;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> {
|
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> {
|
pub fn colortype(&mut self) -> TiffResult<ColorType> {
|
||||||
self.image().colortype()
|
self.image.colortype()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The offset of the directory representing the current image.
|
/// The offset of the directory representing the current image.
|
||||||
pub fn ifd_pointer(&mut self) -> Option<IfdPointer> {
|
pub fn ifd_pointer(&mut self) -> Option<IfdPointer> {
|
||||||
self.current_ifd
|
self.current_ifd_pointer
|
||||||
}
|
|
||||||
|
|
||||||
fn image(&self) -> &Image {
|
|
||||||
&self.image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the IFD at the specified index in the list, if one exists
|
/// 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
|
/// Error guarantee: the current directory and [`Self::ifd_pointer`] is not modified when an
|
||||||
if ifd_index >= self.ifd_offsets.len() {
|
/// error is returned.
|
||||||
// We possibly need to load in the next IFD
|
pub fn seek_to_directory(&mut self, ifd_index: usize) -> TiffResult<()> {
|
||||||
if self.next_ifd.is_none() {
|
let (offset, directory) = self.find_nth_ifd(ifd_index)?;
|
||||||
self.current_ifd = None;
|
self.image = Image::no_image();
|
||||||
|
self.current_ifd_pointer = Some(offset);
|
||||||
return Err(TiffError::FormatError(
|
self.directory = directory;
|
||||||
TiffFormatError::ImageFileDirectoryNotFound,
|
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 {
|
loop {
|
||||||
// Follow the list until we find the one we want, or we reach the end, whichever happens first
|
// Follow the list until we find the one we want, or we reach the end, whichever happens first
|
||||||
let ifd = self.next_ifd()?;
|
let (ifd_pointer, ifd) = self.read_chained_ifd(chained_ifd, next_ifd)?;
|
||||||
|
chained_ifd = Some(ifd_pointer);
|
||||||
if ifd.next().is_none() {
|
next_ifd = ifd.next();
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ifd_index < self.ifd_offsets.len() {
|
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
|
fn read_chained_ifd(
|
||||||
if let Some(ifd_offset) = self.ifd_offsets.get(ifd_index) {
|
&mut self,
|
||||||
let ifd = self.value_reader.read_directory(*ifd_offset)?;
|
current_ifd_pointer: Option<IfdPointer>,
|
||||||
self.next_ifd = ifd.next();
|
next_ifd: Option<IfdPointer>,
|
||||||
self.current_ifd = Some(*ifd_offset);
|
) -> TiffResult<(IfdPointer, Directory)> {
|
||||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
let Some(next_ifd) = next_ifd else {
|
||||||
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::ImageFileDirectoryNotFound,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn next_ifd(&mut self) -> TiffResult<Directory> {
|
|
||||||
let Some(next_ifd) = self.next_ifd.take() else {
|
|
||||||
return Err(TiffError::FormatError(
|
return Err(TiffError::FormatError(
|
||||||
TiffFormatError::ImageFileDirectoryNotFound,
|
TiffFormatError::ImageFileDirectoryNotFound,
|
||||||
));
|
));
|
||||||
@@ -946,28 +976,48 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.seen_ifds.insert_next(next_ifd, ifd.next())?;
|
self.seen_ifds.insert_next(next_ifd, ifd.next())?;
|
||||||
|
|
||||||
// Extend the list of known IFD offsets in this chain, if needed.
|
// 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.ifd_offsets.push(next_ifd);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.current_ifd = Some(next_ifd);
|
Ok((next_ifd, ifd))
|
||||||
self.next_ifd = ifd.next();
|
}
|
||||||
|
|
||||||
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.
|
/// Reads in the next image.
|
||||||
|
///
|
||||||
/// If there is no further image in the TIFF file a format error is returned.
|
/// 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.
|
/// 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<()> {
|
pub fn next_image(&mut self) -> TiffResult<()> {
|
||||||
let ifd = self.next_ifd()?;
|
let next_ifd = self.directory.next();
|
||||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if there is at least one more image available.
|
/// Returns `true` if there is at least one more image available.
|
||||||
pub fn more_images(&self) -> bool {
|
pub fn more_images(&self) -> bool {
|
||||||
self.next_ifd.is_some()
|
self.directory.next().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the byte_order of the file.
|
/// 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<()> {
|
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(
|
return Err(TiffError::UsageError(UsageError::InvalidChunkType(
|
||||||
expected,
|
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
|
/// The chunk type (Strips / Tiles) of the image
|
||||||
pub fn get_chunk_type(&self) -> ChunkType {
|
pub fn get_chunk_type(&self) -> ChunkType {
|
||||||
self.image().chunk_type
|
self.image.chunk_type
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Number of strips in image
|
/// Number of strips in image
|
||||||
pub fn strip_count(&mut self) -> TiffResult<u32> {
|
pub fn strip_count(&mut self) -> TiffResult<u32> {
|
||||||
self.check_chunk_type(ChunkType::Strip)?;
|
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 {
|
if rows_per_strip == 0 {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// rows_per_strip - 1 can never fail since we know it's at least 1
|
// 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,
|
Some(h) => h,
|
||||||
None => return Err(TiffError::IntSizeError),
|
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::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)
|
Ok(strips)
|
||||||
@@ -1157,7 +1207,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// Number of tiles in image
|
/// Number of tiles in image
|
||||||
pub fn tile_count(&mut self) -> TiffResult<u32> {
|
pub fn tile_count(&mut self) -> TiffResult<u32> {
|
||||||
self.check_chunk_type(ChunkType::Tile)?;
|
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(
|
fn read_chunk_to_bytes(
|
||||||
@@ -1186,8 +1236,8 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
) -> TiffResult<BufferLayoutPreference> {
|
) -> TiffResult<BufferLayoutPreference> {
|
||||||
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
let data_dims = self.image.chunk_data_dimensions(chunk_index)?;
|
||||||
let readout = self.image().readout_for_size(data_dims.0, data_dims.1)?;
|
let readout = self.image.readout_for_size(data_dims.0, data_dims.1)?;
|
||||||
|
|
||||||
let extent = readout.result_extent_for_planes(0..1)?;
|
let extent = readout.result_extent_for_planes(0..1)?;
|
||||||
let sample_type = extent.sample_type();
|
let sample_type = extent.sample_type();
|
||||||
@@ -1202,7 +1252,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
planes: 1,
|
planes: 1,
|
||||||
plane_stride,
|
plane_stride,
|
||||||
complete_len: layout.size(),
|
complete_len: layout.size(),
|
||||||
sample_format: self.image().sample_format,
|
sample_format: self.image.sample_format,
|
||||||
sample_type: Some(sample_type),
|
sample_type: Some(sample_type),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1219,15 +1269,15 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
code_unit: TiffCodingUnit,
|
code_unit: TiffCodingUnit,
|
||||||
) -> TiffResult<BufferLayoutPreference> {
|
) -> TiffResult<BufferLayoutPreference> {
|
||||||
match self.image().planar_config {
|
match self.image.planar_config {
|
||||||
PlanarConfiguration::Chunky => return self.image_chunk_buffer_layout(code_unit.0),
|
PlanarConfiguration::Chunky => return self.image_chunk_buffer_layout(code_unit.0),
|
||||||
PlanarConfiguration::Planar => {}
|
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
|
let layout = self
|
||||||
.image()
|
.image
|
||||||
.readout_for_size(width, height)?
|
.readout_for_size(width, height)?
|
||||||
.to_plane_layout()?;
|
.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.
|
/// 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> {
|
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
|
let mut result = readout
|
||||||
.result_extent_for_planes(0..1)?
|
.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.
|
/// 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<()> {
|
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)?;
|
layout.assert_min_layout(buffer)?;
|
||||||
|
|
||||||
self.read_chunk_to_bytes(buffer, chunk_index, &layout)?;
|
self.read_chunk_to_bytes(buffer, chunk_index, &layout)?;
|
||||||
@@ -1289,9 +1339,9 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
output_width: usize,
|
output_width: usize,
|
||||||
) -> TiffResult<()> {
|
) -> 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)?;
|
layout.set_row_stride(output_width)?;
|
||||||
|
|
||||||
let extent = layout.result_extent_for_planes(0..1)?;
|
let extent = layout.result_extent_for_planes(0..1)?;
|
||||||
@@ -1321,8 +1371,8 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
slice: TiffCodingUnit,
|
slice: TiffCodingUnit,
|
||||||
buffer: &mut [u8],
|
buffer: &mut [u8],
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let (width, height) = self.image().chunk_data_dimensions(slice.0)?;
|
let (width, height) = self.image.chunk_data_dimensions(slice.0)?;
|
||||||
let readout = self.image().readout_for_size(width, height)?;
|
let readout = self.image.readout_for_size(width, height)?;
|
||||||
|
|
||||||
let ref layout @ image::PlaneLayout {
|
let ref layout @ image::PlaneLayout {
|
||||||
ref plane_offsets,
|
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() {
|
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
|
||||||
let chunk = slice.0 + idx as u32 * readout.chunks_per_plane;
|
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(
|
self.image.expand_chunk(
|
||||||
&mut self.value_reader,
|
&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
|
/// 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`.
|
/// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`.
|
||||||
pub fn chunk_dimensions(&self) -> (u32, u32) {
|
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,
|
/// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk,
|
||||||
/// minus any padding.
|
/// minus any padding.
|
||||||
pub fn chunk_data_dimensions(&self, chunk_index: u32) -> (u32, u32) {
|
pub fn chunk_data_dimensions(&self, chunk_index: u32) -> (u32, u32) {
|
||||||
self.image()
|
self.image
|
||||||
.chunk_data_dimensions(chunk_index)
|
.chunk_data_dimensions(chunk_index)
|
||||||
.expect("invalid 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
|
/// indicate the layout needed to read the first data plane. This will be fixed in a future
|
||||||
/// major version of `tiff`.
|
/// major version of `tiff`.
|
||||||
pub fn image_buffer_layout(&mut self) -> TiffResult<BufferLayoutPreference> {
|
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))
|
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
|
/// replaced, to ensure that existing code will not run into unexpectedly large allocations
|
||||||
/// that will error on limits instead.
|
/// that will error on limits instead.
|
||||||
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
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
|
let mut result = readout
|
||||||
.result_extent_for_planes(0..1)?
|
.result_extent_for_planes(0..1)?
|
||||||
@@ -1438,9 +1488,10 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// let mut result = DecodingResult::U8(vec![]);
|
/// let mut result = DecodingResult::U8(vec![]);
|
||||||
///
|
///
|
||||||
/// let mut reader = /* */
|
/// 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"
|
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
|
||||||
/// # )))).unwrap();
|
/// # )))).unwrap();
|
||||||
|
/// # reader.next_image().unwrap();
|
||||||
/// let layout = reader.read_image_to_buffer(&mut result)?;
|
/// let layout = reader.read_image_to_buffer(&mut result)?;
|
||||||
///
|
///
|
||||||
/// if result.as_buffer(0).as_bytes().len() < layout.complete_len {
|
/// 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 result = DecodingResult::I8(vec![]);
|
||||||
///
|
///
|
||||||
/// let mut reader = /* */
|
/// 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"
|
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
|
||||||
/// # )))).unwrap();
|
/// # )))).unwrap();
|
||||||
///
|
/// # reader.next_image().unwrap();
|
||||||
/// reader.read_image_to_buffer(&mut result)?;
|
/// reader.read_image_to_buffer(&mut result)?;
|
||||||
///
|
///
|
||||||
/// # Ok::<_, tiff::TiffError>(())
|
/// # Ok::<_, tiff::TiffError>(())
|
||||||
@@ -1470,7 +1521,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
result: &mut DecodingResult,
|
result: &mut DecodingResult,
|
||||||
) -> TiffResult<BufferLayoutPreference> {
|
) -> TiffResult<BufferLayoutPreference> {
|
||||||
let readout = self.image().readout_for_image()?;
|
let readout = self.image.readout_for_image()?;
|
||||||
let planes = readout.to_plane_layout()?;
|
let planes = readout.to_plane_layout()?;
|
||||||
|
|
||||||
let num_planes = if planes.total_bytes <= self.value_reader.limits.decoding_buffer_size {
|
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
|
/// 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.
|
/// images returns an error if the buffer does not fit the required size.
|
||||||
pub fn read_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
|
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 {
|
let ref layout @ image::PlaneLayout {
|
||||||
ref plane_offsets,
|
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() {
|
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
|
||||||
let chunk = chunk + idx as u32 * readout.chunks_per_plane;
|
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(
|
self.image.expand_chunk(
|
||||||
&mut self.value_reader,
|
&mut self.value_reader,
|
||||||
@@ -1539,12 +1590,16 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the IFD decoder for our current image IFD.
|
/// Get the IFD decoder for our current IFD.
|
||||||
pub fn image_ifd(&mut self) -> IfdDecoder<'_> {
|
///
|
||||||
|
/// 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 {
|
IfdDecoder {
|
||||||
inner: tag_reader::TagReader {
|
inner: tag_reader::TagReader {
|
||||||
decoder: &mut self.value_reader,
|
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;
|
/// # use std::io::Cursor;
|
||||||
/// # let mut data = Cursor::new(vec![0]);
|
/// # 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()?;
|
/// let sub_ifds = decoder.get_tag(Tag::SubIfd)?.into_ifd_vec()?;
|
||||||
///
|
///
|
||||||
/// for ifd in sub_ifds {
|
/// 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.
|
/// Tries to retrieve a tag from the current image directory.
|
||||||
/// Return `Ok(None)` if the tag is not present.
|
/// Return `Ok(None)` if the tag is not present.
|
||||||
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
|
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
|
/// Tries to retrieve a tag in the current image directory and convert it to the desired
|
||||||
/// unsigned type.
|
/// unsigned type.
|
||||||
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
|
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
|
/// 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,
|
&mut self,
|
||||||
tag: Tag,
|
tag: Tag,
|
||||||
) -> TiffResult<Option<Vec<T>>> {
|
) -> 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
|
/// 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.
|
/// 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> {
|
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.
|
/// Tries to retrieve a tag from the current image directory.
|
||||||
/// Returns an error if the tag is not present
|
/// Returns an error if the tag is not present
|
||||||
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
|
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> {
|
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)>> + '_ {
|
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 {
|
mod tests {
|
||||||
use super::Decoder;
|
use super::Decoder;
|
||||||
use crate::{
|
use crate::{
|
||||||
bytecast,
|
bytecast, encoder,
|
||||||
tags::{ByteOrder, Tag, ValueBuffer},
|
tags::{ByteOrder, Tag, ValueBuffer},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1923,9 +1980,11 @@ mod tests {
|
|||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut decoder = Decoder::new(file).unwrap();
|
let mut decoder = Decoder::open(file).unwrap();
|
||||||
let file_bo = decoder.byte_order();
|
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
|
let value = ifd
|
||||||
@@ -1972,4 +2031,116 @@ mod tests {
|
|||||||
assert_eq!(&by_bytes[..3], &[8, 8, 8]);
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -260,9 +260,9 @@ impl<R: Read> Read for PackBitsReader<R> {
|
|||||||
pub struct Group4Reader<R: Read> {
|
pub struct Group4Reader<R: Read> {
|
||||||
decoder: fax34::decoder::Group4Decoder<io::Bytes<io::BufReader<io::Take<R>>>>,
|
decoder: fax34::decoder::Group4Decoder<io::Bytes<io::BufReader<io::Take<R>>>>,
|
||||||
line_buf: io::Cursor<Vec<u8>>,
|
line_buf: io::Cursor<Vec<u8>>,
|
||||||
height: u16,
|
height: u32,
|
||||||
width: u16,
|
width: u16,
|
||||||
y: u16,
|
y: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "fax")]
|
#[cfg(feature = "fax")]
|
||||||
@@ -273,7 +273,7 @@ impl<R: Read> Group4Reader<R> {
|
|||||||
compressed_length: u64,
|
compressed_length: u64,
|
||||||
) -> crate::TiffResult<Self> {
|
) -> crate::TiffResult<Self> {
|
||||||
let width = u16::try_from(dimensions.0)?;
|
let width = u16::try_from(dimensions.0)?;
|
||||||
let height = u16::try_from(dimensions.1)?;
|
let height = dimensions.1;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
decoder: fax34::decoder::Group4Decoder::new(
|
decoder: fax34::decoder::Group4Decoder::new(
|
||||||
|
|||||||
+144
-8
@@ -1,5 +1,97 @@
|
|||||||
use crate::tags::{PhotometricInterpretation, SampleFormat};
|
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 {
|
macro_rules! integer_horizontal_predict {
|
||||||
() => {
|
() => {
|
||||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
|
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)),
|
.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];
|
const SAMPLE_FORMAT: &'static [SampleFormat];
|
||||||
|
|
||||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
|
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;
|
pub struct Gray8;
|
||||||
@@ -108,7 +208,11 @@ impl ColorType for Gray32Float {
|
|||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
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];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
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 TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
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 TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
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 TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
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 TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
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];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
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];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-4
@@ -33,9 +33,10 @@ use self::writer::*;
|
|||||||
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
|
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
|
||||||
/// passes ran over the image data to prepare it for compression. This is mostly used for LZW
|
/// passes ran over the image data to prepare it for compression. This is mostly used for LZW
|
||||||
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
|
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
|
||||||
/// ratio over the unpredicted 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;
|
pub type Predictor = crate::tags::Predictor;
|
||||||
#[cfg(feature = "deflate")]
|
#[cfg(feature = "deflate")]
|
||||||
pub type DeflateLevel = compression::DeflateLevel;
|
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> {
|
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
|
||||||
fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
|
fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
|
||||||
match (predictor, compression, T::SAMPLE_FORMAT[0]) {
|
match (predictor, compression, T::SAMPLE_FORMAT[0]) {
|
||||||
|
// Horizontal predictor is not valid for floating-point types
|
||||||
(Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
|
(Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
|
||||||
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
||||||
}
|
}
|
||||||
|
// Floating-point predictor is only valid for IEEEFP sample format
|
||||||
|
(Predictor::FloatingPoint, _, SampleFormat::IEEEFP) => Ok(()),
|
||||||
(Predictor::FloatingPoint, _, _) => {
|
(Predictor::FloatingPoint, _, _) => {
|
||||||
Err(TiffError::UsageError(UsageError::PredictorUnavailable))
|
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
||||||
}
|
}
|
||||||
_ => Ok(()),
|
_ => 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())?
|
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;
|
let byte_count = self.encoder.last_written() as usize;
|
||||||
|
|||||||
@@ -241,7 +241,9 @@ fn encoder_on_short_writes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
write.inner.set_position(0);
|
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();
|
let _layout = tiff_reader.read_image_to_buffer(&mut data).unwrap();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
+12
-1
@@ -357,7 +357,9 @@ pub enum Predictor(u16) {
|
|||||||
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
|
/// 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, ...]`
|
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
|
||||||
Horizontal = 2,
|
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,
|
FloatingPoint = 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -587,3 +589,12 @@ pub(crate) enum EndianBytes {
|
|||||||
Four,
|
Four,
|
||||||
Eight,
|
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,
|
||||||
|
}
|
||||||
@@ -15,7 +15,9 @@ fn test_big_tiff() {
|
|||||||
for filename in filenames.iter() {
|
for filename in filenames.iter() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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!(
|
assert_eq!(
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
decoder.dimensions().expect("Cannot get dimensions"),
|
||||||
(64, 64)
|
(64, 64)
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ fn test_white_ieee_fp16() {
|
|||||||
for filename in filenames.iter() {
|
for filename in filenames.iter() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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!(
|
assert_eq!(
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
decoder.dimensions().expect("Cannot get dimensions"),
|
||||||
(256, 256)
|
(256, 256)
|
||||||
@@ -25,6 +27,7 @@ fn test_white_ieee_fp16() {
|
|||||||
decoder.colortype().expect("Cannot get colortype"),
|
decoder.colortype().expect("Cannot get colortype"),
|
||||||
ColorType::Gray(16)
|
ColorType::Gray(16)
|
||||||
);
|
);
|
||||||
|
|
||||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||||
for p in img {
|
for p in img {
|
||||||
assert!(p == half::f16::from_f32_const(1.0));
|
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() {
|
for filename in filenames.iter() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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!(
|
assert_eq!(
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
decoder.dimensions().expect("Cannot get dimensions"),
|
||||||
(256, 256)
|
(256, 256)
|
||||||
@@ -52,6 +57,7 @@ fn test_one_black_pixel_ieee_fp16() {
|
|||||||
decoder.colortype().expect("Cannot get colortype"),
|
decoder.colortype().expect("Cannot get colortype"),
|
||||||
ColorType::Gray(16)
|
ColorType::Gray(16)
|
||||||
);
|
);
|
||||||
|
|
||||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||||
for (i, p) in img.iter().enumerate() {
|
for (i, p) in img.iter().enumerate() {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
@@ -74,7 +80,9 @@ fn test_pattern_horizontal_differencing_ieee_fp16() {
|
|||||||
for filename in filenames.iter() {
|
for filename in filenames.iter() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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!(
|
assert_eq!(
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
decoder.dimensions().expect("Cannot get dimensions"),
|
||||||
(256, 256)
|
(256, 256)
|
||||||
@@ -83,6 +91,7 @@ fn test_pattern_horizontal_differencing_ieee_fp16() {
|
|||||||
decoder.colortype().expect("Cannot get colortype"),
|
decoder.colortype().expect("Cannot get colortype"),
|
||||||
ColorType::Gray(16)
|
ColorType::Gray(16)
|
||||||
);
|
);
|
||||||
|
|
||||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||||
// 0, 2, 5, 8, 12, 16, 255 are black
|
// 0, 2, 5, 8, 12, 16, 255 are black
|
||||||
let black = [0, 2, 5, 8, 12, 16, 255];
|
let black = [0, 2, 5, 8, 12, 16, 255];
|
||||||
@@ -107,7 +116,9 @@ fn test_pattern_predictor_ieee_fp16() {
|
|||||||
for filename in filenames.iter() {
|
for filename in filenames.iter() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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!(
|
assert_eq!(
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
decoder.dimensions().expect("Cannot get dimensions"),
|
||||||
(256, 256)
|
(256, 256)
|
||||||
@@ -116,6 +127,7 @@ fn test_pattern_predictor_ieee_fp16() {
|
|||||||
decoder.colortype().expect("Cannot get colortype"),
|
decoder.colortype().expect("Cannot get colortype"),
|
||||||
ColorType::Gray(16)
|
ColorType::Gray(16)
|
||||||
);
|
);
|
||||||
|
|
||||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||||
// 0, 2, 5, 8, 12, 16, 255 are black
|
// 0, 2, 5, 8, 12, 16, 255 are black
|
||||||
let black = [0, 2, 5, 8, 12, 16, 255];
|
let black = [0, 2, 5, 8, 12, 16, 255];
|
||||||
@@ -175,7 +187,9 @@ fn test_predictor_ieee_fp16() {
|
|||||||
for filename in filenames.iter() {
|
for filename in filenames.iter() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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!(
|
assert_eq!(
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
decoder.dimensions().expect("Cannot get dimensions"),
|
||||||
(16, 16)
|
(16, 16)
|
||||||
@@ -184,6 +198,7 @@ fn test_predictor_ieee_fp16() {
|
|||||||
decoder.colortype().expect("Cannot get colortype"),
|
decoder.colortype().expect("Cannot get colortype"),
|
||||||
ColorType::Gray(16)
|
ColorType::Gray(16)
|
||||||
);
|
);
|
||||||
|
|
||||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||||
for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) {
|
for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) {
|
||||||
assert!((exp - found.to_f32()).abs() < 0.0001);
|
assert!((exp - found.to_f32()).abs() < 0.0001);
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ fn test_geo_tiff() {
|
|||||||
for filename in filenames.iter() {
|
for filename in filenames.iter() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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 = decoder.with_limits(tiff::decoder::Limits::unlimited());
|
||||||
|
decoder.next_image().expect("Cannot read image");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
decoder.dimensions().expect("Cannot get dimensions"),
|
||||||
@@ -71,16 +72,11 @@ fn test_webp_tiff() {
|
|||||||
for filename in filenames.iter() {
|
for filename in filenames.iter() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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");
|
||||||
|
|
||||||
loop {
|
|
||||||
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
|
||||||
|
|
||||||
if !decoder.more_images() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
while decoder.more_images() {
|
||||||
decoder.next_image().unwrap();
|
decoder.next_image().unwrap();
|
||||||
|
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+67
-19
@@ -14,7 +14,9 @@ macro_rules! test_image_sum {
|
|||||||
fn $name(file: &str, expected_type: ColorType, expected_sum: $sum_ty) {
|
fn $name(file: &str, expected_type: ColorType, expected_sum: $sum_ty) {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
let img_res = decoder.read_image().unwrap();
|
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) {
|
fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
decoder.read_image(),
|
decoder.read_image(),
|
||||||
@@ -166,7 +170,8 @@ fn test_string_tags() {
|
|||||||
for filename in filenames.iter() {
|
for filename in filenames.iter() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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();
|
let software = decoder.get_tag(tiff::tags::Tag::Software).unwrap();
|
||||||
match software {
|
match software {
|
||||||
ifd::Value::Ascii(s) => assert_eq!(
|
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 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.colortype().unwrap(), ColorType::RGB(8));
|
||||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||||
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
||||||
@@ -236,7 +242,8 @@ fn test_tiled_rect_rgb_u8() {
|
|||||||
fn test_inner_access() {
|
fn test_inner_access() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
|
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 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));
|
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||||
|
|
||||||
let c = decoder
|
let c = decoder
|
||||||
@@ -322,7 +329,8 @@ fn test_tiled_incremental() {
|
|||||||
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
|
|
||||||
let tiles = decoder.tile_count().unwrap();
|
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 path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
|
|
||||||
let chunks = decoder.strip_count().unwrap();
|
let chunks = decoder.strip_count().unwrap();
|
||||||
@@ -396,7 +405,8 @@ fn test_div_zero() {
|
|||||||
178, 178, 178,
|
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 {
|
match err {
|
||||||
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
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,
|
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 {
|
match error {
|
||||||
tiff::TiffError::LimitsExceeded => {}
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
@@ -432,7 +443,8 @@ fn fuzzer_testcase5() {
|
|||||||
178, 178, 178,
|
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]
|
#[test]
|
||||||
@@ -445,7 +457,8 @@ fn fuzzer_testcase1() {
|
|||||||
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]
|
#[test]
|
||||||
@@ -458,7 +471,8 @@ fn fuzzer_testcase6() {
|
|||||||
178, 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]
|
#[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,
|
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]
|
#[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,
|
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]
|
#[test]
|
||||||
@@ -498,7 +514,8 @@ fn fuzzer_testcase2() {
|
|||||||
73,
|
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]
|
#[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,
|
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]
|
#[test]
|
||||||
@@ -527,7 +545,8 @@ fn fuzzer_testcase3() {
|
|||||||
255, 255,
|
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]
|
#[test]
|
||||||
@@ -545,7 +564,8 @@ fn timeout() {
|
|||||||
0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32,
|
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 {
|
match error {
|
||||||
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
||||||
@@ -583,7 +603,8 @@ fn test_image_bytes(
|
|||||||
) {
|
) {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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];
|
let mut buffer = vec![0u8; 0];
|
||||||
for (idx, &(expected_type, expected_crc)) in expected.iter().enumerate() {
|
for (idx, &(expected_type, expected_crc)) in expected.iter().enumerate() {
|
||||||
@@ -720,3 +741,30 @@ fn extra_bits_gray() {
|
|||||||
fn extra_bits_rgb() {
|
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::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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -115,9 +115,10 @@ fn main() {
|
|||||||
let Some(path) = entry.path().to_str() else {
|
let Some(path) = entry.path().to_str() else {
|
||||||
continue;
|
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.
|
// 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);
|
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 path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path);
|
||||||
|
|
||||||
let img_file = File::open(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![]);
|
let mut buffer = DecodingResult::U8(vec![]);
|
||||||
|
while decoder.more_images() {
|
||||||
loop {
|
|
||||||
let _layout = decoder.read_image_to_buffer(&mut buffer);
|
|
||||||
|
|
||||||
if !decoder.more_images() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder.next_image()?;
|
decoder.next_image()?;
|
||||||
|
let _layout = decoder.read_image_to_buffer(&mut buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ fn read_all_planes() {
|
|||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join("planar-rgb-u8.tif");
|
let path = PathBuf::from(TEST_IMAGE_DIR).join("planar-rgb-u8.tif");
|
||||||
let file = File::open(path).unwrap();
|
let file = File::open(path).unwrap();
|
||||||
let io = std::io::BufReader::new(file);
|
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();
|
let layout = tif.image_buffer_layout().unwrap();
|
||||||
assert_eq!(layout.planes, 3);
|
assert_eq!(layout.planes, 3);
|
||||||
let plane_stride = layout.plane_stride.map_or(0, |x| x.get());
|
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 path = PathBuf::from(TEST_IMAGE_DIR).join("planar-rgb-u8.tif");
|
||||||
let file = File::open(path).unwrap();
|
let file = File::open(path).unwrap();
|
||||||
let io = std::io::BufReader::new(file);
|
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();
|
let layout = tif.image_coding_unit_layout(TiffCodingUnit(0)).unwrap();
|
||||||
|
|
||||||
assert_eq!(layout.planes, 3);
|
assert_eq!(layout.planes, 3);
|
||||||
@@ -85,12 +87,13 @@ fn read_planes_by_limits() {
|
|||||||
let mut buffer = DecodingResult::U8(vec![]);
|
let mut buffer = DecodingResult::U8(vec![]);
|
||||||
|
|
||||||
// Make room for 1 plane.
|
// Make room for 1 plane.
|
||||||
let mut tif = Decoder::new(io).unwrap();
|
let mut tif = Decoder::open(io).unwrap();
|
||||||
tif = tif.with_limits({
|
tif = tif.with_limits({
|
||||||
limits.decoding_buffer_size = 374 * 499;
|
limits.decoding_buffer_size = 374 * 499;
|
||||||
limits.clone()
|
limits.clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tif.next_image().unwrap();
|
||||||
tif.read_image_to_buffer(&mut buffer).unwrap();
|
tif.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499);
|
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499);
|
||||||
|
|
||||||
|
|||||||
+166
-14
@@ -1,7 +1,7 @@
|
|||||||
extern crate tiff;
|
extern crate tiff;
|
||||||
|
|
||||||
use tiff::decoder::{ifd, Decoder, DecodingResult};
|
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::tags::Tag;
|
||||||
use tiff::ColorType;
|
use tiff::ColorType;
|
||||||
|
|
||||||
@@ -33,7 +33,9 @@ fn encode_decode() {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
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.colortype().unwrap(), ColorType::RGB(8));
|
||||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||||
|
|
||||||
@@ -116,7 +118,9 @@ fn encode_decode_big() {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
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.colortype().unwrap(), ColorType::RGB(8));
|
||||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||||
|
|
||||||
@@ -207,8 +211,10 @@ fn test_encode_ifd() {
|
|||||||
|
|
||||||
// Rewind the cursor for reading
|
// Rewind the cursor for reading
|
||||||
data.set_position(0);
|
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(65000), 42);
|
||||||
assert_eq!(decoder.assert_tag_u32_vec(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 path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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);
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
|
|
||||||
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
||||||
@@ -268,7 +276,8 @@ macro_rules! test_roundtrip {
|
|||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
{
|
{
|
||||||
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
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();
|
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
if let DecodingResult::$buffer(img_res) = buffer {
|
if let DecodingResult::$buffer(img_res) = buffer {
|
||||||
assert_eq!(*image_data, img_res);
|
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_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 {
|
trait AssertDecode {
|
||||||
fn assert_tag_u32(&mut self, tag: u16) -> u32;
|
fn assert_tag_u32(&mut self, tag: u16) -> u32;
|
||||||
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
|
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
|
||||||
@@ -456,7 +603,8 @@ fn test_multiple_byte() {
|
|||||||
|
|
||||||
data.set_position(0);
|
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(65000), [1]);
|
||||||
assert_eq!(decoder.assert_tag_u32_vec(65001), [1, 2]);
|
assert_eq!(decoder.assert_tag_u32_vec(65001), [1, 2]);
|
||||||
@@ -537,7 +685,8 @@ fn test_signed() {
|
|||||||
//Rewind the cursor for reading
|
//Rewind the cursor for reading
|
||||||
data.set_position(0);
|
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(65000), -1);
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65001), [-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();
|
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
|
// check the dimensions of the image in the first page
|
||||||
assert_eq!(img_decoder.dimensions().unwrap(), (2, 2));
|
assert_eq!(decoder.dimensions().unwrap(), (2, 2));
|
||||||
img_decoder.next_image().unwrap();
|
decoder.next_image().unwrap();
|
||||||
// check the dimensions of the image in the second page
|
// 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();
|
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.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2);
|
||||||
assert_eq!(decoder.strip_count().unwrap(), 50);
|
assert_eq!(decoder.strip_count().unwrap(), 50);
|
||||||
|
|
||||||
@@ -664,9 +815,10 @@ fn test_auxiliary_directory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data.set_position(0);
|
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));
|
assert_eq!(decoder.dimensions().unwrap(), (1, 1));
|
||||||
let _ = decoder.read_image().unwrap();
|
let _ = decoder.read_image().unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ fn encode_decode_with_compression(compression: Compression) {
|
|||||||
// Decode tiff
|
// Decode tiff
|
||||||
data.set_position(0);
|
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
|
// Check the RGB image
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
+2
-1
@@ -13,7 +13,8 @@ fn test_directory<F: Fn(File) -> bool>(path: &str, f: F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn decode_tiff(file: File) -> TiffResult<()> {
|
fn decode_tiff(file: File) -> TiffResult<()> {
|
||||||
let mut decoder = Decoder::new(file)?;
|
let mut decoder = Decoder::open(file)?;
|
||||||
|
decoder.next_image()?;
|
||||||
decoder.read_image()?;
|
decoder.read_image()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
+7
-3
@@ -18,8 +18,9 @@ macro_rules! test_predict {
|
|||||||
) {
|
) {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
let file = File::open(path).expect("Cannot find test image!");
|
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);
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
let image_data = match decoder.read_image().unwrap() {
|
let image_data = match decoder.read_image().unwrap() {
|
||||||
DecodingResult::$buffer(res) => res,
|
DecodingResult::$buffer(res) => res,
|
||||||
@@ -123,7 +124,9 @@ macro_rules! test_predict_roundtrip {
|
|||||||
) {
|
) {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
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);
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
|
|
||||||
let image_data = match decoder.read_image().unwrap() {
|
let image_data = match decoder.read_image().unwrap() {
|
||||||
@@ -142,7 +145,8 @@ macro_rules! test_predict_roundtrip {
|
|||||||
}
|
}
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
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) =
|
if let DecodingResult::$buffer(img_res) =
|
||||||
decoder.read_image().expect("Decoding image failed")
|
decoder.read_image().expect("Decoding image failed")
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-1
@@ -18,8 +18,10 @@ use std::fs::File;
|
|||||||
#[test]
|
#[test]
|
||||||
fn decode_seek_chain() {
|
fn decode_seek_chain() {
|
||||||
let file = File::open(TEST_IMAGE_SUBIFD).expect("Cannot open test image");
|
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.
|
// Remember the offsets in the first iteration.
|
||||||
let offset0 = decoder.ifd_pointer().expect("First IFD pointer not found");
|
let offset0 = decoder.ifd_pointer().expect("First IFD pointer not found");
|
||||||
decoder.next_image().unwrap();
|
decoder.next_image().unwrap();
|
||||||
|
|||||||
Reference in new issue
Block a user