Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbcf6b97d4 | ||
|
|
19c811e86d | ||
|
|
50be15e356 | ||
|
|
718f2cb455 | ||
|
|
36a16db659 | ||
|
|
d70c31dcbf | ||
|
|
b589d10a27 | ||
|
|
f4378eeb80 | ||
|
|
49015c6e93 | ||
|
|
29402875cb | ||
|
|
33453289b6 | ||
|
|
ce0ca5c234 | ||
|
|
f671293bd8 | ||
|
|
48d52bed9b | ||
|
|
7707060982 | ||
|
|
c370ac4b92 | ||
|
|
b2bcbbaf23 | ||
|
|
d07da5a6cb | ||
|
|
a3c81e9830 | ||
|
|
46e51cc67e | ||
|
|
f6a762b72c | ||
|
|
23672b2490 | ||
|
|
ca74c03520 | ||
|
|
2942f9de97 | ||
|
|
29add10ca7 | ||
|
|
59d32a66a7 | ||
|
|
5a6b2ad8f3 | ||
|
|
c746653269 | ||
|
|
cc75d051af | ||
|
|
ab4fb4e959 | ||
|
|
7b1683687e | ||
|
|
2e51ee4b15 | ||
|
|
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
|
name: Rust CI
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ main ]
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -45,6 +45,35 @@ jobs:
|
|||||||
- name: Cargo check
|
- name: Cargo check
|
||||||
run: cargo check --features "${{ matrix.feature }}" --no-default-features
|
run: cargo check --features "${{ matrix.feature }}" --no-default-features
|
||||||
|
|
||||||
|
- name: Cargo clippy
|
||||||
|
if: ${{ matrix.feature != '' }}
|
||||||
|
run: cargo clippy --no-deps --all-features --all-targets -- -D warnings
|
||||||
|
|
||||||
|
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:
|
rustfmt:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
+17
@@ -1,3 +1,20 @@
|
|||||||
|
# Version 0.10.1
|
||||||
|
|
||||||
|
New features:
|
||||||
|
- Added a dedicated `Directory` type (#277)
|
||||||
|
- Expose methods related to `Directory` publicly (#278)
|
||||||
|
- Implement writing directories (#281, #283)
|
||||||
|
- Add ICC (color profile) to the set of known tags (#287)
|
||||||
|
- Added `Decoder.inner()` to access the underlying reader (#289)
|
||||||
|
- Added methods for reading into a pre-allocated byte buffer (#285)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- No longer perform color conversion when decoding embedded JPEG. This fixes decoding for JPEG images with alpha channel. (#274)
|
||||||
|
- More lenient LZW decoding to match libtiff (#231)
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Fixed seeking multiple times in a row (#279)
|
||||||
|
|
||||||
# Version 0.10.0
|
# Version 0.10.0
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.10.0"
|
version = "0.10.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
@@ -21,12 +21,14 @@ half = { version = "2.4.1" }
|
|||||||
quick-error = "2.0.1"
|
quick-error = "2.0.1"
|
||||||
|
|
||||||
flate2 = { version = "1.0.20", optional = true }
|
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 }
|
zstd = { version = "0.13", optional = true }
|
||||||
zune-jpeg = { version = "0.4.17", optional = true }
|
zune-jpeg = { version = "0.4.17", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3.1"
|
criterion = "0.3.1"
|
||||||
|
# We may already depend on this via flate2
|
||||||
|
crc32fast = "1.5"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["deflate", "jpeg", "lzw"]
|
default = ["deflate", "jpeg", "lzw"]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# image-tiff
|
# image-tiff
|
||||||
[](https://github.com/image-rs/image-tiff/actions)
|
[](https://github.com/image-rs/image-tiff/actions)
|
||||||
[](https://docs.rs/tiff)
|
[](https://docs.rs/tiff)
|
||||||
[](https://crates.io/crates/tiff)
|
[](https://crates.io/crates/tiff)
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
//! the unsafe code guidelines).
|
//! the unsafe code guidelines).
|
||||||
//!
|
//!
|
||||||
//! TODO: Would like to use std-lib here.
|
//! TODO: Would like to use std-lib here.
|
||||||
|
// Until we implement predictors for f16, we don't need f16_as_ne_bytes. (Due to the macro we do
|
||||||
|
// not apply this directly to the functions). And rust-version is not new enough for `expect`.
|
||||||
|
#![allow(dead_code)]
|
||||||
use std::{mem, slice};
|
use std::{mem, slice};
|
||||||
|
|
||||||
use half::f16;
|
use half::f16;
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
+49
-5
@@ -6,7 +6,7 @@ use std::mem;
|
|||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use super::stream::{ByteOrder, EndianReader};
|
use super::stream::{ByteOrder, EndianReader};
|
||||||
use crate::tags::{Tag, Type};
|
use crate::tags::{IfdPointer, Tag, Type};
|
||||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||||
|
|
||||||
use self::Value::{
|
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> {
|
pub fn into_string(self) -> TiffResult<String> {
|
||||||
match self {
|
match self {
|
||||||
Ascii(val) => Ok(val),
|
Ascii(val) => Ok(val),
|
||||||
@@ -300,6 +319,17 @@ impl Value {
|
|||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
_ => 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)]
|
#[derive(Clone)]
|
||||||
@@ -320,9 +350,9 @@ impl ::std::fmt::Debug for Entry {
|
|||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry {
|
pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry {
|
||||||
let mut offset = offset.to_vec();
|
let mut entry_off = [0u8; 8];
|
||||||
offset.append(&mut vec![0; 4]);
|
entry_off[..4].copy_from_slice(&offset);
|
||||||
Entry::new_u64(type_, count.into(), offset[..].try_into().unwrap())
|
Entry::new_u64(type_, count.into(), entry_off)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_u64(type_: Type, count: u64, offset: [u8; 8]) -> Entry {
|
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
|
/// 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)
|
||||||
@@ -403,7 +445,7 @@ impl Entry {
|
|||||||
// 2b: the value is at most 4 bytes or doesn't fit in the offset field.
|
// 2b: the value is at most 4 bytes or doesn't fit in the offset field.
|
||||||
return Ok(match self.type_ {
|
return Ok(match self.type_ {
|
||||||
Type::BYTE => Byte(self.offset[0]),
|
Type::BYTE => Byte(self.offset[0]),
|
||||||
Type::SBYTE => SignedByte(i8::from(self.offset[0] as i8)),
|
Type::SBYTE => SignedByte(self.offset[0] as i8),
|
||||||
Type::UNDEFINED => Byte(self.offset[0]),
|
Type::UNDEFINED => Byte(self.offset[0]),
|
||||||
Type::SHORT => Short(self.r(bo).read_u16()?),
|
Type::SHORT => Short(self.r(bo).read_u16()?),
|
||||||
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
||||||
@@ -668,4 +710,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>;
|
||||||
+55
-58
@@ -1,13 +1,16 @@
|
|||||||
use super::ifd::{Directory, Value};
|
use super::ifd::Value;
|
||||||
use super::stream::{ByteOrder, PackBitsReader};
|
use super::stream::PackBitsReader;
|
||||||
use super::tag_reader::TagReader;
|
use super::tag_reader::TagReader;
|
||||||
use super::{predict_f16, predict_f32, predict_f64, Limits};
|
use super::ChunkType;
|
||||||
use super::{stream::EndianReader, ChunkType};
|
use super::{predict_f16, predict_f32, predict_f64, ValueReader};
|
||||||
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::num::NonZeroUsize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -27,10 +30,10 @@ pub(crate) struct TileAttributes {
|
|||||||
|
|
||||||
impl TileAttributes {
|
impl TileAttributes {
|
||||||
pub fn tiles_across(&self) -> usize {
|
pub fn tiles_across(&self) -> usize {
|
||||||
(self.image_width + self.tile_width - 1) / self.tile_width
|
self.image_width.div_ceil(self.tile_width)
|
||||||
}
|
}
|
||||||
pub fn tiles_down(&self) -> usize {
|
pub fn tiles_down(&self) -> usize {
|
||||||
(self.image_height + self.tile_length - 1) / self.tile_length
|
self.image_height.div_ceil(self.tile_length)
|
||||||
}
|
}
|
||||||
fn padding_right(&self) -> usize {
|
fn padding_right(&self) -> usize {
|
||||||
(self.tile_width - self.image_width % self.tile_width) % self.tile_width
|
(self.tile_width - self.image_width % self.tile_width) % self.tile_width
|
||||||
@@ -80,17 +83,10 @@ pub(crate) struct Image {
|
|||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
pub fn from_reader<R: Read + Seek>(
|
pub fn from_reader<R: Read + Seek>(
|
||||||
reader: &mut EndianReader<R>,
|
decoder: &mut ValueReader<R>,
|
||||||
ifd: Directory,
|
ifd: Directory,
|
||||||
limits: &Limits,
|
|
||||||
bigtiff: bool,
|
|
||||||
) -> TiffResult<Image> {
|
) -> TiffResult<Image> {
|
||||||
let mut tag_reader = TagReader {
|
let mut tag_reader = TagReader { decoder, ifd: &ifd };
|
||||||
reader,
|
|
||||||
limits,
|
|
||||||
ifd: &ifd,
|
|
||||||
bigtiff,
|
|
||||||
};
|
|
||||||
|
|
||||||
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
||||||
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
||||||
@@ -115,7 +111,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 +206,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;
|
||||||
@@ -368,9 +364,24 @@ impl Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn minimum_row_stride(&self, dims: (u32, u32)) -> Option<NonZeroUsize> {
|
||||||
|
let (width, height) = dims;
|
||||||
|
|
||||||
|
let row_stride = u64::from(width)
|
||||||
|
.saturating_mul(self.samples_per_pixel() as u64)
|
||||||
|
.saturating_mul(self.bits_per_sample as u64)
|
||||||
|
.div_ceil(8);
|
||||||
|
|
||||||
|
// Note: row stride should be smaller than the len if we have an actual buffer. If there
|
||||||
|
// are no pixels in the buffer (height _or_ width is 0) then the stride is not well defined
|
||||||
|
// and we return `None`.
|
||||||
|
(height > 0)
|
||||||
|
.then_some(row_stride as usize)
|
||||||
|
.and_then(NonZeroUsize::new)
|
||||||
|
}
|
||||||
|
|
||||||
fn create_reader<'r, R: 'r + Read>(
|
fn create_reader<'r, R: 'r + Read>(
|
||||||
reader: R,
|
reader: R,
|
||||||
#[allow(unused_variables)] photometric_interpretation: PhotometricInterpretation,
|
|
||||||
compression_method: CompressionMethod,
|
compression_method: CompressionMethod,
|
||||||
compressed_length: u64,
|
compressed_length: u64,
|
||||||
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
|
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
|
||||||
@@ -427,30 +438,14 @@ impl Image {
|
|||||||
|
|
||||||
let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
|
let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
|
||||||
let mut options: zune_core::options::DecoderOptions = Default::default();
|
let mut options: zune_core::options::DecoderOptions = Default::default();
|
||||||
match photometric_interpretation {
|
|
||||||
PhotometricInterpretation::RGB => {
|
// Disable color conversion by setting the output colorspace to the input
|
||||||
options =
|
// colorspace.
|
||||||
options.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::RGB);
|
decoder.decode_headers()?;
|
||||||
}
|
if let Some(colorspace) = decoder.get_input_colorspace() {
|
||||||
PhotometricInterpretation::CMYK => {
|
options = options.jpeg_set_out_colorspace(colorspace);
|
||||||
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,
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder.set_options(options);
|
decoder.set_options(options);
|
||||||
|
|
||||||
let data = decoder.decode()?;
|
let data = decoder.decode()?;
|
||||||
@@ -555,13 +550,20 @@ impl Image {
|
|||||||
|
|
||||||
pub(crate) fn expand_chunk(
|
pub(crate) fn expand_chunk(
|
||||||
&self,
|
&self,
|
||||||
reader: impl Read,
|
reader: &mut ValueReader<impl Read>,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
output_row_stride: usize,
|
output_row_stride: usize,
|
||||||
byte_order: ByteOrder,
|
|
||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
limits: &Limits,
|
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
|
let ValueReader {
|
||||||
|
reader,
|
||||||
|
bigtiff: _,
|
||||||
|
limits,
|
||||||
|
} = reader;
|
||||||
|
|
||||||
|
let byte_order = reader.byte_order;
|
||||||
|
let reader = reader.inner();
|
||||||
|
|
||||||
// Validate that the color type is supported.
|
// Validate that the color type is supported.
|
||||||
let color_type = self.colortype()?;
|
let color_type = self.colortype()?;
|
||||||
match color_type {
|
match color_type {
|
||||||
@@ -640,12 +642,12 @@ impl Image {
|
|||||||
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
|
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
|
||||||
.checked_mul(samples as u64)
|
.checked_mul(samples as u64)
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
|
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
|
||||||
|
|
||||||
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
|
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
|
||||||
.checked_mul(samples as u64)
|
.checked_mul(samples as u64)
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
let data_row_bytes: usize = ((data_row_bits + 7) / 8).try_into()?;
|
let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?;
|
||||||
|
|
||||||
// TODO: Should these return errors instead?
|
// TODO: Should these return errors instead?
|
||||||
assert!(output_row_stride >= data_row_bytes);
|
assert!(output_row_stride >= data_row_bytes);
|
||||||
@@ -653,17 +655,16 @@ impl Image {
|
|||||||
|
|
||||||
let mut reader = Self::create_reader(
|
let mut reader = Self::create_reader(
|
||||||
reader,
|
reader,
|
||||||
photometric_interpretation,
|
|
||||||
compression_method,
|
compression_method,
|
||||||
*compressed_bytes,
|
*compressed_bytes,
|
||||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if output_row_stride == chunk_row_bytes as usize {
|
if output_row_stride == chunk_row_bytes {
|
||||||
let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
|
let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
|
||||||
reader.read_exact(tile)?;
|
reader.read_exact(tile)?;
|
||||||
|
|
||||||
for row in tile.chunks_mut(chunk_row_bytes as usize) {
|
for row in tile.chunks_mut(chunk_row_bytes) {
|
||||||
super::fix_endianness_and_predict(
|
super::fix_endianness_and_predict(
|
||||||
row,
|
row,
|
||||||
color_type.bit_depth(),
|
color_type.bit_depth(),
|
||||||
@@ -694,11 +695,7 @@ impl Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (i, row) in buf
|
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
|
||||||
.chunks_mut(output_row_stride)
|
|
||||||
.take(data_dims.1 as usize)
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
let row = &mut row[..data_row_bytes];
|
let row = &mut row[..data_row_bytes];
|
||||||
reader.read_exact(row)?;
|
reader.read_exact(row)?;
|
||||||
|
|
||||||
|
|||||||
+713
-312
File diff suppressed because it is too large.
Load diff
+20
-16
@@ -18,7 +18,7 @@ pub struct EndianReader<R> {
|
|||||||
pub(crate) byte_order: ByteOrder,
|
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 {
|
pub fn new(reader: R, byte_order: ByteOrder) -> Self {
|
||||||
Self { reader, byte_order }
|
Self { reader, byte_order }
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,10 @@ impl<R: Read + Seek> EndianReader<R> {
|
|||||||
&mut self.reader
|
&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))?;
|
self.reader.seek(io::SeekFrom::Start(offset))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -132,20 +135,17 @@ impl<R: Read + Seek> EndianReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
//
|
||||||
/// # READERS
|
// # READERS
|
||||||
///
|
//
|
||||||
|
|
||||||
///
|
|
||||||
/// ## Deflate Reader
|
|
||||||
///
|
|
||||||
|
|
||||||
|
/// Type alias for the deflate Reader
|
||||||
#[cfg(feature = "deflate")]
|
#[cfg(feature = "deflate")]
|
||||||
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
||||||
|
|
||||||
///
|
//
|
||||||
/// ## LZW Reader
|
// ## LZW Reader
|
||||||
///
|
//
|
||||||
|
|
||||||
/// Reader that decompresses LZW streams
|
/// Reader that decompresses LZW streams
|
||||||
#[cfg(feature = "lzw")]
|
#[cfg(feature = "lzw")]
|
||||||
@@ -158,12 +158,15 @@ pub struct LZWReader<R: Read> {
|
|||||||
impl<R: Read> LZWReader<R> {
|
impl<R: Read> LZWReader<R> {
|
||||||
/// Wraps a reader
|
/// Wraps a reader
|
||||||
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
|
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 {
|
Self {
|
||||||
reader: BufReader::with_capacity(
|
reader: BufReader::with_capacity(
|
||||||
(32 * 1024).min(compressed_length),
|
(32 * 1024).min(compressed_length),
|
||||||
reader.take(u64::try_from(compressed_length).unwrap()),
|
reader.take(u64::try_from(compressed_length).unwrap()),
|
||||||
),
|
),
|
||||||
decoder: weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8),
|
decoder: configuration.build(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,10 +204,11 @@ impl<R: Read> Read for LZWReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
//
|
||||||
/// ## PackBits Reader
|
// ## PackBits Reader
|
||||||
///
|
//
|
||||||
|
|
||||||
|
/// Internal state machine for the PackBitsReader.
|
||||||
enum PackBitsReaderState {
|
enum PackBitsReaderState {
|
||||||
Header,
|
Header,
|
||||||
Literal,
|
Literal,
|
||||||
|
|||||||
+28
-15
@@ -1,25 +1,37 @@
|
|||||||
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::{Entry, Value};
|
||||||
use super::stream::EndianReader;
|
use super::ValueReader;
|
||||||
use super::Limits;
|
|
||||||
|
|
||||||
pub(crate) struct TagReader<'a, R: Read + Seek> {
|
pub(crate) struct TagReader<'lt, R: EntryDecoder + ?Sized> {
|
||||||
pub reader: &'a mut EndianReader<R>,
|
pub(crate) decoder: &'lt mut R,
|
||||||
pub ifd: &'a Directory,
|
pub(crate) ifd: &'lt Directory,
|
||||||
pub limits: &'a Limits,
|
|
||||||
pub bigtiff: bool,
|
|
||||||
}
|
}
|
||||||
impl<'a, R: Read + Seek> TagReader<'a, R> {
|
|
||||||
pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<Value>> {
|
pub(crate) trait EntryDecoder {
|
||||||
Ok(match self.ifd.get(&tag) {
|
/// Turn an entry into a value by fetching the required bytes in the stream.
|
||||||
Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?),
|
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value>;
|
||||||
None => None,
|
}
|
||||||
})
|
|
||||||
|
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>> {
|
||||||
|
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> {
|
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
|
||||||
match self.find_tag(tag)? {
|
match self.find_tag(tag)? {
|
||||||
Some(val) => Ok(val),
|
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>>> {
|
pub fn find_tag_uint_vec<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<Vec<T>>> {
|
||||||
self.find_tag(tag)?
|
self.find_tag(tag)?
|
||||||
.map(|v| v.into_u64_vec())
|
.map(|v| v.into_u64_vec())
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
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 {
|
||||||
|
/// Create a directory in an initial state without entries. Note that an empty directory can
|
||||||
|
/// not be encoded in a file, it must contain at least one entry.
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if there are any entries in this directory. Note that an empty directory can not be
|
||||||
|
/// encoded in the file, it must contain at least one entry.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.entries.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,21 +11,17 @@ pub struct Deflate {
|
|||||||
/// It allows trading compression ratio for compression speed.
|
/// It allows trading compression ratio for compression speed.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[derive(Default)]
|
||||||
pub enum DeflateLevel {
|
pub enum DeflateLevel {
|
||||||
/// The fastest possible compression mode.
|
/// The fastest possible compression mode.
|
||||||
Fast = 1,
|
Fast = 1,
|
||||||
/// The conserative choice between speed and ratio.
|
/// The conserative choice between speed and ratio.
|
||||||
|
#[default]
|
||||||
Balanced = 6,
|
Balanced = 6,
|
||||||
/// The best compression available with Deflate.
|
/// The best compression available with Deflate.
|
||||||
Best = 9,
|
Best = 9,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DeflateLevel {
|
|
||||||
fn default() -> Self {
|
|
||||||
DeflateLevel::Balanced
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deflate {
|
impl Deflate {
|
||||||
/// Create a new deflate compressor with a specific level of compression.
|
/// Create a new deflate compressor with a specific level of compression.
|
||||||
pub fn with_level(level: DeflateLevel) -> Self {
|
pub fn with_level(level: DeflateLevel) -> Self {
|
||||||
|
|||||||
+212
-90
@@ -2,17 +2,17 @@ pub use tiff_value::*;
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
cmp,
|
||||||
collections::BTreeMap,
|
|
||||||
io::{self, Seek, Write},
|
io::{self, Seek, Write},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
mem,
|
mem,
|
||||||
num::TryFromIntError,
|
num::{NonZeroU64, TryFromIntError},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
decoder::ifd::Entry,
|
||||||
error::{TiffResult, UsageError},
|
error::{TiffResult, UsageError},
|
||||||
tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag},
|
tags::{CompressionMethod, IfdPointer, ResolutionUnit, SampleFormat, Tag, Type},
|
||||||
TiffError, TiffFormatError,
|
Directory, TiffError, TiffFormatError,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod colortype;
|
pub mod colortype;
|
||||||
@@ -107,6 +107,8 @@ pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> {
|
|||||||
kind: PhantomData<K>,
|
kind: PhantomData<K>,
|
||||||
predictor: Predictor,
|
predictor: Predictor,
|
||||||
compression: Compression,
|
compression: Compression,
|
||||||
|
/// The offset of the last main image directory's `next` field.
|
||||||
|
last_ifd_chain: NonZeroU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructor functions to create standard Tiff files.
|
/// 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> {
|
impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||||
/// Creates a new Tiff or BigTiff encoder, inferred from the return type.
|
/// Creates a new Tiff or BigTiff encoder, inferred from the return type.
|
||||||
pub fn new_generic(writer: W) -> TiffResult<Self> {
|
pub fn new_generic(writer: W) -> TiffResult<Self> {
|
||||||
let mut encoder = TiffEncoder {
|
let mut writer = TiffWriter::new(writer);
|
||||||
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,
|
kind: PhantomData,
|
||||||
predictor: Predictor::None,
|
predictor: Predictor::None,
|
||||||
compression: Compression::Uncompressed,
|
compression: Compression::Uncompressed,
|
||||||
};
|
last_ifd_chain,
|
||||||
|
})
|
||||||
K::write_header(&mut encoder.writer)?;
|
|
||||||
|
|
||||||
Ok(encoder)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the predictor to use
|
/// 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.
|
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||||
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> {
|
#[deprecated = "`image_directory` replaced the old behavior and clarifies the intent"]
|
||||||
DirectoryEncoder::new(&mut self.writer)
|
#[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.
|
/// Create an [`ImageEncoder`] to encode an image one slice at a time.
|
||||||
@@ -174,8 +198,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> TiffResult<ImageEncoder<W, C, K>> {
|
) -> 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)
|
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,11 +213,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
|||||||
where
|
where
|
||||||
[C::Inner]: TiffValue,
|
[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> =
|
let image: ImageEncoder<W, C, K> =
|
||||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?;
|
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?;
|
||||||
image.write_data(data)
|
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.
|
/// 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.
|
/// Encoding can silently fail while this is dropping.
|
||||||
pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> {
|
pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> {
|
||||||
writer: &'a mut TiffWriter<W>,
|
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
|
// We use BTreeMap to make sure tags are written in correct order
|
||||||
ifd_pointer_pos: u64,
|
directory: Directory,
|
||||||
ifd: BTreeMap<u16, DirectoryEntry<K::OffsetType>>,
|
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> {
|
impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||||
fn new(writer: &'a mut TiffWriter<W>) -> TiffResult<Self> {
|
/// Construct a directory writer appending to data, assuming the writer is currently positioned
|
||||||
// the previous word is the IFD offset position
|
/// immediately after a previously written IFD to which to append.
|
||||||
let ifd_pointer_pos = writer.offset() - mem::size_of::<K::OffsetType>() as u64;
|
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?
|
writer.pad_word_boundary()?; // TODO: Do we need to adjust this for BigTiff?
|
||||||
Ok(DirectoryEncoder {
|
Ok(Self {
|
||||||
writer,
|
writer,
|
||||||
|
chained_ifd_pos,
|
||||||
|
write_chain: chain_into,
|
||||||
|
kind: PhantomData,
|
||||||
|
directory: Directory::empty(),
|
||||||
dropped: false,
|
dropped: false,
|
||||||
ifd_pointer_pos,
|
|
||||||
ifd: BTreeMap::new(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a single ifd tag.
|
/// Write a single ifd tag.
|
||||||
pub fn write_tag<T: TiffValue>(&mut self, tag: Tag, value: T) -> TiffResult<()> {
|
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 bytes = Vec::with_capacity(value.bytes());
|
||||||
{
|
{
|
||||||
let mut writer = TiffWriter::new(&mut bytes);
|
let mut writer = TiffWriter::new(&mut bytes);
|
||||||
value.write(&mut writer)?;
|
value.write(&mut writer)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ifd.insert(
|
let entry = Self::write_value(
|
||||||
tag.to_u16(),
|
self.writer,
|
||||||
DirectoryEntry {
|
&DirectoryEntry {
|
||||||
data_type: <T>::FIELD_TYPE.to_u16(),
|
data_type: <T>::FIELD_TYPE,
|
||||||
count: value.count().try_into()?,
|
count: value.count().try_into()?,
|
||||||
data: bytes,
|
data: bytes,
|
||||||
},
|
},
|
||||||
);
|
)?;
|
||||||
|
|
||||||
|
self.directory.extend([(tag, entry)]);
|
||||||
|
|
||||||
Ok(())
|
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.
|
/// Write some data to the tiff file, the offset of the data is returned.
|
||||||
///
|
///
|
||||||
/// This could be used to write tiff strips.
|
/// 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)
|
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.
|
/// Provides the number of bytes written by the underlying TiffWriter during the last call.
|
||||||
fn last_written(&self) -> u64 {
|
fn last_written(&self) -> u64 {
|
||||||
self.writer.last_written()
|
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 ifd_pointer = self.write_directory()?;
|
||||||
let curr_pos = self.writer.offset();
|
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(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)?;
|
||||||
|
}
|
||||||
|
|
||||||
self.writer.goto_offset(self.ifd_pointer_pos)?;
|
|
||||||
K::write_offset(self.writer, ifd_pointer)?;
|
|
||||||
self.writer.goto_offset(curr_pos)?;
|
|
||||||
K::write_offset(self.writer, 0)?;
|
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;
|
self.dropped = true;
|
||||||
|
|
||||||
Ok(())
|
Ok(DirectoryOffset {
|
||||||
}
|
pointer: IfdPointer(ifd_pointer),
|
||||||
|
offset,
|
||||||
/// Write out the ifd directory.
|
ifd_chain,
|
||||||
pub fn finish(mut self) -> TiffResult<()> {
|
kind: PhantomData,
|
||||||
self.finish_internal()
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,11 +532,11 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
let rows_per_strip = {
|
let rows_per_strip = {
|
||||||
match compression.tag() {
|
match compression.tag() {
|
||||||
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
|
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
|
||||||
_ => (1_000_000 + row_bytes - 1) / row_bytes,
|
_ => 1_000_000_u64.div_ceil(row_bytes),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
|
let strip_count = u64::from(height).div_ceil(rows_per_strip);
|
||||||
|
|
||||||
encoder.write_tag(Tag::ImageWidth, width)?;
|
encoder.write_tag(Tag::ImageWidth, width)?;
|
||||||
encoder.write_tag(Tag::ImageLength, height)?;
|
encoder.write_tag(Tag::ImageLength, height)?;
|
||||||
@@ -584,13 +705,13 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
self.encoder.write_tag(Tag::RowsPerStrip, value)?;
|
self.encoder.write_tag(Tag::RowsPerStrip, value)?;
|
||||||
|
|
||||||
let value: u64 = value as u64;
|
let value: u64 = value as u64;
|
||||||
self.strip_count = (self.height as u64 + value - 1) / value;
|
self.strip_count = (self.height as u64).div_ceil(value);
|
||||||
self.rows_per_strip = value;
|
self.rows_per_strip = value;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_internal(&mut self) -> TiffResult<()> {
|
fn finish_internal(&mut self) -> TiffResult<DirectoryOffset<K>> {
|
||||||
self.encoder
|
self.encoder
|
||||||
.write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?;
|
.write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?;
|
||||||
self.encoder.write_tag(
|
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.
|
/// Write out image and ifd directory.
|
||||||
pub fn finish(mut self) -> TiffResult<()> {
|
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> {
|
struct DirectoryEntry<S> {
|
||||||
data_type: u16,
|
data_type: Type,
|
||||||
count: S,
|
count: S,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-29
@@ -15,7 +15,7 @@ pub trait TiffValue {
|
|||||||
|
|
||||||
/// Access this value as an contiguous sequence of bytes.
|
/// Access this value as an contiguous sequence of bytes.
|
||||||
/// If their is no trivial representation, allocate it on the heap.
|
/// If their is no trivial representation, allocate it on the heap.
|
||||||
fn data(&self) -> Cow<[u8]>;
|
fn data(&self) -> Cow<'_, [u8]>;
|
||||||
|
|
||||||
/// Write this value to a TiffWriter.
|
/// Write this value to a TiffWriter.
|
||||||
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
||||||
@@ -34,7 +34,7 @@ impl TiffValue for [u8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(self)
|
Cow::Borrowed(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ impl TiffValue for [i8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ impl TiffValue for [u16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ impl TiffValue for [i16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ impl TiffValue for [u32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ impl TiffValue for [i32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ impl TiffValue for [u64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ impl TiffValue for [i64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ impl TiffValue for [f32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
// We write using native endian so this should be safe
|
// We write using native endian so this should be safe
|
||||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ impl TiffValue for [f64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
// We write using native endian so this should be safe
|
// We write using native endian so this should be safe
|
||||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ impl TiffValue for u8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(from_ref(self))
|
Cow::Borrowed(from_ref(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@ impl TiffValue for i8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ impl TiffValue for u16 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ impl TiffValue for i16 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ impl TiffValue for u32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ impl TiffValue for i32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,7 +279,7 @@ impl TiffValue for u64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,7 +297,7 @@ impl TiffValue for i64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,7 +315,7 @@ impl TiffValue for f32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,7 +333,7 @@ impl TiffValue for f64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,7 +351,7 @@ impl TiffValue for Ifd {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,7 +369,7 @@ impl TiffValue for Ifd8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +388,7 @@ impl TiffValue for Rational {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
||||||
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
||||||
@@ -411,7 +411,7 @@ impl TiffValue for SRational {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
||||||
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
||||||
@@ -438,7 +438,7 @@ impl TiffValue for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||||
let bytes: &[u8] = self.as_bytes();
|
let bytes: &[u8] = self.as_bytes();
|
||||||
@@ -450,7 +450,7 @@ impl TiffValue for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
impl<T: TiffValue + ?Sized> TiffValue for &'_ T {
|
||||||
const BYTE_LEN: u8 = T::BYTE_LEN;
|
const BYTE_LEN: u8 = T::BYTE_LEN;
|
||||||
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
||||||
|
|
||||||
@@ -462,7 +462,7 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
|||||||
(*self).write(writer)
|
(*self).write(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
T::data(self)
|
T::data(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,7 +484,7 @@ macro_rules! impl_tiff_value_for_contiguous_sequence {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
||||||
for x in self {
|
for x in self {
|
||||||
buf.extend_from_slice(&x.data());
|
buf.extend_from_slice(&x.data());
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ impl<W: Write> TiffWriter<W> {
|
|||||||
self.offset
|
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 {
|
pub fn last_written(&self) -> u64 {
|
||||||
self.byte_count
|
self.byte_count
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ quick_error! {
|
|||||||
PredictorUnavailable {
|
PredictorUnavailable {
|
||||||
display("the requested predictor is not available")
|
display("the requested predictor is not available")
|
||||||
}
|
}
|
||||||
|
InsufficientOutputBufferSize { needed: usize, provided: usize } {
|
||||||
|
display("the borrowed output buffer is not large enough for the decoded data, needed {needed} but have {provided}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+41
-11
@@ -2,7 +2,7 @@ macro_rules! tags {
|
|||||||
{
|
{
|
||||||
// Permit arbitrary meta items, which include documentation.
|
// Permit arbitrary meta items, which include documentation.
|
||||||
$( #[$enum_attr:meta] )*
|
$( #[$enum_attr:meta] )*
|
||||||
$vis:vis enum $name:ident($ty:tt) $(unknown($unknown_doc:literal))* {
|
$vis:vis enum $name:ident($ty:tt) $(unknown(#[$unknown_meta:meta] $unknown_doc:ident))* {
|
||||||
// Each of the `Name = Val,` permitting documentation.
|
// Each of the `Name = Val,` permitting documentation.
|
||||||
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
|
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ macro_rules! tags {
|
|||||||
pub enum $name {
|
pub enum $name {
|
||||||
$($(#[$ident_attr])* $tag,)*
|
$($(#[$ident_attr])* $tag,)*
|
||||||
$(
|
$(
|
||||||
#[doc = $unknown_doc]
|
#[$unknown_meta]
|
||||||
Unknown($ty),
|
Unknown($ty),
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ macro_rules! tags {
|
|||||||
fn __to_inner_type(&self) -> $ty {
|
fn __to_inner_type(&self) -> $ty {
|
||||||
match *self {
|
match *self {
|
||||||
$( $name::$tag => $val, )*
|
$( $name::$tag => $val, )*
|
||||||
$( $name::Unknown(n) => { $unknown_doc; n }, )*
|
$( $name::Unknown($unknown_doc) => { $unknown_doc }, )*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ macro_rules! tags {
|
|||||||
tags!($name, $ty, $($unknown_doc)*);
|
tags!($name, $ty, $($unknown_doc)*);
|
||||||
};
|
};
|
||||||
// For u16 tags, provide direct inherent primitive conversion methods.
|
// For u16 tags, provide direct inherent primitive conversion methods.
|
||||||
($name:tt, u16, $($unknown_doc:literal)*) => {
|
($name:tt, u16, $($unknown_doc:ident)*) => {
|
||||||
impl $name {
|
impl $name {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn from_u16(val: u16) -> Option<Self> {
|
pub fn from_u16(val: u16) -> Option<Self> {
|
||||||
@@ -48,9 +48,8 @@ macro_rules! tags {
|
|||||||
|
|
||||||
$(
|
$(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn from_u16_exhaustive(val: u16) -> Self {
|
pub fn from_u16_exhaustive($unknown_doc: u16) -> Self {
|
||||||
$unknown_doc;
|
Self::__from_inner_type($unknown_doc).unwrap_or_else(|_| $name::Unknown($unknown_doc))
|
||||||
Self::__from_inner_type(val).unwrap_or_else(|_| $name::Unknown(val))
|
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
@@ -68,7 +67,10 @@ macro_rules! tags {
|
|||||||
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
||||||
tags! {
|
tags! {
|
||||||
/// TIFF tags
|
/// TIFF tags
|
||||||
pub enum Tag(u16) unknown("A private or extension tag") {
|
pub enum Tag(u16) unknown(
|
||||||
|
/// A private or extension tag
|
||||||
|
unknown
|
||||||
|
) {
|
||||||
// Baseline tags:
|
// Baseline tags:
|
||||||
Artist = 315,
|
Artist = 315,
|
||||||
// grayscale images PhotometricInterpretation 1 or 3
|
// grayscale images PhotometricInterpretation 1 or 3
|
||||||
@@ -78,7 +80,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
|
||||||
@@ -114,6 +115,7 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
|||||||
TileLength = 323,
|
TileLength = 323,
|
||||||
TileOffsets = 324,
|
TileOffsets = 324,
|
||||||
TileByteCounts = 325,
|
TileByteCounts = 325,
|
||||||
|
SubIfd = 330,
|
||||||
// Data Sample Format
|
// Data Sample Format
|
||||||
SampleFormat = 339,
|
SampleFormat = 339,
|
||||||
SMinSampleValue = 340, // TODO add support
|
SMinSampleValue = 340, // TODO add support
|
||||||
@@ -124,13 +126,35 @@ 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,
|
||||||
|
// <https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf>
|
||||||
|
IccProfile = 34675,
|
||||||
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) {
|
||||||
@@ -172,7 +196,10 @@ pub enum Type(u16) {
|
|||||||
tags! {
|
tags! {
|
||||||
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||||
/// for reference.
|
/// for reference.
|
||||||
pub enum CompressionMethod(u16) unknown("A custom compression method") {
|
pub enum CompressionMethod(u16) unknown(
|
||||||
|
/// A custom compression method
|
||||||
|
unknown
|
||||||
|
) {
|
||||||
None = 1,
|
None = 1,
|
||||||
Huffman = 2,
|
Huffman = 2,
|
||||||
Fax3 = 3,
|
Fax3 = 3,
|
||||||
@@ -234,7 +261,10 @@ pub enum ResolutionUnit(u16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
pub enum SampleFormat(u16) unknown("An unknown extension sample format") {
|
pub enum SampleFormat(u16) unknown(
|
||||||
|
/// An unknown extension sample format
|
||||||
|
unknown
|
||||||
|
) {
|
||||||
Uint = 1,
|
Uint = 1,
|
||||||
Int = 2,
|
Int = 2,
|
||||||
IEEEFP = 3,
|
IEEEFP = 3,
|
||||||
|
|||||||
@@ -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
|
||||||
+160
-10
@@ -4,6 +4,7 @@ use tiff::decoder::{ifd, Decoder, DecodingResult};
|
|||||||
use tiff::ColorType;
|
use tiff::ColorType;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Seek};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
||||||
@@ -45,12 +46,12 @@ fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
|
|||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
assert!(match decoder.read_image() {
|
assert!(matches!(
|
||||||
|
decoder.read_image(),
|
||||||
Err(tiff::TiffError::UnsupportedError(
|
Err(tiff::TiffError::UnsupportedError(
|
||||||
tiff::TiffUnsupportedError::UnsupportedColorType(_),
|
tiff::TiffUnsupportedError::UnsupportedColorType(_),
|
||||||
)) => true,
|
))
|
||||||
_ => false,
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -172,7 +173,7 @@ fn test_string_tags() {
|
|||||||
&s,
|
&s,
|
||||||
"GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/"
|
"GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/"
|
||||||
),
|
),
|
||||||
_ => assert!(false),
|
other => unreachable!("Expected Ascii tag from file, got {other:?}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,6 +232,54 @@ fn test_tiled_rect_rgb_u8() {
|
|||||||
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
|
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inner_access() {
|
||||||
|
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
|
||||||
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||||
|
|
||||||
|
let c = decoder
|
||||||
|
.get_tag(tiff::tags::Tag::Compression)
|
||||||
|
.unwrap()
|
||||||
|
.into_u16()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(c, tiff::tags::CompressionMethod::None.to_u16());
|
||||||
|
|
||||||
|
// Because the image is uncompressed, reading the first tile directly with the inner reader
|
||||||
|
// should yield the same result as reading it with the decoder's read_chunk method.
|
||||||
|
let first_offset = decoder
|
||||||
|
.get_tag_u32_vec(tiff::tags::Tag::TileOffsets)
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
let first_byte_count = decoder
|
||||||
|
.get_tag_u32_vec(tiff::tags::Tag::TileByteCounts)
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
decoder
|
||||||
|
.inner()
|
||||||
|
.seek(std::io::SeekFrom::Start(first_offset as u64))
|
||||||
|
.expect("Cannot seek to tile offset");
|
||||||
|
let mut buf = vec![0; first_byte_count as usize];
|
||||||
|
decoder
|
||||||
|
.inner()
|
||||||
|
.read_exact(&mut buf)
|
||||||
|
.expect("Cannot read tile data");
|
||||||
|
let raw_sum: u64 = buf.into_iter().map(<u64>::from).sum();
|
||||||
|
|
||||||
|
match decoder.read_chunk(0).unwrap() {
|
||||||
|
DecodingResult::U8(chunk) => {
|
||||||
|
let sum: u64 = chunk.into_iter().map(<u64>::from).sum();
|
||||||
|
assert_eq!(sum, raw_sum);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong bit depth"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* #[test]
|
/* #[test]
|
||||||
fn test_tiled_jpeg_rgb_u8() {
|
fn test_tiled_jpeg_rgb_u8() {
|
||||||
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
|
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
|
||||||
@@ -292,6 +341,11 @@ fn test_tiled_incremental() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_planar_rgb_u8() {
|
fn test_planar_rgb_u8() {
|
||||||
|
test_image_sum_u8("planar-rgb-u8.tif", ColorType::RGB(8), 15417630);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_planar_bands() {
|
||||||
// gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
|
// gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
|
||||||
let file = "planar-rgb-u8.tif";
|
let file = "planar-rgb-u8.tif";
|
||||||
let expected_type = ColorType::RGB(8);
|
let expected_type = ColorType::RGB(8);
|
||||||
@@ -328,8 +382,6 @@ fn test_planar_rgb_u8() {
|
|||||||
}
|
}
|
||||||
_ => panic!("Wrong bit depth"),
|
_ => panic!("Wrong bit depth"),
|
||||||
}
|
}
|
||||||
|
|
||||||
test_image_sum_u8(file, ColorType::RGB(8), 15417630);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -348,7 +400,7 @@ fn test_div_zero() {
|
|||||||
|
|
||||||
match err {
|
match err {
|
||||||
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
unexpected => panic!("Unexpected error {unexpected}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +418,7 @@ fn test_too_many_value_bytes() {
|
|||||||
|
|
||||||
match error {
|
match error {
|
||||||
tiff::TiffError::LimitsExceeded => {}
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
unexpected => panic!("Unexpected error {unexpected}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,7 +549,7 @@ fn timeout() {
|
|||||||
|
|
||||||
match error {
|
match error {
|
||||||
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
||||||
e => panic!("Unexpected error {:?}", e),
|
e => panic!("Unexpected error {e:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,3 +574,101 @@ fn test_zstd_compression() {
|
|||||||
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
|
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
|
||||||
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
|
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_image_bytes(
|
||||||
|
file: &str,
|
||||||
|
expected: &[(ColorType, u32)],
|
||||||
|
// Represent all samples as little endian, ensuring check sums on all platforms.
|
||||||
|
normalize_byte_order: fn(&mut [u8]),
|
||||||
|
) {
|
||||||
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
|
|
||||||
|
let mut buffer = vec![0u8; 0];
|
||||||
|
for (idx, &(expected_type, expected_crc)) in expected.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
decoder
|
||||||
|
.next_image()
|
||||||
|
.expect("mismatched number of directories");
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
|
let layout = decoder
|
||||||
|
.image_buffer_layout()
|
||||||
|
.expect("Cannot describe buffer layout");
|
||||||
|
|
||||||
|
buffer.resize(layout.len, 0u8);
|
||||||
|
decoder.read_image_bytes(&mut buffer).unwrap();
|
||||||
|
normalize_byte_order(&mut buffer);
|
||||||
|
|
||||||
|
// Well almost a crc..
|
||||||
|
assert_eq!(crc32fast::hash(&buffer), expected_crc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_order_u16(bytes: &mut [u8]) {
|
||||||
|
for chunk in bytes.chunks_exact_mut(2) {
|
||||||
|
let n = u16::from_ne_bytes(chunk.try_into().unwrap());
|
||||||
|
chunk.copy_from_slice(&n.to_le_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_order_u32(bytes: &mut [u8]) {
|
||||||
|
for chunk in bytes.chunks_exact_mut(4) {
|
||||||
|
let n = u32::from_ne_bytes(chunk.try_into().unwrap());
|
||||||
|
chunk.copy_from_slice(&n.to_le_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_order_u64(bytes: &mut [u8]) {
|
||||||
|
for chunk in bytes.chunks_exact_mut(8) {
|
||||||
|
let n = u64::from_ne_bytes(chunk.try_into().unwrap());
|
||||||
|
chunk.copy_from_slice(&n.to_le_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_cmyk_3c_8b() {
|
||||||
|
test_image_bytes(
|
||||||
|
"cmyk-3c-8b.tiff",
|
||||||
|
&[(ColorType::CMYK(8), 2935032230)],
|
||||||
|
|_| (),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_gray_16b() {
|
||||||
|
test_image_bytes(
|
||||||
|
"minisblack-1c-16b.tiff",
|
||||||
|
&[(ColorType::Gray(16), 3524597615)],
|
||||||
|
byte_order_u16,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_gray_32b() {
|
||||||
|
test_image_bytes(
|
||||||
|
"gradient-1c-32b.tiff",
|
||||||
|
&[(ColorType::Gray(32), 2257036530)],
|
||||||
|
byte_order_u32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_gray_u64() {
|
||||||
|
test_image_bytes(
|
||||||
|
"gradient-1c-64b.tiff",
|
||||||
|
&[(ColorType::Gray(64), 2156547113)],
|
||||||
|
byte_order_u64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_gray_f32() {
|
||||||
|
test_image_bytes(
|
||||||
|
"gradient-1c-32b-float.tiff",
|
||||||
|
&[(ColorType::Gray(32), 3232356730)],
|
||||||
|
byte_order_u32,
|
||||||
|
);
|
||||||
|
}
|
||||||
+57
-3
@@ -57,7 +57,7 @@ fn encode_decode() {
|
|||||||
),
|
),
|
||||||
(Tag::Compression, ifd::Value::Short(1)),
|
(Tag::Compression, ifd::Value::Short(1)),
|
||||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
(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::SamplesPerPixel, ifd::Value::Short(3)),
|
||||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
||||||
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
|
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
|
||||||
@@ -137,7 +137,7 @@ fn encode_decode_big() {
|
|||||||
),
|
),
|
||||||
(Tag::Compression, ifd::Value::Short(1)),
|
(Tag::Compression, ifd::Value::Short(1)),
|
||||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
(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::SamplesPerPixel, ifd::Value::Short(3)),
|
||||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
||||||
(Tag::StripByteCounts, ifd::Value::UnsignedBig(30000)),
|
(Tag::StripByteCounts, ifd::Value::UnsignedBig(30000)),
|
||||||
@@ -621,8 +621,62 @@ fn test_rows_per_strip() {
|
|||||||
let img2 = [i; 2 * 100];
|
let img2 = [i; 2 * 100];
|
||||||
match decoder.read_chunk(i as u32).unwrap() {
|
match decoder.read_chunk(i as u32).unwrap() {
|
||||||
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
|
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
|
||||||
other => panic!("Incorrect strip type {:?}", other),
|
other => panic!("Incorrect strip type {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,7 +116,7 @@ fn encode_decode_with_compression(compression: Compression) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
match decoder.read_image() {
|
match decoder.read_image() {
|
||||||
Ok(DecodingResult::U16(image_data)) => image_data,
|
Ok(DecodingResult::U16(image_data)) => image_data,
|
||||||
unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
|
unexpected => panic!("Descoding RGB failed: {unexpected:?}"),
|
||||||
},
|
},
|
||||||
image_rgb.reference_data()
|
image_rgb.reference_data()
|
||||||
);
|
);
|
||||||
@@ -126,7 +126,7 @@ fn encode_decode_with_compression(compression: Compression) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
match decoder.read_image() {
|
match decoder.read_image() {
|
||||||
Ok(DecodingResult::U8(image_data)) => image_data,
|
Ok(DecodingResult::U8(image_data)) => image_data,
|
||||||
unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
|
unexpected => panic!("Decoding grayscale failed: {unexpected:?}"),
|
||||||
},
|
},
|
||||||
image_grayscale.reference_data()
|
image_grayscale.reference_data()
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
extern crate tiff;
|
extern crate tiff;
|
||||||
|
|
||||||
use tiff::decoder::{Decoder, DecodingResult};
|
use tiff::decoder::{Decoder, DecodingResult};
|
||||||
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
|
use tiff::encoder::{colortype, Predictor, TiffEncoder};
|
||||||
use tiff::ColorType;
|
use tiff::ColorType;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|||||||
@@ -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