Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab4fb4e959 | ||
|
|
7b1683687e | ||
|
|
f6e2aeffe5 | ||
|
|
525bb2b9b1 | ||
|
|
54f733eaa3 | ||
|
|
9b0c9ca84c | ||
|
|
890488b16e | ||
|
|
3e530d27ea | ||
|
|
02699c9588 | ||
|
|
26b6afc787 | ||
|
|
5df003368b | ||
|
|
a582fdcc72 | ||
|
|
e62cdfbf42 | ||
|
|
f021e3e599 | ||
|
|
f5ab3b363b | ||
|
|
f33c7be21c | ||
|
|
98cd3dc2d2 | ||
|
|
6275b7a36d | ||
|
|
97e230584f | ||
|
|
43d7f66c9e | ||
|
|
a1be18b548 | ||
|
|
df4e42eecc | ||
|
|
816fb85304 | ||
|
|
7648317643 | ||
|
|
140f2788de |
No files matched your search
@@ -1,9 +1,9 @@
|
||||
name: Rust CI
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [ main ]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -45,6 +45,31 @@ jobs:
|
||||
- name: Cargo check
|
||||
run: cargo check --features "${{ matrix.feature }}" --no-default-features
|
||||
|
||||
other_archs:
|
||||
# github actions does not support 32-bit or big endian systems directly, but
|
||||
# it does support QEMU. so we install qemu, then build and run the tests on
|
||||
# emulated systems. NOTE: you can also use this approach to test locally.
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [powerpc-unknown-linux-gnu, i686-unknown-linux-gnu]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install or use cached cross-rs/cross
|
||||
uses: baptiste0928/cargo-install@v2
|
||||
with:
|
||||
crate: cross
|
||||
- name: Cache Cargo Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
key: ${{ matrix.arch }}
|
||||
- name: Start Docker (required for cross-rs)
|
||||
run: sudo systemctl start docker
|
||||
- name: Cross-Run Tests using QEMU
|
||||
run: cross test --target ${{ matrix.arch }}
|
||||
|
||||
rustfmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ half = { version = "2.4.1" }
|
||||
quick-error = "2.0.1"
|
||||
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
weezl = { version = "0.1.0", optional = true }
|
||||
weezl = { version = "0.1.10", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
zune-jpeg = { version = "0.4.17", optional = true }
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# image-tiff
|
||||
[](https://github.com/image-rs/image-tiff/actions)
|
||||
[](https://github.com/image-rs/image-tiff/actions)
|
||||
[](https://docs.rs/tiff)
|
||||
[](https://crates.io/crates/tiff)
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
use crate::{tags::IfdPointer, TiffError, TiffFormatError};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// The IFD structure of a TIFF file should be limited to a forest of entries, and a tree when we
|
||||
/// only consider having a primary IFD and its children. There is up to one primary child of a node
|
||||
/// that is the `next` entry in an IFD itself. Since we offer iteration over this chain we want to
|
||||
/// detect when a new edge would introduce a cycle, such that callers can be guaranteed to
|
||||
/// terminate iteration at some point even in malicious images.
|
||||
///
|
||||
/// However, we are not necessarily visiting the IFDs in their topological order.
|
||||
///
|
||||
/// Since we only consider one child, we run union find to assign each pointer to a known chain.
|
||||
/// Then, when we insert a new edge we check if they belong to the same component.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct IfdCycles {
|
||||
/// The root of each component union.
|
||||
component_union: HashMap<ComponentId, ComponentId>,
|
||||
links: HashMap<IfdPointer, u64>,
|
||||
chains: HashMap<IfdPointer, ComponentId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
struct ComponentId(u64);
|
||||
|
||||
impl IfdCycles {
|
||||
pub fn new() -> Self {
|
||||
IfdCycles::default()
|
||||
}
|
||||
|
||||
pub fn insert_next(
|
||||
&mut self,
|
||||
from: IfdPointer,
|
||||
to: Option<IfdPointer>,
|
||||
) -> Result<bool, TiffError> {
|
||||
let to_offset = to.map_or(0, |p| p.0);
|
||||
|
||||
// For some reason we got
|
||||
match self.links.get(&from) {
|
||||
Some(existing) if *existing == to_offset => return Ok(false),
|
||||
// We got here twice with two different reads of the same IFD. That is .. unusual and
|
||||
// we interpret it as a cycle.
|
||||
Some(_) => return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)),
|
||||
None => self.links.insert(from, to_offset),
|
||||
};
|
||||
|
||||
self.ensure_node(from);
|
||||
|
||||
if let Some(to) = to {
|
||||
self.ensure_node(to);
|
||||
|
||||
let parent = self.nominal_component(from);
|
||||
let child = self.nominal_component(to);
|
||||
|
||||
if parent == child {
|
||||
return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets));
|
||||
}
|
||||
|
||||
self.component_union.insert(child, parent);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn ensure_node(&mut self, ifd: IfdPointer) {
|
||||
self.chains.entry(ifd).or_insert_with(|| {
|
||||
let id = ComponentId(self.component_union.len() as u64);
|
||||
self.component_union.insert(id, id);
|
||||
id
|
||||
});
|
||||
}
|
||||
|
||||
fn nominal_component(&mut self, node: IfdPointer) -> ComponentId {
|
||||
let id = self.chains[&node];
|
||||
|
||||
let nomimal = {
|
||||
let mut iter = id;
|
||||
|
||||
loop {
|
||||
let parent = self.component_union[&iter];
|
||||
|
||||
if parent == iter {
|
||||
break parent;
|
||||
}
|
||||
|
||||
iter = parent;
|
||||
}
|
||||
};
|
||||
|
||||
if nomimal != id {
|
||||
// Compress.
|
||||
let mut iter = id;
|
||||
|
||||
loop {
|
||||
let parent = self.component_union[&iter];
|
||||
|
||||
if parent == iter {
|
||||
break;
|
||||
}
|
||||
|
||||
self.component_union.insert(iter, nomimal);
|
||||
iter = parent;
|
||||
}
|
||||
}
|
||||
|
||||
nomimal
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cycles_are_detected() {
|
||||
let mut cycles = IfdCycles::new();
|
||||
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x20), Some(IfdPointer(0x800)))
|
||||
.expect("non-existing link is valid");
|
||||
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x800), Some(IfdPointer(0x20)))
|
||||
.expect_err("cycle must be detected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reflective_cycle() {
|
||||
let mut cycles = IfdCycles::new();
|
||||
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x20), Some(IfdPointer(0x20)))
|
||||
.expect_err("self-referential cycle must be detected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn late_cycle() {
|
||||
let mut cycles = IfdCycles::new();
|
||||
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x20), Some(IfdPointer(0x40)))
|
||||
.expect("non-existing link is valid");
|
||||
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x60), Some(IfdPointer(0x80)))
|
||||
.expect("non-existing link is valid");
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x80), Some(IfdPointer(0x20)))
|
||||
.expect("non-existing link is valid");
|
||||
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x40), Some(IfdPointer(0x60)))
|
||||
.expect_err("non-existing link is valid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn odd_cycle() {
|
||||
let mut cycles = IfdCycles::new();
|
||||
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x20), Some(IfdPointer(0x40)))
|
||||
.expect("non-existing link is valid");
|
||||
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x60), Some(IfdPointer(0x80)))
|
||||
.expect("non-existing link is valid");
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x80), Some(IfdPointer(0x20)))
|
||||
.expect("non-existing link is valid");
|
||||
|
||||
cycles
|
||||
.insert_next(IfdPointer(0x40), Some(IfdPointer(0x80)))
|
||||
.expect_err("non-existing link is valid");
|
||||
}
|
||||
+48
-4
@@ -6,7 +6,7 @@ use std::mem;
|
||||
use std::str;
|
||||
|
||||
use super::stream::{ByteOrder, EndianReader};
|
||||
use crate::tags::{Tag, Type};
|
||||
use crate::tags::{IfdPointer, Tag, Type};
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use self::Value::{
|
||||
@@ -130,6 +130,25 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn this value into an `IfdPointer`.
|
||||
///
|
||||
/// Notice that this does not take an argument, a 64-bit IFD is always allowed. If the
|
||||
/// difference is crucial and you do not want to be permissive you're expected to filter this
|
||||
/// out before.
|
||||
///
|
||||
/// For compatibility the smaller sized tags should always be allowed i.e. you might use a
|
||||
/// non-bigtiff's directory and its tag types and move it straight to a bigtiff. For instance
|
||||
/// the SubIFD tag is defined as `LONG or IFD`:
|
||||
///
|
||||
/// <https://web.archive.org/web/20181105221012/https://www.awaresystems.be/imaging/tiff/tifftags/subifds.html>
|
||||
pub fn into_ifd_pointer(self) -> TiffResult<IfdPointer> {
|
||||
match self {
|
||||
Unsigned(val) | Ifd(val) => Ok(IfdPointer(val.into())),
|
||||
IfdBig(val) => Ok(IfdPointer(val)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> TiffResult<String> {
|
||||
match self {
|
||||
Ascii(val) => Ok(val),
|
||||
@@ -300,6 +319,17 @@ impl Value {
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_ifd_vec(self) -> TiffResult<Vec<IfdPointer>> {
|
||||
let vec = match self {
|
||||
Unsigned(val) | Ifd(val) => return Ok(vec![IfdPointer(val.into())]),
|
||||
IfdBig(val) => return Ok(vec![IfdPointer(val)]),
|
||||
List(vec) => vec,
|
||||
_ => return Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
};
|
||||
|
||||
vec.into_iter().map(Self::into_ifd_pointer).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -320,9 +350,9 @@ impl ::std::fmt::Debug for Entry {
|
||||
|
||||
impl Entry {
|
||||
pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry {
|
||||
let mut offset = offset.to_vec();
|
||||
offset.append(&mut vec![0; 4]);
|
||||
Entry::new_u64(type_, count.into(), offset[..].try_into().unwrap())
|
||||
let mut entry_off = [0u8; 8];
|
||||
entry_off[..4].copy_from_slice(&offset);
|
||||
Entry::new_u64(type_, count.into(), entry_off)
|
||||
}
|
||||
|
||||
pub fn new_u64(type_: Type, count: u64, offset: [u8; 8]) -> Entry {
|
||||
@@ -333,6 +363,18 @@ impl Entry {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn field_type(&self) -> Type {
|
||||
self.type_
|
||||
}
|
||||
|
||||
pub fn count(&self) -> u64 {
|
||||
self.count
|
||||
}
|
||||
|
||||
pub(crate) fn offset(&self) -> &[u8] {
|
||||
&self.offset
|
||||
}
|
||||
|
||||
/// Returns a mem_reader for the offset/value field
|
||||
fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
|
||||
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
||||
@@ -668,4 +710,6 @@ fn offset_to_sbytes(n: usize, entry: &Entry) -> TiffResult<Value> {
|
||||
}
|
||||
|
||||
/// Type representing an Image File Directory
|
||||
#[doc(hidden)]
|
||||
#[deprecated = "Use struct `tiff::Directory` instead which contains all fields relevant to an Image File Directory, including the offset to the next directory"]
|
||||
pub type Directory = HashMap<Tag, Entry>;
|
||||
+32
-48
@@ -1,12 +1,14 @@
|
||||
use super::ifd::{Directory, Value};
|
||||
use super::stream::{ByteOrder, PackBitsReader};
|
||||
use super::ifd::Value;
|
||||
use super::stream::PackBitsReader;
|
||||
use super::tag_reader::TagReader;
|
||||
use super::{predict_f16, predict_f32, predict_f64, Limits};
|
||||
use super::{stream::EndianReader, ChunkType};
|
||||
use super::ChunkType;
|
||||
use super::{predict_f16, predict_f32, predict_f64, ValueReader};
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
|
||||
};
|
||||
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||
use crate::{
|
||||
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||
};
|
||||
use std::io::{self, Cursor, Read, Seek};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -80,17 +82,10 @@ pub(crate) struct Image {
|
||||
|
||||
impl Image {
|
||||
pub fn from_reader<R: Read + Seek>(
|
||||
reader: &mut EndianReader<R>,
|
||||
decoder: &mut ValueReader<R>,
|
||||
ifd: Directory,
|
||||
limits: &Limits,
|
||||
bigtiff: bool,
|
||||
) -> TiffResult<Image> {
|
||||
let mut tag_reader = TagReader {
|
||||
reader,
|
||||
limits,
|
||||
ifd: &ifd,
|
||||
bigtiff,
|
||||
};
|
||||
let mut tag_reader = TagReader { decoder, ifd: &ifd };
|
||||
|
||||
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
||||
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
||||
@@ -115,7 +110,7 @@ impl Image {
|
||||
};
|
||||
|
||||
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
|
||||
&& ifd.contains_key(&Tag::JPEGTables)
|
||||
&& ifd.contains(Tag::JPEGTables)
|
||||
{
|
||||
let vec = tag_reader
|
||||
.find_tag(Tag::JPEGTables)?
|
||||
@@ -210,10 +205,10 @@ impl Image {
|
||||
let strip_decoder;
|
||||
let tile_attributes;
|
||||
match (
|
||||
ifd.contains_key(&Tag::StripByteCounts),
|
||||
ifd.contains_key(&Tag::StripOffsets),
|
||||
ifd.contains_key(&Tag::TileByteCounts),
|
||||
ifd.contains_key(&Tag::TileOffsets),
|
||||
ifd.contains(Tag::StripByteCounts),
|
||||
ifd.contains(Tag::StripOffsets),
|
||||
ifd.contains(Tag::TileByteCounts),
|
||||
ifd.contains(Tag::TileOffsets),
|
||||
) {
|
||||
(true, true, false, false) => {
|
||||
chunk_type = ChunkType::Strip;
|
||||
@@ -370,7 +365,6 @@ impl Image {
|
||||
|
||||
fn create_reader<'r, R: 'r + Read>(
|
||||
reader: R,
|
||||
#[allow(unused_variables)] photometric_interpretation: PhotometricInterpretation,
|
||||
compression_method: CompressionMethod,
|
||||
compressed_length: u64,
|
||||
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
|
||||
@@ -427,30 +421,14 @@ impl Image {
|
||||
|
||||
let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
|
||||
let mut options: zune_core::options::DecoderOptions = Default::default();
|
||||
match photometric_interpretation {
|
||||
PhotometricInterpretation::RGB => {
|
||||
options =
|
||||
options.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::RGB);
|
||||
}
|
||||
PhotometricInterpretation::CMYK => {
|
||||
options = options
|
||||
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::CMYK);
|
||||
}
|
||||
PhotometricInterpretation::YCbCr => {
|
||||
options = options
|
||||
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::YCbCr);
|
||||
}
|
||||
PhotometricInterpretation::WhiteIsZero
|
||||
| PhotometricInterpretation::BlackIsZero
|
||||
| PhotometricInterpretation::TransparencyMask => {}
|
||||
PhotometricInterpretation::RGBPalette | PhotometricInterpretation::CIELab => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedInterpretation(
|
||||
photometric_interpretation,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
// Disable color conversion by setting the output colorspace to the input
|
||||
// colorspace.
|
||||
decoder.decode_headers()?;
|
||||
if let Some(colorspace) = decoder.get_input_colorspace() {
|
||||
options = options.jpeg_set_out_colorspace(colorspace);
|
||||
}
|
||||
|
||||
decoder.set_options(options);
|
||||
|
||||
let data = decoder.decode()?;
|
||||
@@ -555,13 +533,20 @@ impl Image {
|
||||
|
||||
pub(crate) fn expand_chunk(
|
||||
&self,
|
||||
reader: impl Read,
|
||||
reader: &mut ValueReader<impl Read>,
|
||||
buf: &mut [u8],
|
||||
output_row_stride: usize,
|
||||
byte_order: ByteOrder,
|
||||
chunk_index: u32,
|
||||
limits: &Limits,
|
||||
) -> TiffResult<()> {
|
||||
let ValueReader {
|
||||
reader,
|
||||
bigtiff: _,
|
||||
limits,
|
||||
} = reader;
|
||||
|
||||
let byte_order = reader.byte_order;
|
||||
let reader = reader.inner();
|
||||
|
||||
// Validate that the color type is supported.
|
||||
let color_type = self.colortype()?;
|
||||
match color_type {
|
||||
@@ -653,7 +638,6 @@ impl Image {
|
||||
|
||||
let mut reader = Self::create_reader(
|
||||
reader,
|
||||
photometric_interpretation,
|
||||
compression_method,
|
||||
*compressed_bytes,
|
||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||
@@ -694,7 +678,7 @@ impl Image {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i, row) in buf
|
||||
for (_i, row) in buf
|
||||
.chunks_mut(output_row_stride)
|
||||
.take(data_dims.1 as usize)
|
||||
.enumerate()
|
||||
|
||||
+447
-274
@@ -1,19 +1,20 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{self, Read, Seek};
|
||||
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat,
|
||||
Tag, Type,
|
||||
CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor,
|
||||
SampleFormat, Tag, Type,
|
||||
};
|
||||
use crate::{
|
||||
bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||
bytecast, ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError,
|
||||
UsageError,
|
||||
};
|
||||
use half::f16;
|
||||
|
||||
use self::ifd::Directory;
|
||||
use self::image::Image;
|
||||
use self::stream::{ByteOrder, EndianReader};
|
||||
|
||||
mod cycles;
|
||||
pub mod ifd;
|
||||
mod image;
|
||||
mod stream;
|
||||
@@ -260,13 +261,31 @@ pub struct Decoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
/// There are grouped for borrow checker reasons. This allows us to implement methods that
|
||||
/// borrow the stream access and the other fields mutably at the same time.
|
||||
value_reader: ValueReader<R>,
|
||||
current_ifd: Option<IfdPointer>,
|
||||
next_ifd: Option<IfdPointer>,
|
||||
/// The IFDs we visited already in this chain of IFDs.
|
||||
ifd_offsets: Vec<IfdPointer>,
|
||||
/// Map from the ifd into the `ifd_offsets` ordered list.
|
||||
seen_ifds: cycles::IfdCycles,
|
||||
image: Image,
|
||||
}
|
||||
|
||||
/// All the information needed to read and interpret byte slices from the underlying file, i.e. to
|
||||
/// turn an entry of a tag into an `ifd::Value` or otherwise fetch arrays of similar types. Used
|
||||
/// only as the type of the field [`Decoder::value_reader`] and passed to submodules.
|
||||
#[derive(Debug)]
|
||||
struct ValueReader<R> {
|
||||
reader: EndianReader<R>,
|
||||
bigtiff: bool,
|
||||
limits: Limits,
|
||||
next_ifd: Option<u64>,
|
||||
ifd_offsets: Vec<u64>,
|
||||
seen_ifds: HashSet<u64>,
|
||||
image: Image,
|
||||
}
|
||||
|
||||
/// Reads a directory's tag values from an underlying stream.
|
||||
pub struct IfdDecoder<'lt> {
|
||||
inner: tag_reader::TagReader<'lt, dyn tag_reader::EntryDecoder + 'lt>,
|
||||
}
|
||||
|
||||
fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
|
||||
@@ -487,23 +506,27 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let next_ifd = if bigtiff {
|
||||
Some(reader.read_u64()?)
|
||||
} else {
|
||||
Some(u64::from(reader.read_u32()?))
|
||||
};
|
||||
}
|
||||
.map(IfdPointer);
|
||||
|
||||
let mut seen_ifds = HashSet::new();
|
||||
seen_ifds.insert(*next_ifd.as_ref().unwrap());
|
||||
let ifd_offsets = vec![*next_ifd.as_ref().unwrap()];
|
||||
let current_ifd = *next_ifd.as_ref().unwrap();
|
||||
let ifd_offsets = vec![current_ifd];
|
||||
|
||||
let mut decoder = Decoder {
|
||||
value_reader: ValueReader {
|
||||
reader,
|
||||
bigtiff,
|
||||
limits: Default::default(),
|
||||
},
|
||||
next_ifd,
|
||||
ifd_offsets,
|
||||
seen_ifds,
|
||||
current_ifd: None,
|
||||
seen_ifds: cycles::IfdCycles::new(),
|
||||
image: Image {
|
||||
ifd: None,
|
||||
width: 0,
|
||||
@@ -528,7 +551,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
}
|
||||
|
||||
pub fn with_limits(mut self, limits: Limits) -> Decoder<R> {
|
||||
self.limits = limits;
|
||||
self.value_reader.limits = limits;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -540,6 +563,11 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
self.image().colortype()
|
||||
}
|
||||
|
||||
/// The offset of the directory representing the current image.
|
||||
pub fn ifd_pointer(&mut self) -> Option<IfdPointer> {
|
||||
self.current_ifd
|
||||
}
|
||||
|
||||
fn image(&self) -> &Image {
|
||||
&self.image
|
||||
}
|
||||
@@ -550,6 +578,8 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
if ifd_index >= self.ifd_offsets.len() {
|
||||
// We possibly need to load in the next IFD
|
||||
if self.next_ifd.is_none() {
|
||||
self.current_ifd = None;
|
||||
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::ImageFileDirectoryNotFound,
|
||||
));
|
||||
@@ -557,9 +587,9 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
|
||||
loop {
|
||||
// Follow the list until we find the one we want, or we reach the end, whichever happens first
|
||||
let (_ifd, next_ifd) = self.next_ifd()?;
|
||||
let ifd = self.next_ifd()?;
|
||||
|
||||
if next_ifd.is_none() {
|
||||
if ifd.next().is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -571,9 +601,10 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
|
||||
// If the index is within the list of ifds then we can load the selected image/IFD
|
||||
if let Some(ifd_offset) = self.ifd_offsets.get(ifd_index) {
|
||||
let (ifd, _next_ifd) = Self::read_ifd(&mut self.reader, self.bigtiff, *ifd_offset)?;
|
||||
|
||||
self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?;
|
||||
let ifd = self.value_reader.read_directory(*ifd_offset)?;
|
||||
self.next_ifd = ifd.next();
|
||||
self.current_ifd = Some(*ifd_offset);
|
||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -583,37 +614,35 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
}
|
||||
}
|
||||
|
||||
fn next_ifd(&mut self) -> TiffResult<(Directory, Option<u64>)> {
|
||||
if self.next_ifd.is_none() {
|
||||
fn next_ifd(&mut self) -> TiffResult<Directory> {
|
||||
let Some(next_ifd) = self.next_ifd.take() else {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::ImageFileDirectoryNotFound,
|
||||
));
|
||||
};
|
||||
|
||||
let ifd = self.value_reader.read_directory(next_ifd)?;
|
||||
|
||||
// Ensure this walk does not get us into a cycle.
|
||||
self.seen_ifds.insert_next(next_ifd, ifd.next())?;
|
||||
|
||||
// Extend the list of known IFD offsets in this chain, if needed.
|
||||
if self.ifd_offsets.last().copied() == self.current_ifd {
|
||||
self.ifd_offsets.push(next_ifd);
|
||||
}
|
||||
|
||||
let (ifd, next_ifd) = Self::read_ifd(
|
||||
&mut self.reader,
|
||||
self.bigtiff,
|
||||
self.next_ifd.take().unwrap(),
|
||||
)?;
|
||||
self.current_ifd = Some(next_ifd);
|
||||
self.next_ifd = ifd.next();
|
||||
|
||||
if let Some(next) = next_ifd {
|
||||
if !self.seen_ifds.insert(next) {
|
||||
return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets));
|
||||
}
|
||||
self.next_ifd = Some(next);
|
||||
self.ifd_offsets.push(next);
|
||||
}
|
||||
|
||||
Ok((ifd, next_ifd))
|
||||
Ok(ifd)
|
||||
}
|
||||
|
||||
/// Reads in the next image.
|
||||
/// If there is no further image in the TIFF file a format error is returned.
|
||||
/// To determine whether there are more images call `TIFFDecoder::more_images` instead.
|
||||
pub fn next_image(&mut self) -> TiffResult<()> {
|
||||
let (ifd, _next_ifd) = self.next_ifd()?;
|
||||
|
||||
self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?;
|
||||
let ifd = self.next_ifd()?;
|
||||
self.image = Image::from_reader(&mut self.value_reader, ifd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -622,14 +651,14 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
self.next_ifd.is_some()
|
||||
}
|
||||
|
||||
/// Returns the byte_order
|
||||
/// Returns the byte_order of the file.
|
||||
pub fn byte_order(&self) -> ByteOrder {
|
||||
self.reader.byte_order
|
||||
self.value_reader.reader.byte_order
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_ifd_offset(&mut self) -> Result<u64, io::Error> {
|
||||
if self.bigtiff {
|
||||
if self.value_reader.bigtiff {
|
||||
self.read_long8()
|
||||
} else {
|
||||
self.read_long().map(u64::from)
|
||||
@@ -640,61 +669,61 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
#[inline]
|
||||
pub fn read_byte(&mut self) -> Result<u8, io::Error> {
|
||||
let mut buf = [0; 1];
|
||||
self.reader.inner().read_exact(&mut buf)?;
|
||||
self.value_reader.reader.inner().read_exact(&mut buf)?;
|
||||
Ok(buf[0])
|
||||
}
|
||||
|
||||
/// Reads a TIFF short value
|
||||
#[inline]
|
||||
pub fn read_short(&mut self) -> Result<u16, io::Error> {
|
||||
self.reader.read_u16()
|
||||
self.value_reader.reader.read_u16()
|
||||
}
|
||||
|
||||
/// Reads a TIFF sshort value
|
||||
#[inline]
|
||||
pub fn read_sshort(&mut self) -> Result<i16, io::Error> {
|
||||
self.reader.read_i16()
|
||||
self.value_reader.reader.read_i16()
|
||||
}
|
||||
|
||||
/// Reads a TIFF long value
|
||||
#[inline]
|
||||
pub fn read_long(&mut self) -> Result<u32, io::Error> {
|
||||
self.reader.read_u32()
|
||||
self.value_reader.reader.read_u32()
|
||||
}
|
||||
|
||||
/// Reads a TIFF slong value
|
||||
#[inline]
|
||||
pub fn read_slong(&mut self) -> Result<i32, io::Error> {
|
||||
self.reader.read_i32()
|
||||
self.value_reader.reader.read_i32()
|
||||
}
|
||||
|
||||
/// Reads a TIFF float value
|
||||
#[inline]
|
||||
pub fn read_float(&mut self) -> Result<f32, io::Error> {
|
||||
self.reader.read_f32()
|
||||
self.value_reader.reader.read_f32()
|
||||
}
|
||||
|
||||
/// Reads a TIFF double value
|
||||
#[inline]
|
||||
pub fn read_double(&mut self) -> Result<f64, io::Error> {
|
||||
self.reader.read_f64()
|
||||
self.value_reader.reader.read_f64()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_long8(&mut self) -> Result<u64, io::Error> {
|
||||
self.reader.read_u64()
|
||||
self.value_reader.reader.read_u64()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_slong8(&mut self) -> Result<i64, io::Error> {
|
||||
self.reader.read_i64()
|
||||
self.value_reader.reader.read_i64()
|
||||
}
|
||||
|
||||
/// Reads a string
|
||||
#[inline]
|
||||
pub fn read_string(&mut self, length: usize) -> TiffResult<String> {
|
||||
let mut out = vec![0; length];
|
||||
self.reader.inner().read_exact(&mut out)?;
|
||||
self.value_reader.reader.inner().read_exact(&mut out)?;
|
||||
// Strings may be null-terminated, so we trim anything downstream of the null byte
|
||||
if let Some(first) = out.iter().position(|&b| b == 0) {
|
||||
out.truncate(first);
|
||||
@@ -705,13 +734,13 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
/// Reads a TIFF IFA offset/value field
|
||||
#[inline]
|
||||
pub fn read_offset(&mut self) -> TiffResult<[u8; 4]> {
|
||||
if self.bigtiff {
|
||||
if self.value_reader.bigtiff {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
));
|
||||
}
|
||||
let mut val = [0; 4];
|
||||
self.reader.inner().read_exact(&mut val)?;
|
||||
self.value_reader.reader.inner().read_exact(&mut val)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -719,7 +748,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
#[inline]
|
||||
pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> {
|
||||
let mut val = [0; 8];
|
||||
self.reader.inner().read_exact(&mut val)?;
|
||||
self.value_reader.reader.inner().read_exact(&mut val)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -731,205 +760,30 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
|
||||
#[inline]
|
||||
pub fn goto_offset_u64(&mut self, offset: u64) -> io::Result<()> {
|
||||
self.reader.goto_offset(offset)
|
||||
self.value_reader.reader.goto_offset(offset)
|
||||
}
|
||||
|
||||
/// Reads a IFD entry.
|
||||
// An IFD entry has four fields:
|
||||
//
|
||||
// Tag 2 bytes
|
||||
// Type 2 bytes
|
||||
// Count 4 bytes
|
||||
// Value 4 bytes either a pointer the value itself
|
||||
fn read_entry(
|
||||
reader: &mut EndianReader<R>,
|
||||
bigtiff: bool,
|
||||
) -> TiffResult<Option<(Tag, ifd::Entry)>> {
|
||||
let tag = Tag::from_u16_exhaustive(reader.read_u16()?);
|
||||
let type_ = match Type::from_u16(reader.read_u16()?) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
// Unknown type. Skip this entry according to spec.
|
||||
reader.read_u32()?;
|
||||
reader.read_u32()?;
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
let entry = if bigtiff {
|
||||
let mut offset = [0; 8];
|
||||
|
||||
let count = reader.read_u64()?;
|
||||
reader.inner().read_exact(&mut offset)?;
|
||||
ifd::Entry::new_u64(type_, count, offset)
|
||||
} else {
|
||||
let mut offset = [0; 4];
|
||||
|
||||
let count = reader.read_u32()?;
|
||||
reader.inner().read_exact(&mut offset)?;
|
||||
ifd::Entry::new(type_, count, offset)
|
||||
};
|
||||
Ok(Some((tag, entry)))
|
||||
}
|
||||
|
||||
/// Reads the IFD starting at the indicated location.
|
||||
fn read_ifd(
|
||||
reader: &mut EndianReader<R>,
|
||||
bigtiff: bool,
|
||||
ifd_location: u64,
|
||||
) -> TiffResult<(Directory, Option<u64>)> {
|
||||
reader.goto_offset(ifd_location)?;
|
||||
|
||||
let mut dir: Directory = HashMap::new();
|
||||
|
||||
let num_tags = if bigtiff {
|
||||
reader.read_u64()?
|
||||
} else {
|
||||
reader.read_u16()?.into()
|
||||
};
|
||||
for _ in 0..num_tags {
|
||||
let (tag, entry) = match Self::read_entry(reader, bigtiff)? {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
continue;
|
||||
} // Unknown data type in tag, skip
|
||||
};
|
||||
dir.insert(tag, entry);
|
||||
}
|
||||
|
||||
let next_ifd = if bigtiff {
|
||||
reader.read_u64()?
|
||||
} else {
|
||||
reader.read_u32()?.into()
|
||||
};
|
||||
|
||||
let next_ifd = match next_ifd {
|
||||
0 => None,
|
||||
_ => Some(next_ifd),
|
||||
};
|
||||
|
||||
Ok((dir, next_ifd))
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag.
|
||||
/// Return `Ok(None)` if the tag is not present.
|
||||
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
|
||||
let entry = match self.image().ifd.as_ref().unwrap().get(&tag) {
|
||||
None => return Ok(None),
|
||||
Some(entry) => entry.clone(),
|
||||
};
|
||||
|
||||
Ok(Some(entry.val(
|
||||
&self.limits,
|
||||
self.bigtiff,
|
||||
&mut self.reader,
|
||||
)?))
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired unsigned type.
|
||||
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
|
||||
self.find_tag(tag)?
|
||||
.map(|v| v.into_u64())
|
||||
.transpose()?
|
||||
.map(|value| {
|
||||
T::try_from(value).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a vector of all a tag's values and convert them to
|
||||
/// the desired unsigned type.
|
||||
pub fn find_tag_unsigned_vec<T: TryFrom<u64>>(
|
||||
&mut self,
|
||||
tag: Tag,
|
||||
) -> TiffResult<Option<Vec<T>>> {
|
||||
self.find_tag(tag)?
|
||||
.map(|v| v.into_u64_vec())
|
||||
.transpose()?
|
||||
.map(|v| {
|
||||
v.into_iter()
|
||||
.map(|u| {
|
||||
T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired unsigned type.
|
||||
/// Returns an error if the tag is not present.
|
||||
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
|
||||
self.find_tag_unsigned(tag)?
|
||||
.ok_or_else(|| TiffFormatError::RequiredTagNotFound(tag).into())
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag.
|
||||
/// Returns an error if the tag is not present
|
||||
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
|
||||
match self.find_tag(tag)? {
|
||||
Some(val) => Ok(val),
|
||||
None => Err(TiffError::FormatError(
|
||||
TiffFormatError::RequiredTagNotFound(tag),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
|
||||
self.get_tag(tag)?.into_u32()
|
||||
}
|
||||
pub fn get_tag_u64(&mut self, tag: Tag) -> TiffResult<u64> {
|
||||
self.get_tag(tag)?.into_u64()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f32(&mut self, tag: Tag) -> TiffResult<f32> {
|
||||
self.get_tag(tag)?.into_f32()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f64(&mut self, tag: Tag) -> TiffResult<f64> {
|
||||
self.get_tag(tag)?.into_f64()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult<Vec<u32>> {
|
||||
self.get_tag(tag)?.into_u32_vec()
|
||||
}
|
||||
|
||||
pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult<Vec<u16>> {
|
||||
self.get_tag(tag)?.into_u16_vec()
|
||||
}
|
||||
pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult<Vec<u64>> {
|
||||
self.get_tag(tag)?.into_u64_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f32_vec(&mut self, tag: Tag) -> TiffResult<Vec<f32>> {
|
||||
self.get_tag(tag)?.into_f32_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f64_vec(&mut self, tag: Tag) -> TiffResult<Vec<f64>> {
|
||||
self.get_tag(tag)?.into_f64_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to a 8bit vector.
|
||||
pub fn get_tag_u8_vec(&mut self, tag: Tag) -> TiffResult<Vec<u8>> {
|
||||
self.get_tag(tag)?.into_u8_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to a ascii vector.
|
||||
pub fn get_tag_ascii_string(&mut self, tag: Tag) -> TiffResult<String> {
|
||||
self.get_tag(tag)?.into_string()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all tags in the current image, along with their values.
|
||||
pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
|
||||
self.image.ifd.as_ref().unwrap().iter().map(|(tag, entry)| {
|
||||
entry
|
||||
.val(&self.limits, self.bigtiff, &mut self.reader)
|
||||
.map(|value| (*tag, value))
|
||||
})
|
||||
/// Read a tag-entry map from a known offset.
|
||||
///
|
||||
/// A TIFF [`Directory`], aka. image file directory aka. IFD, refers to a map from
|
||||
/// tags–identified by a `u16`–to a typed vector of elements. It is encoded as a list
|
||||
/// of ascending tag values with the offset and type of their corresponding values. The
|
||||
/// semantic interpretations of a tag and its type requirements depend on the context of the
|
||||
/// directory. The main image directories, those iterated over by the `Decoder` after
|
||||
/// construction, are represented by [`Tag`] and [`ifd::Value`]. Other forms are EXIF and GPS
|
||||
/// data as well as thumbnail Sub-IFD representations associated with each image file.
|
||||
///
|
||||
/// This method allows the decoding of a directory from an arbitrary offset in the image file
|
||||
/// with no specific semantic interpretation. Such an offset is usually found as the value of
|
||||
/// a tag, e.g. [`Tag::SubIfd`], [`Tag::ExifDirectory`], [`Tag::GpsDirectory`] and recovered
|
||||
/// from the associated value by [`ifd::Value::into_ifd_pointer`].
|
||||
///
|
||||
/// The library will not verify whether the offset overlaps any other directory or would form a
|
||||
/// cycle with any other directory when calling this method. This will modify the position of
|
||||
/// the reader, i.e. continuing with direct reads at a later point will require going back with
|
||||
/// [`Self::goto_offset`].
|
||||
pub fn read_directory(&mut self, ptr: IfdPointer) -> TiffResult<Directory> {
|
||||
self.value_reader.read_directory(ptr)
|
||||
}
|
||||
|
||||
fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> {
|
||||
@@ -984,9 +838,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
output_width: usize,
|
||||
) -> TiffResult<()> {
|
||||
let offset = self.image.chunk_file_range(chunk_index)?.0;
|
||||
self.reader.goto_offset(offset)?;
|
||||
|
||||
let byte_order = self.reader.byte_order;
|
||||
self.goto_offset_u64(offset)?;
|
||||
|
||||
let output_row_stride = (output_width as u64)
|
||||
.saturating_mul(self.image.samples_per_pixel() as u64)
|
||||
@@ -994,12 +846,10 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
/ 8;
|
||||
|
||||
self.image.expand_chunk(
|
||||
self.reader.inner(),
|
||||
&mut self.value_reader,
|
||||
buffer.as_bytes_mut(),
|
||||
output_row_stride.try_into()?,
|
||||
byte_order,
|
||||
chunk_index,
|
||||
&self.limits,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -1022,29 +872,31 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
|
||||
let max_sample_bits = self.image().bits_per_sample;
|
||||
let limits = &self.value_reader.limits;
|
||||
|
||||
match self.image().sample_format {
|
||||
SampleFormat::Uint => match max_sample_bits {
|
||||
n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits),
|
||||
n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits),
|
||||
n if n <= 32 => DecodingResult::new_u32(buffer_size, &self.limits),
|
||||
n if n <= 64 => DecodingResult::new_u64(buffer_size, &self.limits),
|
||||
n if n <= 8 => DecodingResult::new_u8(buffer_size, limits),
|
||||
n if n <= 16 => DecodingResult::new_u16(buffer_size, limits),
|
||||
n if n <= 32 => DecodingResult::new_u32(buffer_size, limits),
|
||||
n if n <= 64 => DecodingResult::new_u64(buffer_size, limits),
|
||||
n => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||
)),
|
||||
},
|
||||
SampleFormat::IEEEFP => match max_sample_bits {
|
||||
16 => DecodingResult::new_f16(buffer_size, &self.limits),
|
||||
32 => DecodingResult::new_f32(buffer_size, &self.limits),
|
||||
64 => DecodingResult::new_f64(buffer_size, &self.limits),
|
||||
16 => DecodingResult::new_f16(buffer_size, limits),
|
||||
32 => DecodingResult::new_f32(buffer_size, limits),
|
||||
64 => DecodingResult::new_f64(buffer_size, limits),
|
||||
n => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||
)),
|
||||
},
|
||||
SampleFormat::Int => match max_sample_bits {
|
||||
n if n <= 8 => DecodingResult::new_i8(buffer_size, &self.limits),
|
||||
n if n <= 16 => DecodingResult::new_i16(buffer_size, &self.limits),
|
||||
n if n <= 32 => DecodingResult::new_i32(buffer_size, &self.limits),
|
||||
n if n <= 64 => DecodingResult::new_i64(buffer_size, &self.limits),
|
||||
n if n <= 8 => DecodingResult::new_i8(buffer_size, limits),
|
||||
n if n <= 16 => DecodingResult::new_i16(buffer_size, limits),
|
||||
n if n <= 32 => DecodingResult::new_i32(buffer_size, limits),
|
||||
n if n <= 64 => DecodingResult::new_i64(buffer_size, limits),
|
||||
n => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||
)),
|
||||
@@ -1129,23 +981,344 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
// * pass requested band as parameter
|
||||
// * collect bands to a RGB encoding result in case of RGB bands
|
||||
for chunk in 0..image_chunks {
|
||||
self.reader.goto_offset(self.image().chunk_offsets[chunk])?;
|
||||
self.goto_offset_u64(self.image().chunk_offsets[chunk])?;
|
||||
|
||||
let x = chunk % chunks_across;
|
||||
let y = chunk / chunks_across;
|
||||
let buffer_offset =
|
||||
y * output_row_stride * chunk_dimensions.1 as usize + x * chunk_row_bytes;
|
||||
let byte_order = self.reader.byte_order;
|
||||
self.image.expand_chunk(
|
||||
self.reader.inner(),
|
||||
&mut self.value_reader,
|
||||
&mut result.as_buffer(0).as_bytes_mut()[buffer_offset..],
|
||||
output_row_stride,
|
||||
byte_order,
|
||||
chunk as u32,
|
||||
&self.limits,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Get the IFD decoder for our current image IFD.
|
||||
fn image_ifd(&mut self) -> IfdDecoder<'_> {
|
||||
IfdDecoder {
|
||||
inner: tag_reader::TagReader {
|
||||
decoder: &mut self.value_reader,
|
||||
ifd: self.image.ifd.as_ref().unwrap(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare reading values for tags of a given directory.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// This method may be used to read the values of tags in directories that have been previously
|
||||
/// read with [`Decoder::read_directory`].
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tiff::decoder::Decoder;
|
||||
/// use tiff::tags::Tag;
|
||||
///
|
||||
/// # use std::io::Cursor;
|
||||
/// # let mut data = Cursor::new(vec![0]);
|
||||
/// let mut decoder = Decoder::new(&mut data).unwrap();
|
||||
/// let sub_ifds = decoder.get_tag(Tag::SubIfd)?.into_ifd_vec()?;
|
||||
///
|
||||
/// for ifd in sub_ifds {
|
||||
/// let subdir = decoder.read_directory(ifd)?;
|
||||
/// let subfile = decoder.read_directory_tags(&subdir).find_tag(Tag::SubfileType)?;
|
||||
/// // omitted: handle the subfiles, e.g. thumbnails
|
||||
/// }
|
||||
///
|
||||
/// # Ok::<_, tiff::TiffError>(())
|
||||
/// ```
|
||||
pub fn read_directory_tags<'ifd>(&'ifd mut self, ifd: &'ifd Directory) -> IfdDecoder<'ifd> {
|
||||
IfdDecoder {
|
||||
inner: tag_reader::TagReader {
|
||||
decoder: &mut self.value_reader,
|
||||
ifd,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag from the current image directory.
|
||||
/// Return `Ok(None)` if the tag is not present.
|
||||
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
|
||||
self.image_ifd().find_tag(tag)
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag in the current image directory and convert it to the desired
|
||||
/// unsigned type.
|
||||
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
|
||||
self.image_ifd().find_tag_unsigned(tag)
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag from the current image directory and convert it to the desired
|
||||
/// unsigned type. Returns an error if the tag is not present.
|
||||
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
|
||||
self.image_ifd().get_tag_unsigned(tag)
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag from the current image directory.
|
||||
/// Returns an error if the tag is not present
|
||||
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
|
||||
self.image_ifd().get_tag(tag)
|
||||
}
|
||||
|
||||
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
|
||||
self.get_tag(tag)?.into_u32()
|
||||
}
|
||||
|
||||
pub fn get_tag_u64(&mut self, tag: Tag) -> TiffResult<u64> {
|
||||
self.get_tag(tag)?.into_u64()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f32(&mut self, tag: Tag) -> TiffResult<f32> {
|
||||
self.get_tag(tag)?.into_f32()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f64(&mut self, tag: Tag) -> TiffResult<f64> {
|
||||
self.get_tag(tag)?.into_f64()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult<Vec<u32>> {
|
||||
self.get_tag(tag)?.into_u32_vec()
|
||||
}
|
||||
|
||||
pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult<Vec<u16>> {
|
||||
self.get_tag(tag)?.into_u16_vec()
|
||||
}
|
||||
|
||||
pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult<Vec<u64>> {
|
||||
self.get_tag(tag)?.into_u64_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f32_vec(&mut self, tag: Tag) -> TiffResult<Vec<f32>> {
|
||||
self.get_tag(tag)?.into_f32_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f64_vec(&mut self, tag: Tag) -> TiffResult<Vec<f64>> {
|
||||
self.get_tag(tag)?.into_f64_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to a 8bit vector.
|
||||
pub fn get_tag_u8_vec(&mut self, tag: Tag) -> TiffResult<Vec<u8>> {
|
||||
self.get_tag(tag)?.into_u8_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to a ascii vector.
|
||||
pub fn get_tag_ascii_string(&mut self, tag: Tag) -> TiffResult<String> {
|
||||
self.get_tag(tag)?.into_string()
|
||||
}
|
||||
|
||||
pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
|
||||
self.image_ifd().tag_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Seek + Read> ValueReader<R> {
|
||||
pub(crate) fn read_directory(&mut self, ptr: IfdPointer) -> Result<Directory, TiffError> {
|
||||
Self::read_ifd(&mut self.reader, self.bigtiff, ptr)
|
||||
}
|
||||
|
||||
/// Reads a IFD entry.
|
||||
// An IFD entry has four fields:
|
||||
//
|
||||
// Tag 2 bytes
|
||||
// Type 2 bytes
|
||||
// Count 4 bytes
|
||||
// Value 4 bytes either a pointer the value itself
|
||||
fn read_entry(
|
||||
reader: &mut EndianReader<R>,
|
||||
bigtiff: bool,
|
||||
) -> TiffResult<Option<(Tag, ifd::Entry)>> {
|
||||
let tag = Tag::from_u16_exhaustive(reader.read_u16()?);
|
||||
let type_ = match Type::from_u16(reader.read_u16()?) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
// Unknown type. Skip this entry according to spec.
|
||||
reader.read_u32()?;
|
||||
reader.read_u32()?;
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
let entry = if bigtiff {
|
||||
let mut offset = [0; 8];
|
||||
|
||||
let count = reader.read_u64()?;
|
||||
reader.inner().read_exact(&mut offset)?;
|
||||
ifd::Entry::new_u64(type_, count, offset)
|
||||
} else {
|
||||
let mut offset = [0; 4];
|
||||
|
||||
let count = reader.read_u32()?;
|
||||
reader.inner().read_exact(&mut offset)?;
|
||||
ifd::Entry::new(type_, count, offset)
|
||||
};
|
||||
Ok(Some((tag, entry)))
|
||||
}
|
||||
|
||||
/// Reads the IFD starting at the indicated location.
|
||||
fn read_ifd(
|
||||
reader: &mut EndianReader<R>,
|
||||
bigtiff: bool,
|
||||
ifd_location: IfdPointer,
|
||||
) -> TiffResult<Directory> {
|
||||
reader.goto_offset(ifd_location.0)?;
|
||||
|
||||
let mut entries: BTreeMap<_, _> = BTreeMap::new();
|
||||
|
||||
let num_tags = if bigtiff {
|
||||
reader.read_u64()?
|
||||
} else {
|
||||
reader.read_u16()?.into()
|
||||
};
|
||||
|
||||
for _ in 0..num_tags {
|
||||
let (tag, entry) = match Self::read_entry(reader, bigtiff)? {
|
||||
Some(val) => val,
|
||||
None => {
|
||||
continue;
|
||||
} // Unknown data type in tag, skip
|
||||
};
|
||||
|
||||
entries.insert(tag.to_u16(), entry);
|
||||
}
|
||||
|
||||
let next_ifd = if bigtiff {
|
||||
reader.read_u64()?
|
||||
} else {
|
||||
reader.read_u32()?.into()
|
||||
};
|
||||
|
||||
let next_ifd = core::num::NonZeroU64::new(next_ifd);
|
||||
|
||||
Ok(Directory { entries, next_ifd })
|
||||
}
|
||||
}
|
||||
|
||||
impl IfdDecoder<'_> {
|
||||
/// Tries to retrieve a tag.
|
||||
/// Return `Ok(None)` if the tag is not present.
|
||||
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
|
||||
self.inner.find_tag(tag)
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired unsigned type.
|
||||
pub fn find_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<T>> {
|
||||
self.find_tag(tag)?
|
||||
.map(|v| v.into_u64())
|
||||
.transpose()?
|
||||
.map(|value| {
|
||||
T::try_from(value).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a vector of all a tag's values and convert them to
|
||||
/// the desired unsigned type.
|
||||
pub fn find_tag_unsigned_vec<T: TryFrom<u64>>(
|
||||
&mut self,
|
||||
tag: Tag,
|
||||
) -> TiffResult<Option<Vec<T>>> {
|
||||
self.find_tag(tag)?
|
||||
.map(|v| v.into_u64_vec())
|
||||
.transpose()?
|
||||
.map(|v| {
|
||||
v.into_iter()
|
||||
.map(|u| {
|
||||
T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired unsigned type.
|
||||
/// Returns an error if the tag is not present.
|
||||
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
|
||||
self.find_tag_unsigned(tag)?
|
||||
.ok_or_else(|| TiffFormatError::RequiredTagNotFound(tag).into())
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag.
|
||||
/// Returns an error if the tag is not present
|
||||
pub fn get_tag(&mut self, tag: Tag) -> TiffResult<ifd::Value> {
|
||||
match self.find_tag(tag)? {
|
||||
Some(val) => Ok(val),
|
||||
None => Err(TiffError::FormatError(
|
||||
TiffFormatError::RequiredTagNotFound(tag),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult<u32> {
|
||||
self.get_tag(tag)?.into_u32()
|
||||
}
|
||||
|
||||
pub fn get_tag_u64(&mut self, tag: Tag) -> TiffResult<u64> {
|
||||
self.get_tag(tag)?.into_u64()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f32(&mut self, tag: Tag) -> TiffResult<f32> {
|
||||
self.get_tag(tag)?.into_f32()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f64(&mut self, tag: Tag) -> TiffResult<f64> {
|
||||
self.get_tag(tag)?.into_f64()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult<Vec<u32>> {
|
||||
self.get_tag(tag)?.into_u32_vec()
|
||||
}
|
||||
|
||||
pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult<Vec<u16>> {
|
||||
self.get_tag(tag)?.into_u16_vec()
|
||||
}
|
||||
|
||||
pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult<Vec<u64>> {
|
||||
self.get_tag(tag)?.into_u64_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f32_vec(&mut self, tag: Tag) -> TiffResult<Vec<f32>> {
|
||||
self.get_tag(tag)?.into_f32_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to the desired type.
|
||||
pub fn get_tag_f64_vec(&mut self, tag: Tag) -> TiffResult<Vec<f64>> {
|
||||
self.get_tag(tag)?.into_f64_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to a 8bit vector.
|
||||
pub fn get_tag_u8_vec(&mut self, tag: Tag) -> TiffResult<Vec<u8>> {
|
||||
self.get_tag(tag)?.into_u8_vec()
|
||||
}
|
||||
|
||||
/// Tries to retrieve a tag and convert it to a ascii vector.
|
||||
pub fn get_tag_ascii_string(&mut self, tag: Tag) -> TiffResult<String> {
|
||||
self.get_tag(tag)?.into_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'l> IfdDecoder<'l> {
|
||||
/// Returns an iterator over all tags in the current image, along with their values.
|
||||
pub fn tag_iter(self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + 'l {
|
||||
self.inner
|
||||
.ifd
|
||||
.iter()
|
||||
.map(|(tag, entry)| match self.inner.decoder.entry_val(entry) {
|
||||
Ok(value) => Ok((tag, value)),
|
||||
Err(err) => Err(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ pub struct EndianReader<R> {
|
||||
pub(crate) byte_order: ByteOrder,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> EndianReader<R> {
|
||||
impl<R: Read> EndianReader<R> {
|
||||
pub fn new(reader: R, byte_order: ByteOrder) -> Self {
|
||||
Self { reader, byte_order }
|
||||
}
|
||||
@@ -27,7 +27,10 @@ impl<R: Read + Seek> EndianReader<R> {
|
||||
&mut self.reader
|
||||
}
|
||||
|
||||
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
|
||||
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()>
|
||||
where
|
||||
R: Seek,
|
||||
{
|
||||
self.reader.seek(io::SeekFrom::Start(offset))?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -158,12 +161,15 @@ pub struct LZWReader<R: Read> {
|
||||
impl<R: Read> LZWReader<R> {
|
||||
/// Wraps a reader
|
||||
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
|
||||
let configuration =
|
||||
weezl::decode::Configuration::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
|
||||
.with_yield_on_full_buffer(true);
|
||||
Self {
|
||||
reader: BufReader::with_capacity(
|
||||
(32 * 1024).min(compressed_length),
|
||||
reader.take(u64::try_from(compressed_length).unwrap()),
|
||||
),
|
||||
decoder: weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8),
|
||||
decoder: configuration.build(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-14
@@ -1,25 +1,37 @@
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
use crate::tags::Tag;
|
||||
use crate::{tags::Tag, Directory};
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use super::ifd::{Directory, Value};
|
||||
use super::stream::EndianReader;
|
||||
use super::Limits;
|
||||
use super::ifd::{Entry, Value};
|
||||
use super::ValueReader;
|
||||
|
||||
pub(crate) struct TagReader<'a, R: Read + Seek> {
|
||||
pub reader: &'a mut EndianReader<R>,
|
||||
pub ifd: &'a Directory,
|
||||
pub limits: &'a Limits,
|
||||
pub bigtiff: bool,
|
||||
pub(crate) struct TagReader<'lt, R: EntryDecoder + ?Sized> {
|
||||
pub(crate) decoder: &'lt mut R,
|
||||
pub(crate) ifd: &'lt Directory,
|
||||
}
|
||||
impl<'a, R: Read + Seek> TagReader<'a, R> {
|
||||
|
||||
pub(crate) trait EntryDecoder {
|
||||
/// Turn an entry into a value by fetching the required bytes in the stream.
|
||||
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value>;
|
||||
}
|
||||
|
||||
impl<R: Seek + Read> EntryDecoder for ValueReader<R> {
|
||||
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value> {
|
||||
entry.val(&self.limits, self.bigtiff, &mut self.reader)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R: EntryDecoder + ?Sized> TagReader<'a, R> {
|
||||
pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<Value>> {
|
||||
Ok(match self.ifd.get(&tag) {
|
||||
Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?),
|
||||
None => None,
|
||||
})
|
||||
let entry = match self.ifd.get(tag) {
|
||||
None => return Ok(None),
|
||||
Some(entry) => entry.clone(),
|
||||
};
|
||||
|
||||
self.decoder.entry_val(&entry).map(Some)
|
||||
}
|
||||
|
||||
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
|
||||
match self.find_tag(tag)? {
|
||||
Some(val) => Ok(val),
|
||||
@@ -28,6 +40,7 @@ impl<'a, R: Read + Seek> TagReader<'a, R> {
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_tag_uint_vec<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<Vec<T>>> {
|
||||
self.find_tag(tag)?
|
||||
.map(|v| v.into_u64_vec())
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
use core::fmt;
|
||||
use std::{collections::BTreeMap, num::NonZeroU64};
|
||||
|
||||
use crate::{
|
||||
decoder::ifd::Entry,
|
||||
tags::{IfdPointer, Tag},
|
||||
};
|
||||
|
||||
/// An Image File Directory (IFD).
|
||||
///
|
||||
/// A directory is a map of [`Tag`]s to [`Value`](crate::decoder::ifd::Value)s. The values are
|
||||
/// stored anywhere in the file, with the directory containing the offsets and length of the
|
||||
/// associated values for each tag present in the directory.
|
||||
///
|
||||
/// A directory can be created with with
|
||||
/// [`Decoder::read_directory`](crate::decoder::Decoder::read_directory) or as an empty directory
|
||||
/// to be extended with entries. A directory may be used with
|
||||
/// [`Decoder::read_directory_tags`](crate::decoder::Decoder::read_directory_tags) to read the
|
||||
/// values associated with tags from an underlying file.
|
||||
#[doc(alias = "IFD")]
|
||||
pub struct Directory {
|
||||
/// There are at most `u16::MAX` entries in any single directory, the count is stored as a
|
||||
/// 2-byte value. The order in the file is implied to be ascending by tag value (the decoder
|
||||
/// does not mind unordered entries).
|
||||
pub(crate) entries: BTreeMap<u16, Entry>,
|
||||
pub(crate) next_ifd: Option<NonZeroU64>,
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
pub fn empty() -> Self {
|
||||
Directory {
|
||||
entries: BTreeMap::new(),
|
||||
next_ifd: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the value associated with a tag.
|
||||
pub fn get(&self, tag: Tag) -> Option<&Entry> {
|
||||
self.entries.get(&tag.to_u16())
|
||||
}
|
||||
|
||||
/// Check if the directory contains a specified tag.
|
||||
pub fn contains(&self, tag: Tag) -> bool {
|
||||
self.entries.contains_key(&tag.to_u16())
|
||||
}
|
||||
|
||||
/// Iterate over all known and unknown tags in this directory.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Tag, &Entry)> + '_ {
|
||||
self.entries
|
||||
.iter()
|
||||
.map(|(k, v)| (Tag::from_u16_exhaustive(*k), v))
|
||||
}
|
||||
|
||||
/// Insert additional entries into the directory.
|
||||
///
|
||||
/// Note that a directory can contain at most `u16::MAX` values. There may be one entry that
|
||||
/// does not fit into the directory. This entry is silently ignored (please check [`Self::len`]
|
||||
/// to detect the condition). Providing a tag multiple times or a tag that already exists
|
||||
/// within this directory overwrites the entry.
|
||||
pub fn extend(&mut self, iter: impl IntoIterator<Item = (Tag, Entry)>) {
|
||||
// Code size conscious extension, avoid monomorphic extensions with the assumption of these
|
||||
// not being performance sensitive in practice. (Maybe we have a polymorphic interface for
|
||||
// the crate usage in the future.
|
||||
self.extend_inner(iter.into_iter().by_ref())
|
||||
}
|
||||
|
||||
/// Get the number of entries.
|
||||
pub fn len(&self) -> usize {
|
||||
// The keys are `u16`. Since IFDs are required to have at least one entry this would have
|
||||
// been a trivial thing to do in the specification by storing it minus one but alas. In
|
||||
// BigTIFF the count is stored as 8-bit anyways.
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
/// Get the pointer to the next IFD, if it was defined.
|
||||
pub fn next(&self) -> Option<IfdPointer> {
|
||||
self.next_ifd.map(|n| IfdPointer(n.get()))
|
||||
}
|
||||
|
||||
pub fn set_next(&mut self, next: Option<IfdPointer>) {
|
||||
self.next_ifd = next.and_then(|n| NonZeroU64::new(n.0));
|
||||
}
|
||||
|
||||
fn extend_inner(&mut self, iter: &mut dyn Iterator<Item = (Tag, Entry)>) {
|
||||
for (tag, entry) in iter {
|
||||
// If the tag is already present, it will be overwritten.
|
||||
let map_entry = self.entries.entry(tag.to_u16());
|
||||
|
||||
match map_entry {
|
||||
std::collections::btree_map::Entry::Vacant(vacant_entry) => {
|
||||
vacant_entry.insert(entry);
|
||||
}
|
||||
std::collections::btree_map::Entry::Occupied(mut occupied_entry) => {
|
||||
occupied_entry.insert(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Directory {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Directory")
|
||||
.field(
|
||||
"entries",
|
||||
&self.entries.iter().map(|(k, v)| (Tag::from_u16(*k), v)),
|
||||
)
|
||||
.field("next_ifd", &self.next_ifd)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Directory;
|
||||
use crate::{decoder::ifd::Entry, tags::Tag};
|
||||
|
||||
#[test]
|
||||
fn directory_multiple_entries() {
|
||||
let mut dir = Directory::empty();
|
||||
assert_eq!(dir.len(), 0);
|
||||
|
||||
dir.extend((0..=u16::MAX).map(|i| {
|
||||
let tag = Tag::Unknown(1);
|
||||
let entry = Entry::new_u64(crate::tags::Type::BYTE, i.into(), [0; 8]);
|
||||
(tag, entry)
|
||||
}));
|
||||
|
||||
assert_eq!(dir.len(), 1, "Only one tag was ever modified");
|
||||
|
||||
assert_eq!(
|
||||
dir.get(Tag::Unknown(1))
|
||||
.expect("tag 1 should be present after this chain")
|
||||
.count(),
|
||||
u16::MAX.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iteration_order() {
|
||||
let mut dir = Directory::empty();
|
||||
assert_eq!(dir.len(), 0);
|
||||
|
||||
let fake_entry = Entry::new_u64(crate::tags::Type::BYTE, 0, [0; 8]);
|
||||
dir.extend((0..32).map(|i| {
|
||||
let tag = Tag::Unknown(i);
|
||||
let entry = fake_entry.clone();
|
||||
(tag, entry)
|
||||
}));
|
||||
|
||||
let iter_order: Vec<u16> = dir.iter().map(|(tag, _e)| tag.to_u16()).collect();
|
||||
assert_eq!(
|
||||
iter_order,
|
||||
(0..32).collect::<Vec<_>>(),
|
||||
"Tags must be in ascending order according to the specification"
|
||||
);
|
||||
}
|
||||
}
|
||||
+205
-83
@@ -2,17 +2,17 @@ pub use tiff_value::*;
|
||||
|
||||
use std::{
|
||||
cmp,
|
||||
collections::BTreeMap,
|
||||
io::{self, Seek, Write},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
num::TryFromIntError,
|
||||
num::{NonZeroU64, TryFromIntError},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
decoder::ifd::Entry,
|
||||
error::{TiffResult, UsageError},
|
||||
tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag},
|
||||
TiffError, TiffFormatError,
|
||||
tags::{CompressionMethod, IfdPointer, ResolutionUnit, SampleFormat, Tag, Type},
|
||||
Directory, TiffError, TiffFormatError,
|
||||
};
|
||||
|
||||
pub mod colortype;
|
||||
@@ -107,6 +107,8 @@ pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> {
|
||||
kind: PhantomData<K>,
|
||||
predictor: Predictor,
|
||||
compression: Compression,
|
||||
/// The offset of the last main image directory's `next` field.
|
||||
last_ifd_chain: NonZeroU64,
|
||||
}
|
||||
|
||||
/// Constructor functions to create standard Tiff files.
|
||||
@@ -135,16 +137,19 @@ impl<W: Write + Seek> TiffEncoder<W, TiffKindBig> {
|
||||
impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
/// Creates a new Tiff or BigTiff encoder, inferred from the return type.
|
||||
pub fn new_generic(writer: W) -> TiffResult<Self> {
|
||||
let mut encoder = TiffEncoder {
|
||||
writer: TiffWriter::new(writer),
|
||||
let mut writer = TiffWriter::new(writer);
|
||||
K::write_header(&mut writer)?;
|
||||
|
||||
let last_ifd_chain = NonZeroU64::new(writer.previous_ifd_pointer::<K>())
|
||||
.expect("Header is at a non-zero offset");
|
||||
|
||||
Ok(TiffEncoder {
|
||||
writer,
|
||||
kind: PhantomData,
|
||||
predictor: Predictor::None,
|
||||
compression: Compression::Uncompressed,
|
||||
};
|
||||
|
||||
K::write_header(&mut encoder.writer)?;
|
||||
|
||||
Ok(encoder)
|
||||
last_ifd_chain,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the predictor to use
|
||||
@@ -165,8 +170,27 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
}
|
||||
|
||||
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> {
|
||||
DirectoryEncoder::new(&mut self.writer)
|
||||
#[deprecated = "`image_directory` replaced the old behavior and clarifies the intent"]
|
||||
#[doc(hidden)]
|
||||
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<'_, W, K>> {
|
||||
Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)
|
||||
}
|
||||
|
||||
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||
///
|
||||
/// The caller is responsible for ensuring that the directory is a valid image in the main TIFF
|
||||
/// IFD sequence. To encode additional directories that are not linked into the sequence, use
|
||||
/// [`Self::extra_directory`][TiffEncoder::extra_directory].
|
||||
pub fn image_directory(&mut self) -> TiffResult<DirectoryEncoder<'_, W, K>> {
|
||||
Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)
|
||||
}
|
||||
|
||||
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||
///
|
||||
/// The directory is not linked into the sequence of directories. For instance, encode Exif
|
||||
/// directories or SubIfd directories with this method.
|
||||
pub fn extra_directory(&mut self) -> TiffResult<DirectoryEncoder<'_, W, K>> {
|
||||
Self::unchained_directory(&mut self.writer)
|
||||
}
|
||||
|
||||
/// Create an [`ImageEncoder`] to encode an image one slice at a time.
|
||||
@@ -175,7 +199,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> TiffResult<ImageEncoder<W, C, K>> {
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?;
|
||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
||||
}
|
||||
|
||||
@@ -189,11 +213,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
where
|
||||
[C::Inner]: TiffValue,
|
||||
{
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?;
|
||||
let image: ImageEncoder<W, C, K> =
|
||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?;
|
||||
image.write_data(data)
|
||||
}
|
||||
|
||||
fn chain_directory<'lt>(
|
||||
writer: &'lt mut TiffWriter<W>,
|
||||
last_ifd_chain: &'lt mut NonZeroU64,
|
||||
) -> TiffResult<DirectoryEncoder<'lt, W, K>> {
|
||||
let last_ifd = *last_ifd_chain;
|
||||
DirectoryEncoder::new(writer, Some(last_ifd), Some(last_ifd_chain))
|
||||
}
|
||||
|
||||
fn unchained_directory(writer: &mut TiffWriter<W>) -> TiffResult<DirectoryEncoder<'_, W, K>> {
|
||||
DirectoryEncoder::new(writer, None, None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Low level interface to encode ifd directories.
|
||||
@@ -202,88 +238,77 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
/// Encoding can silently fail while this is dropping.
|
||||
pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> {
|
||||
writer: &'a mut TiffWriter<W>,
|
||||
dropped: bool,
|
||||
/// The position of the previous directory's `next` field, if any.
|
||||
chained_ifd_pos: Option<NonZeroU64>,
|
||||
/// An output to write the `next` field offset on completion.
|
||||
write_chain: Option<&'a mut NonZeroU64>,
|
||||
kind: PhantomData<K>,
|
||||
// We use BTreeMap to make sure tags are written in correct order
|
||||
ifd_pointer_pos: u64,
|
||||
ifd: BTreeMap<u16, DirectoryEntry<K::OffsetType>>,
|
||||
directory: Directory,
|
||||
dropped: bool,
|
||||
}
|
||||
|
||||
/// The offset of an encoded directory in the file.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct DirectoryOffset<K: TiffKind> {
|
||||
/// The start of the directory as a Tiff value.
|
||||
///
|
||||
/// This is a bit of a wart in the strongly typed design of the encoder. The value _type_ must
|
||||
/// itself know how to be represented in the Tiff file but that may differ based on endianess
|
||||
/// as well as the offset size (`u32` or BigTIFF's `u64`). Thankfully we're allowed to
|
||||
/// represent offsets with `LONG` or `IFD` in the usual case.
|
||||
pub offset: K::OffsetType,
|
||||
/// The start of the directory as a pure offset.
|
||||
pub pointer: IfdPointer,
|
||||
/// The offset of its sequence link field, in our private representation.
|
||||
ifd_chain: NonZeroU64,
|
||||
/// The kind of Tiff file the offset is for.
|
||||
kind: PhantomData<K>,
|
||||
}
|
||||
|
||||
impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
fn new(writer: &'a mut TiffWriter<W>) -> TiffResult<Self> {
|
||||
// the previous word is the IFD offset position
|
||||
let ifd_pointer_pos = writer.offset() - mem::size_of::<K::OffsetType>() as u64;
|
||||
/// Construct a directory writer appending to data, assuming the writer is currently positioned
|
||||
/// immediately after a previously written IFD to which to append.
|
||||
fn new(
|
||||
writer: &'a mut TiffWriter<W>,
|
||||
chained_ifd_pos: Option<NonZeroU64>,
|
||||
chain_into: Option<&'a mut NonZeroU64>,
|
||||
) -> TiffResult<Self> {
|
||||
writer.pad_word_boundary()?; // TODO: Do we need to adjust this for BigTiff?
|
||||
Ok(DirectoryEncoder {
|
||||
Ok(Self {
|
||||
writer,
|
||||
chained_ifd_pos,
|
||||
write_chain: chain_into,
|
||||
kind: PhantomData,
|
||||
directory: Directory::empty(),
|
||||
dropped: false,
|
||||
ifd_pointer_pos,
|
||||
ifd: BTreeMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Write a single ifd tag.
|
||||
pub fn write_tag<T: TiffValue>(&mut self, tag: Tag, value: T) -> TiffResult<()> {
|
||||
// Encodes the value if necessary. In the current bytes all integers are taken as native
|
||||
// endian and thus transparent but this keeps the interface generic.
|
||||
let mut bytes = Vec::with_capacity(value.bytes());
|
||||
{
|
||||
let mut writer = TiffWriter::new(&mut bytes);
|
||||
value.write(&mut writer)?;
|
||||
}
|
||||
|
||||
self.ifd.insert(
|
||||
tag.to_u16(),
|
||||
DirectoryEntry {
|
||||
data_type: <T>::FIELD_TYPE.to_u16(),
|
||||
let entry = Self::write_value(
|
||||
&mut self.writer,
|
||||
&DirectoryEntry {
|
||||
data_type: <T>::FIELD_TYPE,
|
||||
count: value.count().try_into()?,
|
||||
data: bytes,
|
||||
},
|
||||
);
|
||||
)?;
|
||||
|
||||
self.directory.extend([(tag, entry)]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_directory(&mut self) -> TiffResult<u64> {
|
||||
// Start by writing out all values
|
||||
for &mut DirectoryEntry {
|
||||
data: ref mut bytes,
|
||||
..
|
||||
} in self.ifd.values_mut()
|
||||
{
|
||||
let data_bytes = mem::size_of::<K::OffsetType>();
|
||||
|
||||
if bytes.len() > data_bytes {
|
||||
let offset = self.writer.offset();
|
||||
self.writer.write_bytes(bytes)?;
|
||||
*bytes = vec![0; data_bytes];
|
||||
let mut writer = TiffWriter::new(bytes as &mut [u8]);
|
||||
K::write_offset(&mut writer, offset)?;
|
||||
} else {
|
||||
while bytes.len() < data_bytes {
|
||||
bytes.push(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let offset = self.writer.offset();
|
||||
|
||||
K::write_entry_count(self.writer, self.ifd.len())?;
|
||||
for (
|
||||
tag,
|
||||
DirectoryEntry {
|
||||
data_type: field_type,
|
||||
count,
|
||||
data: offset,
|
||||
},
|
||||
) in self.ifd.iter()
|
||||
{
|
||||
self.writer.write_u16(*tag)?;
|
||||
self.writer.write_u16(*field_type)?;
|
||||
(*count).write(self.writer)?;
|
||||
self.writer.write_bytes(offset)?;
|
||||
}
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/// Write some data to the tiff file, the offset of the data is returned.
|
||||
///
|
||||
/// This could be used to write tiff strips.
|
||||
@@ -293,28 +318,124 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/// Define the parent directory.
|
||||
///
|
||||
/// Each directory has an offset based link to its successor, forming a linked list in the
|
||||
/// file. The encoder writes its own offset into the parent's field when [`Self::finish`] is
|
||||
/// called or it is dropped. This redefines the parent. An offset representation of the parent
|
||||
/// must be acquired by finishing it with [`Self::finish_with_offsets`].
|
||||
pub fn set_parent(&mut self, offset: &DirectoryOffset<K>) {
|
||||
self.chained_ifd_pos = Some(offset.ifd_chain);
|
||||
}
|
||||
|
||||
/// Write out the ifd directory itself.
|
||||
pub fn finish(mut self) -> TiffResult<()> {
|
||||
self.finish_internal()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finish_with_offsets(mut self) -> TiffResult<DirectoryOffset<K>> {
|
||||
self.finish_internal()
|
||||
}
|
||||
|
||||
fn write_directory(&mut self) -> TiffResult<u64> {
|
||||
// Start by turning all buffered unwritten values into entries.
|
||||
let offset = self.writer.offset();
|
||||
K::write_entry_count(self.writer, self.directory.len())?;
|
||||
|
||||
let offset_bytes = mem::size_of::<K::OffsetType>();
|
||||
for (tag, entry) in self.directory.iter() {
|
||||
self.writer.write_u16(tag.to_u16())?;
|
||||
self.writer.write_u16(entry.field_type().to_u16())?;
|
||||
let count = K::convert_offset(entry.count())?;
|
||||
count.write(self.writer)?;
|
||||
self.writer.write_bytes(&entry.offset()[..offset_bytes])?;
|
||||
}
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
fn write_value(
|
||||
writer: &mut TiffWriter<W>,
|
||||
value: &DirectoryEntry<K::OffsetType>,
|
||||
) -> TiffResult<Entry> {
|
||||
let &DirectoryEntry {
|
||||
data: ref bytes,
|
||||
ref count,
|
||||
data_type,
|
||||
} = value;
|
||||
|
||||
let in_entry_bytes = mem::size_of::<K::OffsetType>();
|
||||
let mut offset_bytes = [0; 8];
|
||||
|
||||
if bytes.len() > in_entry_bytes {
|
||||
let offset = writer.offset();
|
||||
writer.write_bytes(&bytes)?;
|
||||
|
||||
let offset = K::convert_offset(offset)?;
|
||||
offset_bytes[..offset.bytes()].copy_from_slice(&offset.data());
|
||||
} else {
|
||||
// Note: we have indicated our native byte order in the header, hence this
|
||||
// corresponds to our byte order no matter the value type.
|
||||
offset_bytes[..bytes.len()].copy_from_slice(&bytes);
|
||||
}
|
||||
|
||||
// Undoing some hidden type. Offset is either u32 or u64. Due to the trait API being public
|
||||
// and some oversight, we can not clone the `count: K::OffsetType` and thus not convert it.
|
||||
// Instead, write it to a buffer...
|
||||
let mut count_bytes = [0; 8];
|
||||
// Nominally Cow but we only expect `Cow::Borrowed`.
|
||||
count_bytes[..count.bytes()].copy_from_slice(&count.data());
|
||||
|
||||
Ok(if in_entry_bytes == 4 {
|
||||
let count = u32::from_ne_bytes(count_bytes[..4].try_into().unwrap());
|
||||
Entry::new(data_type, count, offset_bytes[..4].try_into().unwrap())
|
||||
} else {
|
||||
debug_assert_eq!(in_entry_bytes, 8);
|
||||
let count = u64::from_ne_bytes(count_bytes);
|
||||
Entry::new_u64(data_type, count, offset_bytes)
|
||||
})
|
||||
}
|
||||
|
||||
/// Provides the number of bytes written by the underlying TiffWriter during the last call.
|
||||
fn last_written(&self) -> u64 {
|
||||
self.writer.last_written()
|
||||
}
|
||||
|
||||
fn finish_internal(&mut self) -> TiffResult<()> {
|
||||
fn finish_internal(&mut self) -> TiffResult<DirectoryOffset<K>> {
|
||||
let ifd_pointer = self.write_directory()?;
|
||||
let offset = K::convert_offset(ifd_pointer)?;
|
||||
|
||||
if let Some(prior) = self.chained_ifd_pos {
|
||||
let curr_pos = self.writer.offset();
|
||||
|
||||
self.writer.goto_offset(self.ifd_pointer_pos)?;
|
||||
self.writer.goto_offset(prior.get())?;
|
||||
// Note how we are not writing the `offset` type itself here as doing so would need to
|
||||
// go through the `TiffValue` trait—the type is not constrained by much. But for the
|
||||
// trait we need a `TiffWriter` which comes with a bunch of additional state such as
|
||||
// compressor etc. that we have no need for.
|
||||
K::write_offset(self.writer, ifd_pointer)?;
|
||||
|
||||
self.writer.goto_offset(curr_pos)?;
|
||||
}
|
||||
|
||||
K::write_offset(self.writer, 0)?;
|
||||
|
||||
let ifd_chain = NonZeroU64::new(self.writer.previous_ifd_pointer::<K>())
|
||||
.expect("IFD chain field is at a non-zero offset");
|
||||
|
||||
if let Some(prior) = self.write_chain.take() {
|
||||
*prior = ifd_chain;
|
||||
}
|
||||
|
||||
self.dropped = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write out the ifd directory.
|
||||
pub fn finish(mut self) -> TiffResult<()> {
|
||||
self.finish_internal()
|
||||
Ok(DirectoryOffset {
|
||||
pointer: IfdPointer(ifd_pointer),
|
||||
offset,
|
||||
ifd_chain,
|
||||
kind: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,7 +711,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish_internal(&mut self) -> TiffResult<()> {
|
||||
fn finish_internal(&mut self) -> TiffResult<DirectoryOffset<K>> {
|
||||
self.encoder
|
||||
.write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?;
|
||||
self.encoder.write_tag(
|
||||
@@ -609,7 +730,8 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
|
||||
/// Write out image and ifd directory.
|
||||
pub fn finish(mut self) -> TiffResult<()> {
|
||||
self.finish_internal()
|
||||
self.finish_internal()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +744,7 @@ impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W
|
||||
}
|
||||
|
||||
struct DirectoryEntry<S> {
|
||||
data_type: u16,
|
||||
data_type: Type,
|
||||
count: S,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@ impl<W: Write> TiffWriter<W> {
|
||||
self.offset
|
||||
}
|
||||
|
||||
pub(crate) fn previous_ifd_pointer<K: super::TiffKind>(&self) -> u64 {
|
||||
self.offset() - core::mem::size_of::<K::OffsetType>() as u64
|
||||
}
|
||||
|
||||
pub fn last_written(&self) -> u64 {
|
||||
self.byte_count
|
||||
}
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
|
||||
mod bytecast;
|
||||
pub mod decoder;
|
||||
mod directory;
|
||||
pub mod encoder;
|
||||
mod error;
|
||||
pub mod tags;
|
||||
|
||||
pub use self::directory::Directory;
|
||||
pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||
|
||||
/// An enumeration over supported color types and their bit depths
|
||||
|
||||
+21
-1
@@ -78,7 +78,6 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
||||
// palette-color images (PhotometricInterpretation 3)
|
||||
ColorMap = 320, // TODO add support
|
||||
Compression = 259, // TODO add support for 2 and 32773
|
||||
Copyright = 33_432,
|
||||
DateTime = 306,
|
||||
ExtraSamples = 338, // TODO add support
|
||||
FillOrder = 266, // TODO add support
|
||||
@@ -114,6 +113,7 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
||||
TileLength = 323,
|
||||
TileOffsets = 324,
|
||||
TileByteCounts = 325,
|
||||
SubIfd = 330,
|
||||
// Data Sample Format
|
||||
SampleFormat = 339,
|
||||
SMinSampleValue = 340, // TODO add support
|
||||
@@ -124,13 +124,33 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
||||
ModelPixelScaleTag = 33550, // (SoftDesk)
|
||||
ModelTransformationTag = 34264, // (JPL Carto Group)
|
||||
ModelTiepointTag = 33922, // (Intergraph)
|
||||
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
||||
// *Do note its typo in the Decimal id*
|
||||
Copyright = 33_432,
|
||||
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
||||
ExifDirectory = 0x8769,
|
||||
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
||||
GpsDirectory = 0x8825,
|
||||
GeoKeyDirectoryTag = 34735, // (SPOT)
|
||||
GeoDoubleParamsTag = 34736, // (SPOT)
|
||||
GeoAsciiParamsTag = 34737, // (SPOT)
|
||||
ExifVersion = 0x9000,
|
||||
GdalNodata = 42113, // Contains areas with missing data
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifies the offset of an IFD.
|
||||
///
|
||||
/// This is represented as a 64-bit integer but only BigTIFF can utilize the bits. It is encoded
|
||||
/// as 32-bit unsigned value ([`Type::LONG`]) in regular TIFF files and as 64-bit unsigned value
|
||||
/// ([`Type::IFD8`]) in BigTIFF files.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
// We could be using `NonZeroU64` here but I find that this complicates semantics. This type
|
||||
// represents the integer value stored as a value in a tag. The semantics of treating `0` as an end
|
||||
// marker are imposed by the IFD. (It's unclear if Pointer tags such as Exif would allow `0` but in
|
||||
// practice it just returns garbage and the validity does not matter greatly to us).
|
||||
pub struct IfdPointer(pub u64);
|
||||
|
||||
tags! {
|
||||
/// The type of an IFD entry (a 2 byte field).
|
||||
pub enum Type(u16) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
subsubifd.tif:
|
||||
url: https://data.kitware.com/api/v1/file/hashsum/sha512/372ca32735c8a8fdbe2286dabead9e779da63f10ba81eda84625e5273f76d74ca1a47a978f67e9c00c12f7f72009c7b2c07a641e643bb0c463812f4ae7f15d6e/download
|
||||
credit: https://github.com/DigitalSlideArchive/tifftools/commit/4d00c70dbce828262f86c1431f7c66b1748965ac
|
||||
license: CC0
|
||||
+58
-2
@@ -57,7 +57,7 @@ fn encode_decode() {
|
||||
),
|
||||
(Tag::Compression, ifd::Value::Short(1)),
|
||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
||||
(Tag::StripOffsets, ifd::Value::Unsigned(8)),
|
||||
(Tag::StripOffsets, ifd::Value::Unsigned(47)),
|
||||
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
|
||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
||||
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
|
||||
@@ -137,7 +137,7 @@ fn encode_decode_big() {
|
||||
),
|
||||
(Tag::Compression, ifd::Value::Short(1)),
|
||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
||||
(Tag::StripOffsets, ifd::Value::UnsignedBig(16)),
|
||||
(Tag::StripOffsets, ifd::Value::UnsignedBig(27)),
|
||||
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
|
||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
||||
(Tag::StripByteCounts, ifd::Value::UnsignedBig(30000)),
|
||||
@@ -626,3 +626,59 @@ fn test_rows_per_strip() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auxiliary_directory() {
|
||||
const ARTIST: &str = "Auxiliary-tiff";
|
||||
|
||||
let mut data = Cursor::new(Vec::new());
|
||||
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
||||
|
||||
{
|
||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
||||
image_encoder.write_strip(&[1]).unwrap();
|
||||
}
|
||||
|
||||
let exif = {
|
||||
let mut extra = tiff.extra_directory().unwrap();
|
||||
extra.write_tag(Tag::Artist, ARTIST).unwrap();
|
||||
extra.finish_with_offsets().unwrap()
|
||||
};
|
||||
|
||||
{
|
||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(2, 1).unwrap();
|
||||
image_encoder.write_strip(&[1, 2]).unwrap();
|
||||
let encoder = image_encoder.encoder();
|
||||
encoder.write_tag(Tag::ExifDirectory, exif.offset).unwrap();
|
||||
}
|
||||
|
||||
drop(tiff);
|
||||
|
||||
data.set_position(0);
|
||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||
|
||||
{
|
||||
assert_eq!(decoder.dimensions().unwrap(), (1, 1));
|
||||
let _ = decoder.read_image().unwrap();
|
||||
}
|
||||
|
||||
{
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(decoder.dimensions().unwrap(), (2, 1));
|
||||
let _ = decoder.read_image().unwrap();
|
||||
|
||||
let exif_location = decoder
|
||||
.get_tag(Tag::ExifDirectory)
|
||||
.expect("second directory missing Exif")
|
||||
.into_ifd_pointer()
|
||||
.expect("exif directory is not an IFD pointer");
|
||||
|
||||
let exif = decoder.read_directory(exif_location).unwrap();
|
||||
|
||||
let artist = decoder
|
||||
.read_directory_tags(&exif)
|
||||
.get_tag_ascii_string(Tag::Artist)
|
||||
.expect("EXIF directory missing artist tag");
|
||||
assert_eq!(artist, ARTIST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use tiff::decoder::Decoder;
|
||||
|
||||
// This file has the following IFD structure:
|
||||
//
|
||||
// ```
|
||||
// IFD -> IFD -> IFD
|
||||
// |-> IFD
|
||||
// |-> IFD -> IFD
|
||||
// | \-> IFD -> IFD
|
||||
// \-> IFD
|
||||
// ```
|
||||
//
|
||||
// where the main chain of IFD has 3 images, the second of which has several sub-IFDs and some have
|
||||
// more nested IFDs.
|
||||
const TEST_IMAGE_SUBIFD: &str = "./tests/subsubifds.tif";
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
fn decode_seek_chain() {
|
||||
let file = File::open(TEST_IMAGE_SUBIFD).expect("Cannot open test image");
|
||||
let mut decoder = Decoder::new(file).expect("Invalid format to create decoder");
|
||||
|
||||
// Remember the offsets in the first iteration.
|
||||
let offset0 = decoder.ifd_pointer().expect("First IFD pointer not found");
|
||||
decoder.next_image().unwrap();
|
||||
let offset1 = decoder.ifd_pointer().expect("Second IFD pointer not found");
|
||||
decoder.next_image().unwrap();
|
||||
let offset2 = decoder.ifd_pointer().expect("Third IFD pointer not found");
|
||||
assert!(!decoder.more_images());
|
||||
|
||||
// Ensure we can seek, to do this again.
|
||||
decoder
|
||||
.seek_to_image(0)
|
||||
.expect("Failed to seek to first image");
|
||||
assert_eq!(Some(offset0), decoder.ifd_pointer());
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(Some(offset1), decoder.ifd_pointer());
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(Some(offset2), decoder.ifd_pointer());
|
||||
assert!(!decoder.more_images());
|
||||
}
|
||||
Binary file not shown.
Reference in new issue
Block a user