Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46086e2bcf | ||
|
|
d7be2a7b67 | ||
|
|
29b71ff597 | ||
|
|
83e6d64751 | ||
|
|
dc2c67469b | ||
|
|
b5d8ee5f86 | ||
|
|
f44da4a00d | ||
|
|
ee0dbf373b | ||
|
|
aeab243a96 | ||
|
|
36f7b3b4aa | ||
|
|
3bfb43e83e | ||
|
|
d42e6be507 | ||
|
|
a19cf37587 | ||
|
|
ff52aea06e | ||
|
|
96d3a02efe | ||
|
|
e28ad564d9 | ||
|
|
de6d9cf131 | ||
|
|
6dc7a266d3 | ||
|
|
307cd8be97 | ||
|
|
0c54a18e21 | ||
|
|
90ae5b8e54 | ||
|
|
9b06e3726d | ||
|
|
895c70a1ad | ||
|
|
b0ba4a6788 | ||
|
|
4a2e76858b | ||
|
|
a67f50568c | ||
|
|
d6f6b8a01e | ||
|
|
8ec91b64f6 | ||
|
|
60f866c119 | ||
|
|
daa0e3eabf | ||
|
|
24d203c7c3 | ||
|
|
473868a7bb | ||
|
|
aefea7786f | ||
|
|
d1ad9b2664 | ||
|
|
bd1d085189 | ||
|
|
fcfcecb2df | ||
|
|
aa10d27a28 | ||
|
|
8fa175c371 | ||
|
|
2a15aa6b7e | ||
|
|
42feeba8da | ||
|
|
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
+42
-17
@@ -9,32 +9,57 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust: ["1.34.2", stable, beta, nightly]
|
||||
command: [build, test]
|
||||
rust: ["1.74.0", stable, beta, nightly]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: rustup default ${{ matrix.rust }}
|
||||
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
if: ${{ matrix.rust == '1.74.0' }}
|
||||
- name: Generate Cargo.lock with minimal-version dependencies
|
||||
if: ${{ matrix.rust == '1.74.0' }}
|
||||
run: cargo -Zminimal-versions generate-lockfile
|
||||
|
||||
- uses: dtolnay/rust-toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
- name: Cache Cargo Dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: build
|
||||
run: >
|
||||
cargo build --verbose --no-default-features --features "$FEATURES"
|
||||
run: cargo build -v
|
||||
- name: test
|
||||
run: >
|
||||
cargo test --tests --benches --no-default-features --features "$FEATURES"
|
||||
if: ${{ matrix.rust != '1.34.2' }}
|
||||
env:
|
||||
FEATURES: ${{ matrix.features }}
|
||||
if: ${{ matrix.rust != '1.74.0' }}
|
||||
run: cargo test -v && cargo doc -v
|
||||
|
||||
features:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
feature: ["", deflate, jpeg, lzw, zstd, default, "default,zstd"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- name: Cargo check
|
||||
run: cargo check --features "${{ matrix.feature }}" --no-default-features
|
||||
|
||||
rustfmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt
|
||||
- name: Run rustfmt check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: -- --check
|
||||
run: cargo fmt -- --check
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- run: cargo clippy --all-features # TODO: enable "-- -D warnings" once all outstanding clippy lints are fixed
|
||||
+141
@@ -1,3 +1,144 @@
|
||||
# Version 0.10.0
|
||||
|
||||
New features:
|
||||
- Added `ColorType::Multiband` and `ColorType::CMYKA`.
|
||||
- Added `Value::Byte` and `Value::SignedByte` to represent `u8` and `i8` tags.
|
||||
- Encoding can now use horizontal predictor.
|
||||
- The `Decoder::tag_iter` method allows iterating over all tags in the image.
|
||||
- Half precision floating point values are now supported during decoding.
|
||||
- ZStandard compression has been added as an additional compression method
|
||||
during decoding, but requires the `zstd` feature flag.
|
||||
|
||||
Changes:
|
||||
- Switched `u16` and `i16` values to be intepreted as `Value::Short` and
|
||||
`Value::SignedShort` respectively.
|
||||
- Changed JPEG compression to use the `zune-jpeg` crate instead of
|
||||
`jpeg-decoder`.
|
||||
- The `deflate`, `jpeg`, and `lzw` compression methods are now controlled by
|
||||
(enabled by default) feature flags.
|
||||
|
||||
Fixes:
|
||||
- Fixed a bug in 1 bit-per-pixel decoding.
|
||||
|
||||
# Version 0.9.1
|
||||
|
||||
New features:
|
||||
- Basic support for planar configuration.
|
||||
- Allow arbitrary number of samples as long as all have the same bit depth.
|
||||
|
||||
Fixes:
|
||||
- Don't panic when parsing metadata when custom compression is used.
|
||||
|
||||
# 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:
|
||||
|
||||
+27
-15
@@ -1,30 +1,42 @@
|
||||
[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.10.0"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
||||
rust-version = "1.74.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 }
|
||||
flate2 = "1.0.20"
|
||||
half = { version = "2.4.1" }
|
||||
quick-error = "2.0.1"
|
||||
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
weezl = { version = "0.1.0", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
zune-jpeg = { version = "0.4.17", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.1"
|
||||
|
||||
[features]
|
||||
default = ["deflate", "jpeg", "lzw"]
|
||||
|
||||
# Compression algorithms
|
||||
deflate = ["dep:flate2"]
|
||||
jpeg = ["dep:zune-jpeg"]
|
||||
lzw = ["dep:weezl"]
|
||||
zstd = ["dep:zstd"]
|
||||
|
||||
[[bench]]
|
||||
name = "lzw"
|
||||
harness = false
|
||||
@@ -16,25 +16,28 @@ 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) |
|
||||
| `CMYKA` | CMYKA(8) | |
|
||||
| `YCbCr` | | |
|
||||
| `CIELab` | | |
|
||||
|
||||
### Compressions
|
||||
|
||||
| | Decoding | Encoding |
|
||||
|-|-|-|
|
||||
| None | ✓ | ✓ |
|
||||
| LZW | ✓ | |
|
||||
| Deflate | ✓ | |
|
||||
| PackBits | ✓ | |
|
||||
| | Decoding | Encoding |
|
||||
| -------- | -------- | -------- |
|
||||
| None | ✓ | ✓ |
|
||||
| LZW | ✓ | ✓ |
|
||||
| Deflate | ✓ | ✓ |
|
||||
| PackBits | ✓ | ✓ |
|
||||
| JPEG | ✓ | not yet |
|
||||
| ZSTD | ✓ | not yet |
|
||||
|
||||
|
||||
## Not yet supported
|
||||
@@ -44,3 +47,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;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -12,6 +12,8 @@
|
||||
//! TODO: Would like to use std-lib here.
|
||||
use std::{mem, slice};
|
||||
|
||||
use half::f16;
|
||||
|
||||
macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => {
|
||||
pub(crate) fn $const(slice: &[$int]) -> &[u8] {
|
||||
assert!(mem::align_of::<$int>() <= mem::size_of::<$int>());
|
||||
@@ -31,4 +33,5 @@ integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(i64, i64_as_ne_bytes, i64_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(f16, f16_as_ne_bytes, f16_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes);
|
||||
+166
-143
@@ -1,25 +1,27 @@
|
||||
//! Function for reading TIFF tags
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::io::{self, Read, Seek};
|
||||
use std::mem;
|
||||
use std::str;
|
||||
|
||||
use super::stream::{ByteOrder, EndianReader, SmartReader};
|
||||
use super::stream::{ByteOrder, EndianReader};
|
||||
use crate::tags::{Tag, Type};
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use self::Value::{
|
||||
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
|
||||
Short, Signed, SignedBig, Unsigned, UnsignedBig,
|
||||
Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig,
|
||||
};
|
||||
|
||||
#[allow(unused_qualifications)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum Value {
|
||||
Byte(u8),
|
||||
Short(u16),
|
||||
SignedByte(i8),
|
||||
SignedShort(i16),
|
||||
Signed(i32),
|
||||
SignedBig(i64),
|
||||
Unsigned(u32),
|
||||
@@ -34,99 +36,104 @@ pub enum Value {
|
||||
Ascii(String),
|
||||
Ifd(u32),
|
||||
IfdBig(u64),
|
||||
#[doc(hidden)] // Do not match against this.
|
||||
__NonExhaustive,
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn into_u8(self) -> TiffResult<u8> {
|
||||
match self {
|
||||
Byte(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
pub fn into_i8(self) -> TiffResult<i8> {
|
||||
match self {
|
||||
SignedByte(val) => Ok(val),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_u16(self) -> TiffResult<u16> {
|
||||
match self {
|
||||
Byte(val) => Ok(val.into()),
|
||||
Short(val) => Ok(val),
|
||||
Unsigned(val) => Ok(u16::try_from(val)?),
|
||||
UnsignedBig(val) => Ok(u16::try_from(val)?),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_i16(self) -> TiffResult<i16> {
|
||||
match self {
|
||||
SignedByte(val) => Ok(val.into()),
|
||||
SignedShort(val) => Ok(val),
|
||||
Signed(val) => Ok(i16::try_from(val)?),
|
||||
SignedBig(val) => Ok(i16::try_from(val)?),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_u32(self) -> TiffResult<u32> {
|
||||
match self {
|
||||
Byte(val) => Ok(val.into()),
|
||||
Short(val) => Ok(val.into()),
|
||||
Unsigned(val) => Ok(val),
|
||||
UnsignedBig(val) => Ok(u32::try_from(val)?),
|
||||
Ifd(val) => Ok(val),
|
||||
IfdBig(val) => Ok(u32::try_from(val)?),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_i32(self) -> TiffResult<i32> {
|
||||
match self {
|
||||
SignedByte(val) => Ok(val.into()),
|
||||
SignedShort(val) => Ok(val.into()),
|
||||
Signed(val) => Ok(val),
|
||||
SignedBig(val) => Ok(i32::try_from(val)?),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_u64(self) -> TiffResult<u64> {
|
||||
match self {
|
||||
Byte(val) => Ok(val.into()),
|
||||
Short(val) => Ok(val.into()),
|
||||
Unsigned(val) => Ok(val.into()),
|
||||
UnsignedBig(val) => Ok(val),
|
||||
Ifd(val) => Ok(val.into()),
|
||||
IfdBig(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_i64(self) -> TiffResult<i64> {
|
||||
match self {
|
||||
SignedByte(val) => Ok(val.into()),
|
||||
SignedShort(val) => Ok(val.into()),
|
||||
Signed(val) => Ok(val.into()),
|
||||
SignedBig(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_f32(self) -> TiffResult<f32> {
|
||||
match self {
|
||||
Float(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_f64(self) -> TiffResult<f64> {
|
||||
match self {
|
||||
Double(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> TiffResult<String> {
|
||||
match self {
|
||||
Ascii(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +146,8 @@ impl Value {
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
Byte(val) => Ok(vec![val.into()]),
|
||||
Short(val) => Ok(vec![val.into()]),
|
||||
Unsigned(val) => Ok(vec![val]),
|
||||
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
|
||||
Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
@@ -148,9 +157,7 @@ impl Value {
|
||||
Ifd(val) => Ok(vec![val]),
|
||||
IfdBig(val) => Ok(vec![u32::try_from(val)?]),
|
||||
Ascii(val) => Ok(val.chars().map(u32::from).collect()),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,9 +172,7 @@ impl Value {
|
||||
}
|
||||
Byte(val) => Ok(vec![val]),
|
||||
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,10 +185,9 @@ impl Value {
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
Byte(val) => Ok(vec![val.into()]),
|
||||
Short(val) => Ok(vec![val]),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,15 +210,15 @@ impl Value {
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
SignedByte(val) => Ok(vec![val.into()]),
|
||||
SignedShort(val) => Ok(vec![val.into()]),
|
||||
Signed(val) => Ok(vec![val]),
|
||||
SignedBig(val) => Ok(vec![i32::try_from(val)?]),
|
||||
SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
SRationalBig(numerator, denominator) => {
|
||||
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
|
||||
}
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,9 +232,7 @@ impl Value {
|
||||
Ok(new_vec)
|
||||
}
|
||||
Float(val) => Ok(vec![val]),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,9 +246,7 @@ impl Value {
|
||||
Ok(new_vec)
|
||||
}
|
||||
Double(val) => Ok(vec![val]),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +259,8 @@ impl Value {
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
Byte(val) => Ok(vec![val.into()]),
|
||||
Short(val) => Ok(vec![val.into()]),
|
||||
Unsigned(val) => Ok(vec![val.into()]),
|
||||
UnsignedBig(val) => Ok(vec![val]),
|
||||
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
||||
@@ -266,9 +268,7 @@ impl Value {
|
||||
Ifd(val) => Ok(vec![val.into()]),
|
||||
IfdBig(val) => Ok(vec![val]),
|
||||
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,13 +291,13 @@ impl Value {
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
SignedByte(val) => Ok(vec![val.into()]),
|
||||
SignedShort(val) => Ok(vec![val.into()]),
|
||||
Signed(val) => Ok(vec![val.into()]),
|
||||
SignedBig(val) => Ok(vec![val]),
|
||||
SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
||||
SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,39 +334,46 @@ impl Entry {
|
||||
}
|
||||
|
||||
/// Returns a mem_reader for the offset/value field
|
||||
fn r(&self, byte_order: ByteOrder) -> SmartReader<io::Cursor<Vec<u8>>> {
|
||||
SmartReader::wrap(io::Cursor::new(self.offset.to_vec()), byte_order)
|
||||
fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
|
||||
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
||||
}
|
||||
|
||||
pub fn val<R: Read + Seek>(
|
||||
pub(crate) fn val<R: Read + Seek>(
|
||||
&self,
|
||||
limits: &super::Limits,
|
||||
decoder: &mut super::Decoder<R>,
|
||||
bigtiff: bool,
|
||||
reader: &mut EndianReader<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,17 +397,16 @@ impl Entry {
|
||||
| Type::SLONG
|
||||
| Type::FLOAT
|
||||
| Type::IFD => unreachable!(),
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
});
|
||||
}
|
||||
|
||||
// 2b: the value is at most 4 bytes or doesn't fit in the offset field.
|
||||
return Ok(match self.type_ {
|
||||
Type::BYTE => Unsigned(u32::from(self.offset[0])),
|
||||
Type::SBYTE => Signed(i32::from(self.offset[0] as i8)),
|
||||
Type::BYTE => Byte(self.offset[0]),
|
||||
Type::SBYTE => SignedByte(i8::from(self.offset[0] as i8)),
|
||||
Type::UNDEFINED => Byte(self.offset[0]),
|
||||
Type::SHORT => Unsigned(u32::from(self.r(bo).read_u16()?)),
|
||||
Type::SSHORT => Signed(i32::from(self.r(bo).read_i16()?)),
|
||||
Type::SHORT => Short(self.r(bo).read_u16()?),
|
||||
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
||||
Type::LONG => Unsigned(self.r(bo).read_u32()?),
|
||||
Type::SLONG => Signed(self.r(bo).read_i32()?),
|
||||
Type::FLOAT => Float(self.r(bo).read_f32()?),
|
||||
@@ -412,42 +418,41 @@ 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)?;
|
||||
buf.copy_from_slice(&self.offset[..self.count as usize]);
|
||||
if buf.is_ascii() && buf.ends_with(&[0]) {
|
||||
let v = str::from_utf8(&buf)?;
|
||||
let v = v.trim_matches(char::from(0));
|
||||
@@ -476,7 +481,7 @@ impl Entry {
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Signed(i32::from(r.read_i16()?)));
|
||||
v.push(SignedShort(r.read_i16()?));
|
||||
}
|
||||
return Ok(List(v));
|
||||
}
|
||||
@@ -520,7 +525,6 @@ impl Entry {
|
||||
| Type::IFD8 => {
|
||||
unreachable!()
|
||||
}
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,66 +532,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.inner().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()?)))
|
||||
}),
|
||||
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(Short(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(SignedShort(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.inner().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.inner().read_exact(&mut out)?;
|
||||
// Strings may be null-terminated, so we trim anything downstream of the null byte
|
||||
if let Some(first) = out.iter().position(|&b| b == 0) {
|
||||
out.truncate(first);
|
||||
}
|
||||
Ok(Ascii(String::from_utf8(out)?))
|
||||
}
|
||||
Type::__NonExhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,13 +615,14 @@ impl Entry {
|
||||
&self,
|
||||
value_count: u64,
|
||||
bo: ByteOrder,
|
||||
bigtiff: bool,
|
||||
limits: &super::Limits,
|
||||
decoder: &mut super::Decoder<R>,
|
||||
reader: &mut EndianReader<R>,
|
||||
decode_fn: F,
|
||||
) -> TiffResult<Value>
|
||||
where
|
||||
R: Read + Seek,
|
||||
F: Fn(&mut super::Decoder<R>) -> TiffResult<Value>,
|
||||
F: Fn(&mut EndianReader<R>) -> TiffResult<Value>,
|
||||
{
|
||||
let value_count = usize::try_from(value_count)?;
|
||||
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
|
||||
@@ -610,13 +630,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,726 @@
|
||||
use super::ifd::{Directory, Value};
|
||||
use super::stream::{ByteOrder, PackBitsReader};
|
||||
use super::tag_reader::TagReader;
|
||||
use super::{predict_f16, predict_f32, predict_f64, Limits};
|
||||
use super::{stream::EndianReader, ChunkType};
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
|
||||
};
|
||||
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||
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: u8,
|
||||
pub samples: u16,
|
||||
pub sample_format: SampleFormat,
|
||||
pub photometric_interpretation: PhotometricInterpretation,
|
||||
pub compression_method: CompressionMethod,
|
||||
pub predictor: Predictor,
|
||||
pub jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||
pub chunk_type: ChunkType,
|
||||
pub planar_config: PlanarConfiguration,
|
||||
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 EndianReader<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_exhaustive(val.into_u16()?),
|
||||
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: u16 = tag_reader
|
||||
.find_tag(Tag::SamplesPerPixel)?
|
||||
.map(Value::into_u16)
|
||||
.transpose()?
|
||||
.unwrap_or(1);
|
||||
if samples == 0 {
|
||||
return Err(TiffFormatError::SamplesPerPixelIsZero.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[0]
|
||||
}
|
||||
None => SampleFormat::Uint,
|
||||
};
|
||||
|
||||
let bits_per_sample: Vec<u8> = tag_reader
|
||||
.find_tag_uint_vec(Tag::BitsPerSample)?
|
||||
.unwrap_or_else(|| vec![1]);
|
||||
|
||||
// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows
|
||||
// it to be a single value that applies to all samples.
|
||||
if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
));
|
||||
}
|
||||
|
||||
// This library (and libtiff) do not support mixed sample formats and zero bits per sample
|
||||
// doesn't make sense.
|
||||
if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) || bits_per_sample[0] == 0 {
|
||||
return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).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 planar_config = tag_reader
|
||||
.find_tag(Tag::PlanarConfiguration)?
|
||||
.map(Value::into_u16)
|
||||
.transpose()?
|
||||
.map(|p| {
|
||||
PlanarConfiguration::from_u16(p).ok_or(TiffError::FormatError(
|
||||
TiffFormatError::UnknownPlanarConfiguration(p),
|
||||
))
|
||||
})
|
||||
.transpose()?
|
||||
.unwrap_or(PlanarConfiguration::Chunky);
|
||||
|
||||
let planes = match planar_config {
|
||||
PlanarConfiguration::Chunky => 1,
|
||||
PlanarConfiguration::Planar => samples,
|
||||
};
|
||||
|
||||
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) * planes as u32
|
||||
{
|
||||
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() * planes as usize
|
||||
{
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
));
|
||||
}
|
||||
}
|
||||
(_, _, _, _) => {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::StripTileTagConflict,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Image {
|
||||
ifd: Some(ifd),
|
||||
width,
|
||||
height,
|
||||
bits_per_sample: bits_per_sample[0],
|
||||
samples,
|
||||
sample_format,
|
||||
photometric_interpretation,
|
||||
compression_method,
|
||||
jpeg_tables,
|
||||
predictor,
|
||||
chunk_type,
|
||||
planar_config,
|
||||
strip_decoder,
|
||||
tile_attributes,
|
||||
chunk_offsets,
|
||||
chunk_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
|
||||
match self.photometric_interpretation {
|
||||
PhotometricInterpretation::RGB => match self.samples {
|
||||
3 => Ok(ColorType::RGB(self.bits_per_sample)),
|
||||
4 => Ok(ColorType::RGBA(self.bits_per_sample)),
|
||||
// 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,
|
||||
vec![self.bits_per_sample; self.samples as usize],
|
||||
),
|
||||
)),
|
||||
},
|
||||
PhotometricInterpretation::CMYK => match self.samples {
|
||||
4 => Ok(ColorType::CMYK(self.bits_per_sample)),
|
||||
5 => Ok(ColorType::CMYKA(self.bits_per_sample)),
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
vec![self.bits_per_sample; self.samples as usize],
|
||||
),
|
||||
)),
|
||||
},
|
||||
PhotometricInterpretation::YCbCr => match self.samples {
|
||||
3 => Ok(ColorType::YCbCr(self.bits_per_sample)),
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
vec![self.bits_per_sample; self.samples as usize],
|
||||
),
|
||||
)),
|
||||
},
|
||||
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
|
||||
match self.samples {
|
||||
1 => Ok(ColorType::Gray(self.bits_per_sample)),
|
||||
_ => Ok(ColorType::Multiband {
|
||||
bit_depth: self.bits_per_sample,
|
||||
num_samples: self.samples,
|
||||
}),
|
||||
}
|
||||
}
|
||||
// TODO: this is bad we should not fail at this point
|
||||
PhotometricInterpretation::RGBPalette
|
||||
| PhotometricInterpretation::TransparencyMask
|
||||
| PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
vec![self.bits_per_sample; self.samples as usize],
|
||||
),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_reader<'r, R: 'r + Read>(
|
||||
reader: R,
|
||||
#[allow(unused_variables)] photometric_interpretation: PhotometricInterpretation,
|
||||
compression_method: CompressionMethod,
|
||||
compressed_length: u64,
|
||||
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
|
||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||
Ok(match compression_method {
|
||||
CompressionMethod::None => Box::new(reader),
|
||||
#[cfg(feature = "lzw")]
|
||||
CompressionMethod::LZW => Box::new(super::stream::LZWReader::new(
|
||||
reader,
|
||||
usize::try_from(compressed_length)?,
|
||||
)),
|
||||
#[cfg(feature = "zstd")]
|
||||
CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?),
|
||||
CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)),
|
||||
#[cfg(feature = "deflate")]
|
||||
CompressionMethod::Deflate | CompressionMethod::OldDeflate => {
|
||||
Box::new(super::stream::DeflateReader::new(reader))
|
||||
}
|
||||
#[cfg(feature = "jpeg")]
|
||||
CompressionMethod::ModernJPEG => {
|
||||
use zune_jpeg::zune_core;
|
||||
|
||||
if jpeg_tables.is_some() && compressed_length < 2 {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
||||
));
|
||||
}
|
||||
|
||||
// Construct new jpeg_reader wrapping a SmartReader.
|
||||
//
|
||||
// JPEG compression in TIFF allows saving quantization and/or huffman tables in one
|
||||
// central location. These `jpeg_tables` are simply prepended to the remaining jpeg image data.
|
||||
// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
|
||||
// which is also at the beginning of the remaining JPEG image data and would
|
||||
// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
|
||||
// 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`).
|
||||
let mut jpeg_reader = match jpeg_tables {
|
||||
Some(jpeg_tables) => {
|
||||
let mut reader = reader.take(compressed_length);
|
||||
reader.read_exact(&mut [0; 2])?;
|
||||
|
||||
Box::new(
|
||||
Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2])
|
||||
.chain(reader.take(compressed_length)),
|
||||
) as Box<dyn Read>
|
||||
}
|
||||
None => Box::new(reader.take(compressed_length)),
|
||||
};
|
||||
|
||||
let mut jpeg_data = Vec::new();
|
||||
jpeg_reader.read_to_end(&mut jpeg_data)?;
|
||||
|
||||
let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
|
||||
let mut options: zune_core::options::DecoderOptions = Default::default();
|
||||
match photometric_interpretation {
|
||||
PhotometricInterpretation::RGB => {
|
||||
options =
|
||||
options.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::RGB);
|
||||
}
|
||||
PhotometricInterpretation::CMYK => {
|
||||
options = options
|
||||
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::CMYK);
|
||||
}
|
||||
PhotometricInterpretation::YCbCr => {
|
||||
options = options
|
||||
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::YCbCr);
|
||||
}
|
||||
PhotometricInterpretation::WhiteIsZero
|
||||
| PhotometricInterpretation::BlackIsZero
|
||||
| PhotometricInterpretation::TransparencyMask => {}
|
||||
PhotometricInterpretation::RGBPalette | PhotometricInterpretation::CIELab => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedInterpretation(
|
||||
photometric_interpretation,
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
decoder.set_options(options);
|
||||
|
||||
let data = decoder.decode()?;
|
||||
|
||||
Box::new(Cursor::new(data))
|
||||
}
|
||||
method => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Samples per pixel within chunk.
|
||||
///
|
||||
/// In planar config, samples are stored in separate strips/chunks, also called bands.
|
||||
///
|
||||
/// Example with `bits_per_sample = [8, 8, 8]` and `PhotometricInterpretation::RGB`:
|
||||
/// * `PlanarConfiguration::Chunky` -> 3 (RGBRGBRGB...)
|
||||
/// * `PlanarConfiguration::Planar` -> 1 (RRR...) (GGG...) (BBB...)
|
||||
pub(crate) fn samples_per_pixel(&self) -> usize {
|
||||
match self.planar_config {
|
||||
PlanarConfiguration::Chunky => self.samples.into(),
|
||||
PlanarConfiguration::Planar => 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of strips per pixel.
|
||||
pub(crate) fn strips_per_pixel(&self) -> usize {
|
||||
match self.planar_config {
|
||||
PlanarConfiguration::Chunky => 1,
|
||||
PlanarConfiguration::Planar => self.samples.into(),
|
||||
}
|
||||
}
|
||||
|
||||
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_attrs = self.strip_decoder.as_ref().unwrap();
|
||||
let strips_per_band =
|
||||
self.height.saturating_sub(1) / strip_attrs.rows_per_strip + 1;
|
||||
let strip_height_without_padding = (chunk_index % strips_per_band)
|
||||
.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,
|
||||
buf: &mut [u8],
|
||||
output_row_stride: usize,
|
||||
byte_order: ByteOrder,
|
||||
chunk_index: u32,
|
||||
limits: &Limits,
|
||||
) -> TiffResult<()> {
|
||||
// Validate that the color type is supported.
|
||||
let color_type = self.colortype()?;
|
||||
match color_type {
|
||||
ColorType::RGB(n)
|
||||
| ColorType::RGBA(n)
|
||||
| ColorType::CMYK(n)
|
||||
| ColorType::CMYKA(n)
|
||||
| ColorType::YCbCr(n)
|
||||
| ColorType::Gray(n)
|
||||
| ColorType::Multiband {
|
||||
bit_depth: n,
|
||||
num_samples: _,
|
||||
} if n == 8 || n == 16 || n == 32 || n == 64 => {}
|
||||
ColorType::Gray(n)
|
||||
| ColorType::Multiband {
|
||||
bit_depth: n,
|
||||
num_samples: _,
|
||||
} 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, self.sample_format) {
|
||||
(
|
||||
Predictor::Horizontal,
|
||||
SampleFormat::Int | SampleFormat::Uint | SampleFormat::IEEEFP,
|
||||
) => {}
|
||||
(Predictor::Horizontal, _) => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::HorizontalPredictor(color_type),
|
||||
));
|
||||
}
|
||||
(Predictor::FloatingPoint, SampleFormat::IEEEFP) => {}
|
||||
(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,
|
||||
))?;
|
||||
if *compressed_bytes > limits.intermediate_buffer_size as u64 {
|
||||
return Err(TiffError::LimitsExceeded);
|
||||
}
|
||||
|
||||
let compression_method = self.compression_method;
|
||||
let photometric_interpretation = self.photometric_interpretation;
|
||||
let predictor = self.predictor;
|
||||
let samples = self.samples_per_pixel();
|
||||
|
||||
let chunk_dims = self.chunk_dimensions()?;
|
||||
let data_dims = self.chunk_data_dimensions(chunk_index)?;
|
||||
|
||||
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
|
||||
|
||||
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let data_row_bytes: usize = ((data_row_bits + 7) / 8).try_into()?;
|
||||
|
||||
// TODO: Should these return errors instead?
|
||||
assert!(output_row_stride >= data_row_bytes);
|
||||
assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
|
||||
|
||||
let mut reader = Self::create_reader(
|
||||
reader,
|
||||
photometric_interpretation,
|
||||
compression_method,
|
||||
*compressed_bytes,
|
||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||
)?;
|
||||
|
||||
if output_row_stride == chunk_row_bytes as usize {
|
||||
let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
|
||||
reader.read_exact(tile)?;
|
||||
|
||||
for row in tile.chunks_mut(chunk_row_bytes as usize) {
|
||||
super::fix_endianness_and_predict(
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
samples,
|
||||
byte_order,
|
||||
predictor,
|
||||
);
|
||||
}
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(tile, color_type, self.sample_format);
|
||||
}
|
||||
} else if chunk_row_bytes > data_row_bytes && 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_row_bytes];
|
||||
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
|
||||
reader.read_exact(&mut encoded)?;
|
||||
|
||||
let row = &mut row[..data_row_bytes];
|
||||
match color_type.bit_depth() {
|
||||
16 => predict_f16(&mut encoded, row, samples),
|
||||
32 => predict_f32(&mut encoded, row, samples),
|
||||
64 => predict_f64(&mut encoded, row, samples),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(row, color_type, self.sample_format);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i, row) in buf
|
||||
.chunks_mut(output_row_stride)
|
||||
.take(data_dims.1 as usize)
|
||||
.enumerate()
|
||||
{
|
||||
let row = &mut row[..data_row_bytes];
|
||||
reader.read_exact(row)?;
|
||||
|
||||
// Skip horizontal padding
|
||||
if chunk_row_bytes > data_row_bytes {
|
||||
let len = u64::try_from(chunk_row_bytes - data_row_bytes)?;
|
||||
io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
|
||||
}
|
||||
|
||||
super::fix_endianness_and_predict(
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
samples,
|
||||
byte_order,
|
||||
predictor,
|
||||
);
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(row, color_type, self.sample_format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+547
-1060
File diff suppressed because it is too large.
Load diff
+137
-218
@@ -1,6 +1,6 @@
|
||||
//! All IO functionality needed for TIFF decoding
|
||||
|
||||
use std::io::{self, Read, Seek};
|
||||
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
||||
|
||||
/// Byte order of the TIFF file.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -12,16 +12,32 @@ pub enum ByteOrder {
|
||||
}
|
||||
|
||||
/// Reader that is aware of the byte order.
|
||||
pub trait EndianReader: Read {
|
||||
/// Byte order that should be adhered to
|
||||
fn byte_order(&self) -> ByteOrder;
|
||||
#[derive(Debug)]
|
||||
pub struct EndianReader<R> {
|
||||
reader: R,
|
||||
pub(crate) byte_order: ByteOrder,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> EndianReader<R> {
|
||||
pub fn new(reader: R, byte_order: ByteOrder) -> Self {
|
||||
Self { reader, byte_order }
|
||||
}
|
||||
|
||||
pub fn inner(&mut self) -> &mut R {
|
||||
&mut self.reader
|
||||
}
|
||||
|
||||
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
|
||||
self.reader.seek(io::SeekFrom::Start(offset))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads an u16
|
||||
#[inline(always)]
|
||||
fn read_u16(&mut self) -> Result<u16, io::Error> {
|
||||
pub fn read_u16(&mut self) -> Result<u16, io::Error> {
|
||||
let mut n = [0u8; 2];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => u16::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => u16::from_be_bytes(n),
|
||||
})
|
||||
@@ -29,10 +45,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an i8
|
||||
#[inline(always)]
|
||||
fn read_i8(&mut self) -> Result<i8, io::Error> {
|
||||
pub fn read_i8(&mut self) -> Result<i8, io::Error> {
|
||||
let mut n = [0u8; 1];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => i8::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i8::from_be_bytes(n),
|
||||
})
|
||||
@@ -40,10 +56,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an i16
|
||||
#[inline(always)]
|
||||
fn read_i16(&mut self) -> Result<i16, io::Error> {
|
||||
pub fn read_i16(&mut self) -> Result<i16, io::Error> {
|
||||
let mut n = [0u8; 2];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => i16::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i16::from_be_bytes(n),
|
||||
})
|
||||
@@ -51,10 +67,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an u32
|
||||
#[inline(always)]
|
||||
fn read_u32(&mut self) -> Result<u32, io::Error> {
|
||||
pub fn read_u32(&mut self) -> Result<u32, io::Error> {
|
||||
let mut n = [0u8; 4];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => u32::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => u32::from_be_bytes(n),
|
||||
})
|
||||
@@ -62,10 +78,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an i32
|
||||
#[inline(always)]
|
||||
fn read_i32(&mut self) -> Result<i32, io::Error> {
|
||||
pub fn read_i32(&mut self) -> Result<i32, io::Error> {
|
||||
let mut n = [0u8; 4];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => i32::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i32::from_be_bytes(n),
|
||||
})
|
||||
@@ -73,10 +89,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an u64
|
||||
#[inline(always)]
|
||||
fn read_u64(&mut self) -> Result<u64, io::Error> {
|
||||
pub fn read_u64(&mut self) -> Result<u64, io::Error> {
|
||||
let mut n = [0u8; 8];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => u64::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => u64::from_be_bytes(n),
|
||||
})
|
||||
@@ -84,10 +100,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an i64
|
||||
#[inline(always)]
|
||||
fn read_i64(&mut self) -> Result<i64, io::Error> {
|
||||
pub fn read_i64(&mut self) -> Result<i64, io::Error> {
|
||||
let mut n = [0u8; 8];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => i64::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i64::from_be_bytes(n),
|
||||
})
|
||||
@@ -95,10 +111,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an f32
|
||||
#[inline(always)]
|
||||
fn read_f32(&mut self) -> Result<f32, io::Error> {
|
||||
pub fn read_f32(&mut self) -> Result<f32, io::Error> {
|
||||
let mut n = [0u8; 4];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(f32::from_bits(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(f32::from_bits(match self.byte_order {
|
||||
ByteOrder::LittleEndian => u32::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => u32::from_be_bytes(n),
|
||||
}))
|
||||
@@ -106,10 +122,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an f64
|
||||
#[inline(always)]
|
||||
fn read_f64(&mut self) -> Result<f64, io::Error> {
|
||||
pub fn read_f64(&mut self) -> Result<f64, io::Error> {
|
||||
let mut n = [0u8; 8];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(f64::from_bits(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(f64::from_bits(match self.byte_order {
|
||||
ByteOrder::LittleEndian => u64::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => u64::from_be_bytes(n),
|
||||
}))
|
||||
@@ -124,127 +140,64 @@ pub trait EndianReader: Read {
|
||||
/// ## Deflate Reader
|
||||
///
|
||||
|
||||
pub type DeflateReader<'r, R> = flate2::read::ZlibDecoder<&'r mut SmartReader<R>>;
|
||||
#[cfg(feature = "deflate")]
|
||||
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
||||
|
||||
///
|
||||
/// ## LZW Reader
|
||||
///
|
||||
|
||||
/// Reader that decompresses LZW streams
|
||||
pub struct LZWReader {
|
||||
buffer: io::Cursor<Vec<u8>>,
|
||||
#[cfg(feature = "lzw")]
|
||||
pub struct LZWReader<R: Read> {
|
||||
reader: BufReader<Take<R>>,
|
||||
decoder: weezl::decode::Decoder,
|
||||
}
|
||||
|
||||
impl LZWReader {
|
||||
#[cfg(feature = "lzw")]
|
||||
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;
|
||||
#[cfg(feature = "lzw")]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7))
|
||||
///
|
||||
|
||||
pub(crate) struct JpegReader {
|
||||
buffer: io::Cursor<Vec<u8>>,
|
||||
}
|
||||
impl JpegReader {
|
||||
/// Constructs new JpegReader wrapping a SmartReader.
|
||||
/// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one
|
||||
/// central location, the constructor accepts this data as `jpeg_tables` here containing either
|
||||
/// or both.
|
||||
/// These `jpeg_tables` are simply prepended to the remaining jpeg image data.
|
||||
/// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
|
||||
/// which is also at the beginning of the remaining JPEG image data and would
|
||||
/// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
|
||||
/// 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,
|
||||
{
|
||||
// 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..]);
|
||||
|
||||
Ok(JpegReader {
|
||||
buffer: io::Cursor::new(jpeg_data),
|
||||
})
|
||||
}
|
||||
None => Ok(JpegReader {
|
||||
buffer: io::Cursor::new(segment),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for JpegReader {
|
||||
// #[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.buffer.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,104 +205,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;
|
||||
if (-127..=-1).contains(&h) {
|
||||
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!(),
|
||||
};
|
||||
|
||||
///
|
||||
/// ## SmartReader Reader
|
||||
///
|
||||
|
||||
/// Reader that is aware of the byte order.
|
||||
#[derive(Debug)]
|
||||
pub struct SmartReader<R>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
reader: R,
|
||||
pub byte_order: ByteOrder,
|
||||
}
|
||||
|
||||
impl<R> SmartReader<R>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
/// Wraps a reader
|
||||
pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> {
|
||||
SmartReader { reader, byte_order }
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> EndianReader for SmartReader<R>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn byte_order(&self) -> ByteOrder {
|
||||
self.byte_order
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for SmartReader<R> {
|
||||
#[inline]
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.reader.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> Seek for SmartReader<R> {
|
||||
#[inline]
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||
self.reader.seek(pos)
|
||||
self.count -= actual;
|
||||
if self.count == 0 {
|
||||
self.state = PackBitsReaderState::Header;
|
||||
}
|
||||
Ok(actual)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +285,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,44 @@
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
use crate::tags::Tag;
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use super::ifd::{Directory, Value};
|
||||
use super::stream::EndianReader;
|
||||
use super::Limits;
|
||||
|
||||
pub(crate) struct TagReader<'a, R: Read + Seek> {
|
||||
pub reader: &'a mut EndianReader<R>,
|
||||
pub ifd: &'a Directory,
|
||||
pub limits: &'a Limits,
|
||||
pub bigtiff: bool,
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,31 @@
|
||||
use crate::tags::{PhotometricInterpretation, SampleFormat};
|
||||
|
||||
macro_rules! integer_horizontal_predict {
|
||||
() => {
|
||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
|
||||
let sample_size = Self::SAMPLE_FORMAT.len();
|
||||
|
||||
if row.len() < sample_size {
|
||||
debug_assert!(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let (start, rest) = row.split_at(sample_size);
|
||||
|
||||
result.extend_from_slice(start);
|
||||
if result.capacity() - result.len() < rest.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
result.extend(
|
||||
row.into_iter()
|
||||
.zip(rest)
|
||||
.map(|(prev, current)| current.wrapping_sub(*prev)),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Trait for different colortypes that can be encoded.
|
||||
pub trait ColorType {
|
||||
/// The type of each sample of this colortype
|
||||
@@ -10,6 +36,8 @@ pub trait ColorType {
|
||||
const BITS_PER_SAMPLE: &'static [u16];
|
||||
/// The value of the tiff tag `SampleFormat`
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat];
|
||||
|
||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
|
||||
}
|
||||
|
||||
pub struct Gray8;
|
||||
@@ -18,6 +46,18 @@ impl ColorType for Gray8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray16;
|
||||
@@ -26,6 +66,18 @@ impl ColorType for Gray16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray32;
|
||||
@@ -34,6 +86,18 @@ impl ColorType for Gray32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray32Float;
|
||||
@@ -42,6 +106,10 @@ impl ColorType for Gray32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Gray64;
|
||||
@@ -50,6 +118,18 @@ impl ColorType for Gray64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray64Float;
|
||||
@@ -58,6 +138,10 @@ impl ColorType for Gray64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGB8;
|
||||
@@ -66,6 +150,8 @@ impl ColorType for RGB8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB16;
|
||||
@@ -74,6 +160,8 @@ impl ColorType for RGB16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB32;
|
||||
@@ -82,6 +170,8 @@ impl ColorType for RGB32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB32Float;
|
||||
@@ -90,6 +180,9 @@ impl ColorType for RGB32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGB64;
|
||||
@@ -98,6 +191,8 @@ impl ColorType for RGB64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB64Float;
|
||||
@@ -106,6 +201,9 @@ impl ColorType for RGB64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGBA8;
|
||||
@@ -114,6 +212,8 @@ impl ColorType for RGBA8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA16;
|
||||
@@ -122,6 +222,8 @@ impl ColorType for RGBA16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA32;
|
||||
@@ -130,6 +232,8 @@ impl ColorType for RGBA32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA32Float;
|
||||
@@ -138,6 +242,9 @@ impl ColorType for RGBA32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGBA64;
|
||||
@@ -146,6 +253,8 @@ impl ColorType for RGBA64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA64Float;
|
||||
@@ -154,6 +263,9 @@ impl ColorType for RGBA64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CMYK8;
|
||||
@@ -162,6 +274,8 @@ impl ColorType for CMYK8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK16;
|
||||
@@ -170,6 +284,8 @@ impl ColorType for CMYK16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK32;
|
||||
@@ -178,6 +294,8 @@ impl ColorType for CMYK32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK32Float;
|
||||
@@ -186,6 +304,10 @@ impl ColorType for CMYK32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CMYK64;
|
||||
@@ -194,6 +316,8 @@ impl ColorType for CMYK64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK64Float;
|
||||
@@ -202,4 +326,28 @@ impl ColorType for CMYK64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYKA8;
|
||||
impl ColorType for CMYKA8 {
|
||||
type Inner = u8;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 5];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use crate::encoder::compression::*;
|
||||
use flate2::{write::ZlibEncoder, Compression as FlateCompression};
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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,46 @@
|
||||
use crate::encoder::compression::*;
|
||||
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.write_to(&mut writer, TEST_DATA).unwrap();
|
||||
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use crate::tags::CompressionMethod;
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[cfg(feature = "deflate")]
|
||||
mod deflate;
|
||||
#[cfg(feature = "lzw")]
|
||||
mod lzw;
|
||||
mod packbits;
|
||||
mod uncompressed;
|
||||
|
||||
#[cfg(feature = "deflate")]
|
||||
pub use self::deflate::{Deflate, DeflateLevel};
|
||||
|
||||
#[cfg(feature = "lzw")]
|
||||
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.
|
||||
#[non_exhaustive]
|
||||
pub enum Compressor {
|
||||
Uncompressed(Uncompressed),
|
||||
#[cfg(feature = "lzw")]
|
||||
Lzw(Lzw),
|
||||
#[cfg(feature = "deflate")]
|
||||
Deflate(Deflate),
|
||||
Packbits(Packbits),
|
||||
}
|
||||
|
||||
impl Default for Compressor {
|
||||
/// The default compression strategy does not apply any compression.
|
||||
fn default() -> Self {
|
||||
Compressor::Uncompressed(Uncompressed)
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
#[cfg(feature = "lzw")]
|
||||
Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes),
|
||||
#[cfg(feature = "deflate")]
|
||||
Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes),
|
||||
Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub const TEST_DATA: &[u8] = b"This is a string for checking various compression algorithms.";
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
use crate::encoder::compression::*;
|
||||
use std::io::{BufWriter, Error, ErrorKind};
|
||||
|
||||
/// 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_all(&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_all(&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_all(&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.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: &[u8] =
|
||||
b"This strrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrring hangs.";
|
||||
const EXPECTED_COMPRESSED_DATA: &[u8] = b"\x06This st\xD1r\x09ing hangs.";
|
||||
|
||||
let mut compressed_data = Vec::<u8>::new();
|
||||
let mut writer = Cursor::new(&mut compressed_data);
|
||||
Packbits.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.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: &[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.write_to(&mut writer, TEST_DATA).unwrap();
|
||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use crate::encoder::compression::*;
|
||||
|
||||
/// 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.write_to(&mut writer, TEST_DATA).unwrap();
|
||||
assert_eq!(TEST_DATA, compressed_data);
|
||||
}
|
||||
}
|
||||
+158
-20
@@ -3,7 +3,6 @@ pub use tiff_value::*;
|
||||
use std::{
|
||||
cmp,
|
||||
collections::BTreeMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
io::{self, Seek, Write},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
@@ -11,17 +10,73 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::TiffResult,
|
||||
tags::{self, ResolutionUnit, Tag},
|
||||
error::{TiffResult, UsageError},
|
||||
tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag},
|
||||
TiffError, TiffFormatError,
|
||||
};
|
||||
|
||||
pub mod colortype;
|
||||
pub mod compression;
|
||||
mod tiff_value;
|
||||
mod writer;
|
||||
|
||||
use self::colortype::*;
|
||||
use self::compression::Compression as Comp;
|
||||
use self::compression::*;
|
||||
use self::writer::*;
|
||||
|
||||
/// Type of prediction to prepare the image with.
|
||||
///
|
||||
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
|
||||
/// passes ran over the image data to prepare it for compression. This is mostly used for LZW
|
||||
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
|
||||
/// ratio over the unpredicted compression !
|
||||
///
|
||||
/// [Predictor::FloatingPoint] is currently not supported.
|
||||
pub type Predictor = crate::tags::Predictor;
|
||||
#[cfg(feature = "deflate")]
|
||||
pub type DeflateLevel = compression::DeflateLevel;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Compression {
|
||||
Uncompressed,
|
||||
#[cfg(feature = "lzw")]
|
||||
Lzw,
|
||||
#[cfg(feature = "deflate")]
|
||||
Deflate(DeflateLevel),
|
||||
Packbits,
|
||||
}
|
||||
|
||||
impl Default for Compression {
|
||||
fn default() -> Self {
|
||||
Self::Uncompressed
|
||||
}
|
||||
}
|
||||
|
||||
impl Compression {
|
||||
fn tag(&self) -> CompressionMethod {
|
||||
match self {
|
||||
Compression::Uncompressed => CompressionMethod::None,
|
||||
#[cfg(feature = "lzw")]
|
||||
Compression::Lzw => CompressionMethod::LZW,
|
||||
#[cfg(feature = "deflate")]
|
||||
Compression::Deflate(_) => CompressionMethod::Deflate,
|
||||
Compression::Packbits => CompressionMethod::PackBits,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_algorithm(&self) -> Compressor {
|
||||
match self {
|
||||
Compression::Uncompressed => compression::Uncompressed {}.get_algorithm(),
|
||||
#[cfg(feature = "lzw")]
|
||||
Compression::Lzw => compression::Lzw {}.get_algorithm(),
|
||||
#[cfg(feature = "deflate")]
|
||||
Compression::Deflate(level) => compression::Deflate::with_level(*level).get_algorithm(),
|
||||
Compression::Packbits => compression::Packbits {}.get_algorithm(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encoder for Tiff and BigTiff files.
|
||||
///
|
||||
/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder`
|
||||
@@ -50,6 +105,8 @@ use self::writer::*;
|
||||
pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> {
|
||||
writer: TiffWriter<W>,
|
||||
kind: PhantomData<K>,
|
||||
predictor: Predictor,
|
||||
compression: Compression,
|
||||
}
|
||||
|
||||
/// Constructor functions to create standard Tiff files.
|
||||
@@ -81,6 +138,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
let mut encoder = TiffEncoder {
|
||||
writer: TiffWriter::new(writer),
|
||||
kind: PhantomData,
|
||||
predictor: Predictor::None,
|
||||
compression: Compression::Uncompressed,
|
||||
};
|
||||
|
||||
K::write_header(&mut encoder.writer)?;
|
||||
@@ -88,6 +147,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
Ok(encoder)
|
||||
}
|
||||
|
||||
/// Set the predictor to use
|
||||
///
|
||||
/// A predictor is used to simplify the file before writing it. This is very
|
||||
/// useful when writing a file compressed using LZW as it can improve efficiency
|
||||
pub fn with_predictor(mut self, predictor: Predictor) -> Self {
|
||||
self.predictor = predictor;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the compression method to use
|
||||
pub fn with_compression(mut self, compression: Compression) -> Self {
|
||||
self.compression = compression;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> {
|
||||
DirectoryEncoder::new(&mut self.writer)
|
||||
@@ -100,7 +176,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
height: u32,
|
||||
) -> TiffResult<ImageEncoder<W, C, K>> {
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
ImageEncoder::new(encoder, width, height)
|
||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
||||
}
|
||||
|
||||
/// Convenience function to write an entire image from memory.
|
||||
@@ -114,7 +190,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
[C::Inner]: TiffValue,
|
||||
{
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?;
|
||||
let image: ImageEncoder<W, C, K> =
|
||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?;
|
||||
image.write_data(data)
|
||||
}
|
||||
}
|
||||
@@ -188,19 +265,19 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
|
||||
let offset = self.writer.offset();
|
||||
|
||||
K::write_entry_count(&mut self.writer, self.ifd.len())?;
|
||||
K::write_entry_count(self.writer, self.ifd.len())?;
|
||||
for (
|
||||
tag,
|
||||
&DirectoryEntry {
|
||||
data_type: ref field_type,
|
||||
ref count,
|
||||
data: ref offset,
|
||||
DirectoryEntry {
|
||||
data_type: field_type,
|
||||
count,
|
||||
data: offset,
|
||||
},
|
||||
) in self.ifd.iter()
|
||||
{
|
||||
self.writer.write_u16(*tag)?;
|
||||
self.writer.write_u16(*field_type)?;
|
||||
(*count).write(&mut self.writer)?;
|
||||
(*count).write(self.writer)?;
|
||||
self.writer.write_bytes(offset)?;
|
||||
}
|
||||
|
||||
@@ -212,18 +289,23 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
/// This could be used to write tiff strips.
|
||||
pub fn write_data<T: TiffValue>(&mut self, value: T) -> TiffResult<u64> {
|
||||
let offset = self.writer.offset();
|
||||
value.write(&mut self.writer)?;
|
||||
value.write(self.writer)?;
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/// Provides the number of bytes written by the underlying TiffWriter during the last call.
|
||||
fn last_written(&self) -> u64 {
|
||||
self.writer.last_written()
|
||||
}
|
||||
|
||||
fn finish_internal(&mut self) -> TiffResult<()> {
|
||||
let ifd_pointer = self.write_directory()?;
|
||||
let curr_pos = self.writer.offset();
|
||||
|
||||
self.writer.goto_offset(self.ifd_pointer_pos)?;
|
||||
K::write_offset(&mut self.writer, ifd_pointer)?;
|
||||
K::write_offset(self.writer, ifd_pointer)?;
|
||||
self.writer.goto_offset(curr_pos)?;
|
||||
K::write_offset(&mut self.writer, 0)?;
|
||||
K::write_offset(self.writer, 0)?;
|
||||
|
||||
self.dropped = true;
|
||||
|
||||
@@ -288,23 +370,57 @@ 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: Compression,
|
||||
predictor: Predictor,
|
||||
_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> {
|
||||
fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
|
||||
match (predictor, compression, T::SAMPLE_FORMAT[0]) {
|
||||
(Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
|
||||
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
||||
}
|
||||
(Predictor::FloatingPoint, _, _) => {
|
||||
Err(TiffError::UsageError(UsageError::PredictorUnavailable))
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(
|
||||
mut encoder: DirectoryEncoder<'a, W, K>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
compression: Compression,
|
||||
predictor: Predictor,
|
||||
) -> TiffResult<Self> {
|
||||
if width == 0 || height == 0 {
|
||||
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
|
||||
width, height,
|
||||
)));
|
||||
}
|
||||
|
||||
Self::sanity_check(compression, predictor)?;
|
||||
|
||||
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 compression.tag() {
|
||||
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, compression.tag().to_u16())?;
|
||||
encoder.write_tag(Tag::Predictor, predictor.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 +448,8 @@ 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,
|
||||
predictor,
|
||||
_phantom: ::std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
@@ -354,7 +472,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 +481,23 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
.into());
|
||||
}
|
||||
|
||||
let offset = self.encoder.write_data(value)?;
|
||||
// Write the (possible compressed) data to the encoder.
|
||||
let offset = match self.predictor {
|
||||
Predictor::None => self.encoder.write_data(value)?,
|
||||
Predictor::Horizontal => {
|
||||
let mut row_result = Vec::with_capacity(value.len());
|
||||
for row in value.chunks_exact(self.row_samples as usize) {
|
||||
T::horizontal_predict(row, &mut row_result);
|
||||
}
|
||||
self.encoder.write_data(row_result.as_slice())?
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
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 +523,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(())
|
||||
}
|
||||
|
||||
+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);
|
||||
|
||||
+64
-24
@@ -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(())
|
||||
}
|
||||
@@ -142,7 +182,7 @@ impl<W: Write> TiffWriter<W> {
|
||||
impl<W: Seek> TiffWriter<W> {
|
||||
pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> {
|
||||
self.offset = offset;
|
||||
self.writer.seek(SeekFrom::Start(offset as u64))?;
|
||||
self.writer.seek(SeekFrom::Start(offset))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+180
-245
@@ -1,259 +1,192 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::str;
|
||||
use std::string;
|
||||
|
||||
use crate::decoder::{ifd::Value, ChunkType};
|
||||
use quick_error::quick_error;
|
||||
|
||||
use crate::decoder::ChunkType;
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
|
||||
};
|
||||
use crate::ColorType;
|
||||
|
||||
/// Tiff error kinds.
|
||||
#[derive(Debug)]
|
||||
pub enum TiffError {
|
||||
/// The Image is not formatted properly.
|
||||
FormatError(TiffFormatError),
|
||||
quick_error! {
|
||||
/// Tiff error kinds.
|
||||
#[derive(Debug)]
|
||||
pub enum TiffError {
|
||||
/// The Image is not formatted properly.
|
||||
FormatError(err: TiffFormatError) {
|
||||
display("format error: {err}")
|
||||
from()
|
||||
source(err)
|
||||
}
|
||||
/// The Decoder does not support features required by the image.
|
||||
UnsupportedError(err: TiffUnsupportedError) {
|
||||
display("unsupported error: {err}")
|
||||
from()
|
||||
source(err)
|
||||
}
|
||||
/// An I/O Error occurred while decoding the image.
|
||||
IoError(err: io::Error) {
|
||||
display("{err}")
|
||||
from()
|
||||
source(err)
|
||||
}
|
||||
/// The Limits of the Decoder is exceeded.
|
||||
LimitsExceeded {
|
||||
display("decoder limits exceeded")
|
||||
}
|
||||
/// An integer conversion to or from a platform size failed.
|
||||
IntSizeError {
|
||||
display("platform or format size limits exceeded")
|
||||
}
|
||||
/// The image does not support the requested operation
|
||||
UsageError(err: UsageError) {
|
||||
display("usage error: {err}")
|
||||
from()
|
||||
source(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quick_error! {
|
||||
/// The image is not formatted properly.
|
||||
///
|
||||
/// This indicates that the encoder producing the image might behave incorrectly or that the
|
||||
/// input file has been corrupted.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum TiffFormatError {
|
||||
TiffSignatureNotFound {
|
||||
display("TIFF signature not found")
|
||||
}
|
||||
TiffSignatureInvalid {
|
||||
display("TIFF signature invalid")
|
||||
}
|
||||
ImageFileDirectoryNotFound {
|
||||
display("image file directory not found")
|
||||
}
|
||||
InconsistentSizesEncountered {
|
||||
display("inconsistent sizes encountered")
|
||||
}
|
||||
InvalidDimensions(width: u32, height: u32) {
|
||||
display("invalid dimensions: {width}x{height}")
|
||||
}
|
||||
InvalidTag {
|
||||
display("image contains invalid tag")
|
||||
}
|
||||
InvalidTagValueType(tag: Tag) {
|
||||
display("tag `{tag:?}` did not have the expected value type")
|
||||
}
|
||||
RequiredTagNotFound(tag: Tag) {
|
||||
display("required tag `{tag:?}` not found")
|
||||
}
|
||||
UnknownPredictor(pred: u16) {
|
||||
display("unknown predictor “{pred}” encountered")
|
||||
}
|
||||
UnknownPlanarConfiguration(cfg: u16) {
|
||||
display("unknown planar configuration “{cfg}”")
|
||||
}
|
||||
InvalidTypeForTag {
|
||||
display("tag has invalid type")
|
||||
}
|
||||
StripTileTagConflict {
|
||||
display("file should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found")
|
||||
}
|
||||
CycleInOffsets {
|
||||
display("file contained a cycle in the list of IFDs")
|
||||
}
|
||||
SamplesPerPixelIsZero {
|
||||
display("samples per pixel is zero")
|
||||
}
|
||||
CompressedDataCorrupt(message: String) {
|
||||
display("compressed data is corrupt: {message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quick_error! {
|
||||
/// The Decoder does not support features required by the image.
|
||||
UnsupportedError(TiffUnsupportedError),
|
||||
|
||||
/// An I/O Error occurred while decoding the image.
|
||||
IoError(io::Error),
|
||||
|
||||
/// The Limits of the Decoder is exceeded.
|
||||
LimitsExceeded,
|
||||
|
||||
/// An integer conversion to or from a platform size failed, either due to
|
||||
/// limits of the platform size or limits of the format.
|
||||
IntSizeError,
|
||||
|
||||
/// The image does not support the requested operation
|
||||
UsageError(UsageError),
|
||||
}
|
||||
|
||||
/// The image is not formatted properly.
|
||||
///
|
||||
/// This indicates that the encoder producing the image might behave incorrectly or that the input
|
||||
/// file has been corrupted.
|
||||
///
|
||||
/// 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)]
|
||||
pub enum TiffFormatError {
|
||||
TiffSignatureNotFound,
|
||||
TiffSignatureInvalid,
|
||||
ImageFileDirectoryNotFound,
|
||||
InconsistentSizesEncountered,
|
||||
UnexpectedCompressedData {
|
||||
actual_bytes: usize,
|
||||
required_bytes: usize,
|
||||
},
|
||||
InconsistentStripSamples {
|
||||
actual_samples: usize,
|
||||
required_samples: usize,
|
||||
},
|
||||
InvalidTag,
|
||||
InvalidTagValueType(Tag),
|
||||
RequiredTagNotFound(Tag),
|
||||
UnknownPredictor(u16),
|
||||
ByteExpected(Value),
|
||||
UnsignedIntegerExpected(Value),
|
||||
SignedIntegerExpected(Value),
|
||||
Format(String),
|
||||
RequiredTagEmpty(Tag),
|
||||
#[doc(hidden)]
|
||||
/// Do not match against this variant. It may get removed.
|
||||
__NonExhaustive,
|
||||
}
|
||||
|
||||
impl fmt::Display for TiffFormatError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use self::TiffFormatError::*;
|
||||
match *self {
|
||||
TiffSignatureNotFound => write!(fmt, "TIFF signature not found."),
|
||||
TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."),
|
||||
ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
|
||||
InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."),
|
||||
UnexpectedCompressedData {
|
||||
actual_bytes,
|
||||
required_bytes,
|
||||
} => {
|
||||
write!(
|
||||
fmt,
|
||||
"Decompression returned different amount of bytes than expected: got {}, expected {}.",
|
||||
actual_bytes, required_bytes
|
||||
)
|
||||
}
|
||||
InconsistentStripSamples {
|
||||
actual_samples,
|
||||
required_samples,
|
||||
} => {
|
||||
write!(
|
||||
fmt,
|
||||
"Inconsistent elements in strip: got {}, expected {}.",
|
||||
actual_samples, required_samples
|
||||
)
|
||||
}
|
||||
InvalidTag => write!(fmt, "Image contains invalid tag."),
|
||||
InvalidTagValueType(ref tag) => {
|
||||
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
|
||||
}
|
||||
RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag),
|
||||
UnknownPredictor(ref predictor) => {
|
||||
write!(fmt, "Unknown predictor “{}” encountered", predictor)
|
||||
}
|
||||
ByteExpected(ref val) => write!(fmt, "Expected byte, {:?} found.", val),
|
||||
UnsignedIntegerExpected(ref val) => {
|
||||
write!(fmt, "Expected unsigned integer, {:?} found.", val)
|
||||
}
|
||||
SignedIntegerExpected(ref val) => {
|
||||
write!(fmt, "Expected signed integer, {:?} found.", val)
|
||||
}
|
||||
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
|
||||
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
|
||||
__NonExhaustive => unreachable!(),
|
||||
///
|
||||
/// This only captures known failures for which the standard either does not require support or an
|
||||
/// implementation has been planned but not yet completed. Some variants may become unused over
|
||||
/// time and will then get deprecated before being removed.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum TiffUnsupportedError {
|
||||
FloatingPointPredictor(color_type: ColorType) {
|
||||
display("floating point predictor for {color_type:?} is unsupported")
|
||||
}
|
||||
HorizontalPredictor(color_type: ColorType) {
|
||||
display("horizontal predictor for {color_type:?} is unsupported")
|
||||
}
|
||||
InconsistentBitsPerSample(bits_per_sample: Vec<u8>) {
|
||||
display("inconsistent bits per sample: {bits_per_sample:?}")
|
||||
}
|
||||
InterpretationWithBits(interpretation: PhotometricInterpretation, bits_per_sample: Vec<u8>) {
|
||||
display("Photometric interpretation {interpretation:?} with bits per sample {bits_per_sample:?} is unsupported")
|
||||
}
|
||||
UnknownInterpretation {
|
||||
display("unknown photometric interpretation")
|
||||
}
|
||||
UnknownCompressionMethod {
|
||||
display("unknown compression method")
|
||||
}
|
||||
UnsupportedCompressionMethod(method: CompressionMethod) {
|
||||
display("compression method {method:?} is unsupported")
|
||||
}
|
||||
UnsupportedSampleDepth(depth: u8) {
|
||||
display("{depth} samples per pixel is unsupported")
|
||||
}
|
||||
UnsupportedSampleFormat(sample_format: Vec<SampleFormat>) {
|
||||
display("sample format {sample_format:?} is unsupported")
|
||||
}
|
||||
UnsupportedColorType(color_type: ColorType) {
|
||||
display("color type {color_type:?} is unsupported")
|
||||
}
|
||||
UnsupportedBitsPerChannel(bits_per_channel: u8) {
|
||||
display("{bits_per_channel} bits per channel not supported")
|
||||
}
|
||||
UnsupportedPlanarConfig(planar: Option<PlanarConfiguration>) {
|
||||
display("unsupported planar configuration “{planar:?}”")
|
||||
}
|
||||
UnsupportedDataType {
|
||||
display("unsupported data type.")
|
||||
}
|
||||
UnsupportedInterpretation(interpretation: PhotometricInterpretation) {
|
||||
display("unsupported photometric interpretation \"{interpretation:?}\"")
|
||||
}
|
||||
MisalignedTileBoundaries {
|
||||
display("tile rows are not aligned to byte boundaries")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Decoder does not support features required by the image.
|
||||
///
|
||||
/// This only captures known failures for which the standard either does not require support or an
|
||||
/// implementation has been planned but not yet completed. Some variants may become unused over
|
||||
/// time and will then get deprecated before being removed.
|
||||
///
|
||||
/// The list of variants may grow. Matching against this exhaustively is not covered by interface
|
||||
/// stability guarantees.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum TiffUnsupportedError {
|
||||
HorizontalPredictor(ColorType),
|
||||
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
|
||||
UnknownInterpretation,
|
||||
UnknownCompressionMethod,
|
||||
UnsupportedCompressionMethod(CompressionMethod),
|
||||
UnsupportedSampleDepth(u8),
|
||||
UnsupportedSampleFormat(Vec<SampleFormat>),
|
||||
UnsupportedColorType(ColorType),
|
||||
UnsupportedBitsPerChannel(u8),
|
||||
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
|
||||
UnsupportedDataType,
|
||||
#[doc(hidden)]
|
||||
/// Do not match against this variant. It may get removed.
|
||||
__NonExhaustive,
|
||||
}
|
||||
|
||||
impl fmt::Display for TiffUnsupportedError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use self::TiffUnsupportedError::*;
|
||||
match *self {
|
||||
HorizontalPredictor(color_type) => write!(
|
||||
fmt,
|
||||
"Horizontal predictor for {:?} is unsupported.",
|
||||
color_type
|
||||
),
|
||||
InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => write!(
|
||||
fmt,
|
||||
"{:?} with {:?} bits per sample is unsupported",
|
||||
photometric_interpretation, bits_per_sample
|
||||
),
|
||||
UnknownInterpretation => write!(
|
||||
fmt,
|
||||
"The image is using an unknown photometric interpretation."
|
||||
),
|
||||
UnknownCompressionMethod => write!(fmt, "Unknown compression method."),
|
||||
UnsupportedCompressionMethod(method) => {
|
||||
write!(fmt, "Compression method {:?} is unsupported", method)
|
||||
}
|
||||
UnsupportedSampleDepth(samples) => {
|
||||
write!(fmt, "{} samples per pixel is unsupported.", samples)
|
||||
}
|
||||
UnsupportedSampleFormat(ref formats) => {
|
||||
write!(fmt, "Sample format {:?} is unsupported.", formats)
|
||||
}
|
||||
UnsupportedColorType(color_type) => {
|
||||
write!(fmt, "Color type {:?} is unsupported", color_type)
|
||||
}
|
||||
UnsupportedBitsPerChannel(bits) => {
|
||||
write!(fmt, "{} bits per channel not supported", bits)
|
||||
}
|
||||
UnsupportedPlanarConfig(config) => {
|
||||
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
||||
}
|
||||
UnsupportedDataType => write!(fmt, "Unsupported data type."),
|
||||
__NonExhaustive => unreachable!(),
|
||||
quick_error! {
|
||||
/// User attempted to use the Decoder in a way that is incompatible with a specific image.
|
||||
///
|
||||
/// For example: attempting to read a tile from a stripped image.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum UsageError {
|
||||
InvalidChunkType(expected: ChunkType, actual: ChunkType) {
|
||||
display("requested operation is only valid for images with chunk encoding of type {expected:?} but got {actual:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// User attempted to use the Decoder in a way that is incompatible with a specific image.
|
||||
///
|
||||
/// For example: attempting to read a tile from a stripped image.
|
||||
#[derive(Debug)]
|
||||
pub enum UsageError {
|
||||
InvalidChunkType(ChunkType, ChunkType),
|
||||
}
|
||||
|
||||
impl fmt::Display for UsageError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use self::UsageError::*;
|
||||
match *self {
|
||||
InvalidChunkType(expected, actual) => {
|
||||
write!(
|
||||
fmt,
|
||||
"Requested operation is only valid for images with chunk encoding of type: {:?}, got {:?}.",
|
||||
expected, actual
|
||||
)
|
||||
}
|
||||
InvalidChunkIndex(index: u32) {
|
||||
display("invalid chunk index ({index}) requested")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TiffError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
TiffError::FormatError(ref e) => write!(fmt, "Format error: {}", e),
|
||||
TiffError::UnsupportedError(ref f) => write!(
|
||||
fmt,
|
||||
"The Decoder does not support the \
|
||||
image format `{}`",
|
||||
f
|
||||
),
|
||||
TiffError::IoError(ref e) => e.fmt(fmt),
|
||||
TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"),
|
||||
TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"),
|
||||
TiffError::UsageError(ref e) => write!(fmt, "Usage error: {}", e),
|
||||
PredictorCompressionMismatch {
|
||||
display("requested predictor is not compatible with the requested compression")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TiffError {
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
TiffError::FormatError(..) => "Format error",
|
||||
TiffError::UnsupportedError(..) => "Unsupported error",
|
||||
TiffError::IoError(..) => "IO error",
|
||||
TiffError::LimitsExceeded => "Decoder limits exceeded",
|
||||
TiffError::IntSizeError => "Platform or format size limits exceeded",
|
||||
TiffError::UsageError(..) => "Invalid usage",
|
||||
PredictorIncompatible {
|
||||
display("the requested predictor is not compatible with the image's format")
|
||||
}
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn Error> {
|
||||
match *self {
|
||||
TiffError::IoError(ref e) => Some(e),
|
||||
_ => None,
|
||||
PredictorUnavailable {
|
||||
display("the requested predictor is not available")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for TiffError {
|
||||
fn from(err: io::Error) -> TiffError {
|
||||
TiffError::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<str::Utf8Error> for TiffError {
|
||||
fn from(_err: str::Utf8Error) -> TiffError {
|
||||
TiffError::FormatError(TiffFormatError::InvalidTag)
|
||||
@@ -266,23 +199,25 @@ impl From<string::FromUtf8Error> for TiffError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TiffFormatError> for TiffError {
|
||||
fn from(err: TiffFormatError) -> TiffError {
|
||||
TiffError::FormatError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TiffUnsupportedError> for TiffError {
|
||||
fn from(err: TiffUnsupportedError) -> TiffError {
|
||||
TiffError::UnsupportedError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::num::TryFromIntError> for TiffError {
|
||||
fn from(_err: std::num::TryFromIntError) -> TiffError {
|
||||
TiffError::IntSizeError
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "lzw")]
|
||||
impl From<weezl::LzwError> for TiffError {
|
||||
fn from(err: weezl::LzwError) -> TiffError {
|
||||
TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "jpeg")]
|
||||
impl From<zune_jpeg::errors::DecodeErrors> for TiffError {
|
||||
fn from(err: zune_jpeg::errors::DecodeErrors) -> Self {
|
||||
TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of an image decoding/encoding process
|
||||
pub type TiffResult<T> = Result<T, TiffError>;
|
||||
+26
-4
@@ -4,10 +4,7 @@
|
||||
//! lossless and lossy compression.
|
||||
//!
|
||||
//! # Related Links
|
||||
//! * <https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
||||
|
||||
extern crate jpeg;
|
||||
extern crate weezl;
|
||||
//! * <https://web.archive.org/web/20210108073850/https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
||||
|
||||
mod bytecast;
|
||||
pub mod decoder;
|
||||
@@ -19,6 +16,7 @@ pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedErr
|
||||
|
||||
/// An enumeration over supported color types and their bit depths
|
||||
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum ColorType {
|
||||
/// Pixel is grayscale
|
||||
Gray(u8),
|
||||
@@ -37,4 +35,28 @@ pub enum ColorType {
|
||||
|
||||
/// Pixel is CMYK
|
||||
CMYK(u8),
|
||||
|
||||
/// Pixel is CMYK with an alpha channel
|
||||
CMYKA(u8),
|
||||
|
||||
/// Pixel is YCbCr
|
||||
YCbCr(u8),
|
||||
|
||||
/// Pixel has multiple bands/channels
|
||||
Multiband { bit_depth: u8, num_samples: u16 },
|
||||
}
|
||||
impl ColorType {
|
||||
fn bit_depth(&self) -> u8 {
|
||||
match *self {
|
||||
ColorType::Gray(b)
|
||||
| ColorType::RGB(b)
|
||||
| ColorType::Palette(b)
|
||||
| ColorType::GrayA(b)
|
||||
| ColorType::RGBA(b)
|
||||
| ColorType::CMYK(b)
|
||||
| ColorType::CMYKA(b)
|
||||
| ColorType::YCbCr(b)
|
||||
| ColorType::Multiband { bit_depth: b, .. } => b,
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-5
@@ -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!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,7 +172,7 @@ pub enum Type(u16) {
|
||||
tags! {
|
||||
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||
/// for reference.
|
||||
pub enum CompressionMethod(u16) {
|
||||
pub enum CompressionMethod(u16) unknown("A custom compression method") {
|
||||
None = 1,
|
||||
Huffman = 2,
|
||||
Fax3 = 3,
|
||||
@@ -187,6 +184,9 @@ pub enum CompressionMethod(u16) {
|
||||
Deflate = 8,
|
||||
OldDeflate = 0x80B2,
|
||||
PackBits = 0x8005,
|
||||
|
||||
// Self-assigned by libtiff
|
||||
ZSTD = 0xC350,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,8 +212,15 @@ pub enum PlanarConfiguration(u16) {
|
||||
|
||||
tags! {
|
||||
pub enum Predictor(u16) {
|
||||
/// No changes were made to the data
|
||||
None = 1,
|
||||
/// The images' rows were processed to contain the difference of each pixel from the previous one.
|
||||
///
|
||||
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
|
||||
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
|
||||
Horizontal = 2,
|
||||
/// Not currently supported
|
||||
FloatingPoint = 3,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
extern crate tiff;
|
||||
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::ColorType;
|
||||
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
||||
|
||||
/// Test a basic all white image
|
||||
#[test]
|
||||
fn test_white_ieee_fp16() {
|
||||
let filenames = ["white-fp16.tiff"];
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
for p in img {
|
||||
assert!(p == half::f16::from_f32_const(1.0));
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test a single black pixel, to make sure scaling is ok
|
||||
#[test]
|
||||
fn test_one_black_pixel_ieee_fp16() {
|
||||
let filenames = ["single-black-fp16.tiff"];
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
for (i, p) in img.iter().enumerate() {
|
||||
if i == 0 {
|
||||
assert!(p < &half::f16::from_f32_const(0.001));
|
||||
} else {
|
||||
assert!(p == &half::f16::from_f32_const(1.0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test white with horizontal differencing predictor
|
||||
#[test]
|
||||
fn test_pattern_horizontal_differencing_ieee_fp16() {
|
||||
let filenames = ["white-fp16-pred2.tiff"];
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
// 0, 2, 5, 8, 12, 16, 255 are black
|
||||
let black = [0, 2, 5, 8, 12, 16, 255];
|
||||
for (i, p) in img.iter().enumerate() {
|
||||
if black.contains(&i) {
|
||||
assert!(p < &half::f16::from_f32_const(0.001));
|
||||
} else {
|
||||
assert!(p == &half::f16::from_f32_const(1.0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test white with floating point predictor
|
||||
#[test]
|
||||
fn test_pattern_predictor_ieee_fp16() {
|
||||
let filenames = ["white-fp16-pred3.tiff"];
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
// 0, 2, 5, 8, 12, 16, 255 are black
|
||||
let black = [0, 2, 5, 8, 12, 16, 255];
|
||||
for (i, p) in img.iter().enumerate() {
|
||||
if black.contains(&i) {
|
||||
assert!(p < &half::f16::from_f32_const(0.001));
|
||||
} else {
|
||||
assert!(p == &half::f16::from_f32_const(1.0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test several random images
|
||||
/// we'rell compare against a pnm file, that scales from 0 (for 0.0) to 65767 (for 1.0)
|
||||
#[test]
|
||||
fn test_predictor_ieee_fp16() {
|
||||
// first parse pnm, skip the first 4 \n
|
||||
let pnm_path = PathBuf::from(TEST_IMAGE_DIR).join("random-fp16.pgm");
|
||||
let pnm_bytes = std::fs::read(pnm_path).expect("Failed to read expected PNM file");
|
||||
|
||||
// PGM looks like this:
|
||||
// ---
|
||||
// P5
|
||||
// #Created with GIMP
|
||||
// 16 16
|
||||
// 65535
|
||||
// ... <big-endian bytes>
|
||||
// ---
|
||||
// get index of 4th \n
|
||||
let byte_start = pnm_bytes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, &v)| v == b'\n')
|
||||
.map(|(i, _)| i)
|
||||
.nth(3)
|
||||
.expect("Must be 4 \\n's");
|
||||
|
||||
let pnm_values: Vec<f32> = pnm_bytes[(byte_start + 1)..]
|
||||
.chunks(2)
|
||||
.map(|slice| {
|
||||
let bts = [slice[0], slice[1]];
|
||||
(u16::from_be_bytes(bts) as f32) / (u16::MAX as f32)
|
||||
})
|
||||
.collect();
|
||||
assert!(pnm_values.len() == 256);
|
||||
|
||||
let filenames = [
|
||||
"random-fp16-pred2.tiff",
|
||||
"random-fp16-pred3.tiff",
|
||||
"random-fp16.tiff",
|
||||
];
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(16, 16)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) {
|
||||
assert!((exp - found.to_f32()).abs() < 0.0001);
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
extern crate tiff;
|
||||
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::tags::Tag;
|
||||
use tiff::ColorType;
|
||||
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TEST_IMAGE_DIR: &str = "./tests/images";
|
||||
|
||||
#[test]
|
||||
fn test_geo_tiff() {
|
||||
let filenames = ["geo-5b.tif"];
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
decoder = decoder.with_limits(tiff::decoder::Limits::unlimited());
|
||||
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(10, 10)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Multiband {
|
||||
bit_depth: 16,
|
||||
num_samples: 5
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
decoder
|
||||
.get_tag_u64(Tag::StripOffsets)
|
||||
.expect("Cannot get StripOffsets"),
|
||||
418
|
||||
);
|
||||
assert_eq!(
|
||||
decoder
|
||||
.get_tag_u64(Tag::RowsPerStrip)
|
||||
.expect("Cannot get RowsPerStrip"),
|
||||
10
|
||||
);
|
||||
assert_eq!(
|
||||
decoder
|
||||
.get_tag_u64(Tag::StripByteCounts)
|
||||
.expect("Cannot get StripByteCounts"),
|
||||
1000
|
||||
);
|
||||
assert_eq!(
|
||||
decoder
|
||||
.get_tag(Tag::ModelPixelScaleTag)
|
||||
.expect("Cannot get pixel scale")
|
||||
.into_f64_vec()
|
||||
.expect("Cannot get pixel scale"),
|
||||
vec![60.0, 60.0, 0.0]
|
||||
);
|
||||
let DecodingResult::I16(data) = decoder.read_image().unwrap() else {
|
||||
panic!("Cannot read band data")
|
||||
};
|
||||
assert_eq!(data.len(), 500);
|
||||
}
|
||||
}
|
||||
+239
-1
@@ -216,6 +216,11 @@ fn issue_69() {
|
||||
//assert!(img_res.is_ok());
|
||||
//}
|
||||
|
||||
#[test]
|
||||
fn test_tiled_gray_i1() {
|
||||
test_image_sum_u8("tiled-gray-i1.tif", ColorType::Gray(1), 30531);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tiled_rgb_u8() {
|
||||
test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948);
|
||||
@@ -275,7 +280,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 +289,236 @@ fn test_tiled_incremental() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_planar_rgb_u8() {
|
||||
// gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
|
||||
let file = "planar-rgb-u8.tif";
|
||||
let expected_type = ColorType::RGB(8);
|
||||
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
|
||||
let chunks = decoder.strip_count().unwrap();
|
||||
assert_eq!(chunks as usize, 72);
|
||||
|
||||
// convert -quiet planar-rgb-u8.tif[0] -crop 1x1+0+0 txt:
|
||||
// 0,0: (73,51,30) #49331E srgb(73,51,30)
|
||||
|
||||
// 1st band (red)
|
||||
match decoder.read_chunk(0).unwrap() {
|
||||
DecodingResult::U8(chunk) => {
|
||||
assert_eq!(chunk[0], 73);
|
||||
}
|
||||
_ => panic!("Wrong bit depth"),
|
||||
}
|
||||
// 2nd band (green)
|
||||
match decoder.read_chunk(chunks / 3).unwrap() {
|
||||
DecodingResult::U8(chunk) => {
|
||||
assert_eq!(chunk[0], 51);
|
||||
}
|
||||
_ => panic!("Wrong bit depth"),
|
||||
}
|
||||
// 3rd band (blue)
|
||||
match decoder.read_chunk(chunks / 3 * 2).unwrap() {
|
||||
DecodingResult::U8(chunk) => {
|
||||
assert_eq!(chunk[0], 30);
|
||||
}
|
||||
_ => panic!("Wrong bit depth"),
|
||||
}
|
||||
|
||||
test_image_sum_u8(file, ColorType::RGB(8), 15417630);
|
||||
}
|
||||
|
||||
#[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 _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&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 fuzzer_testcase6() {
|
||||
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 _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zstd")]
|
||||
fn test_zstd_compression() {
|
||||
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
|
||||
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
|
||||
}
|
||||
+108
-1
@@ -36,6 +36,47 @@ fn encode_decode() {
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||
|
||||
let mut all_tags = decoder
|
||||
.tag_iter()
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
all_tags.sort_by_key(|(t, _)| t.to_u16());
|
||||
assert_eq!(
|
||||
all_tags,
|
||||
vec![
|
||||
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
|
||||
(Tag::ImageLength, ifd::Value::Unsigned(100)),
|
||||
(
|
||||
Tag::BitsPerSample,
|
||||
ifd::Value::List(vec![
|
||||
ifd::Value::Short(8),
|
||||
ifd::Value::Short(8),
|
||||
ifd::Value::Short(8)
|
||||
])
|
||||
),
|
||||
(Tag::Compression, ifd::Value::Short(1)),
|
||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
||||
(Tag::StripOffsets, ifd::Value::Unsigned(8)),
|
||||
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
|
||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
||||
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
|
||||
(Tag::XResolution, ifd::Value::Rational(1, 1)),
|
||||
(Tag::YResolution, ifd::Value::Rational(1, 1)),
|
||||
(Tag::ResolutionUnit, ifd::Value::Short(1)),
|
||||
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
|
||||
(Tag::Predictor, ifd::Value::Short(1)),
|
||||
(
|
||||
Tag::SampleFormat,
|
||||
ifd::Value::List(vec![
|
||||
ifd::Value::Short(1),
|
||||
ifd::Value::Short(1),
|
||||
ifd::Value::Short(1)
|
||||
])
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
decoder.get_tag(Tag::Artist).unwrap(),
|
||||
ifd::Value::Ascii("Image-tiff".into())
|
||||
@@ -75,6 +116,47 @@ fn encode_decode_big() {
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||
|
||||
let mut all_tags = decoder
|
||||
.tag_iter()
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
all_tags.sort_by_key(|(t, _)| t.to_u16());
|
||||
assert_eq!(
|
||||
all_tags,
|
||||
vec![
|
||||
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
|
||||
(Tag::ImageLength, ifd::Value::Unsigned(100)),
|
||||
(
|
||||
Tag::BitsPerSample,
|
||||
ifd::Value::List(vec![
|
||||
ifd::Value::Short(8),
|
||||
ifd::Value::Short(8),
|
||||
ifd::Value::Short(8)
|
||||
])
|
||||
),
|
||||
(Tag::Compression, ifd::Value::Short(1)),
|
||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
||||
(Tag::StripOffsets, ifd::Value::UnsignedBig(16)),
|
||||
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
|
||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
||||
(Tag::StripByteCounts, ifd::Value::UnsignedBig(30000)),
|
||||
(Tag::XResolution, ifd::Value::Rational(1, 1)),
|
||||
(Tag::YResolution, ifd::Value::Rational(1, 1)),
|
||||
(Tag::ResolutionUnit, ifd::Value::Short(1)),
|
||||
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
|
||||
(Tag::Predictor, ifd::Value::Short(1)),
|
||||
(
|
||||
Tag::SampleFormat,
|
||||
ifd::Value::List(vec![
|
||||
ifd::Value::Short(1),
|
||||
ifd::Value::Short(1),
|
||||
ifd::Value::Short(1)
|
||||
])
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
decoder.get_tag(Tag::Artist).unwrap(),
|
||||
ifd::Value::Ascii("Image-tiff".into())
|
||||
@@ -94,6 +176,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
|
||||
@@ -187,7 +270,9 @@ macro_rules! test_roundtrip {
|
||||
}
|
||||
|
||||
test_roundtrip!(test_u8_roundtrip, U8, u8);
|
||||
test_roundtrip!(test_i8_roundtrip, I8, i8);
|
||||
test_roundtrip!(test_u16_roundtrip, U16, u16);
|
||||
test_roundtrip!(test_i16_roundtrip, I16, i16);
|
||||
test_roundtrip!(test_u32_roundtrip, U32, u32);
|
||||
test_roundtrip!(test_u64_roundtrip, U64, u64);
|
||||
test_roundtrip!(test_f32_roundtrip, F32, f32);
|
||||
@@ -198,6 +283,11 @@ fn test_gray_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i8_roundtrip() {
|
||||
test_i8_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
|
||||
@@ -208,11 +298,21 @@ fn test_cmyk_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmyka_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::CMYKA8>("cmyk-4c-8b.tiff", ColorType::CMYKA(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u16_roundtrip() {
|
||||
test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i16_roundtrip() {
|
||||
test_i16_roundtrip::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u16_roundtrip() {
|
||||
test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
|
||||
@@ -263,6 +363,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 +425,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 +466,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 +619,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,158 @@
|
||||
extern crate tiff;
|
||||
|
||||
use std::io::{Cursor, Seek, Write};
|
||||
use tiff::{
|
||||
decoder::{Decoder, DecodingResult},
|
||||
encoder::{
|
||||
colortype::{self, ColorType},
|
||||
compression::*,
|
||||
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<W: Write + Seek>(&self, encoder: &mut TiffEncoder<W>)
|
||||
where
|
||||
[<Self::Color as ColorType>::Inner]: TiffValue,
|
||||
{
|
||||
let image = encoder
|
||||
.new_image::<Self::Color>(Self::WIDTH, Self::HEIGHT)
|
||||
.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(compression: Compression) {
|
||||
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()
|
||||
.with_compression(compression);
|
||||
image_rgb.compress(&mut encoder);
|
||||
image_grayscale.compress(&mut encoder);
|
||||
}
|
||||
|
||||
// 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(Compression::Uncompressed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "lzw")]
|
||||
fn encode_decode_with_lzw() {
|
||||
encode_decode_with_compression(Compression::Lzw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "deflate")]
|
||||
fn encode_decode_with_deflate() {
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_with_packbits() {
|
||||
encode_decode_with_compression(Compression::Packbits);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,231 @@
|
||||
extern crate tiff;
|
||||
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
|
||||
use tiff::ColorType;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Seek, SeekFrom};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
||||
|
||||
macro_rules! test_predict {
|
||||
($name:ident, $buffer:ident, $buffer_ty:ty) => {
|
||||
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
|
||||
file: &str,
|
||||
expected_type: ColorType,
|
||||
) {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(file).expect("Cannot create decoder!");
|
||||
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
let image_data = match decoder.read_image().unwrap() {
|
||||
DecodingResult::$buffer(res) => res,
|
||||
_ => panic!("Wrong data type"),
|
||||
};
|
||||
|
||||
let mut predicted = Vec::with_capacity(image_data.len());
|
||||
C::horizontal_predict(&image_data, &mut predicted);
|
||||
|
||||
let sample_size = C::SAMPLE_FORMAT.len();
|
||||
|
||||
(0..sample_size).for_each(|i| {
|
||||
assert_eq!(predicted[i], image_data[i]);
|
||||
});
|
||||
|
||||
(sample_size..image_data.len()).for_each(|i| {
|
||||
predicted[i] = predicted[i].wrapping_add(predicted[i - sample_size]);
|
||||
assert_eq!(predicted[i], image_data[i]);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_predict!(test_u8_predict, U8, u8);
|
||||
test_predict!(test_i8_predict, I8, i8);
|
||||
test_predict!(test_u16_predict, U16, u16);
|
||||
test_predict!(test_i16_predict, I16, i16);
|
||||
test_predict!(test_u32_predict, U32, u32);
|
||||
test_predict!(test_u64_predict, U64, u64);
|
||||
|
||||
#[test]
|
||||
fn test_gray_u8_predict() {
|
||||
test_u8_predict::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i8_predict() {
|
||||
test_i8_predict::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u8_predict() {
|
||||
test_u8_predict::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmyk_u8_predict() {
|
||||
test_u8_predict::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u16_predict() {
|
||||
test_u16_predict::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i16_predict() {
|
||||
test_i16_predict::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u16_predict() {
|
||||
test_u16_predict::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmyk_u16_predict() {
|
||||
test_u16_predict::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u32_predict() {
|
||||
test_u32_predict::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u32_predict() {
|
||||
test_u32_predict::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u64_predict() {
|
||||
test_u64_predict::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u64_predict() {
|
||||
test_u64_predict::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ycbcr_u8_predict() {
|
||||
test_u8_predict::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
||||
}
|
||||
|
||||
macro_rules! test_predict_roundtrip {
|
||||
($name:ident, $buffer:ident, $buffer_ty:ty) => {
|
||||
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
|
||||
file: &str,
|
||||
expected_type: ColorType,
|
||||
) {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
|
||||
let image_data = match decoder.read_image().unwrap() {
|
||||
DecodingResult::$buffer(res) => res,
|
||||
_ => panic!("Wrong data type"),
|
||||
};
|
||||
|
||||
let mut file = Cursor::new(Vec::new());
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut file)
|
||||
.unwrap()
|
||||
.with_predictor(Predictor::Horizontal);
|
||||
|
||||
let (width, height) = decoder.dimensions().unwrap();
|
||||
tiff.write_image::<C>(width, height, &image_data).unwrap();
|
||||
}
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
if let DecodingResult::$buffer(img_res) =
|
||||
decoder.read_image().expect("Decoding image failed")
|
||||
{
|
||||
assert_eq!(image_data, img_res);
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_predict_roundtrip!(test_u8_predict_roundtrip, U8, u8);
|
||||
test_predict_roundtrip!(test_i8_predict_roundtrip, I8, i8);
|
||||
test_predict_roundtrip!(test_u16_predict_roundtrip, U16, u16);
|
||||
test_predict_roundtrip!(test_i16_predict_roundtrip, I16, i16);
|
||||
test_predict_roundtrip!(test_u32_predict_roundtrip, U32, u32);
|
||||
test_predict_roundtrip!(test_u64_predict_roundtrip, U64, u64);
|
||||
|
||||
#[test]
|
||||
fn test_gray_u8_predict_roundtrip() {
|
||||
test_u8_predict_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i8_predict_roundtrip() {
|
||||
test_i8_predict_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u8_predict_roundtrip() {
|
||||
test_u8_predict_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmyk_u8_predict_roundtrip() {
|
||||
test_u8_predict_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u16_predict_roundtrip() {
|
||||
test_u16_predict_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i16_predict_roundtrip() {
|
||||
test_i16_predict_roundtrip::<colortype::GrayI16>(
|
||||
"minisblack-1c-i16b.tiff",
|
||||
ColorType::Gray(16),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u16_predict_roundtrip() {
|
||||
test_u16_predict_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmyk_u16_predict_roundtrip() {
|
||||
test_u16_predict_roundtrip::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u32_predict_roundtrip() {
|
||||
test_u32_predict_roundtrip::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u32_predict_roundtrip() {
|
||||
test_u32_predict_roundtrip::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u64_predict_roundtrip() {
|
||||
test_u64_predict_roundtrip::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u64_predict_roundtrip() {
|
||||
test_u64_predict_roundtrip::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ycbcr_u8_predict_roundtrip() {
|
||||
test_u8_predict_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
||||
}
|
||||
Reference in new issue
Block a user