Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcfcecb2df | ||
|
|
0da9770916 | ||
|
|
c30ea911a2 | ||
|
|
da07c0f4f5 | ||
|
|
289fab2fcd | ||
|
|
21252d9d89 | ||
|
|
ffaccf2b52 | ||
|
|
4dea7c4c81 | ||
|
|
aa42fcb1bb | ||
|
|
1f7b25ef55 | ||
|
|
6c65e66440 | ||
|
|
a733ff13d9 | ||
|
|
b1cd459330 | ||
|
|
4f1c88e35f | ||
|
|
7307e96782 | ||
|
|
a08604b015 | ||
|
|
9333d99c92 | ||
|
|
65bc24d5d9 | ||
|
|
7fc75f194a | ||
|
|
7679be19c1 | ||
|
|
0e9f8e3b5b | ||
|
|
c900129f2c | ||
|
|
61234c0005 | ||
|
|
1416af1018 | ||
|
|
87d652ae5e | ||
|
|
b5a78fbec4 | ||
|
|
30640d50ff | ||
|
|
b87770225f | ||
|
|
d38b65eeb2 | ||
|
|
ee27553cf6 | ||
|
|
5a809a728e | ||
|
|
b0b99fc536 | ||
|
|
3bce92781c | ||
|
|
907bbee576 | ||
|
|
fc0dafcde2 | ||
|
|
945a02d1e1 | ||
|
|
ef3294d1fe | ||
|
|
0311402999 | ||
|
|
361578df00 | ||
|
|
bd23428f52 | ||
|
|
d32adf8ef4 | ||
|
|
2ec1ab7489 | ||
|
|
80e7c46151 | ||
|
|
5003803c97 | ||
|
|
7c4a6fae08 | ||
|
|
723ef6431a | ||
|
|
802949f6e1 | ||
|
|
5cadbffebf | ||
|
|
e1f4161e0b | ||
|
|
8c0c13f022 | ||
|
|
d5e3f4aa8a | ||
|
|
3c42a6e1f4 | ||
|
|
5e7702a874 | ||
|
|
8e5c5b44c5 | ||
|
|
8a6e93f66e | ||
|
|
6e398ce7e3 | ||
|
|
8bd02ac389 | ||
|
|
0447d81b07 | ||
|
|
54664d553d | ||
|
|
6bec36ab20 | ||
|
|
e57ca1a926 | ||
|
|
ca4da33dd7 | ||
|
|
6d4c4a49a1 | ||
|
|
d8520bde1f | ||
|
|
56c20c2c20 | ||
|
|
6efa89c249 | ||
|
|
68d677d1b3 | ||
|
|
72ca636625 | ||
|
|
1f501b5acb | ||
|
|
697d2877a1 | ||
|
|
805a1e5cef | ||
|
|
dc8311fb99 | ||
|
|
f25530adec | ||
|
|
dd8c9e4f2b | ||
|
|
8f34748d01 | ||
|
|
4ce3c2f41f | ||
|
|
f809d7d1bb | ||
|
|
902f945375 | ||
|
|
d437c2708b | ||
|
|
a133056287 | ||
|
|
ce00bb5a28 | ||
|
|
623a3d957d | ||
|
|
9e6f9771d3 | ||
|
|
b196d19238 | ||
|
|
436cfaf69f | ||
|
|
8fcf49deac | ||
|
|
2cf33d634d | ||
|
|
eeb79ee07a | ||
|
|
592405268e | ||
|
|
9f2508e37e | ||
|
|
4f46bacfda | ||
|
|
b286457337 | ||
|
|
7881291032 | ||
|
|
167088b33d | ||
|
|
ff33343372 | ||
|
|
4097bf37b1 | ||
|
|
c910ef8534 | ||
|
|
1063d6dd27 | ||
|
|
e4eac36c1c | ||
|
|
9394f6d043 | ||
|
|
3399f93620 | ||
|
|
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 | ||
|
|
ad0f75222a |
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.61", 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.61' }}
|
||||
env:
|
||||
FEATURES: ${{ matrix.features }}
|
||||
rustfmt:
|
||||
|
||||
+110
@@ -1,3 +1,113 @@
|
||||
# Version 0.9.0
|
||||
|
||||
New features:
|
||||
* Added support for photometric interpretation `YCbCr` and added related
|
||||
`ColorType`.
|
||||
|
||||
Fixes:
|
||||
* Decoding tiled images calculates padding correctly when image width or height
|
||||
is a multiple of tile size. It could previously corrupt the last tile per row
|
||||
by skipping over data.
|
||||
|
||||
# Version 0.8.1
|
||||
|
||||
Changes:
|
||||
* The jpeg decoder gained to ability to utilize the Photometric Interpretation
|
||||
directly instead of relying on a custom APP segment.
|
||||
|
||||
Fixes:
|
||||
* A spurious error within the PackBits decoder lead to the incorrect results
|
||||
(wrong bits or errors), based on the maximum size of reads from the
|
||||
underlying reader.
|
||||
* Removed a panic path in jpeg decoding, when a feature such as photometric
|
||||
interpretation is not supported. An error is returned instead.
|
||||
|
||||
# Version 0.8.0
|
||||
|
||||
Changes:
|
||||
* The minimum supported rust version is now indicated in `Cargo.toml`.
|
||||
* The enums `TiffFormatError` and `TiffUnsupportedError` are now
|
||||
marked with the `#[non_exhaustive]` attribute.
|
||||
* Additionally, tag related enums `Value`, `Tags`, `Type`, `CompressionMethod`,
|
||||
`PhotometricInterpretation`, `PlanarConfiguration`, `Predictor`,
|
||||
`ResolutionUnit`, `SampleFormat` are also changed.
|
||||
|
||||
Removals:
|
||||
* Removed deprecated methods of `Decoder`: `init`, `read_jpeg`,
|
||||
`read_strip_to_buffer`, `read_strip`, `read_tile`. The implicit chunk (row or
|
||||
tile) index order could not be easily tracked by the caller. New separate
|
||||
utility interfaces may be introduced at a later point, for now callers are
|
||||
obligated to choose their own.
|
||||
|
||||
Fixes:
|
||||
* Update to `jpeg_decoder = 0.3`.
|
||||
|
||||
# Version 0.7.4
|
||||
|
||||
New features:
|
||||
* Creating an encoder for invalid, zero-sized images is now rejected.
|
||||
|
||||
Fixes:
|
||||
* Fix panic, in a case where decoding jpeg encoded images did not expect the
|
||||
jpeg decoder to return an error.
|
||||
* Fix panic by validating `rows_per_strip` better, fixing a division-by-zero.
|
||||
|
||||
# Version 0.7.3
|
||||
|
||||
New features:
|
||||
* Allow decoder to access specific tiles by index.
|
||||
* Add support for floating point predictor.
|
||||
* Tiled jpeg file support.
|
||||
|
||||
Changes:
|
||||
* Various refactoring and performance improvements.
|
||||
|
||||
# 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:
|
||||
|
||||
+12
-13
@@ -1,25 +1,24 @@
|
||||
[package]
|
||||
name = "tiff"
|
||||
version = "0.7.0"
|
||||
authors = [
|
||||
"ccgn",
|
||||
"bvssvni <bvssvni@gmail.com>",
|
||||
"nwin",
|
||||
"TyOverby <ty@pre-alpha.com>",
|
||||
"HeroicKatora",
|
||||
"Calum",
|
||||
"CensoredUsername <cens.username@gmail.com>",
|
||||
"Robzz",
|
||||
"birktj"]
|
||||
version = "0.9.0"
|
||||
edition = "2018"
|
||||
resolver = "2"
|
||||
|
||||
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
||||
rust-version = "1.61.0"
|
||||
|
||||
license = "MIT"
|
||||
description = "TIFF decoding and encoding library in pure Rust"
|
||||
authors = ["The image-rs Developers"]
|
||||
|
||||
repository = "https://github.com/image-rs/image-tiff"
|
||||
categories = ["multimedia::images", "multimedia::encoding"]
|
||||
|
||||
exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
weezl = "0.1.0"
|
||||
jpeg = { package = "jpeg-decoder", version = "0.1.17", default-features = false }
|
||||
jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false }
|
||||
flate2 = "1.0.20"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
+104
-82
@@ -17,6 +17,7 @@ use self::Value::{
|
||||
|
||||
#[allow(unused_qualifications)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum Value {
|
||||
Byte(u8),
|
||||
Short(u16),
|
||||
@@ -34,8 +35,6 @@ pub enum Value {
|
||||
Ascii(String),
|
||||
Ifd(u32),
|
||||
IfdBig(u64),
|
||||
#[doc(hidden)] // Do not match against this.
|
||||
__NonExhaustive,
|
||||
}
|
||||
|
||||
impl Value {
|
||||
@@ -341,32 +340,39 @@ 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 | Type::IFD => 4,
|
||||
Type::LONG8
|
||||
| Type::SLONG8
|
||||
| Type::DOUBLE
|
||||
| Type::RATIONAL
|
||||
| Type::SRATIONAL
|
||||
| Type::IFD8 => 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,
|
||||
};
|
||||
|
||||
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()?),
|
||||
@@ -390,7 +396,6 @@ impl Entry {
|
||||
| Type::SLONG
|
||||
| Type::FLOAT
|
||||
| Type::IFD => unreachable!(),
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -412,39 +417,38 @@ 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 => {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||
SignedBig(decoder.read_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 => {
|
||||
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||
IfdBig(decoder.read_long8()?)
|
||||
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::BYTE => return offset_to_bytes(self.count as usize, self),
|
||||
Type::SBYTE => return 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)?;
|
||||
@@ -520,7 +524,6 @@ impl Entry {
|
||||
| Type::IFD8 => {
|
||||
unreachable!()
|
||||
}
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,66 +531,81 @@ 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::SLONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(SignedBig(decoder.read_slong8()?))
|
||||
}),
|
||||
Type::IFD => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(Ifd(decoder.read_long()?))
|
||||
}),
|
||||
Type::IFD8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| {
|
||||
Ok(IfdBig(decoder.read_long8()?))
|
||||
}),
|
||||
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!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,13 +614,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>() {
|
||||
@@ -610,13 +629,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,601 @@
|
||||
use super::ifd::{Directory, Value};
|
||||
use super::stream::{ByteOrder, DeflateReader, JpegReader, LZWReader, PackBitsReader};
|
||||
use super::tag_reader::TagReader;
|
||||
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
|
||||
use super::{stream::SmartReader, ChunkType};
|
||||
use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag};
|
||||
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::io::{self, Cursor, Read, Seek};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[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 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) % self.tile_width
|
||||
}
|
||||
fn padding_down(&self) -> usize {
|
||||
(self.tile_length - self.image_height % self.tile_length) % self.tile_length
|
||||
}
|
||||
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<Arc<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()?;
|
||||
if width == 0 || height == 0 {
|
||||
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
|
||||
width, height,
|
||||
)));
|
||||
}
|
||||
|
||||
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(Arc::new(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()
|
||||
|| rows_per_strip == 0
|
||||
|| u32::try_from(chunk_offsets.len())?
|
||||
!= height.saturating_sub(1) / rows_per_strip + 1
|
||||
{
|
||||
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)?,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
|
||||
match self.photometric_interpretation {
|
||||
PhotometricInterpretation::RGB => match self.bits_per_sample[..] {
|
||||
[r, g, b] if [r, r] == [g, b] => Ok(ColorType::RGB(r)),
|
||||
[r, g, b, a] if [r, r, r] == [g, b, a] => Ok(ColorType::RGBA(r)),
|
||||
// FIXME: We should _ignore_ other components. In particular:
|
||||
// > Beware of extra components. Some TIFF files may have more components per pixel
|
||||
// than you think. A Baseline TIFF reader must skip over them gracefully,using the
|
||||
// values of the SamplesPerPixel and BitsPerSample fields.
|
||||
// > -- TIFF 6.0 Specification, Section 7, Additional Baseline requirements.
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
self.bits_per_sample.clone(),
|
||||
),
|
||||
)),
|
||||
},
|
||||
PhotometricInterpretation::CMYK => match self.bits_per_sample[..] {
|
||||
[c, m, y, k] if [c, c, c] == [m, y, k] => Ok(ColorType::CMYK(c)),
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
self.bits_per_sample.clone(),
|
||||
),
|
||||
)),
|
||||
},
|
||||
PhotometricInterpretation::YCbCr => match self.bits_per_sample[..] {
|
||||
[y, cb, cr] if [y, y] == [cb, cr] => Ok(ColorType::YCbCr(y)),
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
self.bits_per_sample.clone(),
|
||||
),
|
||||
)),
|
||||
},
|
||||
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
|
||||
if self.bits_per_sample.len() == 1 =>
|
||||
{
|
||||
Ok(ColorType::Gray(self.bits_per_sample[0]))
|
||||
}
|
||||
|
||||
// TODO: this is bad we should not fail at this point
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
self.bits_per_sample.clone(),
|
||||
),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_reader<'r, R: 'r + Read>(
|
||||
reader: R,
|
||||
photometric_interpretation: PhotometricInterpretation,
|
||||
compression_method: CompressionMethod,
|
||||
compressed_length: u64,
|
||||
jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||
Ok(match compression_method {
|
||||
CompressionMethod::None => Box::new(reader),
|
||||
CompressionMethod::LZW => {
|
||||
Box::new(LZWReader::new(reader, usize::try_from(compressed_length)?))
|
||||
}
|
||||
CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)),
|
||||
CompressionMethod::Deflate | CompressionMethod::OldDeflate => {
|
||||
Box::new(DeflateReader::new(reader))
|
||||
}
|
||||
CompressionMethod::ModernJPEG => {
|
||||
if jpeg_tables.is_some() && compressed_length < 2 {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
||||
));
|
||||
}
|
||||
|
||||
let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?;
|
||||
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
||||
|
||||
match photometric_interpretation {
|
||||
PhotometricInterpretation::RGB => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::RGB)
|
||||
}
|
||||
PhotometricInterpretation::WhiteIsZero => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||
}
|
||||
PhotometricInterpretation::BlackIsZero => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||
}
|
||||
PhotometricInterpretation::TransparencyMask => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||
}
|
||||
PhotometricInterpretation::CMYK => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::CMYK)
|
||||
}
|
||||
PhotometricInterpretation::YCbCr => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::YCbCr)
|
||||
}
|
||||
photometric_interpretation => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedInterpretation(
|
||||
photometric_interpretation,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let data = decoder.decode()?;
|
||||
|
||||
Box::new(Cursor::new(data))
|
||||
}
|
||||
method => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> {
|
||||
let file_offset = self
|
||||
.chunk_offsets
|
||||
.get(chunk as usize)
|
||||
.ok_or(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
))?;
|
||||
|
||||
let compressed_bytes =
|
||||
self.chunk_bytes
|
||||
.get(chunk as usize)
|
||||
.ok_or(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
))?;
|
||||
|
||||
Ok((*file_offset, *compressed_bytes))
|
||||
}
|
||||
|
||||
pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> {
|
||||
match self.chunk_type {
|
||||
ChunkType::Strip => {
|
||||
let strip_attrs = self.strip_decoder.as_ref().unwrap();
|
||||
Ok((self.width, strip_attrs.rows_per_strip))
|
||||
}
|
||||
ChunkType::Tile => {
|
||||
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
||||
Ok((
|
||||
u32::try_from(tile_attrs.tile_width)?,
|
||||
u32::try_from(tile_attrs.tile_length)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
|
||||
let dims = self.chunk_dimensions()?;
|
||||
|
||||
match self.chunk_type {
|
||||
ChunkType::Strip => {
|
||||
let strip_height_without_padding = chunk_index
|
||||
.checked_mul(dims.1)
|
||||
.and_then(|x| self.height.checked_sub(x))
|
||||
.ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex(
|
||||
chunk_index,
|
||||
)))?;
|
||||
|
||||
// Ignore potential vertical padding on the bottommost strip
|
||||
let strip_height = dims.1.min(strip_height_without_padding);
|
||||
|
||||
Ok((dims.0, strip_height))
|
||||
}
|
||||
ChunkType::Tile => {
|
||||
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
||||
let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize);
|
||||
|
||||
let tile_width = tile_attrs.tile_width - padding_right;
|
||||
let tile_length = tile_attrs.tile_length - padding_down;
|
||||
|
||||
Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expand_chunk(
|
||||
&self,
|
||||
reader: impl Read,
|
||||
mut buffer: DecodingBuffer,
|
||||
output_width: usize,
|
||||
byte_order: ByteOrder,
|
||||
chunk_index: u32,
|
||||
) -> TiffResult<()> {
|
||||
// Validate that the provided buffer is of the expected type.
|
||||
let color_type = self.colortype()?;
|
||||
match (color_type, &buffer) {
|
||||
(ColorType::RGB(n), _)
|
||||
| (ColorType::RGBA(n), _)
|
||||
| (ColorType::CMYK(n), _)
|
||||
| (ColorType::YCbCr(n), _)
|
||||
| (ColorType::Gray(n), _)
|
||||
if usize::from(n) == buffer.byte_len() * 8 => {}
|
||||
(ColorType::Gray(n), DecodingBuffer::U8(_)) if n < 8 => match self.predictor {
|
||||
Predictor::None => {}
|
||||
Predictor::Horizontal => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::HorizontalPredictor(color_type),
|
||||
))
|
||||
}
|
||||
Predictor::FloatingPoint => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::FloatingPointPredictor(color_type),
|
||||
));
|
||||
}
|
||||
},
|
||||
(type_, _) => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedColorType(type_),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that the predictor is supported for the sample type.
|
||||
match (self.predictor, &buffer) {
|
||||
(Predictor::Horizontal, DecodingBuffer::F32(_))
|
||||
| (Predictor::Horizontal, DecodingBuffer::F64(_)) => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::HorizontalPredictor(color_type),
|
||||
));
|
||||
}
|
||||
(Predictor::FloatingPoint, DecodingBuffer::F32(_))
|
||||
| (Predictor::FloatingPoint, DecodingBuffer::F64(_)) => {}
|
||||
(Predictor::FloatingPoint, _) => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::FloatingPointPredictor(color_type),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let compressed_bytes =
|
||||
self.chunk_bytes
|
||||
.get(chunk_index as usize)
|
||||
.ok_or(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
))?;
|
||||
|
||||
let byte_len = buffer.byte_len();
|
||||
let compression_method = self.compression_method;
|
||||
let photometric_interpretation = self.photometric_interpretation;
|
||||
let predictor = self.predictor;
|
||||
let samples = self.bits_per_sample.len();
|
||||
|
||||
let chunk_dims = self.chunk_dimensions()?;
|
||||
let data_dims = self.chunk_data_dimensions(chunk_index)?;
|
||||
|
||||
let padding_right = chunk_dims.0 - data_dims.0;
|
||||
|
||||
let jpeg_tables = self.jpeg_tables.clone();
|
||||
let mut reader = Self::create_reader(
|
||||
reader,
|
||||
photometric_interpretation,
|
||||
compression_method,
|
||||
*compressed_bytes,
|
||||
jpeg_tables,
|
||||
)?;
|
||||
|
||||
if output_width == data_dims.0 as usize && padding_right == 0 {
|
||||
let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples;
|
||||
let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len];
|
||||
reader.read_exact(tile)?;
|
||||
|
||||
for row in 0..data_dims.1 as usize {
|
||||
let row_start = row as usize * output_width as usize * samples;
|
||||
let row_end = (row + 1) * output_width as usize * samples;
|
||||
let row = buffer.subrange(row_start..row_end);
|
||||
super::fix_endianness_and_predict(row, samples, byte_order, predictor);
|
||||
}
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(&mut buffer.subrange(0..total_samples), color_type);
|
||||
}
|
||||
} else if padding_right > 0 && self.predictor == Predictor::FloatingPoint {
|
||||
// The floating point predictor shuffles the padding bytes into the encoded output, so
|
||||
// this case is handled specially when needed.
|
||||
let mut encoded = vec![0u8; chunk_dims.0 as usize * samples * byte_len];
|
||||
|
||||
for row in 0..data_dims.1 as usize {
|
||||
let row_start = row * output_width as usize * samples;
|
||||
let row_end = row_start + data_dims.0 as usize * samples;
|
||||
|
||||
reader.read_exact(&mut encoded)?;
|
||||
match buffer.subrange(row_start..row_end) {
|
||||
DecodingBuffer::F32(buf) => fp_predict_f32(&mut encoded, buf, samples),
|
||||
DecodingBuffer::F64(buf) => fp_predict_f64(&mut encoded, buf, samples),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for row in 0..data_dims.1 as usize {
|
||||
let row_start = row * output_width as usize * samples;
|
||||
let row_end = row_start + data_dims.0 as usize * samples;
|
||||
|
||||
let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)];
|
||||
reader.read_exact(row)?;
|
||||
|
||||
// Skip horizontal padding
|
||||
if padding_right > 0 {
|
||||
let len = u64::try_from(padding_right as usize * samples * byte_len)?;
|
||||
io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
|
||||
}
|
||||
|
||||
let mut row = buffer.subrange(row_start..row_end);
|
||||
super::fix_endianness_and_predict(row.copy(), samples, byte_order, predictor);
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(&mut row, color_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+456
-944
File diff suppressed because it is too large.
Load diff
+154
-99
@@ -1,6 +1,8 @@
|
||||
//! All IO functionality needed for TIFF decoding
|
||||
|
||||
use std::io::{self, Read, Seek};
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Take};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Byte order of the TIFF file.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -124,70 +126,60 @@ pub trait EndianReader: Read {
|
||||
/// ## Deflate Reader
|
||||
///
|
||||
|
||||
pub type DeflateReader<'r, R> = flate2::read::ZlibDecoder<&'r mut SmartReader<R>>;
|
||||
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
||||
|
||||
///
|
||||
/// ## LZW Reader
|
||||
///
|
||||
|
||||
/// Reader that decompresses LZW streams
|
||||
pub struct LZWReader {
|
||||
buffer: io::Cursor<Vec<u8>>,
|
||||
pub struct LZWReader<R: Read> {
|
||||
reader: BufReader<Take<R>>,
|
||||
decoder: weezl::decode::Decoder,
|
||||
}
|
||||
|
||||
impl LZWReader {
|
||||
impl<R: Read> LZWReader<R> {
|
||||
/// Wraps a reader
|
||||
pub fn new<R>(
|
||||
reader: &mut SmartReader<R>,
|
||||
compressed_length: usize,
|
||||
max_uncompressed_length: usize,
|
||||
) -> io::Result<(usize, LZWReader)>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let mut compressed = vec![0; compressed_length as usize];
|
||||
reader.read_exact(&mut compressed[..])?;
|
||||
let mut uncompressed = vec![0; max_uncompressed_length];
|
||||
let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
|
||||
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
|
||||
Self {
|
||||
reader: BufReader::with_capacity(
|
||||
(32 * 1024).min(compressed_length),
|
||||
reader.take(u64::try_from(compressed_length).unwrap()),
|
||||
),
|
||||
decoder: weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut bytes_read = 0;
|
||||
let mut bytes_written = 0;
|
||||
|
||||
while bytes_written < max_uncompressed_length {
|
||||
let result = decoder.decode_bytes(
|
||||
&compressed[bytes_read..],
|
||||
&mut uncompressed[bytes_written..],
|
||||
);
|
||||
bytes_read += result.consumed_in;
|
||||
bytes_written += result.consumed_out;
|
||||
impl<R: Read> Read for LZWReader<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
loop {
|
||||
let result = self.decoder.decode_bytes(self.reader.fill_buf()?, buf);
|
||||
self.reader.consume(result.consumed_in);
|
||||
|
||||
match result.status {
|
||||
Ok(weezl::LzwStatus::Ok) => {}
|
||||
Ok(weezl::LzwStatus::Done) => break,
|
||||
Ok(weezl::LzwStatus::Ok) => {
|
||||
if result.consumed_out == 0 {
|
||||
continue;
|
||||
} else {
|
||||
return Ok(result.consumed_out);
|
||||
}
|
||||
}
|
||||
Ok(weezl::LzwStatus::NoProgress) => {
|
||||
assert_eq!(result.consumed_in, 0);
|
||||
assert_eq!(result.consumed_out, 0);
|
||||
assert!(self.reader.buffer().is_empty());
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"no lzw end code found",
|
||||
))
|
||||
));
|
||||
}
|
||||
Ok(weezl::LzwStatus::Done) => {
|
||||
return Ok(result.consumed_out);
|
||||
}
|
||||
Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)),
|
||||
}
|
||||
}
|
||||
|
||||
uncompressed.resize(bytes_written, 0);
|
||||
Ok((
|
||||
uncompressed.len(),
|
||||
LZWReader {
|
||||
buffer: io::Cursor::new(uncompressed),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for LZWReader {
|
||||
#[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.buffer.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +188,13 @@ impl Read for LZWReader {
|
||||
///
|
||||
|
||||
pub(crate) struct JpegReader {
|
||||
jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||
|
||||
buffer: io::Cursor<Vec<u8>>,
|
||||
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl JpegReader {
|
||||
/// Constructs new JpegReader wrapping a SmartReader.
|
||||
/// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one
|
||||
@@ -210,32 +207,44 @@ impl JpegReader {
|
||||
/// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
|
||||
/// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
|
||||
/// this has to be removed as well (last two bytes of `jpeg_tables`).
|
||||
|
||||
pub fn new<R>(
|
||||
reader: &mut SmartReader<R>,
|
||||
length: u32,
|
||||
jpeg_tables: &Option<Vec<u8>>,
|
||||
) -> io::Result<JpegReader>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
pub fn new<R: Read>(
|
||||
mut reader: R,
|
||||
length: u64,
|
||||
jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||
) -> io::Result<JpegReader> {
|
||||
// Read jpeg image data
|
||||
let mut segment = vec![0; length as usize];
|
||||
|
||||
reader.read_exact(&mut segment[..])?;
|
||||
|
||||
match jpeg_tables {
|
||||
Some(tables) => {
|
||||
let mut jpeg_data = tables.clone();
|
||||
let truncated_length = jpeg_data.len() - 2;
|
||||
jpeg_data.truncate(truncated_length);
|
||||
jpeg_data.extend_from_slice(&segment[2..]);
|
||||
Some(jpeg_tables) => {
|
||||
assert!(
|
||||
jpeg_tables.len() >= 2,
|
||||
"jpeg_tables, if given, must be at least 2 bytes long. Got {:?}",
|
||||
jpeg_tables
|
||||
);
|
||||
|
||||
assert!(
|
||||
length >= 2,
|
||||
"if jpeg_tables is given, length must be at least 2 bytes long, got {}",
|
||||
length
|
||||
);
|
||||
|
||||
let mut buffer = io::Cursor::new(segment);
|
||||
// Skip the first two bytes (marker bytes)
|
||||
buffer.seek(SeekFrom::Start(2))?;
|
||||
|
||||
Ok(JpegReader {
|
||||
buffer: io::Cursor::new(jpeg_data),
|
||||
buffer,
|
||||
jpeg_tables: Some(jpeg_tables),
|
||||
offset: 0,
|
||||
})
|
||||
}
|
||||
None => Ok(JpegReader {
|
||||
buffer: io::Cursor::new(segment),
|
||||
jpeg_tables: None,
|
||||
offset: 0,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -244,7 +253,31 @@ impl JpegReader {
|
||||
impl Read for JpegReader {
|
||||
// #[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.buffer.read(buf)
|
||||
let mut start = 0;
|
||||
|
||||
if let Some(jpeg_tables) = &self.jpeg_tables {
|
||||
if jpeg_tables.len() - 2 > self.offset {
|
||||
// Read (rest of) jpeg_tables to buf (without the last two bytes)
|
||||
let size_remaining = jpeg_tables.len() - self.offset - 2;
|
||||
let to_copy = size_remaining.min(buf.len());
|
||||
|
||||
buf[start..start + to_copy]
|
||||
.copy_from_slice(&jpeg_tables[self.offset..self.offset + to_copy]);
|
||||
|
||||
self.offset += to_copy;
|
||||
|
||||
if to_copy == buf.len() {
|
||||
return Ok(to_copy);
|
||||
}
|
||||
|
||||
start += to_copy;
|
||||
}
|
||||
}
|
||||
|
||||
let read = self.buffer.read(&mut buf[start..])?;
|
||||
self.offset += read;
|
||||
|
||||
Ok(read + start)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,56 +285,70 @@ impl Read for JpegReader {
|
||||
/// ## PackBits Reader
|
||||
///
|
||||
|
||||
/// Reader that unpacks Apple's `PackBits` format
|
||||
pub struct PackBitsReader {
|
||||
buffer: io::Cursor<Vec<u8>>,
|
||||
enum PackBitsReaderState {
|
||||
Header,
|
||||
Literal,
|
||||
Repeat { value: u8 },
|
||||
}
|
||||
|
||||
impl PackBitsReader {
|
||||
/// Reader that unpacks Apple's `PackBits` format
|
||||
pub struct PackBitsReader<R: Read> {
|
||||
reader: Take<R>,
|
||||
state: PackBitsReaderState,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl<R: Read> PackBitsReader<R> {
|
||||
/// Wraps a reader
|
||||
pub fn new<R: Read + Seek>(
|
||||
mut reader: R,
|
||||
length: usize,
|
||||
) -> io::Result<(usize, PackBitsReader)> {
|
||||
let mut buffer = Vec::new();
|
||||
let mut header: [u8; 1] = [0];
|
||||
let mut data: [u8; 1] = [0];
|
||||
|
||||
let mut bytes_read = 0;
|
||||
while bytes_read < length {
|
||||
reader.read_exact(&mut header)?;
|
||||
bytes_read += 1;
|
||||
pub fn new(reader: R, length: u64) -> Self {
|
||||
Self {
|
||||
reader: reader.take(length),
|
||||
state: PackBitsReaderState::Header,
|
||||
count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for PackBitsReader<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
while let PackBitsReaderState::Header = self.state {
|
||||
if self.reader.limit() == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
let mut header: [u8; 1] = [0];
|
||||
self.reader.read_exact(&mut header)?;
|
||||
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 data)?;
|
||||
buffer.resize(new_len, data[0]);
|
||||
bytes_read += 1;
|
||||
let mut data: [u8; 1] = [0];
|
||||
self.reader.read_exact(&mut data)?;
|
||||
self.state = PackBitsReaderState::Repeat { value: data[0] };
|
||||
self.count = (1 - h as isize) as usize;
|
||||
} 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..])?;
|
||||
bytes_read += num_vals
|
||||
self.state = PackBitsReaderState::Literal;
|
||||
self.count = h as usize + 1;
|
||||
} else {
|
||||
// h = -128 is a no-op.
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
buffer.len(),
|
||||
PackBitsReader {
|
||||
buffer: io::Cursor::new(buffer),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
let length = buf.len().min(self.count);
|
||||
let actual = match self.state {
|
||||
PackBitsReaderState::Literal => self.reader.read(&mut buf[..length])?,
|
||||
PackBitsReaderState::Repeat { value } => {
|
||||
for b in &mut buf[..length] {
|
||||
*b = value;
|
||||
}
|
||||
|
||||
impl Read for PackBitsReader {
|
||||
#[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.buffer.read(buf)
|
||||
length
|
||||
}
|
||||
PackBitsReaderState::Header => unreachable!(),
|
||||
};
|
||||
|
||||
self.count -= actual;
|
||||
if self.count == 0 {
|
||||
self.state = PackBitsReaderState::Header;
|
||||
}
|
||||
return Ok(actual);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,6 +374,14 @@ where
|
||||
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>
|
||||
@@ -366,7 +421,7 @@ mod test {
|
||||
let encoded_len = encoded.len();
|
||||
|
||||
let buff = io::Cursor::new(encoded);
|
||||
let (_, mut decoder) = PackBitsReader::new(buff, encoded_len).unwrap();
|
||||
let mut decoder = PackBitsReader::new(buff, encoded_len as u64);
|
||||
|
||||
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;
|
||||
@@ -203,3 +235,11 @@ impl ColorType for CMYK64Float {
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
}
|
||||
|
||||
pub struct YCbCr8;
|
||||
impl ColorType for YCbCr8 {
|
||||
type Inner = u8;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+90
-10
@@ -12,14 +12,17 @@ use std::{
|
||||
|
||||
use crate::{
|
||||
error::TiffResult,
|
||||
tags::{self, ResolutionUnit, Tag},
|
||||
tags::{CompressionMethod, ResolutionUnit, Tag},
|
||||
TiffError, TiffFormatError,
|
||||
};
|
||||
|
||||
pub mod colortype;
|
||||
pub mod compression;
|
||||
mod tiff_value;
|
||||
mod writer;
|
||||
|
||||
use self::colortype::*;
|
||||
use self::compression::*;
|
||||
use self::writer::*;
|
||||
|
||||
/// Encoder for Tiff and BigTiff files.
|
||||
@@ -98,11 +101,22 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> TiffResult<ImageEncoder<W, C, K>> {
|
||||
) -> 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,
|
||||
@@ -117,6 +131,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Low level interface to encode ifd directories.
|
||||
@@ -216,6 +247,11 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
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();
|
||||
@@ -277,7 +313,13 @@ impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> {
|
||||
/// # }
|
||||
/// ```
|
||||
/// You can also call write_data function wich will encode by strip and finish
|
||||
pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
|
||||
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,
|
||||
@@ -288,23 +330,49 @@ pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
|
||||
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, K: TiffKind> ImageEncoder<'a, W, T, K> {
|
||||
fn new(mut encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self> {
|
||||
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,
|
||||
compression: D,
|
||||
) -> TiffResult<Self> {
|
||||
if width == 0 || height == 0 {
|
||||
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
|
||||
width, height,
|
||||
)));
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -332,6 +400,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
strip_offsets: Vec::new(),
|
||||
strip_byte_count: Vec::new(),
|
||||
dropped: false,
|
||||
compression: compression,
|
||||
_phantom: ::std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
@@ -354,7 +423,6 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> 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(
|
||||
@@ -364,9 +432,12 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
.into());
|
||||
}
|
||||
|
||||
// Write the (possible compressed) data to the encoder.
|
||||
let offset = self.encoder.write_data(value)?;
|
||||
let byte_count = self.encoder.last_written() as usize;
|
||||
|
||||
self.strip_offsets.push(K::convert_offset(offset)?);
|
||||
self.strip_byte_count.push(value.bytes().try_into()?);
|
||||
self.strip_byte_count.push(byte_count.try_into()?);
|
||||
|
||||
self.strip_idx += 1;
|
||||
Ok(())
|
||||
@@ -392,12 +463,19 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> 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(())
|
||||
}
|
||||
@@ -475,7 +553,9 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W, C, K> {
|
||||
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();
|
||||
|
||||
+147
-107
@@ -1,4 +1,4 @@
|
||||
use std::io::Write;
|
||||
use std::{borrow::Cow, io::Write, slice::from_ref};
|
||||
|
||||
use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
@@ -12,7 +12,18 @@ pub trait TiffValue {
|
||||
fn bytes(&self) -> usize {
|
||||
self.count() * usize::from(Self::BYTE_LEN)
|
||||
}
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()>;
|
||||
|
||||
/// 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] {
|
||||
@@ -23,9 +34,8 @@ impl TiffValue for [u8] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
writer.write_bytes(self)?;
|
||||
Ok(())
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +47,8 @@ impl TiffValue for [i8] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::i8_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,10 +60,8 @@ impl TiffValue for [u16] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::u16_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,10 +73,8 @@ impl TiffValue for [i16] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::i16_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,10 +86,8 @@ impl TiffValue for [u32] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::u32_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,10 +99,8 @@ impl TiffValue for [i32] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::i32_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,10 +112,8 @@ impl TiffValue for [u64] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::u64_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,10 +125,8 @@ impl TiffValue for [i64] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
let slice = bytecast::i64_as_ne_bytes(self);
|
||||
writer.write_bytes(slice)?;
|
||||
Ok(())
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,11 +138,9 @@ impl TiffValue for [f32] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
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(())
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
// We write using native endian so this should be safe
|
||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,75 +152,9 @@ impl TiffValue for [f64] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
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 [Ifd] {
|
||||
const BYTE_LEN: u8 = 4;
|
||||
const FIELD_TYPE: Type = Type::IFD;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
for x in self {
|
||||
x.write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [Ifd8] {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::IFD8;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||
for x in self {
|
||||
x.write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for [Rational] {
|
||||
const BYTE_LEN: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::RATIONAL;
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
|
||||
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: u8 = 8;
|
||||
const FIELD_TYPE: Type = Type::SRATIONAL;
|
||||
|
||||
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]> {
|
||||
// We write using native endian so this should be safe
|
||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +170,10 @@ impl TiffValue for u8 {
|
||||
writer.write_u8(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(from_ref(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i8 {
|
||||
@@ -256,6 +188,10 @@ impl TiffValue for i8 {
|
||||
writer.write_i8(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u16 {
|
||||
@@ -270,6 +206,10 @@ impl TiffValue for u16 {
|
||||
writer.write_u16(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i16 {
|
||||
@@ -284,6 +224,10 @@ impl TiffValue for i16 {
|
||||
writer.write_i16(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u32 {
|
||||
@@ -298,6 +242,10 @@ impl TiffValue for u32 {
|
||||
writer.write_u32(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i32 {
|
||||
@@ -312,6 +260,10 @@ impl TiffValue for i32 {
|
||||
writer.write_i32(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for u64 {
|
||||
@@ -326,6 +278,10 @@ impl TiffValue for u64 {
|
||||
writer.write_u64(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for i64 {
|
||||
@@ -340,6 +296,10 @@ impl TiffValue for i64 {
|
||||
writer.write_i64(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for f32 {
|
||||
@@ -354,6 +314,10 @@ impl TiffValue for f32 {
|
||||
writer.write_f32(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for f64 {
|
||||
@@ -368,6 +332,10 @@ impl TiffValue for f64 {
|
||||
writer.write_f64(*self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TiffValue for Ifd {
|
||||
@@ -382,6 +350,10 @@ impl TiffValue for Ifd {
|
||||
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 {
|
||||
@@ -396,6 +368,10 @@ impl TiffValue for Ifd8 {
|
||||
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 {
|
||||
@@ -411,6 +387,14 @@ impl TiffValue for Rational {
|
||||
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 {
|
||||
@@ -426,6 +410,14 @@ impl TiffValue for SRational {
|
||||
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 {
|
||||
@@ -445,6 +437,17 @@ impl TiffValue for str {
|
||||
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 {
|
||||
@@ -458,8 +461,45 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
||||
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);
|
||||
|
||||
+63
-23
@@ -1,3 +1,4 @@
|
||||
use crate::encoder::compression::*;
|
||||
use crate::error::TiffResult;
|
||||
use std::io::{self, Seek, SeekFrom, Write};
|
||||
|
||||
@@ -42,87 +43,126 @@ pub fn write_bigtiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<
|
||||
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.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_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(())
|
||||
}
|
||||
|
||||
+89
-8
@@ -1,8 +1,12 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::io;
|
||||
use std::str;
|
||||
use std::string;
|
||||
use std::sync::Arc;
|
||||
|
||||
use jpeg::UnsupportedFeature;
|
||||
|
||||
use crate::decoder::{ifd::Value, ChunkType};
|
||||
use crate::tags::{
|
||||
@@ -10,6 +14,8 @@ use crate::tags::{
|
||||
};
|
||||
use crate::ColorType;
|
||||
|
||||
use crate::weezl::LzwError;
|
||||
|
||||
/// Tiff error kinds.
|
||||
#[derive(Debug)]
|
||||
pub enum TiffError {
|
||||
@@ -41,6 +47,7 @@ pub enum TiffError {
|
||||
/// The list of variants may grow to incorporate errors of future features. Matching against this
|
||||
/// exhaustively is not covered by interface stability guarantees.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum TiffFormatError {
|
||||
TiffSignatureNotFound,
|
||||
TiffSignatureInvalid,
|
||||
@@ -54,6 +61,7 @@ pub enum TiffFormatError {
|
||||
actual_samples: usize,
|
||||
required_samples: usize,
|
||||
},
|
||||
InvalidDimensions(u32, u32),
|
||||
InvalidTag,
|
||||
InvalidTagValueType(Tag),
|
||||
RequiredTagNotFound(Tag),
|
||||
@@ -63,9 +71,9 @@ pub enum TiffFormatError {
|
||||
SignedIntegerExpected(Value),
|
||||
Format(String),
|
||||
RequiredTagEmpty(Tag),
|
||||
#[doc(hidden)]
|
||||
/// Do not match against this variant. It may get removed.
|
||||
__NonExhaustive,
|
||||
StripTileTagConflict,
|
||||
CycleInOffsets,
|
||||
JpegDecoder(JpegDecoderError),
|
||||
}
|
||||
|
||||
impl fmt::Display for TiffFormatError {
|
||||
@@ -96,6 +104,7 @@ impl fmt::Display for TiffFormatError {
|
||||
actual_samples, required_samples
|
||||
)
|
||||
}
|
||||
InvalidDimensions(width, height) => write!(fmt, "Invalid dimensions: {}x{}.", width, height),
|
||||
InvalidTag => write!(fmt, "Image contains invalid tag."),
|
||||
InvalidTagValueType(ref tag) => {
|
||||
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
|
||||
@@ -113,7 +122,9 @@ impl fmt::Display for TiffFormatError {
|
||||
}
|
||||
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
|
||||
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
|
||||
__NonExhaustive => unreachable!(),
|
||||
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"),
|
||||
JpegDecoder(ref error) => write!(fmt, "{}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,7 +138,9 @@ impl fmt::Display for TiffFormatError {
|
||||
/// The list of variants may grow. Matching against this exhaustively is not covered by interface
|
||||
/// stability guarantees.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum TiffUnsupportedError {
|
||||
FloatingPointPredictor(ColorType),
|
||||
HorizontalPredictor(ColorType),
|
||||
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
|
||||
UnknownInterpretation,
|
||||
@@ -139,15 +152,19 @@ pub enum TiffUnsupportedError {
|
||||
UnsupportedBitsPerChannel(u8),
|
||||
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
|
||||
UnsupportedDataType,
|
||||
#[doc(hidden)]
|
||||
/// Do not match against this variant. It may get removed.
|
||||
__NonExhaustive,
|
||||
UnsupportedInterpretation(PhotometricInterpretation),
|
||||
UnsupportedJpegFeature(UnsupportedFeature),
|
||||
}
|
||||
|
||||
impl fmt::Display for TiffUnsupportedError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use self::TiffUnsupportedError::*;
|
||||
match *self {
|
||||
FloatingPointPredictor(color_type) => write!(
|
||||
fmt,
|
||||
"Floating point predictor for {:?} is unsupported.",
|
||||
color_type
|
||||
),
|
||||
HorizontalPredictor(color_type) => write!(
|
||||
fmt,
|
||||
"Horizontal predictor for {:?} is unsupported.",
|
||||
@@ -182,7 +199,16 @@ impl fmt::Display for TiffUnsupportedError {
|
||||
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
||||
}
|
||||
UnsupportedDataType => write!(fmt, "Unsupported data type."),
|
||||
__NonExhaustive => unreachable!(),
|
||||
UnsupportedInterpretation(interpretation) => {
|
||||
write!(
|
||||
fmt,
|
||||
"Unsupported photometric interpretation \"{:?}\".",
|
||||
interpretation
|
||||
)
|
||||
}
|
||||
UnsupportedJpegFeature(ref unsupported_feature) => {
|
||||
write!(fmt, "Unsupported JPEG feature {:?}", unsupported_feature)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,6 +219,7 @@ impl fmt::Display for TiffUnsupportedError {
|
||||
#[derive(Debug)]
|
||||
pub enum UsageError {
|
||||
InvalidChunkType(ChunkType, ChunkType),
|
||||
InvalidChunkIndex(u32),
|
||||
}
|
||||
|
||||
impl fmt::Display for UsageError {
|
||||
@@ -206,6 +233,7 @@ impl fmt::Display for UsageError {
|
||||
expected, actual
|
||||
)
|
||||
}
|
||||
InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({}) requested.", index),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,11 +306,64 @@ impl From<TiffUnsupportedError> for TiffError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UsageError> for TiffError {
|
||||
fn from(err: UsageError) -> TiffError {
|
||||
TiffError::UsageError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::num::TryFromIntError> for TiffError {
|
||||
fn from(_err: std::num::TryFromIntError) -> TiffError {
|
||||
TiffError::IntSizeError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LzwError> for TiffError {
|
||||
fn from(err: LzwError) -> TiffError {
|
||||
match err {
|
||||
LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from(
|
||||
"LZW compressed data corrupted",
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JpegDecoderError {
|
||||
inner: Arc<jpeg::Error>,
|
||||
}
|
||||
|
||||
impl JpegDecoderError {
|
||||
fn new(error: jpeg::Error) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for JpegDecoderError {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.inner, &other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for JpegDecoderError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JpegDecoderError> for TiffError {
|
||||
fn from(error: JpegDecoderError) -> Self {
|
||||
TiffError::FormatError(TiffFormatError::JpegDecoder(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jpeg::Error> for TiffError {
|
||||
fn from(error: jpeg::Error) -> Self {
|
||||
JpegDecoderError::new(error).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of an image decoding/encoding process
|
||||
pub type TiffResult<T> = Result<T, TiffError>;
|
||||
+4
-1
@@ -4,7 +4,7 @@
|
||||
//! lossless and lossy compression.
|
||||
//!
|
||||
//! # Related Links
|
||||
//! * <https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
||||
//! * <https://web.archive.org/web/20210108073850/https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
||||
|
||||
extern crate jpeg;
|
||||
extern crate weezl;
|
||||
@@ -37,4 +37,7 @@ pub enum ColorType {
|
||||
|
||||
/// Pixel is CMYK
|
||||
CMYK(u8),
|
||||
|
||||
/// Pixel is YCbCr
|
||||
YCbCr(u8),
|
||||
}
|
||||
+2
-4
@@ -9,11 +9,9 @@ macro_rules! tags {
|
||||
} => {
|
||||
$( #[$enum_attr] )*
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum $name {
|
||||
$($(#[$ident_attr])* $tag,)*
|
||||
// FIXME: switch to non_exhaustive once stabilized and compiler requirement new enough
|
||||
#[doc(hidden)]
|
||||
__NonExhaustive,
|
||||
$(
|
||||
#[doc = $unknown_doc]
|
||||
Unknown($ty),
|
||||
@@ -34,7 +32,6 @@ macro_rules! tags {
|
||||
match *self {
|
||||
$( $name::$tag => $val, )*
|
||||
$( $name::Unknown(n) => { $unknown_doc; n }, )*
|
||||
$name::__NonExhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,6 +211,7 @@ tags! {
|
||||
pub enum Predictor(u16) {
|
||||
None = 1,
|
||||
Horizontal = 2,
|
||||
FloatingPoint = 3,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+194
-1
@@ -275,7 +275,7 @@ fn test_tiled_incremental() {
|
||||
assert_eq!(tiles as usize, sums.len());
|
||||
|
||||
for tile in 0..tiles {
|
||||
match decoder.read_tile().unwrap() {
|
||||
match decoder.read_chunk(tile).unwrap() {
|
||||
DecodingResult::U8(res) => {
|
||||
let sum: u64 = res.into_iter().map(<u64>::from).sum();
|
||||
assert_eq!(sum, sums[tile as usize]);
|
||||
@@ -284,3 +284,196 @@ fn test_tiled_incremental() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 fuzzer_testcase5() {
|
||||
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 _ = decoder.read_image().unwrap_err();
|
||||
}
|
||||
|
||||
#[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 _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fuzzer_testcase4() {
|
||||
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 _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
}
|
||||
|
||||
#[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() {
|
||||
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 _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_predictor_3_rgb_f32() {
|
||||
test_image_sum_f32("predictor-3-rgb-f32.tif", ColorType::RGB(32), 54004.33);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_predictor_3_gray_f32() {
|
||||
test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275);
|
||||
}
|
||||
@@ -94,6 +94,7 @@ fn test_encode_ifd() {
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
||||
image_encoder.write_strip(&[1]).unwrap();
|
||||
let encoder = image_encoder.encoder();
|
||||
|
||||
// Use the "reusable" tags section as per the TIFF6 spec
|
||||
@@ -263,6 +264,11 @@ fn test_gray_f64_roundtrip() {
|
||||
test_f64_roundtrip::<colortype::Gray64Float>("gradient-1c-64b-float.tiff", ColorType::Gray(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ycbcr_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
||||
}
|
||||
|
||||
trait AssertDecode {
|
||||
fn assert_tag_u32(&mut self, tag: u16) -> u32;
|
||||
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
|
||||
@@ -320,6 +326,7 @@ fn test_multiple_byte() {
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
||||
image_encoder.write_strip(&[1]).unwrap();
|
||||
let encoder = image_encoder.encoder();
|
||||
|
||||
encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]).unwrap();
|
||||
@@ -360,6 +367,7 @@ fn test_signed() {
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
||||
image_encoder.write_strip(&[1]).unwrap();
|
||||
let encoder = image_encoder.encoder();
|
||||
|
||||
//Use the "reusable" tags section as per the TIFF6 spec
|
||||
@@ -512,7 +520,7 @@ fn test_rows_per_strip() {
|
||||
|
||||
for i in 0..50 {
|
||||
let img2 = [i; 2 * 100];
|
||||
match decoder.read_strip().unwrap() {
|
||||
match decoder.read_chunk(i as u32).unwrap() {
|
||||
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
|
||||
other => panic!("Incorrect strip type {:?}", other),
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user