Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef7592cf82 | ||
|
|
7cdfd154ba | ||
|
|
fa0d6eb99f | ||
|
|
a780da0b34 | ||
|
|
ca56836a79 | ||
|
|
2c7c8891f4 | ||
|
|
01df985ef8 | ||
|
|
593479e5e3 | ||
|
|
bc2055243a | ||
|
|
7a7d4f0305 | ||
|
|
720e6586b9 | ||
|
|
47080293db | ||
|
|
b385a6e752 | ||
|
|
6ba5553d20 | ||
|
|
a36cd6bd90 | ||
|
|
63e6ef8bba | ||
|
|
2d10cff97e | ||
|
|
23fe194ad9 | ||
|
|
2d572c0370 | ||
|
|
4c942ca690 | ||
|
|
a19d35233a | ||
|
|
dc95ce10ac | ||
|
|
31da6737b9 | ||
|
|
e9aa39d895 | ||
|
|
77f15b5c0a | ||
|
|
6f89033fd3 | ||
|
|
9df18a1674 | ||
|
|
37cfcf82aa | ||
|
|
1d8cbbc6a1 | ||
|
|
9f1e3d7a89 | ||
|
|
fb4c119b88 | ||
|
|
3242d328d1 | ||
|
|
d6c42dd60a | ||
|
|
de95ac35a2 | ||
|
|
30bf508983 | ||
|
|
bc7be68d0f | ||
|
|
99b2aa9b3a | ||
|
|
d7d63e648b | ||
|
|
b8459f413a | ||
|
|
26e760e303 | ||
|
|
a4878e9658 | ||
|
|
73285481cd | ||
|
|
b065539c07 | ||
|
|
1c5800daa0 | ||
|
|
885d762def | ||
|
|
2c9e02d347 | ||
|
|
b318481c55 | ||
|
|
1774b362d3 | ||
|
|
f2fec92283 | ||
|
|
be0619d6a1 | ||
|
|
e92cf219a9 | ||
|
|
3e5f977460 | ||
|
|
f0666aebbf | ||
|
|
65a1fbe8ae | ||
|
|
03b6ed413b | ||
|
|
5b7814c994 | ||
|
|
a15d619471 | ||
|
|
458a713827 | ||
|
|
2f6ad07f28 | ||
|
|
153ed48ee7 | ||
|
|
420667955a | ||
|
|
3bf19822a2 | ||
|
|
2bb37a4ecd | ||
|
|
b41ac105ab | ||
|
|
1e10b9803b | ||
|
|
2f4e491874 | ||
|
|
285b881e27 | ||
|
|
bdfe2f55fd | ||
|
|
eda5a8ac30 | ||
|
|
bd1e37d78b | ||
|
|
e8bc935618 | ||
|
|
150ff8303b | ||
|
|
e44b98ddf9 | ||
|
|
ad0f75222a | ||
|
|
4df26011d9 | ||
|
|
4dc7c2a986 | ||
|
|
b7c4f668ae | ||
|
|
9fe9b2e0a0 | ||
|
|
b7e8f210d1 | ||
|
|
371a9b53c3 | ||
|
|
d0e4f37a28 | ||
|
|
46c7efd836 | ||
|
|
65832e01bd | ||
|
|
b9590e0842 | ||
|
|
521b58dc07 | ||
|
|
e70121efbb | ||
|
|
a6fb855121 | ||
|
|
a874b9c3a5 | ||
|
|
d4ce174236 | ||
|
|
a43aa1fbc7 | ||
|
|
69c34ac33c | ||
|
|
00e0eb9006 | ||
|
|
ebd0e266f7 | ||
|
|
9b94e9de88 | ||
|
|
47dfa0aa3e | ||
|
|
1eb9c4fab7 | ||
|
|
201b95f749 | ||
|
|
7641dcbd3f | ||
|
|
60c6e0cd90 | ||
|
|
65a1c553af | ||
|
|
fddc048849 | ||
|
|
68f2182918 | ||
|
|
b2a7e35c85 | ||
|
|
91fe529859 | ||
|
|
5ca2730416 | ||
|
|
8fcacc829f | ||
|
|
59d0b7389d | ||
|
|
0ac444b63f | ||
|
|
ce1ee6776f | ||
|
|
29a020e5ec | ||
|
|
9021985477 | ||
|
|
d4c958f915 | ||
|
|
dc04282290 | ||
|
|
4cfed1c7b1 | ||
|
|
745ed62808 | ||
|
|
6fe9b78509 | ||
|
|
f5754159cb | ||
|
|
53d0256585 | ||
|
|
09aa28d699 | ||
|
|
d086e289dd | ||
|
|
2cba04dfe7 | ||
|
|
3de87a7001 | ||
|
|
4900c82871 | ||
|
|
8a8e3ed753 | ||
|
|
cd2f4f6462 | ||
|
|
aa13e31894 | ||
|
|
6c211b9179 | ||
|
|
332ab1dc4d | ||
|
|
ec98e32a3b | ||
|
|
7ec2b75c70 | ||
|
|
9788085786 |
No files matched your search
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust: ["1.34.2", stable, beta, nightly]
|
||||
rust: ["1.48", stable, beta, nightly]
|
||||
command: [build, test]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: test
|
||||
run: >
|
||||
cargo test --tests --benches --no-default-features --features "$FEATURES"
|
||||
if: ${{ matrix.rust != '1.34.2' }}
|
||||
if: ${{ matrix.rust != '1.48' }}
|
||||
env:
|
||||
FEATURES: ${{ matrix.features }}
|
||||
rustfmt:
|
||||
|
||||
+77
@@ -1,3 +1,80 @@
|
||||
# Version 0.7.2
|
||||
|
||||
New features:
|
||||
* Encoding with `ImageEncoder` now takes an optional compressor argument,
|
||||
allowing compressed encoding. See the methods
|
||||
`TiffEncoder::{new_image,write_image}_with_compression`.
|
||||
* `jpeg_decoder` has been upgraded, now supports lossless JPEG.
|
||||
|
||||
Changes:
|
||||
* Decoding now more consistently reads and interprets the initial IFD, instead
|
||||
of performing _some_ interpretation lazily. (This change prepares fully lazy
|
||||
and backwards seeking.)
|
||||
|
||||
# Version 0.7.1
|
||||
|
||||
New features:
|
||||
* Encoding signed integer formats is now supported.
|
||||
* Extensive fuzzing with `cargo fuzz`.
|
||||
|
||||
Changes:
|
||||
* Tile decoding should be a little faster, requires one less intermediate buffer.
|
||||
* Images whose IFDs form a cycle due to their offsets will now raise an error
|
||||
when the cycle would be entered (jumping back should still be supported).
|
||||
|
||||
Fixes:
|
||||
* Fixed a regression that caused conflict between strips and tile images,
|
||||
causing errors in decoding some images.
|
||||
* Use checked integer arithmetic in limit calculations, fixes overflows.
|
||||
* IFD Tags are now always cleared between images.
|
||||
* Found by fuzzing: Several memory limit overflows; JPEG now correctly
|
||||
validates offsets and a minimum size of its table; Check upper limit of strip
|
||||
byte size correctly;
|
||||
|
||||
Notes:
|
||||
Our CI has warned that this version no longer builds on `1.34.2` out of the
|
||||
box. We're still committed to the MSRV on this major version yet one
|
||||
dependency—`flate2`—has already bumped it in a SemVer compatible version of its
|
||||
own. This is out-of-our-control (cargo's dependency resolution does not allow
|
||||
us to address this in a reasonable manner).
|
||||
|
||||
This can be address this by pinning the version of `flate2` to `1.0.21` in your
|
||||
own files. However, you should understand that this puts you in considerable
|
||||
maintenance debt as you will no longer receive any updates for this dependency
|
||||
and any package that _requires_ a new version of the `1.0` series would be
|
||||
incompatible with this requirement (cargo might yell at you very loudly).
|
||||
|
||||
# Version 0.7.0
|
||||
|
||||
New features:
|
||||
* Support for encoding BigTiff ([#122](https://github.com/image-rs/image-tiff/pull/122))
|
||||
* _Breaking:_ Encoder types now have a generic parameter to differentiate BigTiff and standard Tiff encoding. Defaults to standard Tiff.
|
||||
* Basic tile decoding ([#125](https://github.com/image-rs/image-tiff/pull/125))
|
||||
* _Breaking:_ There is a new `TiffError::UsageError` variant.
|
||||
* Support for datatypes `Int8` and `Int16` ([#114](https://github.com/image-rs/image-tiff/pull/114))
|
||||
* _Breaking:_ `DecodingResult` and `DecodingBuffer` have the two new variants `I8` and `I16`.
|
||||
* Support for `i32` arrays ([#118](https://github.com/image-rs/image-tiff/pull/118/files))
|
||||
* _Breaking:_ `DecodingResult` and `DecodingBuffer` have a new `I32` variant.
|
||||
* Support for `Ifd` and `IfdBig` tag types and `I64` data type ([#119](https://github.com/image-rs/image-tiff/pull/119))
|
||||
* _Breaking:_ `DecodingResult` and `DecodingBuffer` have a new `I64` variant.
|
||||
* Add `SMinSampleValue` and `SMaxSampleValue` ([#123](https://github.com/image-rs/image-tiff/pull/123))
|
||||
|
||||
Changes:
|
||||
* Improve deflate support ([#132](https://github.com/image-rs/image-tiff/pull/132))
|
||||
* Switch to streaming decompression via `flate2`. Aside from performance improvements and lower RAM consumption, this fixes a bug where `max_uncompressed_length` was precalculated for a single tile but then used as a hard limit on the whole data, failing to decompress any tiled images.
|
||||
* Add support for new `Deflate` tag in addition to `OldDeflate`.
|
||||
* _Breaking:_ Remove `InflateError`, which is no longer needed with `flate2` ([#134](https://github.com/image-rs/image-tiff/pull/134))
|
||||
* _Breaking:_ Support for `MinIsWhite` is restricted to unsigned and floating
|
||||
point values. This is expected to be be re-added once some contradictory
|
||||
interpretation regarding semantics for signed values is resolved.
|
||||
|
||||
Fixes:
|
||||
* Validate that ASCII tags are valid ASCII and end with a null byte ([#121](https://github.com/image-rs/image-tiff/pull/121))
|
||||
|
||||
Internal:
|
||||
* Simplify decompression logic ([#126](https://github.com/image-rs/image-tiff/pull/126))
|
||||
* Simplify `expand_strip` ([#128](https://github.com/image-rs/image-tiff/pull/128))
|
||||
|
||||
# Version 0.6.1
|
||||
|
||||
New features:
|
||||
|
||||
+3
-6
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tiff"
|
||||
version = "0.6.1"
|
||||
version = "0.7.2"
|
||||
authors = [
|
||||
"ccgn",
|
||||
"bvssvni <bvssvni@gmail.com>",
|
||||
@@ -19,11 +19,8 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
weezl = "0.1.0"
|
||||
jpeg = { package = "jpeg-decoder", version = "0.1.17", default-features = false }
|
||||
|
||||
[dependencies.miniz_oxide]
|
||||
version = "0.4.1"
|
||||
features = ["no_extern_crate_alloc"]
|
||||
jpeg = { package = "jpeg-decoder", version = "0.2.4", default-features = false }
|
||||
flate2 = "1.0.20"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.1"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# image-tiff
|
||||
[](https://travis-ci.org/image-rs/image-tiff)
|
||||
[](https://github.com/image-rs/image-tiff/actions)
|
||||
[](https://docs.rs/tiff)
|
||||
[](https://crates.io/crates/tiff)
|
||||
|
||||
@@ -16,25 +16,25 @@ TIFF decoding and encoding library in pure Rust
|
||||
### Formats
|
||||
This table lists photometric interpretations and sample formats which are supported for encoding and decoding. The entries are `ColorType` variants for which sample bit depths are supported. Only samples where all bit depths are equal are currently supported. For example, `RGB(8)` means that the bit depth [8, 8, 8] is supported and will be interpreted as an 8 bit per channel RGB color type.
|
||||
|
||||
| `PhotometricInterpretation` | UINT Format | IEEEFP Format |
|
||||
|-|-|-|
|
||||
| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
|
||||
| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
|
||||
| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) |
|
||||
| `RGBPalette` | | |
|
||||
| `Mask` | | |
|
||||
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
|
||||
| `YCbCr` | | |
|
||||
| `CIELab` | | |
|
||||
| `PhotometricInterpretation` | UINT Format | IEEEFP Format |
|
||||
| --------------------------- | --------------------------------------- | ------------------------- |
|
||||
| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
|
||||
| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
|
||||
| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) |
|
||||
| `RGBPalette` | | |
|
||||
| `Mask` | | |
|
||||
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
|
||||
| `YCbCr` | | |
|
||||
| `CIELab` | | |
|
||||
|
||||
### Compressions
|
||||
|
||||
| | Decoding | Encoding |
|
||||
|-|-|-|
|
||||
| None | ✓ | ✓ |
|
||||
| LZW | ✓ | |
|
||||
| Deflate | ✓ | |
|
||||
| PackBits | ✓ | |
|
||||
| | Decoding | Encoding |
|
||||
| -------- | -------- | -------- |
|
||||
| None | ✓ | ✓ |
|
||||
| LZW | ✓ | ✓ |
|
||||
| Deflate | ✓ | ✓ |
|
||||
| PackBits | ✓ | ✓ |
|
||||
|
||||
|
||||
## Not yet supported
|
||||
@@ -44,3 +44,11 @@ Formats and interpretations not listed above or with empty entries are unsupport
|
||||
- Baseline tags
|
||||
- `ExtraSamples`
|
||||
- Extension tags
|
||||
|
||||
## Fuzzing
|
||||
|
||||
This crate uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) in order to test the image parser.
|
||||
|
||||
After installing it with `cargo install cargo-fuzz` on a nightly rustc, the
|
||||
fuzzing harness can be run with recommended settings using
|
||||
`cargo fuzz run decode_image -snone -- -timeout=5`.
|
||||
+1
-1
@@ -33,7 +33,7 @@ fn main() {
|
||||
def.data,
|
||||
|b, input| b.iter(|| read_image(input)),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
let mut c = Criterion::default().configure_from_args();
|
||||
let mut group = c.benchmark_group("tiff-lzw");
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "tiff-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
|
||||
[dependencies.tiff]
|
||||
path = ".."
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
name = "decode_image"
|
||||
path = "fuzz_targets/decode_image.rs"
|
||||
test = false
|
||||
doc = false
|
||||
@@ -0,0 +1,31 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let mut decoder = if let Ok(d) = tiff::decoder::Decoder::new(std::io::Cursor::new(data)) {
|
||||
d
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut limits = tiff::decoder::Limits::default();
|
||||
limits.decoding_buffer_size = 1_000_000;
|
||||
limits.ifd_value_size = 1_000_000;
|
||||
limits.intermediate_buffer_size = 1_000_000;
|
||||
|
||||
decoder = decoder.with_limits(limits);
|
||||
|
||||
loop {
|
||||
if let Err(_) = decoder.read_image() {
|
||||
break;
|
||||
}
|
||||
|
||||
if !decoder.more_images() {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Err(_) = decoder.next_image() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
+4
-3
@@ -23,11 +23,12 @@ macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) =>
|
||||
})*
|
||||
}}
|
||||
|
||||
integral_slice_as_bytes!(i8, i8_as_ne_bytes);
|
||||
integral_slice_as_bytes!(i8, i8_as_ne_bytes, i8_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(u16, u16_as_ne_bytes, u16_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(i16, i16_as_ne_bytes);
|
||||
integral_slice_as_bytes!(i16, i16_as_ne_bytes, i16_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(u32, u32_as_ne_bytes, u32_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(i32, i32_as_ne_bytes);
|
||||
integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(i64, i64_as_ne_bytes, i64_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes);
|
||||
+147
-63
@@ -4,14 +4,15 @@ use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::io::{self, Read, Seek};
|
||||
use std::mem;
|
||||
use std::str;
|
||||
|
||||
use super::stream::{ByteOrder, EndianReader, SmartReader};
|
||||
use crate::tags::{Tag, Type};
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use self::Value::{
|
||||
Ascii, Byte, Double, Float, List, Rational, RationalBig, SRational, SRationalBig, Short,
|
||||
Signed, SignedBig, Unsigned, UnsignedBig,
|
||||
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
|
||||
Short, Signed, SignedBig, Unsigned, UnsignedBig,
|
||||
};
|
||||
|
||||
#[allow(unused_qualifications)]
|
||||
@@ -31,6 +32,8 @@ pub enum Value {
|
||||
SRational(i32, i32),
|
||||
SRationalBig(i64, i64),
|
||||
Ascii(String),
|
||||
Ifd(u32),
|
||||
IfdBig(u64),
|
||||
#[doc(hidden)] // Do not match against this.
|
||||
__NonExhaustive,
|
||||
}
|
||||
@@ -59,6 +62,8 @@ impl Value {
|
||||
Short(val) => Ok(val.into()),
|
||||
Unsigned(val) => Ok(val),
|
||||
UnsignedBig(val) => Ok(u32::try_from(val)?),
|
||||
Ifd(val) => Ok(val),
|
||||
IfdBig(val) => Ok(u32::try_from(val)?),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
@@ -80,6 +85,8 @@ impl Value {
|
||||
Short(val) => Ok(val.into()),
|
||||
Unsigned(val) => Ok(val.into()),
|
||||
UnsignedBig(val) => Ok(val),
|
||||
Ifd(val) => Ok(val.into()),
|
||||
IfdBig(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
@@ -138,6 +145,8 @@ impl Value {
|
||||
RationalBig(numerator, denominator) => {
|
||||
Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?])
|
||||
}
|
||||
Ifd(val) => Ok(vec![val]),
|
||||
IfdBig(val) => Ok(vec![u32::try_from(val)?]),
|
||||
Ascii(val) => Ok(val.chars().map(u32::from).collect()),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
@@ -254,6 +263,8 @@ impl Value {
|
||||
UnsignedBig(val) => Ok(vec![val]),
|
||||
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
||||
RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
Ifd(val) => Ok(vec![val.into()]),
|
||||
IfdBig(val) => Ok(vec![val]),
|
||||
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
@@ -330,29 +341,43 @@ impl Entry {
|
||||
pub fn val<R: Read + Seek>(
|
||||
&self,
|
||||
limits: &super::Limits,
|
||||
decoder: &mut super::Decoder<R>,
|
||||
bigtiff: bool,
|
||||
reader: &mut SmartReader<R>,
|
||||
) -> TiffResult<Value> {
|
||||
// Case 1: there are no values so we can return immediately.
|
||||
if self.count == 0 {
|
||||
return Ok(List(Vec::new()));
|
||||
}
|
||||
|
||||
let bo = decoder.byte_order();
|
||||
let value_bytes = self.count
|
||||
* match self.type_ {
|
||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
||||
Type::SHORT | Type::SSHORT => 2,
|
||||
Type::LONG | Type::SLONG | Type::FLOAT => 4,
|
||||
Type::LONG8 | Type::DOUBLE | Type::RATIONAL | Type::SRATIONAL => 8,
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
};
|
||||
let bo = reader.byte_order();
|
||||
|
||||
let tag_size = match self.type_ {
|
||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
||||
Type::SHORT | Type::SSHORT => 2,
|
||||
Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
|
||||
Type::LONG8
|
||||
| Type::SLONG8
|
||||
| Type::DOUBLE
|
||||
| Type::RATIONAL
|
||||
| Type::SRATIONAL
|
||||
| Type::IFD8 => 8,
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
};
|
||||
|
||||
let value_bytes = match self.count.checked_mul(tag_size) {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
return Err(TiffError::LimitsExceeded);
|
||||
}
|
||||
};
|
||||
|
||||
// Case 2: there is one value.
|
||||
if self.count == 1 {
|
||||
// 2a: the value is 5-8 bytes and we're in BigTiff mode.
|
||||
if decoder.bigtiff && value_bytes > 4 && value_bytes <= 8 {
|
||||
if bigtiff && value_bytes > 4 && value_bytes <= 8 {
|
||||
return Ok(match self.type_ {
|
||||
Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?),
|
||||
Type::SLONG8 => SignedBig(self.r(bo).read_i64()?),
|
||||
Type::DOUBLE => Double(self.r(bo).read_f64()?),
|
||||
Type::RATIONAL => {
|
||||
let mut r = self.r(bo);
|
||||
@@ -362,6 +387,7 @@ impl Entry {
|
||||
let mut r = self.r(bo);
|
||||
SRational(r.read_i32()?, r.read_i32()?)
|
||||
}
|
||||
Type::IFD8 => IfdBig(self.r(bo).read_u64()?),
|
||||
Type::BYTE
|
||||
| Type::SBYTE
|
||||
| Type::ASCII
|
||||
@@ -370,7 +396,8 @@ impl Entry {
|
||||
| Type::SSHORT
|
||||
| Type::LONG
|
||||
| Type::SLONG
|
||||
| Type::FLOAT => unreachable!(),
|
||||
| Type::FLOAT
|
||||
| Type::IFD => unreachable!(),
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
});
|
||||
}
|
||||
@@ -393,36 +420,49 @@ impl Entry {
|
||||
}
|
||||
}
|
||||
Type::LONG8 => {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||
UnsignedBig(decoder.read_long8()?)
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
UnsignedBig(reader.read_u64()?)
|
||||
}
|
||||
Type::SLONG8 => {
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
SignedBig(reader.read_i64()?)
|
||||
}
|
||||
Type::DOUBLE => {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||
Double(decoder.read_double()?)
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
Double(reader.read_f64()?)
|
||||
}
|
||||
Type::RATIONAL => {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||
Rational(decoder.read_long()?, decoder.read_long()?)
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
Rational(reader.read_u32()?, reader.read_u32()?)
|
||||
}
|
||||
Type::SRATIONAL => {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||
SRational(decoder.read_slong()?, decoder.read_slong()?)
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
SRational(reader.read_i32()?, reader.read_i32()?)
|
||||
}
|
||||
Type::IFD => Ifd(self.r(bo).read_u32()?),
|
||||
Type::IFD8 => {
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||
IfdBig(reader.read_u64()?)
|
||||
}
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
});
|
||||
}
|
||||
|
||||
// Case 3: There is more than one value, but it fits in the offset field.
|
||||
if value_bytes <= 4 || decoder.bigtiff && value_bytes <= 8 {
|
||||
if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
|
||||
match self.type_ {
|
||||
Type::BYTE => return Ok(offset_to_bytes(self.count as usize, self)?),
|
||||
Type::SBYTE => return Ok(offset_to_sbytes(self.count as usize, self)?),
|
||||
Type::ASCII => {
|
||||
let mut buf = vec![0; self.count as usize];
|
||||
self.r(bo).read_exact(&mut buf)?;
|
||||
let v = String::from_utf8(buf)?;
|
||||
let v = v.trim_matches(char::from(0));
|
||||
return Ok(Ascii(v.into()));
|
||||
if buf.is_ascii() && buf.ends_with(&[0]) {
|
||||
let v = str::from_utf8(&buf)?;
|
||||
let v = v.trim_matches(char::from(0));
|
||||
return Ok(Ascii(v.into()));
|
||||
} else {
|
||||
return Err(TiffError::FormatError(TiffFormatError::InvalidTag));
|
||||
}
|
||||
}
|
||||
Type::UNDEFINED => {
|
||||
return Ok(List(
|
||||
@@ -472,7 +512,22 @@ impl Entry {
|
||||
}
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::LONG8 | Type::RATIONAL | Type::SRATIONAL | Type::DOUBLE => unreachable!(),
|
||||
Type::IFD => {
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Ifd(r.read_u32()?));
|
||||
}
|
||||
return Ok(List(v));
|
||||
}
|
||||
Type::LONG8
|
||||
| Type::SLONG8
|
||||
| Type::RATIONAL
|
||||
| Type::SRATIONAL
|
||||
| Type::DOUBLE
|
||||
| Type::IFD8 => {
|
||||
unreachable!()
|
||||
}
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -481,55 +536,80 @@ impl Entry {
|
||||
match self.type_ {
|
||||
// TODO check if this could give wrong results
|
||||
// at a different endianess of file/computer.
|
||||
Type::BYTE => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(UnsignedBig(u64::from(decoder.read_byte()?)))
|
||||
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
let mut buf = [0; 1];
|
||||
reader.read_exact(&mut buf)?;
|
||||
Ok(UnsignedBig(u64::from(buf[0])))
|
||||
}),
|
||||
Type::SBYTE => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(SignedBig(i64::from(decoder.read_byte()? as i8)))
|
||||
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(SignedBig(i64::from(reader.read_i8()? as i8)))
|
||||
}),
|
||||
Type::SHORT => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(UnsignedBig(u64::from(decoder.read_short()?)))
|
||||
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(UnsignedBig(u64::from(reader.read_u16()?)))
|
||||
}),
|
||||
Type::SSHORT => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(SignedBig(i64::from(decoder.read_sshort()?)))
|
||||
Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(SignedBig(i64::from(reader.read_i16()?)))
|
||||
}),
|
||||
Type::LONG => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Unsigned(decoder.read_long()?))
|
||||
Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Unsigned(reader.read_u32()?))
|
||||
}),
|
||||
Type::SLONG => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Signed(decoder.read_slong()?))
|
||||
Type::SLONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Signed(reader.read_i32()?))
|
||||
}),
|
||||
Type::FLOAT => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Float(decoder.read_float()?))
|
||||
Type::FLOAT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Float(reader.read_f32()?))
|
||||
}),
|
||||
Type::DOUBLE => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Double(decoder.read_double()?))
|
||||
Type::DOUBLE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Double(reader.read_f64()?))
|
||||
}),
|
||||
Type::RATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Rational(decoder.read_long()?, decoder.read_long()?))
|
||||
Type::RATIONAL => {
|
||||
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Rational(reader.read_u32()?, reader.read_u32()?))
|
||||
})
|
||||
}
|
||||
Type::SRATIONAL => {
|
||||
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(SRational(reader.read_i32()?, reader.read_i32()?))
|
||||
})
|
||||
}
|
||||
Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(UnsignedBig(reader.read_u64()?))
|
||||
}),
|
||||
Type::SRATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(SRational(decoder.read_slong()?, decoder.read_slong()?))
|
||||
Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(SignedBig(reader.read_i64()?))
|
||||
}),
|
||||
Type::LONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(UnsignedBig(decoder.read_long8()?))
|
||||
Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Ifd(reader.read_u32()?))
|
||||
}),
|
||||
Type::UNDEFINED => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Byte(u8::from(decoder.read_byte()?)))
|
||||
Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(IfdBig(reader.read_u64()?))
|
||||
}),
|
||||
Type::UNDEFINED => {
|
||||
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
let mut buf = [0; 1];
|
||||
reader.read_exact(&mut buf)?;
|
||||
Ok(Byte(buf[0]))
|
||||
})
|
||||
}
|
||||
Type::ASCII => {
|
||||
let n = usize::try_from(self.count)?;
|
||||
if n > limits.decoding_buffer_size {
|
||||
return Err(TiffError::LimitsExceeded);
|
||||
}
|
||||
|
||||
if decoder.bigtiff {
|
||||
decoder.goto_offset_u64(self.r(bo).read_u64()?)?
|
||||
if bigtiff {
|
||||
reader.goto_offset(self.r(bo).read_u64()?)?
|
||||
} else {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?
|
||||
reader.goto_offset(self.r(bo).read_u32()?.into())?
|
||||
}
|
||||
let string = decoder.read_string(n)?;
|
||||
Ok(Ascii(string))
|
||||
|
||||
let mut out = vec![0; n];
|
||||
reader.read_exact(&mut out)?;
|
||||
// Strings may be null-terminated, so we trim anything downstream of the null byte
|
||||
if let Some(first) = out.iter().position(|&b| b == 0) {
|
||||
out.truncate(first);
|
||||
}
|
||||
Ok(Ascii(String::from_utf8(out)?))
|
||||
}
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
}
|
||||
@@ -540,13 +620,14 @@ impl Entry {
|
||||
&self,
|
||||
value_count: u64,
|
||||
bo: ByteOrder,
|
||||
bigtiff: bool,
|
||||
limits: &super::Limits,
|
||||
decoder: &mut super::Decoder<R>,
|
||||
reader: &mut SmartReader<R>,
|
||||
decode_fn: F,
|
||||
) -> TiffResult<Value>
|
||||
where
|
||||
R: Read + Seek,
|
||||
F: Fn(&mut super::Decoder<R>) -> TiffResult<Value>,
|
||||
F: Fn(&mut SmartReader<R>) -> TiffResult<Value>,
|
||||
{
|
||||
let value_count = usize::try_from(value_count)?;
|
||||
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
|
||||
@@ -554,13 +635,16 @@ impl Entry {
|
||||
}
|
||||
|
||||
let mut v = Vec::with_capacity(value_count);
|
||||
if decoder.bigtiff {
|
||||
decoder.goto_offset_u64(self.r(bo).read_u64()?)?
|
||||
|
||||
let offset = if bigtiff {
|
||||
self.r(bo).read_u64()?
|
||||
} else {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?
|
||||
}
|
||||
self.r(bo).read_u32()?.into()
|
||||
};
|
||||
reader.goto_offset(offset)?;
|
||||
|
||||
for _ in 0..value_count {
|
||||
v.push(decode_fn(decoder)?)
|
||||
v.push(decode_fn(reader)?)
|
||||
}
|
||||
Ok(List(v))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
use super::ifd::{Directory, Value};
|
||||
use super::tag_reader::TagReader;
|
||||
use super::Limits;
|
||||
use super::{stream::SmartReader, ChunkType};
|
||||
use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag};
|
||||
use crate::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct StripDecodeState {
|
||||
pub rows_per_strip: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Computed values useful for tile decoding
|
||||
pub(crate) struct TileAttributes {
|
||||
pub image_width: usize,
|
||||
pub image_height: usize,
|
||||
pub samples_per_pixel: usize,
|
||||
|
||||
pub tile_width: usize,
|
||||
pub tile_length: usize,
|
||||
}
|
||||
|
||||
impl TileAttributes {
|
||||
pub fn tiles_across(&self) -> usize {
|
||||
(self.image_width + self.tile_width - 1) / self.tile_width
|
||||
}
|
||||
pub fn tiles_down(&self) -> usize {
|
||||
(self.image_height + self.tile_length - 1) / self.tile_length
|
||||
}
|
||||
fn padding_right(&self) -> usize {
|
||||
self.tile_width - self.image_width % self.tile_width
|
||||
}
|
||||
fn padding_down(&self) -> usize {
|
||||
self.tile_length - self.image_height % self.tile_length
|
||||
}
|
||||
pub fn row_samples(&self) -> usize {
|
||||
self.tile_width * self.samples_per_pixel
|
||||
}
|
||||
pub fn tile_samples(&self) -> usize {
|
||||
self.tile_length * self.tile_width * self.samples_per_pixel
|
||||
}
|
||||
fn tile_strip_samples(&self) -> usize {
|
||||
(self.tile_samples() * self.tiles_across())
|
||||
- (self.padding_right() * self.tile_length * self.samples_per_pixel)
|
||||
}
|
||||
|
||||
/// Returns the tile offset in the result buffer, counted in samples
|
||||
pub fn get_offset(&self, tile: usize) -> usize {
|
||||
let row = tile / self.tiles_across();
|
||||
let column = tile % self.tiles_across();
|
||||
|
||||
(row * self.tile_strip_samples()) + (column * self.row_samples())
|
||||
}
|
||||
|
||||
pub fn get_padding(&self, tile: usize) -> (usize, usize) {
|
||||
let row = tile / self.tiles_across();
|
||||
let column = tile % self.tiles_across();
|
||||
|
||||
let padding_right = if column == self.tiles_across() - 1 {
|
||||
self.padding_right()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let padding_down = if row == self.tiles_down() - 1 {
|
||||
self.padding_down()
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
(padding_right, padding_down)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Image {
|
||||
pub ifd: Option<Directory>,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub bits_per_sample: Vec<u8>,
|
||||
#[allow(unused)]
|
||||
pub samples: u8,
|
||||
pub sample_format: Vec<SampleFormat>,
|
||||
pub photometric_interpretation: PhotometricInterpretation,
|
||||
pub compression_method: CompressionMethod,
|
||||
pub predictor: Predictor,
|
||||
pub jpeg_tables: Option<Vec<u8>>,
|
||||
pub chunk_type: ChunkType,
|
||||
pub strip_decoder: Option<StripDecodeState>,
|
||||
pub tile_attributes: Option<TileAttributes>,
|
||||
pub chunk_offsets: Vec<u64>,
|
||||
pub chunk_bytes: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn from_reader<R: Read + Seek>(
|
||||
reader: &mut SmartReader<R>,
|
||||
ifd: Directory,
|
||||
limits: &Limits,
|
||||
bigtiff: bool,
|
||||
) -> TiffResult<Image> {
|
||||
let mut tag_reader = TagReader {
|
||||
reader,
|
||||
limits,
|
||||
ifd: &ifd,
|
||||
bigtiff,
|
||||
};
|
||||
|
||||
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
||||
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
||||
|
||||
let photometric_interpretation = tag_reader
|
||||
.find_tag(Tag::PhotometricInterpretation)?
|
||||
.map(Value::into_u16)
|
||||
.transpose()?
|
||||
.and_then(PhotometricInterpretation::from_u16)
|
||||
.ok_or(TiffUnsupportedError::UnknownInterpretation)?;
|
||||
|
||||
// Try to parse both the compression method and the number, format, and bits of the included samples.
|
||||
// If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
|
||||
let compression_method = match tag_reader.find_tag(Tag::Compression)? {
|
||||
Some(val) => CompressionMethod::from_u16(val.into_u16()?)
|
||||
.ok_or(TiffUnsupportedError::UnknownCompressionMethod)?,
|
||||
None => CompressionMethod::None,
|
||||
};
|
||||
|
||||
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
|
||||
&& ifd.contains_key(&Tag::JPEGTables)
|
||||
{
|
||||
let vec = tag_reader
|
||||
.find_tag(Tag::JPEGTables)?
|
||||
.unwrap()
|
||||
.into_u8_vec()?;
|
||||
if vec.len() < 2 {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
||||
));
|
||||
}
|
||||
Some(vec)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let samples = tag_reader
|
||||
.find_tag(Tag::SamplesPerPixel)?
|
||||
.map(Value::into_u16)
|
||||
.transpose()?
|
||||
.unwrap_or(1)
|
||||
.try_into()?;
|
||||
|
||||
let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
|
||||
Some(vals) => {
|
||||
let sample_format: Vec<_> = vals
|
||||
.into_iter()
|
||||
.map(SampleFormat::from_u16_exhaustive)
|
||||
.collect();
|
||||
|
||||
// TODO: for now, only homogenous formats across samples are supported.
|
||||
if !sample_format.windows(2).all(|s| s[0] == s[1]) {
|
||||
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
|
||||
}
|
||||
|
||||
sample_format
|
||||
}
|
||||
None => vec![SampleFormat::Uint],
|
||||
};
|
||||
|
||||
let bits_per_sample = match samples {
|
||||
1 | 3 | 4 => tag_reader
|
||||
.find_tag_uint_vec(Tag::BitsPerSample)?
|
||||
.unwrap_or_else(|| vec![1]),
|
||||
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(samples).into()),
|
||||
};
|
||||
|
||||
let predictor = tag_reader
|
||||
.find_tag(Tag::Predictor)?
|
||||
.map(Value::into_u16)
|
||||
.transpose()?
|
||||
.map(|p| {
|
||||
Predictor::from_u16(p)
|
||||
.ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p)))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or(Predictor::None);
|
||||
|
||||
let chunk_type;
|
||||
let chunk_offsets;
|
||||
let chunk_bytes;
|
||||
let strip_decoder;
|
||||
let tile_attributes;
|
||||
match (
|
||||
ifd.contains_key(&Tag::StripByteCounts),
|
||||
ifd.contains_key(&Tag::StripOffsets),
|
||||
ifd.contains_key(&Tag::TileByteCounts),
|
||||
ifd.contains_key(&Tag::TileOffsets),
|
||||
) {
|
||||
(true, true, false, false) => {
|
||||
chunk_type = ChunkType::Strip;
|
||||
|
||||
chunk_offsets = tag_reader
|
||||
.find_tag(Tag::StripOffsets)?
|
||||
.unwrap()
|
||||
.into_u64_vec()?;
|
||||
chunk_bytes = tag_reader
|
||||
.find_tag(Tag::StripByteCounts)?
|
||||
.unwrap()
|
||||
.into_u64_vec()?;
|
||||
let rows_per_strip = tag_reader
|
||||
.find_tag(Tag::RowsPerStrip)?
|
||||
.map(Value::into_u32)
|
||||
.transpose()?
|
||||
.unwrap_or(height);
|
||||
strip_decoder = Some(StripDecodeState { rows_per_strip });
|
||||
tile_attributes = None;
|
||||
|
||||
if chunk_offsets.len() != chunk_bytes.len() {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
));
|
||||
}
|
||||
}
|
||||
(false, false, true, true) => {
|
||||
chunk_type = ChunkType::Tile;
|
||||
|
||||
let tile_width =
|
||||
usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?;
|
||||
let tile_length =
|
||||
usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?;
|
||||
|
||||
if tile_width == 0 {
|
||||
return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into());
|
||||
} else if tile_length == 0 {
|
||||
return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into());
|
||||
}
|
||||
|
||||
strip_decoder = None;
|
||||
tile_attributes = Some(TileAttributes {
|
||||
image_width: usize::try_from(width)?,
|
||||
image_height: usize::try_from(height)?,
|
||||
samples_per_pixel: bits_per_sample.len(),
|
||||
tile_width,
|
||||
tile_length,
|
||||
});
|
||||
chunk_offsets = tag_reader
|
||||
.find_tag(Tag::TileOffsets)?
|
||||
.unwrap()
|
||||
.into_u64_vec()?;
|
||||
chunk_bytes = tag_reader
|
||||
.find_tag(Tag::TileByteCounts)?
|
||||
.unwrap()
|
||||
.into_u64_vec()?;
|
||||
|
||||
let tile = tile_attributes.as_ref().unwrap();
|
||||
if chunk_offsets.len() != chunk_bytes.len()
|
||||
|| chunk_offsets.len() != tile.tiles_down() * tile.tiles_across()
|
||||
{
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
));
|
||||
}
|
||||
}
|
||||
(_, _, _, _) => {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::StripTileTagConflict,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Image {
|
||||
ifd: Some(ifd),
|
||||
width,
|
||||
height,
|
||||
bits_per_sample,
|
||||
samples,
|
||||
sample_format,
|
||||
photometric_interpretation,
|
||||
compression_method,
|
||||
jpeg_tables,
|
||||
predictor,
|
||||
chunk_type,
|
||||
strip_decoder,
|
||||
tile_attributes,
|
||||
chunk_offsets,
|
||||
chunk_bytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
+790
-409
File diff suppressed because it is too large.
Load diff
+73
-213
@@ -1,8 +1,5 @@
|
||||
//! All IO functionality needed for TIFF decoding
|
||||
|
||||
use crate::bytecast;
|
||||
use crate::error::{TiffError, TiffResult};
|
||||
use miniz_oxide::inflate;
|
||||
use std::io::{self, Read, Seek};
|
||||
|
||||
/// Byte order of the TIFF file.
|
||||
@@ -30,22 +27,15 @@ pub trait EndianReader: Read {
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads an i8
|
||||
#[inline(always)]
|
||||
fn read_u16_into(&mut self, buffer: &mut [u16]) -> Result<(), io::Error> {
|
||||
self.read_exact(bytecast::u16_as_ne_mut_bytes(buffer))?;
|
||||
match self.byte_order() {
|
||||
ByteOrder::LittleEndian => {
|
||||
for n in buffer {
|
||||
*n = u16::from_le(*n);
|
||||
}
|
||||
}
|
||||
ByteOrder::BigEndian => {
|
||||
for n in buffer {
|
||||
*n = u16::from_be(*n);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
fn read_i8(&mut self) -> Result<i8, io::Error> {
|
||||
let mut n = [0u8; 1];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
ByteOrder::LittleEndian => i8::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i8::from_be_bytes(n),
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads an i16
|
||||
@@ -70,24 +60,6 @@ pub trait EndianReader: Read {
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_u32_into(&mut self, buffer: &mut [u32]) -> Result<(), io::Error> {
|
||||
self.read_exact(bytecast::u32_as_ne_mut_bytes(buffer))?;
|
||||
match self.byte_order() {
|
||||
ByteOrder::LittleEndian => {
|
||||
for n in buffer {
|
||||
*n = u32::from_le(*n);
|
||||
}
|
||||
}
|
||||
ByteOrder::BigEndian => {
|
||||
for n in buffer {
|
||||
*n = u32::from_be(*n);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads an i32
|
||||
#[inline(always)]
|
||||
fn read_i32(&mut self) -> Result<i32, io::Error> {
|
||||
@@ -110,22 +82,15 @@ pub trait EndianReader: Read {
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads an i64
|
||||
#[inline(always)]
|
||||
fn read_u64_into(&mut self, buffer: &mut [u64]) -> Result<(), io::Error> {
|
||||
self.read_exact(bytecast::u64_as_ne_mut_bytes(buffer))?;
|
||||
match self.byte_order() {
|
||||
ByteOrder::LittleEndian => {
|
||||
for n in buffer {
|
||||
*n = u64::from_le(*n);
|
||||
}
|
||||
}
|
||||
ByteOrder::BigEndian => {
|
||||
for n in buffer {
|
||||
*n = u64::from_be(*n);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
fn read_i64(&mut self) -> Result<i64, io::Error> {
|
||||
let mut n = [0u8; 8];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
ByteOrder::LittleEndian => i64::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i64::from_be_bytes(n),
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads an f32
|
||||
@@ -139,24 +104,6 @@ pub trait EndianReader: Read {
|
||||
}))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_f32_into(&mut self, buffer: &mut [f32]) -> Result<(), io::Error> {
|
||||
self.read_exact(bytecast::f32_as_ne_mut_bytes(buffer))?;
|
||||
match self.byte_order() {
|
||||
ByteOrder::LittleEndian => {
|
||||
for n in buffer {
|
||||
*n = f32::from_bits(u32::from_le(n.to_bits()));
|
||||
}
|
||||
}
|
||||
ByteOrder::BigEndian => {
|
||||
for n in buffer {
|
||||
*n = f32::from_bits(u32::from_be(n.to_bits()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads an f64
|
||||
#[inline(always)]
|
||||
fn read_f64(&mut self) -> Result<f64, io::Error> {
|
||||
@@ -167,24 +114,6 @@ pub trait EndianReader: Read {
|
||||
ByteOrder::BigEndian => u64::from_be_bytes(n),
|
||||
}))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn read_f64_into(&mut self, buffer: &mut [f64]) -> Result<(), io::Error> {
|
||||
self.read_exact(bytecast::f64_as_ne_mut_bytes(buffer))?;
|
||||
match self.byte_order() {
|
||||
ByteOrder::LittleEndian => {
|
||||
for n in buffer {
|
||||
*n = f64::from_bits(u64::from_le(n.to_bits()));
|
||||
}
|
||||
}
|
||||
ByteOrder::BigEndian => {
|
||||
for n in buffer {
|
||||
*n = f64::from_bits(u64::from_be(n.to_bits()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
@@ -195,51 +124,7 @@ pub trait EndianReader: Read {
|
||||
/// ## Deflate Reader
|
||||
///
|
||||
|
||||
/// Reader that decompresses DEFLATE streams
|
||||
pub struct DeflateReader {
|
||||
buffer: io::Cursor<Vec<u8>>,
|
||||
byte_order: ByteOrder,
|
||||
}
|
||||
|
||||
impl DeflateReader {
|
||||
pub fn new<R: Read + Seek>(
|
||||
reader: &mut SmartReader<R>,
|
||||
max_uncompressed_length: usize,
|
||||
) -> TiffResult<(usize, Self)> {
|
||||
let byte_order = reader.byte_order;
|
||||
let mut compressed = Vec::new();
|
||||
reader.read_to_end(&mut compressed)?;
|
||||
|
||||
// TODO: Implement streaming compression, and remove this (temporary) and somewhat
|
||||
// misleading workaround.
|
||||
if compressed.len() > max_uncompressed_length {
|
||||
return Err(TiffError::LimitsExceeded);
|
||||
}
|
||||
|
||||
let uncompressed =
|
||||
inflate::decompress_to_vec_zlib(&compressed).map_err(TiffError::from_inflate_status)?;
|
||||
|
||||
Ok((
|
||||
uncompressed.len(),
|
||||
Self {
|
||||
byte_order,
|
||||
buffer: io::Cursor::new(uncompressed),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for DeflateReader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.buffer.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl EndianReader for DeflateReader {
|
||||
fn byte_order(&self) -> ByteOrder {
|
||||
self.byte_order
|
||||
}
|
||||
}
|
||||
pub type DeflateReader<'r, R> = flate2::read::ZlibDecoder<&'r mut SmartReader<R>>;
|
||||
|
||||
///
|
||||
/// ## LZW Reader
|
||||
@@ -248,7 +133,6 @@ impl EndianReader for DeflateReader {
|
||||
/// Reader that decompresses LZW streams
|
||||
pub struct LZWReader {
|
||||
buffer: io::Cursor<Vec<u8>>,
|
||||
byte_order: ByteOrder,
|
||||
}
|
||||
|
||||
impl LZWReader {
|
||||
@@ -261,25 +145,21 @@ impl LZWReader {
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let order = reader.byte_order;
|
||||
let mut compressed = vec![0; compressed_length as usize];
|
||||
reader.read_exact(&mut compressed[..])?;
|
||||
let mut uncompressed = Vec::with_capacity(max_uncompressed_length);
|
||||
let mut uncompressed = vec![0; max_uncompressed_length];
|
||||
let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
|
||||
|
||||
let mut bytes_read = 0;
|
||||
let mut bytes_written = 0;
|
||||
|
||||
while uncompressed.len() < max_uncompressed_length {
|
||||
let bytes_written = uncompressed.len();
|
||||
uncompressed.reserve(1 << 12);
|
||||
let buffer_space = uncompressed.capacity().min(max_uncompressed_length);
|
||||
uncompressed.resize(buffer_space, 0u8);
|
||||
|
||||
while bytes_written < max_uncompressed_length {
|
||||
let result = decoder.decode_bytes(
|
||||
&compressed[bytes_read..],
|
||||
&mut uncompressed[bytes_written..],
|
||||
);
|
||||
bytes_read += result.consumed_in;
|
||||
uncompressed.truncate(bytes_written + result.consumed_out);
|
||||
bytes_written += result.consumed_out;
|
||||
|
||||
match result.status {
|
||||
Ok(weezl::LzwStatus::Ok) => {}
|
||||
@@ -294,12 +174,11 @@ impl LZWReader {
|
||||
}
|
||||
}
|
||||
|
||||
let bytes = uncompressed.len();
|
||||
uncompressed.resize(bytes_written, 0);
|
||||
Ok((
|
||||
bytes,
|
||||
uncompressed.len(),
|
||||
LZWReader {
|
||||
buffer: io::Cursor::new(uncompressed),
|
||||
byte_order: order,
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -312,20 +191,12 @@ impl Read for LZWReader {
|
||||
}
|
||||
}
|
||||
|
||||
impl EndianReader for LZWReader {
|
||||
#[inline(always)]
|
||||
fn byte_order(&self) -> ByteOrder {
|
||||
self.byte_order
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7))
|
||||
///
|
||||
|
||||
pub(crate) struct JpegReader {
|
||||
buffer: io::Cursor<Vec<u8>>,
|
||||
byte_order: ByteOrder,
|
||||
}
|
||||
impl JpegReader {
|
||||
/// Constructs new JpegReader wrapping a SmartReader.
|
||||
@@ -348,27 +219,35 @@ impl JpegReader {
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let order = reader.byte_order;
|
||||
|
||||
// Read jpeg image data
|
||||
let mut segment = vec![0; length as usize];
|
||||
reader.read_exact(&mut segment[..])?;
|
||||
|
||||
match jpeg_tables {
|
||||
Some(tables) => {
|
||||
assert!(
|
||||
tables.len() >= 2,
|
||||
"jpeg_tables, if given, must be at least 2 bytes long. Got {:?}",
|
||||
tables
|
||||
);
|
||||
|
||||
assert!(
|
||||
length >= 2,
|
||||
"if jpeg_tables is given, length must be at least 2 bytes long, got {}",
|
||||
length
|
||||
);
|
||||
|
||||
let mut jpeg_data = tables.clone();
|
||||
let truncated_length = jpeg_data.len() - 2;
|
||||
jpeg_data.truncate(truncated_length);
|
||||
jpeg_data.extend_from_slice(&mut segment[2..]);
|
||||
jpeg_data.extend_from_slice(&segment[2..]);
|
||||
|
||||
Ok(JpegReader {
|
||||
buffer: io::Cursor::new(jpeg_data),
|
||||
byte_order: order,
|
||||
})
|
||||
}
|
||||
None => Ok(JpegReader {
|
||||
buffer: io::Cursor::new(segment),
|
||||
byte_order: order,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -381,13 +260,6 @@ impl Read for JpegReader {
|
||||
}
|
||||
}
|
||||
|
||||
impl EndianReader for JpegReader {
|
||||
#[inline(always)]
|
||||
fn byte_order(&self) -> ByteOrder {
|
||||
self.byte_order
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// ## PackBits Reader
|
||||
///
|
||||
@@ -395,58 +267,46 @@ impl EndianReader for JpegReader {
|
||||
/// Reader that unpacks Apple's `PackBits` format
|
||||
pub struct PackBitsReader {
|
||||
buffer: io::Cursor<Vec<u8>>,
|
||||
byte_order: ByteOrder,
|
||||
}
|
||||
|
||||
impl PackBitsReader {
|
||||
/// Wraps a reader
|
||||
pub fn new<R: Read + Seek>(
|
||||
mut reader: R,
|
||||
byte_order: ByteOrder,
|
||||
length: usize,
|
||||
) -> io::Result<(usize, PackBitsReader)> {
|
||||
let mut buffer = Vec::new();
|
||||
let mut read: usize = 0;
|
||||
while read < length {
|
||||
let lread = read_packbits_run(&mut reader, &mut buffer)?;
|
||||
if lread == 0 {
|
||||
return Err(io::ErrorKind::UnexpectedEof.into());
|
||||
}
|
||||
read += lread;
|
||||
}
|
||||
Ok((
|
||||
buffer.len(),
|
||||
PackBitsReader {
|
||||
buffer: io::Cursor::new(buffer),
|
||||
byte_order,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
let mut header: [u8; 1] = [0];
|
||||
let mut data: [u8; 1] = [0];
|
||||
|
||||
fn read_packbits_run<R: Read + Seek>(reader: &mut R, buffer: &mut Vec<u8>) -> io::Result<usize> {
|
||||
let mut header: [u8; 1] = [0];
|
||||
let mut bytes_read = 0;
|
||||
while bytes_read < length {
|
||||
reader.read_exact(&mut header)?;
|
||||
bytes_read += 1;
|
||||
|
||||
let bytes = reader.read(&mut header)?;
|
||||
|
||||
match bytes {
|
||||
0 => Ok(0),
|
||||
_ => match header[0] as i8 {
|
||||
-128 => Ok(1),
|
||||
h if h >= -127 && h <= -1 => {
|
||||
let h = header[0] as i8;
|
||||
if h >= -127 && h <= -1 {
|
||||
let new_len = buffer.len() + (1 - h as isize) as usize;
|
||||
reader.read_exact(&mut header)?;
|
||||
buffer.resize(new_len, header[0]);
|
||||
Ok(2)
|
||||
}
|
||||
h => {
|
||||
reader.read_exact(&mut data)?;
|
||||
buffer.resize(new_len, data[0]);
|
||||
bytes_read += 1;
|
||||
} else if h >= 0 {
|
||||
let num_vals = h as usize + 1;
|
||||
let start = buffer.len();
|
||||
buffer.resize(start + num_vals, 0);
|
||||
reader.read_exact(&mut buffer[start..])?;
|
||||
Ok(num_vals + 1)
|
||||
bytes_read += num_vals
|
||||
} else {
|
||||
// h = -128 is a no-op.
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Ok((
|
||||
buffer.len(),
|
||||
PackBitsReader {
|
||||
buffer: io::Cursor::new(buffer),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,13 +317,6 @@ impl Read for PackBitsReader {
|
||||
}
|
||||
}
|
||||
|
||||
impl EndianReader for PackBitsReader {
|
||||
#[inline(always)]
|
||||
fn byte_order(&self) -> ByteOrder {
|
||||
self.byte_order
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// ## SmartReader Reader
|
||||
///
|
||||
@@ -472,7 +325,7 @@ impl EndianReader for PackBitsReader {
|
||||
#[derive(Debug)]
|
||||
pub struct SmartReader<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
R: Read,
|
||||
{
|
||||
reader: R,
|
||||
pub byte_order: ByteOrder,
|
||||
@@ -480,17 +333,25 @@ where
|
||||
|
||||
impl<R> SmartReader<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
R: Read,
|
||||
{
|
||||
/// Wraps a reader
|
||||
pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> {
|
||||
SmartReader { reader, byte_order }
|
||||
}
|
||||
pub fn into_inner(self) -> R {
|
||||
self.reader
|
||||
}
|
||||
}
|
||||
impl<R: Read + Seek> SmartReader<R> {
|
||||
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
|
||||
self.seek(io::SeekFrom::Start(offset)).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> EndianReader for SmartReader<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
R: Read,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn byte_order(&self) -> ByteOrder {
|
||||
@@ -498,7 +359,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> Read for SmartReader<R> {
|
||||
impl<R: Read> Read for SmartReader<R> {
|
||||
#[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.reader.read(buf)
|
||||
@@ -525,8 +386,7 @@ mod test {
|
||||
let encoded_len = encoded.len();
|
||||
|
||||
let buff = io::Cursor::new(encoded);
|
||||
let (_, mut decoder) =
|
||||
PackBitsReader::new(buff, ByteOrder::LittleEndian, encoded_len).unwrap();
|
||||
let (_, mut decoder) = PackBitsReader::new(buff, encoded_len).unwrap();
|
||||
|
||||
let mut decoded = Vec::new();
|
||||
decoder.read_to_end(&mut decoded).unwrap();
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
use crate::tags::Tag;
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use super::ifd::{Directory, Value};
|
||||
use super::stream::SmartReader;
|
||||
use super::Limits;
|
||||
|
||||
pub(crate) struct TagReader<'a, R: Read + Seek> {
|
||||
pub reader: &'a mut SmartReader<R>,
|
||||
pub ifd: &'a 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>> {
|
||||
Ok(match self.ifd.get(&tag) {
|
||||
Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?),
|
||||
None => None,
|
||||
})
|
||||
}
|
||||
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
|
||||
match self.find_tag(tag)? {
|
||||
Some(val) => Ok(val),
|
||||
None => Err(TiffError::FormatError(
|
||||
TiffFormatError::RequiredTagNotFound(tag),
|
||||
)),
|
||||
}
|
||||
}
|
||||
pub fn find_tag_uint_vec<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<Vec<T>>> {
|
||||
self.find_tag(tag)?
|
||||
.map(|v| v.into_u64_vec())
|
||||
.transpose()?
|
||||
.map(|v| {
|
||||
v.into_iter()
|
||||
.map(|u| {
|
||||
T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,14 @@ impl ColorType for Gray8 {
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
}
|
||||
|
||||
pub struct GrayI8;
|
||||
impl ColorType for GrayI8 {
|
||||
type Inner = i8;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
}
|
||||
|
||||
pub struct Gray16;
|
||||
impl ColorType for Gray16 {
|
||||
type Inner = u16;
|
||||
@@ -28,6 +36,14 @@ impl ColorType for Gray16 {
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
}
|
||||
|
||||
pub struct GrayI16;
|
||||
impl ColorType for GrayI16 {
|
||||
type Inner = i16;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
}
|
||||
|
||||
pub struct Gray32;
|
||||
impl ColorType for Gray32 {
|
||||
type Inner = u32;
|
||||
@@ -36,6 +52,14 @@ impl ColorType for Gray32 {
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
}
|
||||
|
||||
pub struct GrayI32;
|
||||
impl ColorType for GrayI32 {
|
||||
type Inner = i32;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
}
|
||||
|
||||
pub struct Gray32Float;
|
||||
impl ColorType for Gray32Float {
|
||||
type Inner = f32;
|
||||
@@ -52,6 +76,14 @@ impl ColorType for Gray64 {
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
}
|
||||
|
||||
pub struct GrayI64;
|
||||
impl ColorType for GrayI64 {
|
||||
type Inner = i64;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
}
|
||||
|
||||
pub struct Gray64Float;
|
||||
impl ColorType for Gray64Float {
|
||||
type Inner = f64;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||
use flate2::{write::ZlibEncoder, Compression as FlateCompression};
|
||||
use std::io::Write;
|
||||
|
||||
/// The Deflate algorithm used to compress image data in TIFF files.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Deflate {
|
||||
level: FlateCompression,
|
||||
}
|
||||
|
||||
/// The level of compression used by the Deflate algorithm.
|
||||
/// It allows trading compression ratio for compression speed.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
pub enum DeflateLevel {
|
||||
/// The fastest possible compression mode.
|
||||
Fast = 1,
|
||||
/// The conserative choice between speed and ratio.
|
||||
Balanced = 6,
|
||||
/// The best compression available with Deflate.
|
||||
Best = 9,
|
||||
}
|
||||
|
||||
impl Default for DeflateLevel {
|
||||
fn default() -> Self {
|
||||
DeflateLevel::Balanced
|
||||
}
|
||||
}
|
||||
|
||||
impl Deflate {
|
||||
/// Create a new deflate compressor with a specific level of compression.
|
||||
pub fn with_level(level: DeflateLevel) -> Self {
|
||||
Self {
|
||||
level: FlateCompression::new(level as u32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Deflate {
|
||||
fn default() -> Self {
|
||||
Self::with_level(DeflateLevel::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Compression for Deflate {
|
||||
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::Deflate;
|
||||
|
||||
fn get_algorithm(&self) -> Compressor {
|
||||
Compressor::Deflate(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl CompressionAlgorithm for Deflate {
|
||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||
let mut encoder = ZlibEncoder::new(writer, self.level);
|
||||
encoder.write_all(&bytes)?;
|
||||
encoder.try_finish()?;
|
||||
Ok(encoder.total_out())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::encoder::compression::tests::TEST_DATA;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_deflate() {
|
||||
const EXPECTED_COMPRESSED_DATA: [u8; 64] = [
|
||||
0x78, 0x9C, 0x15, 0xC7, 0xD1, 0x0D, 0x80, 0x20, 0x0C, 0x04, 0xD0, 0x55, 0x6E, 0x02,
|
||||
0xA7, 0x71, 0x81, 0xA6, 0x41, 0xDA, 0x28, 0xD4, 0xF4, 0xD0, 0xF9, 0x81, 0xE4, 0xFD,
|
||||
0xBC, 0xD3, 0x9C, 0x58, 0x04, 0x1C, 0xE9, 0xBD, 0xE2, 0x8A, 0x84, 0x5A, 0xD1, 0x7B,
|
||||
0xE7, 0x97, 0xF4, 0xF8, 0x08, 0x8D, 0xF6, 0x66, 0x21, 0x3D, 0x3A, 0xE4, 0xA9, 0x91,
|
||||
0x3E, 0xAC, 0xF1, 0x98, 0xB9, 0x70, 0x17, 0x13,
|
||||
];
|
||||
|
||||
let mut compressed_data = Vec::<u8>::new();
|
||||
let mut writer = Cursor::new(&mut compressed_data);
|
||||
Deflate::default().write_to(&mut writer, TEST_DATA).unwrap();
|
||||
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||
use std::io::Write;
|
||||
use weezl::encode::Encoder as LZWEncoder;
|
||||
|
||||
/// The LZW algorithm used to compress image data in TIFF files.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Lzw;
|
||||
|
||||
impl Compression for Lzw {
|
||||
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::LZW;
|
||||
|
||||
fn get_algorithm(&self) -> Compressor {
|
||||
Compressor::Lzw(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl CompressionAlgorithm for Lzw {
|
||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||
let mut encoder = LZWEncoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
|
||||
let result = encoder.into_stream(writer).encode_all(bytes);
|
||||
let byte_count = result.bytes_written as u64;
|
||||
result.status.map(|_| byte_count)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::encoder::compression::tests::TEST_DATA;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_lzw() {
|
||||
const EXPECTED_COMPRESSED_DATA: [u8; 63] = [
|
||||
0x80, 0x15, 0x0D, 0x06, 0x93, 0x98, 0x82, 0x08, 0x20, 0x30, 0x88, 0x0E, 0x67, 0x43,
|
||||
0x91, 0xA4, 0xDC, 0x67, 0x10, 0x19, 0x8D, 0xE7, 0x21, 0x01, 0x8C, 0xD0, 0x65, 0x31,
|
||||
0x9A, 0xE1, 0xD1, 0x03, 0xB1, 0x86, 0x1A, 0x6F, 0x3A, 0xC1, 0x4C, 0x66, 0xF3, 0x69,
|
||||
0xC0, 0xE4, 0x65, 0x39, 0x9C, 0xCD, 0x26, 0xF3, 0x74, 0x20, 0xD8, 0x67, 0x89, 0x9A,
|
||||
0x4E, 0x86, 0x83, 0x69, 0xCC, 0x5D, 0x01,
|
||||
];
|
||||
|
||||
let mut compressed_data = Vec::<u8>::new();
|
||||
let mut writer = Cursor::new(&mut compressed_data);
|
||||
Lzw::default().write_to(&mut writer, TEST_DATA).unwrap();
|
||||
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use crate::tags::CompressionMethod;
|
||||
use std::io::{self, Write};
|
||||
|
||||
mod deflate;
|
||||
mod lzw;
|
||||
mod packbits;
|
||||
mod uncompressed;
|
||||
|
||||
pub use self::deflate::{Deflate, DeflateLevel};
|
||||
pub use self::lzw::Lzw;
|
||||
pub use self::packbits::Packbits;
|
||||
pub use self::uncompressed::Uncompressed;
|
||||
|
||||
/// An algorithm used for compression
|
||||
pub trait CompressionAlgorithm {
|
||||
/// The algorithm writes data directly into the writer.
|
||||
/// It returns the total number of bytes written.
|
||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error>;
|
||||
}
|
||||
|
||||
/// An algorithm used for compression with associated enums and optional configurations.
|
||||
pub trait Compression: CompressionAlgorithm {
|
||||
/// The corresponding tag to the algorithm.
|
||||
const COMPRESSION_METHOD: CompressionMethod;
|
||||
|
||||
/// Method to optain a type that can store each variant of comression algorithm.
|
||||
fn get_algorithm(&self) -> Compressor;
|
||||
}
|
||||
|
||||
/// An enum to store each compression algorithm.
|
||||
pub enum Compressor {
|
||||
Uncompressed(Uncompressed),
|
||||
Lzw(Lzw),
|
||||
Deflate(Deflate),
|
||||
Packbits(Packbits),
|
||||
}
|
||||
|
||||
impl Default for Compressor {
|
||||
/// The default compression strategy does not apply any compression.
|
||||
fn default() -> Self {
|
||||
Compressor::Uncompressed(Uncompressed::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl CompressionAlgorithm for Compressor {
|
||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||
match self {
|
||||
Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes),
|
||||
Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes),
|
||||
Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes),
|
||||
Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub const TEST_DATA: &'static [u8] =
|
||||
b"This is a string for checking various compression algorithms.";
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||
use std::io::{BufWriter, Error, ErrorKind, Write};
|
||||
|
||||
/// Compressor that uses the Packbits[^note] algorithm to compress bytes.
|
||||
///
|
||||
/// [^note]: PackBits is often ineffective on continuous tone images,
|
||||
/// including many grayscale images. In such cases, it is better
|
||||
/// to leave the image uncompressed.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Packbits;
|
||||
|
||||
impl Compression for Packbits {
|
||||
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::PackBits;
|
||||
|
||||
fn get_algorithm(&self) -> Compressor {
|
||||
Compressor::Packbits(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl CompressionAlgorithm for Packbits {
|
||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||
// Inspired by https://github.com/skirridsystems/packbits
|
||||
|
||||
const MIN_REPT: u8 = 3; // Minimum run to compress between differ blocks
|
||||
const MAX_BYTES: u8 = 128; // Maximum number of bytes that can be encoded in a header byte
|
||||
|
||||
// Encoding for header byte based on number of bytes represented.
|
||||
fn encode_diff(n: u8) -> u8 {
|
||||
n - 1
|
||||
}
|
||||
fn encode_rept(n: u8) -> u8 {
|
||||
let var = 256 - (n - 1) as u16;
|
||||
var as u8
|
||||
}
|
||||
|
||||
fn write_u8<W: Write>(writer: &mut W, byte: u8) -> Result<u64, Error> {
|
||||
writer.write(&[byte]).map(|byte_count| byte_count as u64)
|
||||
}
|
||||
|
||||
let mut bufwriter = BufWriter::new(writer);
|
||||
let mut bytes_written = 0u64; // The number of bytes written into the writer
|
||||
let mut offset: Option<u64> = None; // The index of the first byte written into the writer
|
||||
|
||||
let mut src_index: usize = 0; // Index of the current byte
|
||||
let mut src_count = bytes.len(); //The number of bytes remaining to be compressed
|
||||
|
||||
let mut in_run = false; // Indication whether counting of similar bytes is performed
|
||||
let mut run_index = 0u8; // Distance into pending bytes that a run starts
|
||||
|
||||
let mut bytes_pending = 0u8; // Bytes looked at but not yet output
|
||||
let mut pending_index = 0usize; // Index of the first pending byte
|
||||
|
||||
let mut curr_byte: u8; // Byte currently being considered
|
||||
let mut last_byte: u8; // Previous byte
|
||||
|
||||
// Need at least one byte to compress
|
||||
if src_count == 0 {
|
||||
return Err(Error::new(ErrorKind::WriteZero, "write zero"));
|
||||
}
|
||||
|
||||
// Prime compressor with first character.
|
||||
last_byte = bytes[src_index];
|
||||
src_index += 1;
|
||||
bytes_pending += 1;
|
||||
|
||||
while src_count - 1 != 0 {
|
||||
src_count -= 1;
|
||||
curr_byte = bytes[src_index];
|
||||
src_index += 1;
|
||||
bytes_pending += 1;
|
||||
|
||||
if in_run {
|
||||
if (curr_byte != last_byte) || (bytes_pending > MAX_BYTES) {
|
||||
offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending - 1))?);
|
||||
write_u8(&mut bufwriter, last_byte)?;
|
||||
bytes_written += 2;
|
||||
|
||||
bytes_pending = 1;
|
||||
pending_index = src_index - 1;
|
||||
run_index = 0;
|
||||
in_run = false;
|
||||
}
|
||||
} else {
|
||||
if bytes_pending > MAX_BYTES {
|
||||
// We have as much differing data as we can output in one chunk.
|
||||
// Output MAX_BYTES leaving one byte.
|
||||
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(MAX_BYTES))?);
|
||||
bufwriter.write(&bytes[pending_index..pending_index + MAX_BYTES as usize])?;
|
||||
bytes_written += 1 + MAX_BYTES as u64;
|
||||
|
||||
pending_index += MAX_BYTES as usize;
|
||||
bytes_pending -= MAX_BYTES;
|
||||
run_index = bytes_pending - 1; // A run could start here
|
||||
} else if curr_byte == last_byte {
|
||||
if (bytes_pending - run_index >= MIN_REPT) || (run_index == 0) {
|
||||
// This is a worthwhile run
|
||||
if run_index != 0 {
|
||||
// Flush differing data out of input buffer
|
||||
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(run_index))?);
|
||||
bufwriter
|
||||
.write(&bytes[pending_index..pending_index + run_index as usize])?;
|
||||
bytes_written += 1 + run_index as u64;
|
||||
}
|
||||
bytes_pending -= run_index; // Length of run
|
||||
in_run = true;
|
||||
}
|
||||
} else {
|
||||
run_index = bytes_pending - 1; // A run could start here
|
||||
}
|
||||
}
|
||||
last_byte = curr_byte;
|
||||
}
|
||||
|
||||
// Output the remainder
|
||||
if in_run {
|
||||
bytes_written += 2;
|
||||
offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending))?);
|
||||
write_u8(&mut bufwriter, last_byte)?;
|
||||
} else {
|
||||
bytes_written += 1 + bytes_pending as u64;
|
||||
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(bytes_pending))?);
|
||||
bufwriter.write(&bytes[pending_index..pending_index + bytes_pending as usize])?;
|
||||
}
|
||||
|
||||
bufwriter.flush()?;
|
||||
Ok(bytes_written)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::encoder::compression::tests::TEST_DATA;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_packbits_single_byte() {
|
||||
// compress single byte
|
||||
const UNCOMPRESSED_DATA: [u8; 1] = [0x3F];
|
||||
const EXPECTED_COMPRESSED_DATA: [u8; 2] = [0x00, 0x3F];
|
||||
|
||||
let mut compressed_data = Vec::<u8>::new();
|
||||
let mut writer = Cursor::new(&mut compressed_data);
|
||||
Packbits::default()
|
||||
.write_to(&mut writer, &UNCOMPRESSED_DATA)
|
||||
.unwrap();
|
||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_packbits_rept() {
|
||||
// compress buffer with repetitive sequence
|
||||
const UNCOMPRESSED_DATA: &'static [u8] =
|
||||
b"This strrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrring hangs.";
|
||||
const EXPECTED_COMPRESSED_DATA: &'static [u8] = b"\x06This st\xD1r\x09ing hangs.";
|
||||
|
||||
let mut compressed_data = Vec::<u8>::new();
|
||||
let mut writer = Cursor::new(&mut compressed_data);
|
||||
Packbits::default()
|
||||
.write_to(&mut writer, &UNCOMPRESSED_DATA)
|
||||
.unwrap();
|
||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_packbits_large_rept_nonrept() {
|
||||
// compress buffer with large repetitive and non-repetitive sequence
|
||||
let mut data = b"This st".to_vec();
|
||||
for _i in 0..158 {
|
||||
data.push(b'r');
|
||||
}
|
||||
data.extend_from_slice(b"ing hangs.");
|
||||
for i in 0..158 {
|
||||
data.push(i);
|
||||
}
|
||||
|
||||
const EXPECTED_COMPRESSED_DATA: [u8; 182] = [
|
||||
0x06, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x74, 0x81, 0x72, 0xE3, 0x72, 0x7F, 0x69,
|
||||
0x6E, 0x67, 0x20, 0x68, 0x61, 0x6E, 0x67, 0x73, 0x2E, 0x00, 0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
|
||||
0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
|
||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
|
||||
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
|
||||
0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
|
||||
0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
|
||||
0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
|
||||
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
|
||||
0x75, 0x27, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81,
|
||||
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
|
||||
];
|
||||
|
||||
let mut compressed_data = Vec::<u8>::new();
|
||||
let mut writer = Cursor::new(&mut compressed_data);
|
||||
Packbits::default()
|
||||
.write_to(&mut writer, data.as_slice())
|
||||
.unwrap();
|
||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_packbits() {
|
||||
// compress teststring
|
||||
const EXPECTED_COMPRESSED_DATA: &'static [u8] =
|
||||
b"\x3CThis is a string for checking various compression algorithms.";
|
||||
|
||||
let mut compressed_data = Vec::<u8>::new();
|
||||
let mut writer = Cursor::new(&mut compressed_data);
|
||||
Packbits::default()
|
||||
.write_to(&mut writer, &TEST_DATA)
|
||||
.unwrap();
|
||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||
use std::io::Write;
|
||||
|
||||
/// The default algorithm which does not compress at all.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Uncompressed;
|
||||
|
||||
impl Compression for Uncompressed {
|
||||
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::None;
|
||||
|
||||
fn get_algorithm(&self) -> Compressor {
|
||||
Compressor::Uncompressed(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl CompressionAlgorithm for Uncompressed {
|
||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||
writer.write(bytes).map(|byte_count| byte_count as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::encoder::compression::tests::TEST_DATA;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_no_compression() {
|
||||
let mut compressed_data = Vec::<u8>::new();
|
||||
let mut writer = Cursor::new(&mut compressed_data);
|
||||
Uncompressed::default()
|
||||
.write_to(&mut writer, TEST_DATA)
|
||||
.unwrap();
|
||||
assert_eq!(TEST_DATA, compressed_data);
|
||||
}
|
||||
}
|
||||
+301
-438
@@ -1,402 +1,33 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{Seek, Write};
|
||||
use std::{cmp, io, mem};
|
||||
pub use tiff_value::*;
|
||||
|
||||
use crate::bytecast;
|
||||
use crate::error::{TiffError, TiffFormatError, TiffResult};
|
||||
use crate::tags::{self, ResolutionUnit, Tag, Type};
|
||||
use std::{
|
||||
cmp,
|
||||
collections::BTreeMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
io::{self, Seek, Write},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
num::TryFromIntError,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::TiffResult,
|
||||
tags::{CompressionMethod, ResolutionUnit, Tag},
|
||||
};
|
||||
|
||||
pub mod colortype;
|
||||
pub mod compression;
|
||||
mod tiff_value;
|
||||
mod writer;
|
||||
|
||||
use self::colortype::*;
|
||||
use self::compression::*;
|
||||
use self::writer::*;
|
||||
|
||||
/// Type to represent tiff values of type `RATIONAL`
|
||||
#[derive(Clone)]
|
||||
pub struct Rational {
|
||||
pub n: u32,
|
||||
pub d: u32,
|
||||
}
|
||||
|
||||
/// Type to represent tiff values of type `SRATIONAL`
|
||||
pub struct SRational {
|
||||
pub n: i32,
|
||||
pub d: i32,
|
||||
}
|
||||
|
||||
/// Trait for types that can be encoded in a tiff file
|
||||
pub trait TiffValue {
|
||||
const BYTE_LEN: u32;
|
||||
const FIELD_TYPE: Type;
|
||||
fn count(&self) -> u32;
|
||||
fn bytes(&self) -> u32 {
|
||||
self.count() * Self::BYTE_LEN
|
||||
}
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()>;
|
||||
}
|
||||
|
||||
impl TiffValue for [u8] {
|
||||
const BYTE_LEN: u32 = 1;
|
||||
const FIELD_TYPE: Type = Type::BYTE;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_bytes(self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [i8] {
|
||||
const BYTE_LEN: u32 = 1;
|
||||
const FIELD_TYPE: Type = Type::SBYTE;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::i8_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [u16] {
|
||||
const BYTE_LEN: u32 = 2;
|
||||
const FIELD_TYPE: Type = Type::SHORT;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::u16_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [i16] {
|
||||
const BYTE_LEN: u32 = 2;
|
||||
const FIELD_TYPE: Type = Type::SSHORT;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::i16_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [u32] {
|
||||
const BYTE_LEN: u32 = 4;
|
||||
const FIELD_TYPE: Type = Type::LONG;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::u32_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [i32] {
|
||||
const BYTE_LEN: u32 = 4;
|
||||
const FIELD_TYPE: Type = Type::SLONG;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::i32_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [u64] {
|
||||
const BYTE_LEN: u32 = 8;
|
||||
const FIELD_TYPE: Type = Type::LONG8;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::u64_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [f32] {
|
||||
const BYTE_LEN: u32 = 4;
|
||||
const FIELD_TYPE: Type = Type::FLOAT;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
// We write using nativeedian so this sould be safe
|
||||
let slice = bytecast::f32_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [f64] {
|
||||
const BYTE_LEN: u32 = 8;
|
||||
const FIELD_TYPE: Type = Type::DOUBLE;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
// We write using nativeedian so this sould be safe
|
||||
let slice = bytecast::f64_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [Rational] {
|
||||
const BYTE_LEN: u32 = 8;
|
||||
const FIELD_TYPE: Type = Type::RATIONAL;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
for x in self {
|
||||
x.write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [SRational] {
|
||||
const BYTE_LEN: u32 = 8;
|
||||
const FIELD_TYPE: Type = Type::SRATIONAL;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
for x in self {
|
||||
x.write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u8 {
|
||||
const BYTE_LEN: u32 = 1;
|
||||
const FIELD_TYPE: Type = Type::BYTE;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u8(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i8 {
|
||||
const BYTE_LEN: u32 = 1;
|
||||
const FIELD_TYPE: Type = Type::SBYTE;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_i8(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u16 {
|
||||
const BYTE_LEN: u32 = 2;
|
||||
const FIELD_TYPE: Type = Type::SHORT;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u16(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i16 {
|
||||
const BYTE_LEN: u32 = 2;
|
||||
const FIELD_TYPE: Type = Type::SSHORT;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_i16(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u32 {
|
||||
const BYTE_LEN: u32 = 4;
|
||||
const FIELD_TYPE: Type = Type::LONG;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u32(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i32 {
|
||||
const BYTE_LEN: u32 = 4;
|
||||
const FIELD_TYPE: Type = Type::SLONG;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_i32(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u64 {
|
||||
const BYTE_LEN: u32 = 8;
|
||||
const FIELD_TYPE: Type = Type::LONG8;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u64(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for f32 {
|
||||
const BYTE_LEN: u32 = 4;
|
||||
const FIELD_TYPE: Type = Type::FLOAT;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_f32(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for f64 {
|
||||
const BYTE_LEN: u32 = 8;
|
||||
const FIELD_TYPE: Type = Type::DOUBLE;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_f64(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for Rational {
|
||||
const BYTE_LEN: u32 = 8;
|
||||
const FIELD_TYPE: Type = Type::RATIONAL;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u32(self.n)?;
|
||||
writer.write_u32(self.d)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for SRational {
|
||||
const BYTE_LEN: u32 = 8;
|
||||
const FIELD_TYPE: Type = Type::SRATIONAL;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_i32(self.n)?;
|
||||
writer.write_i32(self.d)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for str {
|
||||
const BYTE_LEN: u32 = 1;
|
||||
const FIELD_TYPE: Type = Type::ASCII;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
self.len() as u32 + 1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||
writer.write_bytes(self.as_bytes())?;
|
||||
writer.write_u8(0)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TiffError::FormatError(TiffFormatError::InvalidTag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
||||
const BYTE_LEN: u32 = T::BYTE_LEN;
|
||||
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
||||
|
||||
fn count(&self) -> u32 {
|
||||
(*self).count()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
(*self).write(writer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tiff encoder.
|
||||
/// Encoder for Tiff and BigTiff files.
|
||||
///
|
||||
/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder`
|
||||
/// to encode tiff ifd directories with images.
|
||||
/// to encode Tiff/BigTiff ifd directories with images.
|
||||
///
|
||||
/// See `DirectoryEncoder` and `ImageEncoder`.
|
||||
///
|
||||
@@ -408,43 +39,83 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
||||
/// # let image_data = vec![0; 100*100*3];
|
||||
/// use tiff::encoder::*;
|
||||
///
|
||||
/// // create a standard Tiff file
|
||||
/// let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
||||
///
|
||||
/// tiff.write_image::<colortype::RGB8>(100, 100, &image_data).unwrap();
|
||||
///
|
||||
/// // create a BigTiff file
|
||||
/// let mut bigtiff = TiffEncoder::new_big(&mut file).unwrap();
|
||||
/// bigtiff.write_image::<colortype::RGB8>(100, 100, &image_data).unwrap();
|
||||
///
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct TiffEncoder<W> {
|
||||
pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> {
|
||||
writer: TiffWriter<W>,
|
||||
kind: PhantomData<K>,
|
||||
}
|
||||
|
||||
/// Constructor functions to create standard Tiff files.
|
||||
impl<W: Write + Seek> TiffEncoder<W> {
|
||||
pub fn new(writer: W) -> TiffResult<TiffEncoder<W>> {
|
||||
/// Creates a new encoder for standard Tiff files.
|
||||
///
|
||||
/// To create BigTiff files, use [`new_big`][TiffEncoder::new_big] or
|
||||
/// [`new_generic`][TiffEncoder::new_generic].
|
||||
pub fn new(writer: W) -> TiffResult<TiffEncoder<W, TiffKindStandard>> {
|
||||
TiffEncoder::new_generic(writer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructor functions to create BigTiff files.
|
||||
impl<W: Write + Seek> TiffEncoder<W, TiffKindBig> {
|
||||
/// Creates a new encoder for BigTiff files.
|
||||
///
|
||||
/// To create standard Tiff files, use [`new`][TiffEncoder::new] or
|
||||
/// [`new_generic`][TiffEncoder::new_generic].
|
||||
pub fn new_big(writer: W) -> TiffResult<Self> {
|
||||
TiffEncoder::new_generic(writer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic functions that are available for both Tiff and BigTiff encoders.
|
||||
impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
/// Creates a new Tiff or BigTiff encoder, inferred from the return type.
|
||||
pub fn new_generic(writer: W) -> TiffResult<Self> {
|
||||
let mut encoder = TiffEncoder {
|
||||
writer: TiffWriter::new(writer),
|
||||
kind: PhantomData,
|
||||
};
|
||||
|
||||
write_tiff_header(&mut encoder.writer)?;
|
||||
// blank the IFD offset location
|
||||
encoder.writer.write_u32(0)?;
|
||||
K::write_header(&mut encoder.writer)?;
|
||||
|
||||
Ok(encoder)
|
||||
}
|
||||
|
||||
/// Create a `DirectoryEncoder` to encode an ifd directory.
|
||||
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W>> {
|
||||
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> {
|
||||
DirectoryEncoder::new(&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.
|
||||
pub fn new_image<C: ColorType>(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> TiffResult<ImageEncoder<W, C>> {
|
||||
) -> TiffResult<ImageEncoder<W, C, K, Uncompressed>> {
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
ImageEncoder::new(encoder, width, height)
|
||||
}
|
||||
|
||||
/// Create an [`ImageEncoder`] to encode an image one slice at a time.
|
||||
pub fn new_image_with_compression<C: ColorType, D: Compression>(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
compression: D,
|
||||
) -> TiffResult<ImageEncoder<W, C, K, D>> {
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
ImageEncoder::with_compression(encoder, width, height, compression)
|
||||
}
|
||||
|
||||
/// Convenience function to write an entire image from memory.
|
||||
pub fn write_image<C: ColorType>(
|
||||
&mut self,
|
||||
@@ -456,7 +127,24 @@ impl<W: Write + Seek> TiffEncoder<W> {
|
||||
[C::Inner]: TiffValue,
|
||||
{
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
let image: ImageEncoder<W, C> = ImageEncoder::new(encoder, width, height)?;
|
||||
let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?;
|
||||
image.write_data(data)
|
||||
}
|
||||
|
||||
/// Convenience function to write an entire image from memory with a given compression.
|
||||
pub fn write_image_with_compression<C: ColorType, D: Compression>(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
compression: D,
|
||||
data: &[C::Inner],
|
||||
) -> TiffResult<()>
|
||||
where
|
||||
[C::Inner]: TiffValue,
|
||||
{
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
let image: ImageEncoder<W, C, K, D> =
|
||||
ImageEncoder::with_compression(encoder, width, height, compression)?;
|
||||
image.write_data(data)
|
||||
}
|
||||
}
|
||||
@@ -465,19 +153,19 @@ impl<W: Write + Seek> TiffEncoder<W> {
|
||||
///
|
||||
/// You should call `finish` on this when you are finished with it.
|
||||
/// Encoding can silently fail while this is dropping.
|
||||
pub struct DirectoryEncoder<'a, W: 'a + Write + Seek> {
|
||||
pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> {
|
||||
writer: &'a mut TiffWriter<W>,
|
||||
dropped: bool,
|
||||
// We use BTreeMap to make sure tags are written in correct order
|
||||
ifd_pointer_pos: u64,
|
||||
ifd: BTreeMap<u16, (u16, u32, Vec<u8>)>,
|
||||
ifd: BTreeMap<u16, DirectoryEntry<K::OffsetType>>,
|
||||
}
|
||||
|
||||
impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
|
||||
fn new(writer: &'a mut TiffWriter<W>) -> TiffResult<DirectoryEncoder<'a, W>> {
|
||||
impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
fn new(writer: &'a mut TiffWriter<W>) -> TiffResult<Self> {
|
||||
// the previous word is the IFD offset position
|
||||
let ifd_pointer_pos = writer.offset() - mem::size_of::<u32>() as u64;
|
||||
writer.pad_word_boundary()?;
|
||||
let ifd_pointer_pos = writer.offset() - mem::size_of::<K::OffsetType>() as u64;
|
||||
writer.pad_word_boundary()?; // TODO: Do we need to adjust this for BigTiff?
|
||||
Ok(DirectoryEncoder {
|
||||
writer,
|
||||
dropped: false,
|
||||
@@ -488,8 +176,7 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
|
||||
|
||||
/// Write a single ifd tag.
|
||||
pub fn write_tag<T: TiffValue>(&mut self, tag: Tag, value: T) -> TiffResult<()> {
|
||||
let len = <T>::BYTE_LEN * value.count();
|
||||
let mut bytes = Vec::with_capacity(usize::try_from(len)?);
|
||||
let mut bytes = Vec::with_capacity(value.bytes());
|
||||
{
|
||||
let mut writer = TiffWriter::new(&mut bytes);
|
||||
value.write(&mut writer)?;
|
||||
@@ -497,7 +184,11 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
|
||||
|
||||
self.ifd.insert(
|
||||
tag.to_u16(),
|
||||
(<T>::FIELD_TYPE.to_u16(), value.count(), bytes),
|
||||
DirectoryEntry {
|
||||
data_type: <T>::FIELD_TYPE.to_u16(),
|
||||
count: value.count().try_into()?,
|
||||
data: bytes,
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -505,15 +196,21 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
|
||||
|
||||
fn write_directory(&mut self) -> TiffResult<u64> {
|
||||
// Start by writing out all values
|
||||
for &mut (_, _, ref mut bytes) in self.ifd.values_mut() {
|
||||
if bytes.len() > 4 {
|
||||
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, 0, 0, 0];
|
||||
*bytes = vec![0; data_bytes];
|
||||
let mut writer = TiffWriter::new(bytes as &mut [u8]);
|
||||
writer.write_u32(u32::try_from(offset)?)?;
|
||||
K::write_offset(&mut writer, offset)?;
|
||||
} else {
|
||||
while bytes.len() < 4 {
|
||||
while bytes.len() < data_bytes {
|
||||
bytes.push(0);
|
||||
}
|
||||
}
|
||||
@@ -521,11 +218,19 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
|
||||
|
||||
let offset = self.writer.offset();
|
||||
|
||||
self.writer.write_u16(u16::try_from(self.ifd.len())?)?;
|
||||
for (tag, &(ref field_type, ref count, ref offset)) in self.ifd.iter() {
|
||||
K::write_entry_count(&mut self.writer, self.ifd.len())?;
|
||||
for (
|
||||
tag,
|
||||
&DirectoryEntry {
|
||||
data_type: ref field_type,
|
||||
ref count,
|
||||
data: ref offset,
|
||||
},
|
||||
) in self.ifd.iter()
|
||||
{
|
||||
self.writer.write_u16(*tag)?;
|
||||
self.writer.write_u16(*field_type)?;
|
||||
self.writer.write_u32(*count)?;
|
||||
(*count).write(&mut self.writer)?;
|
||||
self.writer.write_bytes(offset)?;
|
||||
}
|
||||
|
||||
@@ -541,14 +246,19 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/// Provides the number of bytes written by the underlying TiffWriter during the last call.
|
||||
fn last_written(&self) -> u64 {
|
||||
self.writer.last_written()
|
||||
}
|
||||
|
||||
fn finish_internal(&mut self) -> TiffResult<()> {
|
||||
let ifd_pointer = self.write_directory()?;
|
||||
let curr_pos = self.writer.offset();
|
||||
|
||||
self.writer.goto_offset(self.ifd_pointer_pos)?;
|
||||
self.writer.write_u32(u32::try_from(ifd_pointer)?)?;
|
||||
K::write_offset(&mut self.writer, ifd_pointer)?;
|
||||
self.writer.goto_offset(curr_pos)?;
|
||||
self.writer.write_u32(0)?;
|
||||
K::write_offset(&mut self.writer, 0)?;
|
||||
|
||||
self.dropped = true;
|
||||
|
||||
@@ -561,7 +271,7 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> {
|
||||
impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> {
|
||||
fn drop(&mut self) {
|
||||
if !self.dropped {
|
||||
let _ = self.finish_internal();
|
||||
@@ -602,38 +312,60 @@ impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> {
|
||||
/// # }
|
||||
/// ```
|
||||
/// You can also call write_data function wich will encode by strip and finish
|
||||
pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType> {
|
||||
encoder: DirectoryEncoder<'a, W>,
|
||||
pub struct ImageEncoder<
|
||||
'a,
|
||||
W: 'a + Write + Seek,
|
||||
C: ColorType,
|
||||
K: TiffKind,
|
||||
D: Compression = Uncompressed,
|
||||
> {
|
||||
encoder: DirectoryEncoder<'a, W, K>,
|
||||
strip_idx: u64,
|
||||
strip_count: u64,
|
||||
row_samples: u64,
|
||||
width: u32,
|
||||
height: u32,
|
||||
rows_per_strip: u64,
|
||||
strip_offsets: Vec<u32>,
|
||||
strip_byte_count: Vec<u32>,
|
||||
strip_offsets: Vec<K::OffsetType>,
|
||||
strip_byte_count: Vec<K::OffsetType>,
|
||||
dropped: bool,
|
||||
compression: D,
|
||||
_phantom: ::std::marker::PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
|
||||
fn new(
|
||||
mut encoder: DirectoryEncoder<'a, W>,
|
||||
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
|
||||
ImageEncoder<'a, W, T, K, D>
|
||||
{
|
||||
fn new(encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self>
|
||||
where
|
||||
D: Default,
|
||||
{
|
||||
Self::with_compression(encoder, width, height, D::default())
|
||||
}
|
||||
|
||||
fn with_compression(
|
||||
mut encoder: DirectoryEncoder<'a, W, K>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> TiffResult<ImageEncoder<'a, W, T>> {
|
||||
compression: D,
|
||||
) -> TiffResult<Self> {
|
||||
let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
|
||||
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
|
||||
|
||||
// Limit the strip size to prevent potential memory and security issues.
|
||||
// Also keep the multiple strip handling 'oiled'
|
||||
let rows_per_strip = (1_000_000 + row_bytes - 1) / row_bytes;
|
||||
let rows_per_strip = {
|
||||
match D::COMPRESSION_METHOD {
|
||||
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
|
||||
_ => (1_000_000 + row_bytes - 1) / row_bytes,
|
||||
}
|
||||
};
|
||||
|
||||
let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
|
||||
|
||||
encoder.write_tag(Tag::ImageWidth, width)?;
|
||||
encoder.write_tag(Tag::ImageLength, height)?;
|
||||
encoder.write_tag(Tag::Compression, tags::CompressionMethod::None.to_u16())?;
|
||||
encoder.write_tag(Tag::Compression, D::COMPRESSION_METHOD.to_u16())?;
|
||||
|
||||
encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
|
||||
let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
|
||||
@@ -661,6 +393,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
|
||||
strip_offsets: Vec::new(),
|
||||
strip_byte_count: Vec::new(),
|
||||
dropped: false,
|
||||
compression: compression,
|
||||
_phantom: ::std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
@@ -683,7 +416,6 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
|
||||
where
|
||||
[T::Inner]: TiffValue,
|
||||
{
|
||||
// TODO: Compression
|
||||
let samples = self.next_strip_sample_count();
|
||||
if u64::try_from(value.len())? != samples {
|
||||
return Err(io::Error::new(
|
||||
@@ -693,9 +425,12 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
|
||||
.into());
|
||||
}
|
||||
|
||||
// Write the (possible compressed) data to the encoder.
|
||||
let offset = self.encoder.write_data(value)?;
|
||||
self.strip_offsets.push(u32::try_from(offset)?);
|
||||
self.strip_byte_count.push(value.bytes());
|
||||
let byte_count = self.encoder.last_written() as usize;
|
||||
|
||||
self.strip_offsets.push(K::convert_offset(offset)?);
|
||||
self.strip_byte_count.push(byte_count.try_into()?);
|
||||
|
||||
self.strip_idx += 1;
|
||||
Ok(())
|
||||
@@ -721,12 +456,19 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
self.encoder
|
||||
.writer
|
||||
.set_compression(self.compression.get_algorithm());
|
||||
|
||||
let mut idx = 0;
|
||||
while self.next_strip_sample_count() > 0 {
|
||||
let sample_count = usize::try_from(self.next_strip_sample_count())?;
|
||||
self.write_strip(&data[idx..idx + sample_count])?;
|
||||
idx += sample_count;
|
||||
}
|
||||
|
||||
self.encoder.writer.reset_compression();
|
||||
self.finish()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -783,16 +525,18 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
|
||||
|
||||
fn finish_internal(&mut self) -> TiffResult<()> {
|
||||
self.encoder
|
||||
.write_tag(Tag::StripOffsets, &*self.strip_offsets)?;
|
||||
self.encoder
|
||||
.write_tag(Tag::StripByteCounts, &*self.strip_byte_count)?;
|
||||
.write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?;
|
||||
self.encoder.write_tag(
|
||||
Tag::StripByteCounts,
|
||||
K::convert_slice(&self.strip_byte_count),
|
||||
)?;
|
||||
self.dropped = true;
|
||||
|
||||
self.encoder.finish_internal()
|
||||
}
|
||||
|
||||
/// Get a reference of the underlying `DirectoryEncoder`
|
||||
pub fn encoder(&mut self) -> &mut DirectoryEncoder<'a, W> {
|
||||
pub fn encoder(&mut self) -> &mut DirectoryEncoder<'a, W, K> {
|
||||
&mut self.encoder
|
||||
}
|
||||
|
||||
@@ -802,10 +546,129 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, W: Write + Seek, C: ColorType> Drop for ImageEncoder<'a, W, C> {
|
||||
impl<'a, W: Write + Seek, C: ColorType, K: TiffKind, D: Compression> Drop
|
||||
for ImageEncoder<'a, W, C, K, D>
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if !self.dropped {
|
||||
let _ = self.finish_internal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DirectoryEntry<S> {
|
||||
data_type: u16,
|
||||
count: S,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Trait to abstract over Tiff/BigTiff differences.
|
||||
///
|
||||
/// Implemented for [`TiffKindStandard`] and [`TiffKindBig`].
|
||||
pub trait TiffKind {
|
||||
/// The type of offset fields, `u32` for normal Tiff, `u64` for BigTiff.
|
||||
type OffsetType: TryFrom<usize, Error = TryFromIntError> + Into<u64> + TiffValue;
|
||||
|
||||
/// Needed for the `convert_slice` method.
|
||||
type OffsetArrayType: ?Sized + TiffValue;
|
||||
|
||||
/// Write the (Big)Tiff header.
|
||||
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()>;
|
||||
|
||||
/// Convert a file offset to `Self::OffsetType`.
|
||||
///
|
||||
/// This returns an error for normal Tiff if the offset is larger than `u32::MAX`.
|
||||
fn convert_offset(offset: u64) -> TiffResult<Self::OffsetType>;
|
||||
|
||||
/// Write an offset value to the given writer.
|
||||
///
|
||||
/// Like `convert_offset`, this errors if `offset > u32::MAX` for normal Tiff.
|
||||
fn write_offset<W: Write>(writer: &mut TiffWriter<W>, offset: u64) -> TiffResult<()>;
|
||||
|
||||
/// Write the IFD entry count field with the given `count` value.
|
||||
///
|
||||
/// The entry count field is an `u16` for normal Tiff and `u64` for BigTiff. Errors
|
||||
/// if the given `usize` is larger than the representable values.
|
||||
fn write_entry_count<W: Write>(writer: &mut TiffWriter<W>, count: usize) -> TiffResult<()>;
|
||||
|
||||
/// Internal helper method for satisfying Rust's type checker.
|
||||
///
|
||||
/// The `TiffValue` trait is implemented for both primitive values (e.g. `u8`, `u32`) and
|
||||
/// slices of primitive values (e.g. `[u8]`, `[u32]`). However, this is not represented in
|
||||
/// the type system, so there is no guarantee that that for all `T: TiffValue` there is also
|
||||
/// an implementation of `TiffValue` for `[T]`. This method works around that problem by
|
||||
/// providing a conversion from `[T]` to some value that implements `TiffValue`, thereby
|
||||
/// making all slices of `OffsetType` usable with `write_tag` and similar methods.
|
||||
///
|
||||
/// Implementations of this trait should always set `OffsetArrayType` to `[OffsetType]`.
|
||||
fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType;
|
||||
}
|
||||
|
||||
/// Create a standard Tiff file.
|
||||
pub struct TiffKindStandard;
|
||||
|
||||
impl TiffKind for TiffKindStandard {
|
||||
type OffsetType = u32;
|
||||
type OffsetArrayType = [u32];
|
||||
|
||||
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
write_tiff_header(writer)?;
|
||||
// blank the IFD offset location
|
||||
writer.write_u32(0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_offset(offset: u64) -> TiffResult<Self::OffsetType> {
|
||||
Ok(Self::OffsetType::try_from(offset)?)
|
||||
}
|
||||
|
||||
fn write_offset<W: Write>(writer: &mut TiffWriter<W>, offset: u64) -> TiffResult<()> {
|
||||
writer.write_u32(u32::try_from(offset)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_entry_count<W: Write>(writer: &mut TiffWriter<W>, count: usize) -> TiffResult<()> {
|
||||
writer.write_u16(u16::try_from(count)?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType {
|
||||
slice
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a BigTiff file.
|
||||
pub struct TiffKindBig;
|
||||
|
||||
impl TiffKind for TiffKindBig {
|
||||
type OffsetType = u64;
|
||||
type OffsetArrayType = [u64];
|
||||
|
||||
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
write_bigtiff_header(writer)?;
|
||||
// blank the IFD offset location
|
||||
writer.write_u64(0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_offset(offset: u64) -> TiffResult<Self::OffsetType> {
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
fn write_offset<W: Write>(writer: &mut TiffWriter<W>, offset: u64) -> TiffResult<()> {
|
||||
writer.write_u64(offset)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_entry_count<W: Write>(writer: &mut TiffWriter<W>, count: usize) -> TiffResult<()> {
|
||||
writer.write_u64(u64::try_from(count)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType {
|
||||
slice
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
use std::{borrow::Cow, io::Write, slice::from_ref};
|
||||
|
||||
use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use super::writer::TiffWriter;
|
||||
|
||||
/// Trait for types that can be encoded in a tiff file
|
||||
pub trait TiffValue {
|
||||
const BYTE_LEN: u8;
|
||||
const FIELD_TYPE: Type;
|
||||
fn count(&self) -> usize;
|
||||
fn bytes(&self) -> usize {
|
||||
self.count() * usize::from(Self::BYTE_LEN)
|
||||
}
|
||||
|
||||
/// Access this value as an contiguous sequence of bytes.
|
||||
/// If their is no trivial representation, allocate it on the heap.
|
||||
fn data(&self) -> Cow<[u8]>;
|
||||
|
||||
/// Write this value to a TiffWriter.
|
||||
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
||||
/// The written bytes of any custom implementation MUST be the same as yielded by `self.data()`.
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_bytes(&self.data())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [u8] {
|
||||
const BYTE_LEN: u8 = 1;
|
||||
const FIELD_TYPE: Type = Type::BYTE;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [i8] {
|
||||
const BYTE_LEN: u8 = 1;
|
||||
const FIELD_TYPE: Type = Type::SBYTE;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [u16] {
|
||||
const BYTE_LEN: u8 = 2;
|
||||
const FIELD_TYPE: Type = Type::SHORT;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [i16] {
|
||||
const BYTE_LEN: u8 = 2;
|
||||
const FIELD_TYPE: Type = Type::SSHORT;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [u32] {
|
||||
const BYTE_LEN: u8 = 4;
|
||||
const FIELD_TYPE: Type = Type::LONG;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [i32] {
|
||||
const BYTE_LEN: u8 = 4;
|
||||
const FIELD_TYPE: Type = Type::SLONG;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [u64] {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::LONG8;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [i64] {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::SLONG8;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [f32] {
|
||||
const BYTE_LEN: u8 = 4;
|
||||
const FIELD_TYPE: Type = Type::FLOAT;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
// We write using native endian so this should be safe
|
||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [f64] {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::DOUBLE;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
// We write using native endian so this should be safe
|
||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u8 {
|
||||
const BYTE_LEN: u8 = 1;
|
||||
const FIELD_TYPE: Type = Type::BYTE;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u8(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(from_ref(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i8 {
|
||||
const BYTE_LEN: u8 = 1;
|
||||
const FIELD_TYPE: Type = Type::SBYTE;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_i8(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u16 {
|
||||
const BYTE_LEN: u8 = 2;
|
||||
const FIELD_TYPE: Type = Type::SHORT;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u16(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i16 {
|
||||
const BYTE_LEN: u8 = 2;
|
||||
const FIELD_TYPE: Type = Type::SSHORT;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_i16(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u32 {
|
||||
const BYTE_LEN: u8 = 4;
|
||||
const FIELD_TYPE: Type = Type::LONG;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u32(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i32 {
|
||||
const BYTE_LEN: u8 = 4;
|
||||
const FIELD_TYPE: Type = Type::SLONG;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_i32(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u64 {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::LONG8;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u64(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i64 {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::SLONG8;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_i64(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for f32 {
|
||||
const BYTE_LEN: u8 = 4;
|
||||
const FIELD_TYPE: Type = Type::FLOAT;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_f32(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for f64 {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::DOUBLE;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_f64(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for Ifd {
|
||||
const BYTE_LEN: u8 = 4;
|
||||
const FIELD_TYPE: Type = Type::IFD;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u32(self.0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for Ifd8 {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::IFD8;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u64(self.0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for Rational {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::RATIONAL;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_u32(self.n)?;
|
||||
writer.write_u32(self.d)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Owned({
|
||||
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
||||
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
||||
[first_dword, second_dword].concat()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for SRational {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::SRATIONAL;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_i32(self.n)?;
|
||||
writer.write_i32(self.d)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Owned({
|
||||
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
||||
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
||||
[first_dword, second_dword].concat()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for str {
|
||||
const BYTE_LEN: u8 = 1;
|
||||
const FIELD_TYPE: Type = Type::ASCII;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len() + 1
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||
writer.write_bytes(self.as_bytes())?;
|
||||
writer.write_u8(0)?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TiffError::FormatError(TiffFormatError::InvalidTag))
|
||||
}
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Owned({
|
||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||
let bytes: &[u8] = self.as_bytes();
|
||||
[bytes, &[0]].concat()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
||||
const BYTE_LEN: u8 = T::BYTE_LEN;
|
||||
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
(*self).count()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
(*self).write(writer)
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
T::data(self)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_tiff_value_for_contiguous_sequence {
|
||||
($inner_type:ty; $bytes:expr; $field_type:expr) => {
|
||||
impl $crate::encoder::TiffValue for [$inner_type] {
|
||||
const BYTE_LEN: u8 = $bytes;
|
||||
const FIELD_TYPE: Type = $field_type;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
for x in self {
|
||||
x.write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
||||
for x in self {
|
||||
buf.extend_from_slice(&x.data());
|
||||
}
|
||||
Cow::Owned(buf)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_tiff_value_for_contiguous_sequence!(Ifd; 4; Type::IFD);
|
||||
impl_tiff_value_for_contiguous_sequence!(Ifd8; 8; Type::IFD8);
|
||||
impl_tiff_value_for_contiguous_sequence!(Rational; 8; Type::RATIONAL);
|
||||
impl_tiff_value_for_contiguous_sequence!(SRational; 8; Type::SRATIONAL);
|
||||
|
||||
/// Type to represent tiff values of type `IFD`
|
||||
#[derive(Clone)]
|
||||
pub struct Ifd(pub u32);
|
||||
|
||||
/// Type to represent tiff values of type `IFD8`
|
||||
#[derive(Clone)]
|
||||
pub struct Ifd8(pub u64);
|
||||
|
||||
/// Type to represent tiff values of type `RATIONAL`
|
||||
#[derive(Clone)]
|
||||
pub struct Rational {
|
||||
pub n: u32,
|
||||
pub d: u32,
|
||||
}
|
||||
|
||||
/// Type to represent tiff values of type `SRATIONAL`
|
||||
#[derive(Clone)]
|
||||
pub struct SRational {
|
||||
pub n: i32,
|
||||
pub d: i32,
|
||||
}
|
||||
+93
-21
@@ -1,3 +1,4 @@
|
||||
use crate::encoder::compression::*;
|
||||
use crate::error::TiffResult;
|
||||
use std::io::{self, Seek, SeekFrom, Write};
|
||||
|
||||
@@ -14,83 +15,154 @@ pub fn write_tiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes a BigTiff header, excluding the IFD offset field.
|
||||
///
|
||||
/// Writes the byte order, version number, offset byte size, and zero constant fields. Does
|
||||
// _not_ write the offset to the first IFD, this should be done by the caller.
|
||||
pub fn write_bigtiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
#[cfg(target_endian = "little")]
|
||||
let boi: u8 = 0x49;
|
||||
#[cfg(not(target_endian = "little"))]
|
||||
let boi: u8 = 0x4d;
|
||||
|
||||
// byte order indication
|
||||
writer.writer.write_all(&[boi, boi])?;
|
||||
// version number
|
||||
writer.writer.write_all(&43u16.to_ne_bytes())?;
|
||||
// bytesize of offsets (pointer size)
|
||||
writer.writer.write_all(&8u16.to_ne_bytes())?;
|
||||
// always 0
|
||||
writer.writer.write_all(&0u16.to_ne_bytes())?;
|
||||
|
||||
// we wrote 8 bytes, so set the internal offset accordingly
|
||||
writer.offset += 8;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct TiffWriter<W> {
|
||||
writer: W,
|
||||
offset: u64,
|
||||
byte_count: u64,
|
||||
compressor: Compressor,
|
||||
}
|
||||
|
||||
impl<W: Write> TiffWriter<W> {
|
||||
pub fn new(writer: W) -> Self {
|
||||
Self { writer, offset: 0 }
|
||||
Self {
|
||||
writer,
|
||||
offset: 0,
|
||||
byte_count: 0,
|
||||
compressor: Compressor::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_compression(&mut self, compressor: Compressor) {
|
||||
self.compressor = compressor;
|
||||
}
|
||||
|
||||
pub fn reset_compression(&mut self) {
|
||||
self.compressor = Compressor::default();
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> u64 {
|
||||
self.offset
|
||||
}
|
||||
|
||||
pub fn last_written(&self) -> u64 {
|
||||
self.byte_count
|
||||
}
|
||||
|
||||
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> {
|
||||
self.writer.write_all(bytes)?;
|
||||
self.offset += bytes.len() as u64;
|
||||
self.byte_count = self.compressor.write_to(&mut self.writer, bytes)?;
|
||||
self.offset += self.byte_count;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> {
|
||||
self.writer.write_all(&n.to_ne_bytes())?;
|
||||
self.offset += 1;
|
||||
self.byte_count = self
|
||||
.compressor
|
||||
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||
self.offset += self.byte_count;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> {
|
||||
self.writer.write_all(&n.to_ne_bytes())?;
|
||||
self.offset += 1;
|
||||
self.byte_count = self
|
||||
.compressor
|
||||
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||
self.offset += self.byte_count;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> {
|
||||
self.writer.write_all(&n.to_ne_bytes())?;
|
||||
self.offset += 2;
|
||||
self.byte_count = self
|
||||
.compressor
|
||||
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||
self.offset += self.byte_count;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> {
|
||||
self.writer.write_all(&n.to_ne_bytes())?;
|
||||
self.offset += 2;
|
||||
self.byte_count = self
|
||||
.compressor
|
||||
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||
self.offset += self.byte_count;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> {
|
||||
self.writer.write_all(&n.to_ne_bytes())?;
|
||||
self.offset += 4;
|
||||
self.byte_count = self
|
||||
.compressor
|
||||
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||
self.offset += self.byte_count;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> {
|
||||
self.writer.write_all(&n.to_ne_bytes())?;
|
||||
self.offset += 4;
|
||||
self.byte_count = self
|
||||
.compressor
|
||||
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||
self.offset += self.byte_count;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> {
|
||||
self.writer.write_all(&n.to_ne_bytes())?;
|
||||
self.offset += 8;
|
||||
self.byte_count = self
|
||||
.compressor
|
||||
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||
self.offset += self.byte_count;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> {
|
||||
self.byte_count = self
|
||||
.compressor
|
||||
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||
self.offset += self.byte_count;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> {
|
||||
self.writer.write_all(&u32::to_ne_bytes(n.to_bits()))?;
|
||||
self.offset += 4;
|
||||
self.byte_count = self
|
||||
.compressor
|
||||
.write_to(&mut self.writer, &u32::to_ne_bytes(n.to_bits()))?;
|
||||
self.offset += self.byte_count;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> {
|
||||
self.writer.write_all(&u64::to_ne_bytes(n.to_bits()))?;
|
||||
self.offset += 8;
|
||||
self.byte_count = self
|
||||
.compressor
|
||||
.write_to(&mut self.writer, &u64::to_ne_bytes(n.to_bits()))?;
|
||||
self.offset += self.byte_count;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+52
-22
@@ -1,14 +1,16 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::str;
|
||||
use std::string;
|
||||
|
||||
use crate::decoder::ifd::Value;
|
||||
use crate::decoder::{ifd::Value, ChunkType};
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
|
||||
};
|
||||
use crate::ColorType;
|
||||
use miniz_oxide::inflate::TINFLStatus;
|
||||
|
||||
use crate::weezl::LzwError;
|
||||
|
||||
/// Tiff error kinds.
|
||||
#[derive(Debug)]
|
||||
@@ -28,6 +30,9 @@ pub enum TiffError {
|
||||
/// An integer conversion to or from a platform size failed, either due to
|
||||
/// limits of the platform size or limits of the format.
|
||||
IntSizeError,
|
||||
|
||||
/// The image does not support the requested operation
|
||||
UsageError(UsageError),
|
||||
}
|
||||
|
||||
/// The image is not formatted properly.
|
||||
@@ -58,9 +63,10 @@ pub enum TiffFormatError {
|
||||
ByteExpected(Value),
|
||||
UnsignedIntegerExpected(Value),
|
||||
SignedIntegerExpected(Value),
|
||||
InflateError(InflateError),
|
||||
Format(String),
|
||||
RequiredTagEmpty(Tag),
|
||||
StripTileTagConflict,
|
||||
CycleInOffsets,
|
||||
#[doc(hidden)]
|
||||
/// Do not match against this variant. It may get removed.
|
||||
__NonExhaustive,
|
||||
@@ -109,32 +115,15 @@ impl fmt::Display for TiffFormatError {
|
||||
SignedIntegerExpected(ref val) => {
|
||||
write!(fmt, "Expected signed integer, {:?} found.", val)
|
||||
}
|
||||
InflateError(_) => write!(fmt, "Failed to decode inflate data."),
|
||||
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
|
||||
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
|
||||
StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
|
||||
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
|
||||
__NonExhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decompression failed due to faulty compressed data.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct InflateError {
|
||||
status: TINFLStatus,
|
||||
}
|
||||
|
||||
impl InflateError {
|
||||
pub(crate) fn new(status: TINFLStatus) -> Self {
|
||||
Self { status }
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffError {
|
||||
pub(crate) fn from_inflate_status(status: TINFLStatus) -> Self {
|
||||
TiffError::FormatError(TiffFormatError::InflateError(InflateError::new(status)))
|
||||
}
|
||||
}
|
||||
|
||||
/// The Decoder does not support features required by the image.
|
||||
///
|
||||
/// This only captures known failures for which the standard either does not require support or an
|
||||
@@ -204,6 +193,29 @@ impl fmt::Display for TiffUnsupportedError {
|
||||
}
|
||||
}
|
||||
|
||||
/// User attempted to use the Decoder in a way that is incompatible with a specific image.
|
||||
///
|
||||
/// For example: attempting to read a tile from a stripped image.
|
||||
#[derive(Debug)]
|
||||
pub enum UsageError {
|
||||
InvalidChunkType(ChunkType, ChunkType),
|
||||
}
|
||||
|
||||
impl fmt::Display for UsageError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use self::UsageError::*;
|
||||
match *self {
|
||||
InvalidChunkType(expected, actual) => {
|
||||
write!(
|
||||
fmt,
|
||||
"Requested operation is only valid for images with chunk encoding of type: {:?}, got {:?}.",
|
||||
expected, actual
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TiffError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
@@ -217,6 +229,7 @@ impl fmt::Display for TiffError {
|
||||
TiffError::IoError(ref e) => e.fmt(fmt),
|
||||
TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"),
|
||||
TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"),
|
||||
TiffError::UsageError(ref e) => write!(fmt, "Usage error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,6 +242,7 @@ impl Error for TiffError {
|
||||
TiffError::IoError(..) => "IO error",
|
||||
TiffError::LimitsExceeded => "Decoder limits exceeded",
|
||||
TiffError::IntSizeError => "Platform or format size limits exceeded",
|
||||
TiffError::UsageError(..) => "Invalid usage",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +260,12 @@ impl From<io::Error> for TiffError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<str::Utf8Error> for TiffError {
|
||||
fn from(_err: str::Utf8Error) -> TiffError {
|
||||
TiffError::FormatError(TiffFormatError::InvalidTag)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<string::FromUtf8Error> for TiffError {
|
||||
fn from(_err: string::FromUtf8Error) -> TiffError {
|
||||
TiffError::FormatError(TiffFormatError::InvalidTag)
|
||||
@@ -270,5 +290,15 @@ impl From<std::num::TryFromIntError> for TiffError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LzwError> for TiffError {
|
||||
fn from(err: LzwError) -> TiffError {
|
||||
match err {
|
||||
LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from(
|
||||
"LZW compressed data corrupted",
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of an image decoding/encoding process
|
||||
pub type TiffResult<T> = Result<T, TiffError>;
|
||||
+1
-2
@@ -7,7 +7,6 @@
|
||||
//! * <https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
||||
|
||||
extern crate jpeg;
|
||||
extern crate miniz_oxide;
|
||||
extern crate weezl;
|
||||
|
||||
mod bytecast;
|
||||
@@ -16,7 +15,7 @@ pub mod encoder;
|
||||
mod error;
|
||||
pub mod tags;
|
||||
|
||||
pub use self::error::{InflateError, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
||||
pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||
|
||||
/// An enumeration over supported color types and their bit depths
|
||||
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
|
||||
|
||||
+23
-2
@@ -111,15 +111,18 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
||||
Threshholding = 263, // TODO add support
|
||||
XResolution = 282,
|
||||
YResolution = 283,
|
||||
// JPEG
|
||||
JPEGTables = 347,
|
||||
// Advanced tags
|
||||
Predictor = 317,
|
||||
TileWidth = 322,
|
||||
TileLength = 323,
|
||||
TileOffsets = 324,
|
||||
TileByteCounts = 325,
|
||||
// Data Sample Format
|
||||
SampleFormat = 339,
|
||||
SMinSampleValue = 340, // TODO add support
|
||||
SMaxSampleValue = 341, // TODO add support
|
||||
// JPEG
|
||||
JPEGTables = 347,
|
||||
// GeoTIFF
|
||||
ModelPixelScaleTag = 33550, // (SoftDesk)
|
||||
ModelTransformationTag = 34264, // (JPL Carto Group)
|
||||
@@ -134,20 +137,38 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
||||
tags! {
|
||||
/// The type of an IFD entry (a 2 byte field).
|
||||
pub enum Type(u16) {
|
||||
/// 8-bit unsigned integer
|
||||
BYTE = 1,
|
||||
/// 8-bit byte that contains a 7-bit ASCII code; the last byte must be zero
|
||||
ASCII = 2,
|
||||
/// 16-bit unsigned integer
|
||||
SHORT = 3,
|
||||
/// 32-bit unsigned integer
|
||||
LONG = 4,
|
||||
/// Fraction stored as two 32-bit unsigned integers
|
||||
RATIONAL = 5,
|
||||
/// 8-bit signed integer
|
||||
SBYTE = 6,
|
||||
/// 8-bit byte that may contain anything, depending on the field
|
||||
UNDEFINED = 7,
|
||||
/// 16-bit signed integer
|
||||
SSHORT = 8,
|
||||
/// 32-bit signed integer
|
||||
SLONG = 9,
|
||||
/// Fraction stored as two 32-bit signed integers
|
||||
SRATIONAL = 10,
|
||||
/// 32-bit IEEE floating point
|
||||
FLOAT = 11,
|
||||
/// 64-bit IEEE floating point
|
||||
DOUBLE = 12,
|
||||
/// 32-bit unsigned integer (offset)
|
||||
IFD = 13,
|
||||
/// BigTIFF 64-bit unsigned integer
|
||||
LONG8 = 16,
|
||||
/// BigTIFF 64-bit signed integer
|
||||
SLONG8 = 17,
|
||||
/// BigTIFF 64-bit unsigned integer (offset)
|
||||
IFD8 = 18,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@ macro_rules! test_image_sum {
|
||||
}
|
||||
|
||||
test_image_sum!(test_image_sum_u8, U8, u64);
|
||||
test_image_sum!(test_image_sum_i8, I8, i64);
|
||||
test_image_sum!(test_image_sum_u16, U16, u64);
|
||||
test_image_sum!(test_image_sum_i16, I16, i64);
|
||||
test_image_sum!(test_image_sum_u32, U32, u64);
|
||||
test_image_sum!(test_image_sum_u64, U64, u64);
|
||||
test_image_sum!(test_image_sum_f32, F32, f32);
|
||||
@@ -135,6 +137,26 @@ fn test_rgb_f32() {
|
||||
test_image_sum_f32("gradient-3c-32b-float.tiff", ColorType::RGB(32), 472.8405);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_int8() {
|
||||
test_image_sum_i8("int8.tif", ColorType::Gray(8), 3111)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_int8_rgb() {
|
||||
test_image_sum_i8("int8_rgb.tif", ColorType::RGB(8), -10344)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_int16() {
|
||||
test_image_sum_i16("int16.tif", ColorType::Gray(16), 354396);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_int16_rgb() {
|
||||
test_image_sum_i16("int16_rgb.tif", ColorType::RGB(16), 1063188);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string_tags() {
|
||||
// these files have null-terminated strings for their Software tag. One has extra bytes after
|
||||
@@ -193,3 +215,284 @@ fn issue_69() {
|
||||
//let img_res = decoder.read_image();
|
||||
//assert!(img_res.is_ok());
|
||||
//}
|
||||
|
||||
#[test]
|
||||
fn test_tiled_rgb_u8() {
|
||||
test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tiled_rect_rgb_u8() {
|
||||
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
|
||||
}
|
||||
|
||||
/* #[test]
|
||||
fn test_tiled_jpeg_rgb_u8() {
|
||||
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
|
||||
} */
|
||||
|
||||
#[test]
|
||||
fn test_tiled_oversize_gray_i8() {
|
||||
test_image_sum_i8("tiled-oversize-gray-i8.tif", ColorType::Gray(8), 1214996);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tiled_cmyk_i8() {
|
||||
test_image_sum_i8("tiled-cmyk-i8.tif", ColorType::CMYK(8), 1759101);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tiled_incremental() {
|
||||
let file = "tiled-rgb-u8.tif";
|
||||
let expected_type = ColorType::RGB(8);
|
||||
let sums = [
|
||||
188760, 195639, 108148, 81986, 665088, 366140, 705317, 423366, 172033, 324455, 244102,
|
||||
81853, 181258, 247971, 129486, 55600, 565625, 422102, 730888, 379271, 232142, 292549,
|
||||
244045, 86866, 188141, 115036, 150785, 84389, 353170, 459325, 719619, 329594, 278663,
|
||||
220474, 243048, 113563, 189152, 109684, 179391, 122188, 279651, 622093, 724682, 302459,
|
||||
268428, 204499, 224255, 124674, 170668, 121868, 192768, 183367, 378029, 585651, 657712,
|
||||
296790, 241444, 197083, 198429, 134869, 182318, 86034, 203655, 182338, 297255, 601284,
|
||||
633813, 242531, 228578, 206441, 193552, 125412, 181527, 165439, 202531, 159538, 268388,
|
||||
565790, 611382, 272967, 236497, 215154, 158881, 90806, 106114, 182342, 191824, 186138,
|
||||
215174, 393193, 701228, 198866, 227944, 193830, 166330, 49008, 55719, 122820, 197316,
|
||||
161969, 203152, 170986, 624427, 188605, 186187, 111064, 115192, 39538, 48626, 163929,
|
||||
144682, 135796, 194141, 154198, 584125, 180255, 153524, 121433, 132641, 35743, 47798,
|
||||
152343, 162874, 167664, 160175, 133038, 659882, 138339, 166470, 124173, 118929, 51317,
|
||||
45267, 155776, 161331, 161006, 130052, 137618, 337291, 106481, 161999, 127343, 87724,
|
||||
59540, 63907, 155677, 140668, 141523, 108061, 168657, 186482, 98599, 147614, 139963, 90444,
|
||||
56602, 92547, 125644, 134212, 126569, 144153, 179800, 174516, 133969, 129399, 117681,
|
||||
83305, 55075, 110737, 115108, 128572, 128911, 130922, 179986, 143288, 145884, 155856,
|
||||
96683, 94057, 56238, 79649, 71651, 70182, 75010, 77009, 98855, 78979, 74341, 83482, 53403,
|
||||
59842, 30305,
|
||||
];
|
||||
|
||||
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");
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
|
||||
let tiles = decoder.tile_count().unwrap();
|
||||
assert_eq!(tiles as usize, sums.len());
|
||||
|
||||
for tile in 0..tiles {
|
||||
match decoder.read_tile().unwrap() {
|
||||
DecodingResult::U8(res) => {
|
||||
let sum: u64 = res.into_iter().map(<u64>::from).sum();
|
||||
assert_eq!(sum, sums[tile as usize]);
|
||||
}
|
||||
_ => panic!("Wrong bit depth"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_div_zero() {
|
||||
use tiff::{TiffError, TiffFormatError};
|
||||
|
||||
let image = [
|
||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
||||
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 39, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 158, 0, 0, 251, 67, 1, 3, 0,
|
||||
1, 0, 0, 0, 40, 0, 0, 0, 66, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 178, 178, 178, 178,
|
||||
178, 178, 178,
|
||||
];
|
||||
|
||||
let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
|
||||
match err {
|
||||
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
||||
unexpected => panic!("Unexpected error {}", unexpected),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_too_many_value_bytes() {
|
||||
let image = [
|
||||
73, 73, 43, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 8, 0, 0, 0,
|
||||
23, 0, 12, 0, 0, 65, 4, 0, 1, 6, 0, 0, 1, 16, 0, 1, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0,
|
||||
0, 0, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 59, 73, 84, 186, 202, 83, 240, 66, 1, 53, 22, 56, 47,
|
||||
0, 0, 0, 0, 0, 0, 1, 222, 4, 0, 58, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 4, 0, 0, 100, 0,
|
||||
0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18,
|
||||
];
|
||||
|
||||
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
|
||||
match error {
|
||||
tiff::TiffError::LimitsExceeded => {}
|
||||
unexpected => panic!("Unexpected error {}", unexpected),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bad_strip_count() {
|
||||
let image = [
|
||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0,
|
||||
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 246, 16, 0, 0, 22, 1, 4, 0, 1,
|
||||
0, 0, 0, 40, 0, 251, 255, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 178, 178, 178, 178,
|
||||
178, 178, 178,
|
||||
];
|
||||
|
||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||
|
||||
let err = decoder.read_image().unwrap_err();
|
||||
|
||||
match err {
|
||||
tiff::TiffError::IntSizeError => {}
|
||||
unexpected => panic!("Unexpected error {}", unexpected),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzzer_testcase1() {
|
||||
let image = [
|
||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1,
|
||||
0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 17, 1, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0,
|
||||
22, 1, 4, 0, 1, 0, 0, 0, 44, 0, 0, 0, 23, 1, 4, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 1, 0, 178,
|
||||
178,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stripped_image_overflow() {
|
||||
let image = [
|
||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0,
|
||||
0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, 22,
|
||||
1, 4, 0, 1, 0, 0, 0, 44, 0, 248, 255, 23, 1, 4, 0, 1, 0, 0, 0, 178, 178, 178, 0, 1, 178,
|
||||
178, 178,
|
||||
];
|
||||
|
||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||
|
||||
let err = decoder.read_image().unwrap_err();
|
||||
|
||||
match err {
|
||||
tiff::TiffError::LimitsExceeded => {}
|
||||
unexpected => panic!("Unexpected error {}", unexpected),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oom() {
|
||||
let image = [
|
||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
||||
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0,
|
||||
17, 1, 4, 0, 1, 0, 0, 0, 3, 77, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
|
||||
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178,
|
||||
];
|
||||
|
||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||
|
||||
let err = decoder.read_image().unwrap_err();
|
||||
|
||||
match err {
|
||||
tiff::TiffError::LimitsExceeded => {}
|
||||
unexpected => panic!("Unexpected error {}", unexpected),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oom_2() {
|
||||
let image = [
|
||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
||||
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
|
||||
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13,
|
||||
];
|
||||
|
||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||
|
||||
let err = decoder.read_image().unwrap_err();
|
||||
|
||||
match err {
|
||||
tiff::TiffError::LimitsExceeded => {}
|
||||
unexpected => panic!("Unexpected error {}", unexpected),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzzer_testcase2() {
|
||||
let image = [
|
||||
73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0,
|
||||
0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0,
|
||||
73, 42, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 158, 0, 0, 251, 3, 1,
|
||||
3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 4, 61, 1, 18, 0, 1, 0, 0, 0, 202, 0, 0, 0, 17,
|
||||
1, 100, 0, 129, 0, 0, 0, 0, 0, 0, 0, 232, 254, 252, 255, 254, 255, 255, 255, 1, 29, 0, 0,
|
||||
22, 1, 3, 0, 1, 0, 0, 0, 16, 0, 0, 0, 23, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 123, 73, 254, 0,
|
||||
73,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_jpeg_tag_2() {
|
||||
use tiff::tags;
|
||||
use tiff::{TiffError, TiffFormatError};
|
||||
|
||||
let image = [
|
||||
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 242, 0, 1, 4, 0, 1, 0,
|
||||
0, 0, 0, 129, 16, 0, 1, 1, 4, 0, 1, 0, 0, 0, 214, 0, 0, 248, 253, 1, 3, 0, 1, 0, 0, 0, 64,
|
||||
0, 0, 0, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 64, 14, 1, 0,
|
||||
2, 0, 0, 148, 0, 206, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||
1, 0, 0, 0, 22, 1, 4, 0, 17, 0, 0, 201, 1, 0, 0, 0, 23, 1, 2, 0, 20, 0, 0, 0, 194, 0, 0, 0,
|
||||
91, 1, 7, 0, 5, 0, 0, 0, 64, 0, 0, 0, 237, 254, 65, 255, 255, 255, 255, 255, 1, 0, 0, 0,
|
||||
22, 1, 4, 0, 1, 0, 0, 0, 42, 0, 0, 0, 23, 1, 255, 255, 255, 255, 255, 36, 36, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0,
|
||||
];
|
||||
|
||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||
|
||||
let err = decoder.read_image().unwrap_err();
|
||||
|
||||
match err {
|
||||
TiffError::FormatError(TiffFormatError::InvalidTagValueType(tags::Tag::JPEGTables)) => {}
|
||||
unexpected => panic!("Unexpected error {}", unexpected),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzzer_testcase3() {
|
||||
let image = [
|
||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22,
|
||||
128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 3, 0, 1, 0, 0, 0, 17, 1, 0, 231, 22, 1, 1, 0, 1, 0, 0, 0, 130, 0, 0, 0, 23, 1, 4,
|
||||
0, 14, 0, 0, 0, 0, 0, 0, 0, 133, 133, 133, 77, 77, 77, 0, 0, 22, 128, 0, 255, 255, 255,
|
||||
255, 255,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timeout() {
|
||||
use tiff::{TiffError, TiffFormatError};
|
||||
|
||||
let image = [
|
||||
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 68, 0, 0, 0, 2, 0, 32, 254, 252, 0, 109,
|
||||
0, 129, 0, 0, 0, 32, 0, 58, 0, 1, 4, 0, 1, 0, 6, 0, 0, 0, 8, 0, 0, 1, 73, 73, 42, 0, 8, 0,
|
||||
0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 21, 0, 0, 0, 61, 1, 255, 128, 9, 0, 0, 8, 0, 1, 113, 2,
|
||||
3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 112, 0, 0, 36, 0, 0,
|
||||
0, 112, 56, 200, 0, 5, 0, 0, 64, 0, 0, 1, 0, 4, 0, 0, 0, 2, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0,
|
||||
0, 0, 4, 17, 1, 1, 0, 93, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 3, 6, 0, 231, 22, 1,
|
||||
1, 0, 1, 0, 0, 0, 2, 64, 118, 36, 23, 1, 1, 0, 43, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 8,
|
||||
0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32,
|
||||
];
|
||||
|
||||
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
|
||||
match error {
|
||||
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
||||
e => panic!("Unexpected error {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_rows_per_strip() {
|
||||
test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840);
|
||||
}
|
||||
+125
-5
@@ -1,7 +1,7 @@
|
||||
extern crate tiff;
|
||||
|
||||
use tiff::decoder::{ifd, Decoder, DecodingResult};
|
||||
use tiff::encoder::{colortype, SRational, TiffEncoder};
|
||||
use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder};
|
||||
use tiff::tags::Tag;
|
||||
use tiff::ColorType;
|
||||
|
||||
@@ -48,6 +48,92 @@ fn encode_decode() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_big() {
|
||||
let mut image_data = Vec::new();
|
||||
for x in 0..100 {
|
||||
for y in 0..100u8 {
|
||||
let val = x + y;
|
||||
image_data.push(val);
|
||||
image_data.push(val);
|
||||
image_data.push(val);
|
||||
}
|
||||
}
|
||||
let mut file = Cursor::new(Vec::new());
|
||||
{
|
||||
let mut tiff = TiffEncoder::new_big(&mut file).unwrap();
|
||||
|
||||
let mut image = tiff.new_image::<colortype::RGB8>(100, 100).unwrap();
|
||||
image
|
||||
.encoder()
|
||||
.write_tag(Tag::Artist, "Image-tiff")
|
||||
.unwrap();
|
||||
image.write_data(&image_data).unwrap();
|
||||
}
|
||||
{
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||
assert_eq!(
|
||||
decoder.get_tag(Tag::Artist).unwrap(),
|
||||
ifd::Value::Ascii("Image-tiff".into())
|
||||
);
|
||||
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
||||
assert_eq!(image_data, img_res);
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_ifd() {
|
||||
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();
|
||||
let encoder = image_encoder.encoder();
|
||||
|
||||
// Use the "reusable" tags section as per the TIFF6 spec
|
||||
encoder.write_tag(Tag::Unknown(65000), Ifd(42u32)).unwrap();
|
||||
encoder
|
||||
.write_tag(Tag::Unknown(65001), &[Ifd(100u32)][..])
|
||||
.unwrap();
|
||||
encoder
|
||||
.write_tag(Tag::Unknown(65002), &[Ifd(1u32), Ifd(2u32), Ifd(3u32)][..])
|
||||
.unwrap();
|
||||
|
||||
encoder.write_tag(Tag::Unknown(65010), Ifd8(43u64)).unwrap();
|
||||
encoder
|
||||
.write_tag(Tag::Unknown(65011), &[Ifd8(100u64)][..])
|
||||
.unwrap();
|
||||
encoder
|
||||
.write_tag(
|
||||
Tag::Unknown(65012),
|
||||
&[Ifd8(1u64), Ifd8(2u64), Ifd8(3u64)][..],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// Rewind the cursor for reading
|
||||
data.set_position(0);
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||
|
||||
assert_eq!(decoder.assert_tag_u32(65000), 42);
|
||||
assert_eq!(decoder.assert_tag_u32_vec(65000), [42]);
|
||||
assert_eq!(decoder.assert_tag_u32_vec(65001), [100]);
|
||||
assert_eq!(decoder.assert_tag_u32_vec(65002), [1, 2, 3]);
|
||||
|
||||
assert_eq!(decoder.assert_tag_u64(65010), 43);
|
||||
assert_eq!(decoder.assert_tag_u64_vec(65010), [43]);
|
||||
assert_eq!(decoder.assert_tag_u64_vec(65011), [100]);
|
||||
assert_eq!(decoder.assert_tag_u64_vec(65012), [1, 2, 3]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Test that attempting to encode when the input buffer is undersized returns
|
||||
/// an error rather than panicking.
|
||||
@@ -182,6 +268,10 @@ trait AssertDecode {
|
||||
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
|
||||
fn assert_tag_i32(&mut self, tag: u16) -> i32;
|
||||
fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec<i32>;
|
||||
fn assert_tag_u64(&mut self, tag: u16) -> u64;
|
||||
fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec<u64>;
|
||||
fn assert_tag_i64(&mut self, tag: u16) -> i64;
|
||||
fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec<i64>;
|
||||
}
|
||||
|
||||
impl<R: std::io::Read + std::io::Seek> AssertDecode for Decoder<R> {
|
||||
@@ -203,6 +293,24 @@ impl<R: std::io::Read + std::io::Seek> AssertDecode for Decoder<R> {
|
||||
.into_i32_vec()
|
||||
.unwrap()
|
||||
}
|
||||
fn assert_tag_u64(&mut self, tag: u16) -> u64 {
|
||||
self.get_tag(Tag::Unknown(tag)).unwrap().into_u64().unwrap()
|
||||
}
|
||||
fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec<u64> {
|
||||
self.get_tag(Tag::Unknown(tag))
|
||||
.unwrap()
|
||||
.into_u64_vec()
|
||||
.unwrap()
|
||||
}
|
||||
fn assert_tag_i64(&mut self, tag: u16) -> i64 {
|
||||
self.get_tag(Tag::Unknown(tag)).unwrap().into_i64().unwrap()
|
||||
}
|
||||
fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec<i64> {
|
||||
self.get_tag(Tag::Unknown(tag))
|
||||
.unwrap()
|
||||
.into_i64_vec()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -289,12 +397,20 @@ fn test_signed() {
|
||||
.write_tag(Tag::Unknown(65022), &[-1_i32, 2][..])
|
||||
.unwrap();
|
||||
|
||||
encoder.write_tag(Tag::Unknown(65030), -1_i64).unwrap();
|
||||
encoder
|
||||
.write_tag(Tag::Unknown(65030), make_srational(-1))
|
||||
.write_tag(Tag::Unknown(65031), &[-1_i64][..])
|
||||
.unwrap();
|
||||
encoder
|
||||
.write_tag(Tag::Unknown(65032), &[-1_i64, 2][..])
|
||||
.unwrap();
|
||||
|
||||
encoder
|
||||
.write_tag(Tag::Unknown(65040), make_srational(-1))
|
||||
.unwrap();
|
||||
encoder
|
||||
.write_tag(
|
||||
Tag::Unknown(65031),
|
||||
Tag::Unknown(65041),
|
||||
&[make_srational(-1), make_srational(2)][..],
|
||||
)
|
||||
.unwrap();
|
||||
@@ -321,8 +437,12 @@ fn test_signed() {
|
||||
assert_eq!(decoder.assert_tag_i32_vec(65021), [-1]);
|
||||
assert_eq!(decoder.assert_tag_i32_vec(65022), [-1, 2]);
|
||||
|
||||
assert_eq!(decoder.assert_tag_i32_vec(65030), [-1, 100]);
|
||||
assert_eq!(decoder.assert_tag_i32_vec(65031), [-1_i32, 100, 2, 100]);
|
||||
assert_eq!(decoder.assert_tag_i64(65030), -1);
|
||||
assert_eq!(decoder.assert_tag_i64_vec(65031), [-1]);
|
||||
assert_eq!(decoder.assert_tag_i64_vec(65032), [-1, 2]);
|
||||
|
||||
assert_eq!(decoder.assert_tag_i32_vec(65040), [-1, 100]);
|
||||
assert_eq!(decoder.assert_tag_i32_vec(65041), [-1_i32, 100, 2, 100]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
extern crate tiff;
|
||||
|
||||
use std::io::{Cursor, Seek, Write};
|
||||
use tiff::{
|
||||
decoder::{Decoder, DecodingResult},
|
||||
encoder::{
|
||||
colortype::{self, ColorType},
|
||||
compression::*,
|
||||
TiffEncoder, TiffValue,
|
||||
},
|
||||
};
|
||||
|
||||
trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>::Inner>> {
|
||||
const WIDTH: u32;
|
||||
const HEIGHT: u32;
|
||||
type Color: ColorType;
|
||||
|
||||
fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
|
||||
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
|
||||
|
||||
fn compress<C: Compression, W: Write + Seek>(
|
||||
&self,
|
||||
encoder: &mut TiffEncoder<W>,
|
||||
compression: C,
|
||||
) where
|
||||
[<Self::Color as ColorType>::Inner]: TiffValue,
|
||||
{
|
||||
let image = encoder
|
||||
.new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression)
|
||||
.unwrap();
|
||||
image.write_data(self.reference_data()).unwrap();
|
||||
}
|
||||
|
||||
fn generate() -> Self {
|
||||
assert_eq!(
|
||||
Self::Color::BITS_PER_SAMPLE.len(),
|
||||
NUM_CHANNELS,
|
||||
"Incompatible color type"
|
||||
);
|
||||
|
||||
let mut data = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * NUM_CHANNELS);
|
||||
for x in 0..Self::WIDTH {
|
||||
for y in 0..Self::HEIGHT {
|
||||
data.extend(IntoIterator::into_iter(Self::generate_pixel(x, y)));
|
||||
}
|
||||
}
|
||||
Self::from(data)
|
||||
}
|
||||
}
|
||||
|
||||
struct TestImageColor(Vec<u16>);
|
||||
|
||||
impl From<Vec<u16>> for TestImageColor {
|
||||
fn from(value: Vec<u16>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestImage<3> for TestImageColor {
|
||||
const WIDTH: u32 = 1;
|
||||
const HEIGHT: u32 = 7;
|
||||
type Color = colortype::RGB16;
|
||||
|
||||
fn reference_data(&self) -> &[u16] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 3] {
|
||||
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
|
||||
[val as <Self::Color as ColorType>::Inner; 3]
|
||||
}
|
||||
}
|
||||
|
||||
struct TestImageGrayscale(Vec<u8>);
|
||||
|
||||
impl From<Vec<u8>> for TestImageGrayscale {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestImage<1> for TestImageGrayscale {
|
||||
const WIDTH: u32 = 21;
|
||||
const HEIGHT: u32 = 10;
|
||||
type Color = colortype::Gray8;
|
||||
|
||||
fn reference_data(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 1] {
|
||||
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
|
||||
[val as <Self::Color as ColorType>::Inner]
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
|
||||
let mut data = Cursor::new(Vec::new());
|
||||
|
||||
let image_rgb = TestImageColor::generate();
|
||||
let image_grayscale = TestImageGrayscale::generate();
|
||||
|
||||
// Encode tiff with compression
|
||||
{
|
||||
// Create a multipage image with 2 images
|
||||
let mut encoder = TiffEncoder::new(&mut data).unwrap();
|
||||
image_rgb.compress(&mut encoder, compression.clone());
|
||||
image_grayscale.compress(&mut encoder, compression);
|
||||
}
|
||||
|
||||
// Decode tiff
|
||||
data.set_position(0);
|
||||
{
|
||||
let mut decoder = Decoder::new(data).unwrap();
|
||||
|
||||
// Check the RGB image
|
||||
assert_eq!(
|
||||
match decoder.read_image() {
|
||||
Ok(DecodingResult::U16(image_data)) => image_data,
|
||||
unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
|
||||
},
|
||||
image_rgb.reference_data()
|
||||
);
|
||||
|
||||
// Check the grayscale image
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(
|
||||
match decoder.read_image() {
|
||||
Ok(DecodingResult::U8(image_data)) => image_data,
|
||||
unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
|
||||
},
|
||||
image_grayscale.reference_data()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_without_compression() {
|
||||
encode_decode_with_compression(Uncompressed::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_with_lzw() {
|
||||
encode_decode_with_compression(Lzw::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_with_deflate() {
|
||||
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast));
|
||||
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced));
|
||||
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_with_packbits() {
|
||||
encode_decode_with_compression(Packbits::default());
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user