Add a dedicated Directory type (#277)
This commit is contained in:
1 parent
816fb85304
commit
df4e42eecc
7 files changed
+239
-43
No files matched your search
@@ -333,6 +333,10 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn count(&self) -> u64 {
|
||||||
|
self.count
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a mem_reader for the offset/value field
|
/// Returns a mem_reader for the offset/value field
|
||||||
fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
|
fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
|
||||||
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
||||||
@@ -668,4 +672,6 @@ fn offset_to_sbytes(n: usize, entry: &Entry) -> TiffResult<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Type representing an Image File Directory
|
/// 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>;
|
pub type Directory = HashMap<Tag, Entry>;
|
||||||
+10
-8
@@ -1,4 +1,4 @@
|
|||||||
use super::ifd::{Directory, Value};
|
use super::ifd::Value;
|
||||||
use super::stream::{ByteOrder, PackBitsReader};
|
use super::stream::{ByteOrder, PackBitsReader};
|
||||||
use super::tag_reader::TagReader;
|
use super::tag_reader::TagReader;
|
||||||
use super::{predict_f16, predict_f32, predict_f64, Limits};
|
use super::{predict_f16, predict_f32, predict_f64, Limits};
|
||||||
@@ -6,7 +6,9 @@ use super::{stream::EndianReader, ChunkType};
|
|||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
|
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::io::{self, Cursor, Read, Seek};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -115,7 +117,7 @@ impl Image {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
|
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
|
||||||
&& ifd.contains_key(&Tag::JPEGTables)
|
&& ifd.contains(Tag::JPEGTables)
|
||||||
{
|
{
|
||||||
let vec = tag_reader
|
let vec = tag_reader
|
||||||
.find_tag(Tag::JPEGTables)?
|
.find_tag(Tag::JPEGTables)?
|
||||||
@@ -210,10 +212,10 @@ impl Image {
|
|||||||
let strip_decoder;
|
let strip_decoder;
|
||||||
let tile_attributes;
|
let tile_attributes;
|
||||||
match (
|
match (
|
||||||
ifd.contains_key(&Tag::StripByteCounts),
|
ifd.contains(Tag::StripByteCounts),
|
||||||
ifd.contains_key(&Tag::StripOffsets),
|
ifd.contains(Tag::StripOffsets),
|
||||||
ifd.contains_key(&Tag::TileByteCounts),
|
ifd.contains(Tag::TileByteCounts),
|
||||||
ifd.contains_key(&Tag::TileOffsets),
|
ifd.contains(Tag::TileOffsets),
|
||||||
) {
|
) {
|
||||||
(true, true, false, false) => {
|
(true, true, false, false) => {
|
||||||
chunk_type = ChunkType::Strip;
|
chunk_type = ChunkType::Strip;
|
||||||
@@ -676,7 +678,7 @@ impl Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (i, row) in buf
|
for (_i, row) in buf
|
||||||
.chunks_mut(output_row_stride)
|
.chunks_mut(output_row_stride)
|
||||||
.take(data_dims.1 as usize)
|
.take(data_dims.1 as usize)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
|||||||
+37
-31
@@ -1,16 +1,16 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
use std::io::{self, Read, Seek};
|
use std::io::{self, Read, Seek};
|
||||||
|
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat,
|
CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor,
|
||||||
Tag, Type,
|
SampleFormat, Tag, Type,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
bytecast, ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError,
|
||||||
|
UsageError,
|
||||||
};
|
};
|
||||||
use half::f16;
|
use half::f16;
|
||||||
|
|
||||||
use self::ifd::Directory;
|
|
||||||
use self::image::Image;
|
use self::image::Image;
|
||||||
use self::stream::{ByteOrder, EndianReader};
|
use self::stream::{ByteOrder, EndianReader};
|
||||||
|
|
||||||
@@ -263,9 +263,9 @@ where
|
|||||||
reader: EndianReader<R>,
|
reader: EndianReader<R>,
|
||||||
bigtiff: bool,
|
bigtiff: bool,
|
||||||
limits: Limits,
|
limits: Limits,
|
||||||
next_ifd: Option<u64>,
|
next_ifd: Option<IfdPointer>,
|
||||||
ifd_offsets: Vec<u64>,
|
ifd_offsets: Vec<IfdPointer>,
|
||||||
seen_ifds: HashSet<u64>,
|
seen_ifds: HashSet<IfdPointer>,
|
||||||
image: Image,
|
image: Image,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,11 +487,13 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let next_ifd = if bigtiff {
|
let next_ifd = if bigtiff {
|
||||||
Some(reader.read_u64()?)
|
Some(reader.read_u64()?)
|
||||||
} else {
|
} else {
|
||||||
Some(u64::from(reader.read_u32()?))
|
Some(u64::from(reader.read_u32()?))
|
||||||
};
|
}
|
||||||
|
.map(IfdPointer);
|
||||||
|
|
||||||
let mut seen_ifds = HashSet::new();
|
let mut seen_ifds = HashSet::new();
|
||||||
seen_ifds.insert(*next_ifd.as_ref().unwrap());
|
seen_ifds.insert(*next_ifd.as_ref().unwrap());
|
||||||
@@ -557,9 +559,9 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
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, next_ifd) = self.next_ifd()?;
|
let ifd = self.next_ifd()?;
|
||||||
|
|
||||||
if next_ifd.is_none() {
|
if ifd.next().is_none() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,8 +573,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
|
|
||||||
// If the index is within the list of ifds then we can load the selected image/IFD
|
// If the index is within the list of ifds then we can load the selected image/IFD
|
||||||
if let Some(ifd_offset) = self.ifd_offsets.get(ifd_index) {
|
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)?;
|
let ifd = Self::read_ifd(&mut self.reader, self.bigtiff, *ifd_offset)?;
|
||||||
|
|
||||||
self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?;
|
self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -583,20 +584,20 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_ifd(&mut self) -> TiffResult<(Directory, Option<u64>)> {
|
fn next_ifd(&mut self) -> TiffResult<Directory> {
|
||||||
if self.next_ifd.is_none() {
|
if self.next_ifd.is_none() {
|
||||||
return Err(TiffError::FormatError(
|
return Err(TiffError::FormatError(
|
||||||
TiffFormatError::ImageFileDirectoryNotFound,
|
TiffFormatError::ImageFileDirectoryNotFound,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (ifd, next_ifd) = Self::read_ifd(
|
let ifd = Self::read_ifd(
|
||||||
&mut self.reader,
|
&mut self.reader,
|
||||||
self.bigtiff,
|
self.bigtiff,
|
||||||
self.next_ifd.take().unwrap(),
|
self.next_ifd.take().unwrap(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(next) = next_ifd {
|
if let Some(next) = ifd.next() {
|
||||||
if !self.seen_ifds.insert(next) {
|
if !self.seen_ifds.insert(next) {
|
||||||
return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets));
|
return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets));
|
||||||
}
|
}
|
||||||
@@ -604,15 +605,14 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.ifd_offsets.push(next);
|
self.ifd_offsets.push(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((ifd, next_ifd))
|
Ok(ifd)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
pub fn next_image(&mut self) -> TiffResult<()> {
|
pub fn next_image(&mut self) -> TiffResult<()> {
|
||||||
let (ifd, _next_ifd) = self.next_ifd()?;
|
let ifd = self.next_ifd()?;
|
||||||
|
|
||||||
self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?;
|
self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -734,6 +734,13 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.reader.goto_offset(offset)
|
self.reader.goto_offset(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a directory from a known offset.
|
||||||
|
///
|
||||||
|
/// This may always modify the position of the reader.
|
||||||
|
pub fn read_directory(&mut self, ptr: IfdPointer) -> TiffResult<Directory> {
|
||||||
|
Self::read_ifd(&mut self.reader, self.bigtiff, ptr)
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads a IFD entry.
|
/// Reads a IFD entry.
|
||||||
// An IFD entry has four fields:
|
// An IFD entry has four fields:
|
||||||
//
|
//
|
||||||
@@ -775,17 +782,18 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
fn read_ifd(
|
fn read_ifd(
|
||||||
reader: &mut EndianReader<R>,
|
reader: &mut EndianReader<R>,
|
||||||
bigtiff: bool,
|
bigtiff: bool,
|
||||||
ifd_location: u64,
|
ifd_location: IfdPointer,
|
||||||
) -> TiffResult<(Directory, Option<u64>)> {
|
) -> TiffResult<Directory> {
|
||||||
reader.goto_offset(ifd_location)?;
|
reader.goto_offset(ifd_location.0)?;
|
||||||
|
|
||||||
let mut dir: Directory = HashMap::new();
|
let mut entries: BTreeMap<_, _> = BTreeMap::new();
|
||||||
|
|
||||||
let num_tags = if bigtiff {
|
let num_tags = if bigtiff {
|
||||||
reader.read_u64()?
|
reader.read_u64()?
|
||||||
} else {
|
} else {
|
||||||
reader.read_u16()?.into()
|
reader.read_u16()?.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
for _ in 0..num_tags {
|
for _ in 0..num_tags {
|
||||||
let (tag, entry) = match Self::read_entry(reader, bigtiff)? {
|
let (tag, entry) = match Self::read_entry(reader, bigtiff)? {
|
||||||
Some(val) => val,
|
Some(val) => val,
|
||||||
@@ -793,7 +801,8 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
continue;
|
continue;
|
||||||
} // Unknown data type in tag, skip
|
} // Unknown data type in tag, skip
|
||||||
};
|
};
|
||||||
dir.insert(tag, entry);
|
|
||||||
|
entries.insert(tag.to_u16(), entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
let next_ifd = if bigtiff {
|
let next_ifd = if bigtiff {
|
||||||
@@ -802,18 +811,15 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
reader.read_u32()?.into()
|
reader.read_u32()?.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
let next_ifd = match next_ifd {
|
let next_ifd = core::num::NonZeroU64::new(next_ifd);
|
||||||
0 => None,
|
|
||||||
_ => Some(next_ifd),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((dir, next_ifd))
|
Ok(Directory { entries, next_ifd })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag.
|
/// Tries to retrieve a tag.
|
||||||
/// 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>> {
|
||||||
let entry = match self.image().ifd.as_ref().unwrap().get(&tag) {
|
let entry = match self.image().ifd.as_ref().unwrap().get(tag) {
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
Some(entry) => entry.clone(),
|
Some(entry) => entry.clone(),
|
||||||
};
|
};
|
||||||
@@ -928,7 +934,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.image.ifd.as_ref().unwrap().iter().map(|(tag, entry)| {
|
self.image.ifd.as_ref().unwrap().iter().map(|(tag, entry)| {
|
||||||
entry
|
entry
|
||||||
.val(&self.limits, self.bigtiff, &mut self.reader)
|
.val(&self.limits, self.bigtiff, &mut self.reader)
|
||||||
.map(|value| (*tag, value))
|
.map(|value| (tag, value))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::io::{Read, Seek};
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
use crate::tags::Tag;
|
use crate::{tags::Tag, Directory};
|
||||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||||
|
|
||||||
use super::ifd::{Directory, Value};
|
use super::ifd::Value;
|
||||||
use super::stream::EndianReader;
|
use super::stream::EndianReader;
|
||||||
use super::Limits;
|
use super::Limits;
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ pub(crate) struct TagReader<'a, R: Read + Seek> {
|
|||||||
}
|
}
|
||||||
impl<'a, R: Read + Seek> TagReader<'a, R> {
|
impl<'a, R: Read + Seek> TagReader<'a, R> {
|
||||||
pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<Value>> {
|
pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<Value>> {
|
||||||
Ok(match self.ifd.get(&tag) {
|
Ok(match self.ifd.get(tag) {
|
||||||
Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?),
|
Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?),
|
||||||
None => None,
|
None => None,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
use core::fmt;
|
||||||
|
use std::{collections::BTreeMap, num::NonZeroU64};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
decoder::ifd::Entry,
|
||||||
|
tags::{IfdPointer, Tag},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An Image File Directory (IFD).
|
||||||
|
#[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(next: IfdPointer) -> Self {
|
||||||
|
Directory {
|
||||||
|
entries: BTreeMap::new(),
|
||||||
|
next_ifd: NonZeroU64::new(next.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 length as a 2-byte integer.
|
||||||
|
///
|
||||||
|
/// This is *almost* naturally bounded by the tag type being a `u16` itself. The
|
||||||
|
/// bounds are upheld when extending the iteration, i.e. this always corresponds to the
|
||||||
|
/// underlying data.
|
||||||
|
pub fn len(&self) -> u16 {
|
||||||
|
// 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.
|
||||||
|
self.entries.len() as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extend_inner(&mut self, iter: &mut dyn Iterator<Item = (Tag, Entry)>) {
|
||||||
|
for (tag, entry) in iter {
|
||||||
|
let is_full = self.entries.len() == u16::MAX as usize;
|
||||||
|
// If the tag is already present, it will be overwritten.
|
||||||
|
let map_entry = self.entries.entry(tag.to_u16());
|
||||||
|
|
||||||
|
if is_full {
|
||||||
|
// Only allowed to modify if the entry is already present.
|
||||||
|
map_entry.and_modify(|place| *place = entry);
|
||||||
|
} else {
|
||||||
|
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, IfdPointer};
|
||||||
|
use crate::{decoder::ifd::Entry, tags::Tag};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn directory_upholds_len_invariant() {
|
||||||
|
let mut dir = Directory::empty(IfdPointer(0));
|
||||||
|
assert_eq!(dir.len(), 0);
|
||||||
|
|
||||||
|
let bogus_entry = Entry::new_u64(crate::tags::Type::BYTE, 0x0, [0; 8]);
|
||||||
|
dir.extend((0..=u16::MAX).map(|i| {
|
||||||
|
let tag = Tag::Unknown(i);
|
||||||
|
let entry = bogus_entry.clone();
|
||||||
|
(tag, entry)
|
||||||
|
}));
|
||||||
|
|
||||||
|
assert_eq!(dir.len(), u16::MAX);
|
||||||
|
|
||||||
|
// Ensure the other tags are still writable.
|
||||||
|
dir.extend([(
|
||||||
|
Tag::Unknown(0),
|
||||||
|
Entry::new_u64(crate::tags::Type::BYTE, 0x42, [0; 8]),
|
||||||
|
)]);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
dir.get(Tag::Unknown(0))
|
||||||
|
.expect("tag 0 should be present after overwriting")
|
||||||
|
.count(),
|
||||||
|
0x42
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn directory_multiple_entries() {
|
||||||
|
let mut dir = Directory::empty(IfdPointer(0));
|
||||||
|
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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,10 +8,12 @@
|
|||||||
|
|
||||||
mod bytecast;
|
mod bytecast;
|
||||||
pub mod decoder;
|
pub mod decoder;
|
||||||
|
mod directory;
|
||||||
pub mod encoder;
|
pub mod encoder;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
|
|
||||||
|
pub use self::directory::Directory;
|
||||||
pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||||
|
|
||||||
/// An enumeration over supported color types and their bit depths
|
/// An enumeration over supported color types and their bit depths
|
||||||
|
|||||||
+20
-1
@@ -78,7 +78,6 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
|||||||
// palette-color images (PhotometricInterpretation 3)
|
// palette-color images (PhotometricInterpretation 3)
|
||||||
ColorMap = 320, // TODO add support
|
ColorMap = 320, // TODO add support
|
||||||
Compression = 259, // TODO add support for 2 and 32773
|
Compression = 259, // TODO add support for 2 and 32773
|
||||||
Copyright = 33_432,
|
|
||||||
DateTime = 306,
|
DateTime = 306,
|
||||||
ExtraSamples = 338, // TODO add support
|
ExtraSamples = 338, // TODO add support
|
||||||
FillOrder = 266, // TODO add support
|
FillOrder = 266, // TODO add support
|
||||||
@@ -124,13 +123,33 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
|||||||
ModelPixelScaleTag = 33550, // (SoftDesk)
|
ModelPixelScaleTag = 33550, // (SoftDesk)
|
||||||
ModelTransformationTag = 34264, // (JPL Carto Group)
|
ModelTransformationTag = 34264, // (JPL Carto Group)
|
||||||
ModelTiepointTag = 33922, // (Intergraph)
|
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)
|
GeoKeyDirectoryTag = 34735, // (SPOT)
|
||||||
GeoDoubleParamsTag = 34736, // (SPOT)
|
GeoDoubleParamsTag = 34736, // (SPOT)
|
||||||
GeoAsciiParamsTag = 34737, // (SPOT)
|
GeoAsciiParamsTag = 34737, // (SPOT)
|
||||||
|
ExifVersion = 0x9000,
|
||||||
GdalNodata = 42113, // Contains areas with missing data
|
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! {
|
tags! {
|
||||||
/// The type of an IFD entry (a 2 byte field).
|
/// The type of an IFD entry (a 2 byte field).
|
||||||
pub enum Type(u16) {
|
pub enum Type(u16) {
|
||||||
|
|||||||
Reference in new issue
Block a user