Compare commits
318
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
e8bc935618 | ||
|
|
150ff8303b | ||
|
|
e44b98ddf9 | ||
|
|
ad0f75222a | ||
|
|
4df26011d9 | ||
|
|
4dc7c2a986 | ||
|
|
b7c4f668ae | ||
|
|
9fe9b2e0a0 | ||
|
|
b7e8f210d1 | ||
|
|
371a9b53c3 | ||
|
|
d0e4f37a28 | ||
|
|
46c7efd836 | ||
|
|
65832e01bd | ||
|
|
b9590e0842 | ||
|
|
521b58dc07 | ||
|
|
e70121efbb | ||
|
|
a6fb855121 | ||
|
|
a874b9c3a5 | ||
|
|
d4ce174236 | ||
|
|
a43aa1fbc7 | ||
|
|
69c34ac33c | ||
|
|
00e0eb9006 | ||
|
|
ebd0e266f7 | ||
|
|
9b94e9de88 | ||
|
|
47dfa0aa3e | ||
|
|
1eb9c4fab7 | ||
|
|
201b95f749 | ||
|
|
7641dcbd3f | ||
|
|
60c6e0cd90 | ||
|
|
65a1c553af | ||
|
|
fddc048849 | ||
|
|
68f2182918 | ||
|
|
b2a7e35c85 | ||
|
|
91fe529859 | ||
|
|
5ca2730416 | ||
|
|
8fcacc829f | ||
|
|
59d0b7389d | ||
|
|
0ac444b63f | ||
|
|
ce1ee6776f | ||
|
|
29a020e5ec | ||
|
|
9021985477 | ||
|
|
d4c958f915 | ||
|
|
dc04282290 | ||
|
|
4cfed1c7b1 | ||
|
|
745ed62808 | ||
|
|
6fe9b78509 | ||
|
|
f5754159cb | ||
|
|
53d0256585 | ||
|
|
09aa28d699 | ||
|
|
d086e289dd | ||
|
|
2cba04dfe7 | ||
|
|
3de87a7001 | ||
|
|
4900c82871 | ||
|
|
8a8e3ed753 | ||
|
|
cd2f4f6462 | ||
|
|
aa13e31894 | ||
|
|
6c211b9179 | ||
|
|
49f9b3941d | ||
|
|
332ab1dc4d | ||
|
|
ec98e32a3b | ||
|
|
7ec2b75c70 | ||
|
|
9788085786 | ||
|
|
fc80c5e3dd | ||
|
|
95f0a34bcc | ||
|
|
3e042df438 | ||
|
|
5346a441ae | ||
|
|
76173069a3 | ||
|
|
6cdd340933 | ||
|
|
ef004cee48 | ||
|
|
6d7d92c605 | ||
|
|
53437dd7b4 | ||
|
|
0c06d32ee4 | ||
|
|
4d8c64bf1e | ||
|
|
7da8f01f75 | ||
|
|
67105fe4ec | ||
|
|
1b38ff9410 | ||
|
|
c9703dcb8a | ||
|
|
e2caf86f6a | ||
|
|
923b37d647 | ||
|
|
9f4799912f | ||
|
|
4b218be4ac | ||
|
|
9a0041286e | ||
|
|
0a4b51055e | ||
|
|
96d6d52830 | ||
|
|
62a1a500a5 | ||
|
|
3953bd31e1 | ||
|
|
83004c8344 | ||
|
|
8ab9d30eff | ||
|
|
89aaab12dc | ||
|
|
481ef3781a | ||
|
|
369c602952 | ||
|
|
ed22865f04 | ||
|
|
459b4f3908 | ||
|
|
e72401d4ac | ||
|
|
6281360f10 | ||
|
|
d00ab0cc61 | ||
|
|
6353f71e28 | ||
|
|
7c4ee96aee | ||
|
|
cb5319c426 | ||
|
|
53aefea6a4 | ||
|
|
cb900819d9 | ||
|
|
35ea719d9e | ||
|
|
41050c14ed | ||
|
|
5fd0534ea2 | ||
|
|
72474b569e | ||
|
|
63f09b267a | ||
|
|
b455bd9f2b | ||
|
|
46918b395c | ||
|
|
a510b03e1c | ||
|
|
2cb57a4d04 | ||
|
|
da9bdac8a2 | ||
|
|
647de77b70 | ||
|
|
727d78a57c | ||
|
|
49da46bbd0 | ||
|
|
7b41c823e9 | ||
|
|
bf24b3f935 | ||
|
|
2b12c1b162 | ||
|
|
82c00a84d8 | ||
|
|
ec5b4b9a9e | ||
|
|
f4f11e6399 | ||
|
|
49b1f78ce5 | ||
|
|
e48691ca32 | ||
|
|
7ad6b0e70d | ||
|
|
7cc5b98d9c | ||
|
|
7928c219dd | ||
|
|
44c75a66ab | ||
|
|
d8bfa84d06 | ||
|
|
1492087426 | ||
|
|
23090a3513 | ||
|
|
200355d3bc | ||
|
|
469883aa79 | ||
|
|
99c038c929 | ||
|
|
f3eebee1e9 | ||
|
|
b853d841f3 | ||
|
|
418979f14b | ||
|
|
768e0d4af7 | ||
|
|
3b0da4d1ae | ||
|
|
54c3ebf358 | ||
|
|
5f48444fa0 | ||
|
|
71f3aa8b15 | ||
|
|
0ec8e14910 | ||
|
|
d79d8c3353 | ||
|
|
ae4ae9c36e | ||
|
|
8d4bdce7aa | ||
|
|
a41e33e42a | ||
|
|
c2084835fa | ||
|
|
97c888f166 | ||
|
|
bbc49291e4 |
No files matched your search
@@ -0,0 +1,40 @@
|
|||||||
|
name: Rust CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
rust: ["1.61", stable, beta, nightly]
|
||||||
|
command: [build, test]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- run: rustup default ${{ matrix.rust }}
|
||||||
|
- name: build
|
||||||
|
run: >
|
||||||
|
cargo build --verbose --no-default-features --features "$FEATURES"
|
||||||
|
- name: test
|
||||||
|
run: >
|
||||||
|
cargo test --tests --benches --no-default-features --features "$FEATURES"
|
||||||
|
if: ${{ matrix.rust != '1.61' }}
|
||||||
|
env:
|
||||||
|
FEATURES: ${{ matrix.features }}
|
||||||
|
rustfmt:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
- name: Run rustfmt check
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: -- --check
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
language: rust
|
|
||||||
rust:
|
|
||||||
- 1.34.2
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- nightly
|
|
||||||
+157
-2
@@ -1,11 +1,166 @@
|
|||||||
# Version 0.6.0-alpha
|
# 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:
|
||||||
|
* Support for encoding BigTiff ([#122](https://github.com/image-rs/image-tiff/pull/122))
|
||||||
|
* _Breaking:_ Encoder types now have a generic parameter to differentiate BigTiff and standard Tiff encoding. Defaults to standard Tiff.
|
||||||
|
* Basic tile decoding ([#125](https://github.com/image-rs/image-tiff/pull/125))
|
||||||
|
* _Breaking:_ There is a new `TiffError::UsageError` variant.
|
||||||
|
* Support for datatypes `Int8` and `Int16` ([#114](https://github.com/image-rs/image-tiff/pull/114))
|
||||||
|
* _Breaking:_ `DecodingResult` and `DecodingBuffer` have the two new variants `I8` and `I16`.
|
||||||
|
* Support for `i32` arrays ([#118](https://github.com/image-rs/image-tiff/pull/118/files))
|
||||||
|
* _Breaking:_ `DecodingResult` and `DecodingBuffer` have a new `I32` variant.
|
||||||
|
* Support for `Ifd` and `IfdBig` tag types and `I64` data type ([#119](https://github.com/image-rs/image-tiff/pull/119))
|
||||||
|
* _Breaking:_ `DecodingResult` and `DecodingBuffer` have a new `I64` variant.
|
||||||
|
* Add `SMinSampleValue` and `SMaxSampleValue` ([#123](https://github.com/image-rs/image-tiff/pull/123))
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
* Improve deflate support ([#132](https://github.com/image-rs/image-tiff/pull/132))
|
||||||
|
* Switch to streaming decompression via `flate2`. Aside from performance improvements and lower RAM consumption, this fixes a bug where `max_uncompressed_length` was precalculated for a single tile but then used as a hard limit on the whole data, failing to decompress any tiled images.
|
||||||
|
* Add support for new `Deflate` tag in addition to `OldDeflate`.
|
||||||
|
* _Breaking:_ Remove `InflateError`, which is no longer needed with `flate2` ([#134](https://github.com/image-rs/image-tiff/pull/134))
|
||||||
|
* _Breaking:_ Support for `MinIsWhite` is restricted to unsigned and floating
|
||||||
|
point values. This is expected to be be re-added once some contradictory
|
||||||
|
interpretation regarding semantics for signed values is resolved.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Validate that ASCII tags are valid ASCII and end with a null byte ([#121](https://github.com/image-rs/image-tiff/pull/121))
|
||||||
|
|
||||||
|
Internal:
|
||||||
|
* Simplify decompression logic ([#126](https://github.com/image-rs/image-tiff/pull/126))
|
||||||
|
* Simplify `expand_strip` ([#128](https://github.com/image-rs/image-tiff/pull/128))
|
||||||
|
|
||||||
|
# Version 0.6.1
|
||||||
|
|
||||||
|
New features:
|
||||||
|
* Support for reading `u16` and ascii string tags.
|
||||||
|
* Added `Limits::unlimited` for disabling all limits.
|
||||||
|
* Added `ImageEncoder::rows_per_strip` to overwrite the default.
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
* The default strip size for chunked encoding is now 1MB, up from 8KB. This
|
||||||
|
should lead to more efficient decoding and compression.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Fixed a bug where LZW compressed strips could not be decoded, instead
|
||||||
|
returning an error `Inconsistent sizes encountered`.
|
||||||
|
* Reading a tag with a complex type and a single value returns the proper Value
|
||||||
|
variant, instead of a vector with one entry.
|
||||||
|
|
||||||
|
# Version 0.6.0
|
||||||
|
|
||||||
|
New features:
|
||||||
* Support for decoding BigTIFF with 64-bit offsets
|
* Support for decoding BigTIFF with 64-bit offsets
|
||||||
|
* The value types byte, `f32`, `f64` are now recognized
|
||||||
|
* Support for Modern JPEG encoded images
|
||||||
|
|
||||||
|
Improvements:
|
||||||
* Better support for adding auxiliary tags before encoding image data
|
* Better support for adding auxiliary tags before encoding image data
|
||||||
|
* Switched to lzw decoder library `weezl` for performance
|
||||||
|
* The `ColorType` trait now supports `SAMPLE_ENCODING` hints
|
||||||
|
|
||||||
|
Fixes:
|
||||||
* Fixed decoding of inline ASCII in tags
|
* Fixed decoding of inline ASCII in tags
|
||||||
* Fixed handling after null terminator in ASCII data
|
* Fixed handling after null terminator in ASCII data
|
||||||
* Recognize tile and sample format tags
|
* Recognize tile and sample format tags
|
||||||
* Note: these notes will be merged into 0.6.0 when it is released
|
|
||||||
|
|
||||||
# Version 0.5.0
|
# Version 0.5.0
|
||||||
|
|
||||||
|
|||||||
+21
-14
@@ -1,22 +1,29 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.6.0-alpha"
|
version = "0.8.1"
|
||||||
authors = [
|
edition = "2018"
|
||||||
"ccgn",
|
resolver = "2"
|
||||||
"bvssvni <bvssvni@gmail.com>",
|
|
||||||
"nwin",
|
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
||||||
"TyOverby <ty@pre-alpha.com>",
|
rust-version = "1.61.0"
|
||||||
"HeroicKatora",
|
|
||||||
"Calum",
|
|
||||||
"CensoredUsername <cens.username@gmail.com>",
|
|
||||||
"Robzz",
|
|
||||||
"birktj"]
|
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "TIFF decoding and encoding library in pure Rust"
|
description = "TIFF decoding and encoding library in pure Rust"
|
||||||
|
authors = ["The image-rs Developers"]
|
||||||
|
|
||||||
repository = "https://github.com/image-rs/image-tiff"
|
repository = "https://github.com/image-rs/image-tiff"
|
||||||
|
categories = ["multimedia::images", "multimedia::encoding"]
|
||||||
|
|
||||||
exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.2"
|
weezl = "0.1.0"
|
||||||
lzw = "0.10"
|
jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false }
|
||||||
miniz_oxide = "0.3"
|
flate2 = "1.0.20"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = "0.3.1"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "lzw"
|
||||||
|
harness = false
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# image-tiff
|
# image-tiff
|
||||||
[](https://travis-ci.org/image-rs/image-tiff)
|
[](https://github.com/image-rs/image-tiff/actions)
|
||||||
[](https://docs.rs/tiff)
|
[](https://docs.rs/tiff)
|
||||||
[](https://crates.io/crates/tiff)
|
[](https://crates.io/crates/tiff)
|
||||||
|
|
||||||
@@ -10,30 +10,31 @@ TIFF decoding and encoding library in pure Rust
|
|||||||
### Features
|
### Features
|
||||||
- Baseline spec (other than formats and tags listed below as not supported)
|
- Baseline spec (other than formats and tags listed below as not supported)
|
||||||
- Multipage
|
- Multipage
|
||||||
|
- BigTIFF
|
||||||
- Incremental decoding
|
- Incremental decoding
|
||||||
|
|
||||||
### Formats
|
### 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.
|
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 |
|
| `PhotometricInterpretation` | UINT Format | IEEEFP Format |
|
||||||
|-|-|-|
|
| --------------------------- | --------------------------------------- | ------------------------- |
|
||||||
| `WhiteIsZero` | Gray(8\|16\|32\|64) | |
|
| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
|
||||||
| `BlackIsZero` | Gray(8\|16\|32\|64) | |
|
| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
|
||||||
| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | |
|
| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) |
|
||||||
| `RGBPalette` | | |
|
| `RGBPalette` | | |
|
||||||
| `Mask` | | |
|
| `Mask` | | |
|
||||||
| `CMYK` | CMYK(8\|16\|32\|64) | |
|
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
|
||||||
| `YCbCr` | | |
|
| `YCbCr` | | |
|
||||||
| `CIELab` | | |
|
| `CIELab` | | |
|
||||||
|
|
||||||
### Compressions
|
### Compressions
|
||||||
|
|
||||||
| | Decoding | Encoding |
|
| | Decoding | Encoding |
|
||||||
|-|-|-|
|
| -------- | -------- | -------- |
|
||||||
| None | ✓ | ✓ |
|
| None | ✓ | ✓ |
|
||||||
| LZW | ✓ | |
|
| LZW | ✓ | ✓ |
|
||||||
| Deflate | ✓ | |
|
| Deflate | ✓ | ✓ |
|
||||||
| PackBits | ✓ | |
|
| PackBits | ✓ | ✓ |
|
||||||
|
|
||||||
|
|
||||||
## Not yet supported
|
## Not yet supported
|
||||||
@@ -43,4 +44,11 @@ Formats and interpretations not listed above or with empty entries are unsupport
|
|||||||
- Baseline tags
|
- Baseline tags
|
||||||
- `ExtraSamples`
|
- `ExtraSamples`
|
||||||
- Extension tags
|
- Extension tags
|
||||||
- BigTIFF
|
|
||||||
|
## 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`.
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
extern crate criterion;
|
||||||
|
extern crate tiff;
|
||||||
|
|
||||||
|
use criterion::{
|
||||||
|
black_box, measurement::Measurement, BenchmarkGroup, BenchmarkId, Criterion, Throughput,
|
||||||
|
};
|
||||||
|
use tiff::decoder::Decoder;
|
||||||
|
|
||||||
|
fn read_image(image: &[u8]) {
|
||||||
|
let image = std::io::Cursor::new(image);
|
||||||
|
let decoder = Decoder::new(black_box(image));
|
||||||
|
let mut reader = decoder.unwrap();
|
||||||
|
|
||||||
|
while {
|
||||||
|
reader.read_image().unwrap();
|
||||||
|
reader.more_images()
|
||||||
|
} {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
struct BenchDef {
|
||||||
|
data: &'static [u8],
|
||||||
|
id: &'static str,
|
||||||
|
sample_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_bench_def<M: Measurement>(group: &mut BenchmarkGroup<M>, def: BenchDef) {
|
||||||
|
group
|
||||||
|
.sample_size(def.sample_size)
|
||||||
|
.throughput(Throughput::Bytes(def.data.len() as u64))
|
||||||
|
.bench_with_input(
|
||||||
|
BenchmarkId::new(def.id, def.data.len()),
|
||||||
|
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");
|
||||||
|
|
||||||
|
macro_rules! data_file {
|
||||||
|
($name:literal) => {
|
||||||
|
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), $name))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
run_bench_def(
|
||||||
|
&mut group,
|
||||||
|
BenchDef {
|
||||||
|
data: data_file!("/tests/images/issue_69_lzw.tiff"),
|
||||||
|
id: "issue-69-lzw.tif",
|
||||||
|
sample_size: 500,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
run_bench_def(
|
||||||
|
&mut group,
|
||||||
|
BenchDef {
|
||||||
|
data: data_file!("/tests/benches/kodim02-lzw.tif"),
|
||||||
|
id: "kodim02-lzw.tif",
|
||||||
|
sample_size: 20,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
run_bench_def(
|
||||||
|
&mut group,
|
||||||
|
BenchDef {
|
||||||
|
data: data_file!("/tests/benches/kodim07-lzw.tif"),
|
||||||
|
id: "kodim07-lzw.tif",
|
||||||
|
sample_size: 20,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
run_bench_def(
|
||||||
|
&mut group,
|
||||||
|
BenchDef {
|
||||||
|
data: data_file!("/tests/benches/Transparency-lzw.tif"),
|
||||||
|
id: "Transparency-lzw.tif",
|
||||||
|
sample_size: 20,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//! Trivial, internal byte transmutation.
|
||||||
|
//!
|
||||||
|
//! A dependency like bytemuck would give us extra assurance of the safety but overall would not
|
||||||
|
//! reduce the amount of total unsafety. We don't use it in the interface where the traits would
|
||||||
|
//! really become useful.
|
||||||
|
//!
|
||||||
|
//! SAFETY: These are benign casts as we apply them to fixed size integer types only. All of them
|
||||||
|
//! are naturally aligned, valid for all bit patterns and their alignment is surely at most their
|
||||||
|
//! size (we assert the latter fact since it is 'implementation defined' if following the letter of
|
||||||
|
//! the unsafe code guidelines).
|
||||||
|
//!
|
||||||
|
//! TODO: Would like to use std-lib here.
|
||||||
|
use std::{mem, slice};
|
||||||
|
|
||||||
|
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>());
|
||||||
|
unsafe { slice::from_raw_parts(slice.as_ptr() as *const u8, mem::size_of_val(slice)) }
|
||||||
|
}
|
||||||
|
$(pub(crate) fn $mut(slice: &mut [$int]) -> &mut [u8] {
|
||||||
|
assert!(mem::align_of::<$int>() <= mem::size_of::<$int>());
|
||||||
|
unsafe { slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, mem::size_of_val(slice)) }
|
||||||
|
})*
|
||||||
|
}}
|
||||||
|
|
||||||
|
integral_slice_as_bytes!(i8, i8_as_ne_bytes, i8_as_ne_mut_bytes);
|
||||||
|
integral_slice_as_bytes!(u16, u16_as_ne_bytes, u16_as_ne_mut_bytes);
|
||||||
|
integral_slice_as_bytes!(i16, i16_as_ne_bytes, i16_as_ne_mut_bytes);
|
||||||
|
integral_slice_as_bytes!(u32, u32_as_ne_bytes, u32_as_ne_mut_bytes);
|
||||||
|
integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes);
|
||||||
|
integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes);
|
||||||
|
integral_slice_as_bytes!(i64, i64_as_ne_bytes, i64_as_ne_mut_bytes);
|
||||||
|
integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes);
|
||||||
|
integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes);
|
||||||
+405
-160
@@ -1,39 +1,68 @@
|
|||||||
//! Function for reading TIFF tags
|
//! Function for reading TIFF tags
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::{TryInto, TryFrom};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::io::{self, Read, Seek};
|
use std::io::{self, Read, Seek};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
use super::stream::{ByteOrder, EndianReader, SmartReader};
|
use super::stream::{ByteOrder, EndianReader, SmartReader};
|
||||||
use tags::{Tag, Type};
|
use crate::tags::{Tag, Type};
|
||||||
use {TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||||
|
|
||||||
use self::Value::{Ascii, List, Rational, Unsigned, Signed, SRational, RationalBig, UnsignedBig, SignedBig, SRationalBig};
|
|
||||||
|
|
||||||
|
use self::Value::{
|
||||||
|
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
|
||||||
|
Short, Signed, SignedBig, Unsigned, UnsignedBig,
|
||||||
|
};
|
||||||
|
|
||||||
#[allow(unused_qualifications)]
|
#[allow(unused_qualifications)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
|
Byte(u8),
|
||||||
|
Short(u16),
|
||||||
Signed(i32),
|
Signed(i32),
|
||||||
SignedBig(i64),
|
SignedBig(i64),
|
||||||
Unsigned(u32),
|
Unsigned(u32),
|
||||||
UnsignedBig(u64),
|
UnsignedBig(u64),
|
||||||
|
Float(f32),
|
||||||
|
Double(f64),
|
||||||
List(Vec<Value>),
|
List(Vec<Value>),
|
||||||
Rational(u32, u32),
|
Rational(u32, u32),
|
||||||
RationalBig(u64, u64),
|
RationalBig(u64, u64),
|
||||||
SRational(i32, i32),
|
SRational(i32, i32),
|
||||||
SRationalBig(i64, i64),
|
SRationalBig(i64, i64),
|
||||||
Ascii(String),
|
Ascii(String),
|
||||||
#[doc(hidden)] // Do not match against this.
|
Ifd(u32),
|
||||||
__NonExhaustive,
|
IfdBig(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
pub fn into_u8(self) -> TiffResult<u8> {
|
||||||
|
match self {
|
||||||
|
Byte(val) => Ok(val),
|
||||||
|
val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_u16(self) -> TiffResult<u16> {
|
||||||
|
match self {
|
||||||
|
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),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_u32(self) -> TiffResult<u32> {
|
pub fn into_u32(self) -> TiffResult<u32> {
|
||||||
match self {
|
match self {
|
||||||
|
Short(val) => Ok(val.into()),
|
||||||
Unsigned(val) => Ok(val),
|
Unsigned(val) => Ok(val),
|
||||||
UnsignedBig(val) => Ok(u32::try_from(val)?),
|
UnsignedBig(val) => Ok(u32::try_from(val)?),
|
||||||
|
Ifd(val) => Ok(val),
|
||||||
|
IfdBig(val) => Ok(u32::try_from(val)?),
|
||||||
val => Err(TiffError::FormatError(
|
val => Err(TiffError::FormatError(
|
||||||
TiffFormatError::UnsignedIntegerExpected(val),
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
)),
|
)),
|
||||||
@@ -52,8 +81,11 @@ impl Value {
|
|||||||
|
|
||||||
pub fn into_u64(self) -> TiffResult<u64> {
|
pub fn into_u64(self) -> TiffResult<u64> {
|
||||||
match self {
|
match self {
|
||||||
|
Short(val) => Ok(val.into()),
|
||||||
Unsigned(val) => Ok(val.into()),
|
Unsigned(val) => Ok(val.into()),
|
||||||
UnsignedBig(val) => Ok(val),
|
UnsignedBig(val) => Ok(val),
|
||||||
|
Ifd(val) => Ok(val.into()),
|
||||||
|
IfdBig(val) => Ok(val),
|
||||||
val => Err(TiffError::FormatError(
|
val => Err(TiffError::FormatError(
|
||||||
TiffFormatError::UnsignedIntegerExpected(val),
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
)),
|
)),
|
||||||
@@ -70,6 +102,33 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_f32(self) -> TiffResult<f32> {
|
||||||
|
match self {
|
||||||
|
Float(val) => Ok(val),
|
||||||
|
val => Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::SignedIntegerExpected(val),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_f64(self) -> TiffResult<f64> {
|
||||||
|
match self {
|
||||||
|
Double(val) => Ok(val),
|
||||||
|
val => Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::SignedIntegerExpected(val),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_string(self) -> TiffResult<String> {
|
||||||
|
match self {
|
||||||
|
Ascii(val) => Ok(val),
|
||||||
|
val => Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::SignedIntegerExpected(val),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_u32_vec(self) -> TiffResult<Vec<u32>> {
|
pub fn into_u32_vec(self) -> TiffResult<Vec<u32>> {
|
||||||
match self {
|
match self {
|
||||||
List(vec) => {
|
List(vec) => {
|
||||||
@@ -82,7 +141,11 @@ impl Value {
|
|||||||
Unsigned(val) => Ok(vec![val]),
|
Unsigned(val) => Ok(vec![val]),
|
||||||
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
|
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
|
||||||
Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||||
RationalBig(numerator, denominator) => Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?]),
|
RationalBig(numerator, denominator) => {
|
||||||
|
Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?])
|
||||||
|
}
|
||||||
|
Ifd(val) => Ok(vec![val]),
|
||||||
|
IfdBig(val) => Ok(vec![u32::try_from(val)?]),
|
||||||
Ascii(val) => Ok(val.chars().map(u32::from).collect()),
|
Ascii(val) => Ok(val.chars().map(u32::from).collect()),
|
||||||
val => Err(TiffError::FormatError(
|
val => Err(TiffError::FormatError(
|
||||||
TiffFormatError::UnsignedIntegerExpected(val),
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
@@ -90,6 +153,39 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_u8_vec(self) -> TiffResult<Vec<u8>> {
|
||||||
|
match self {
|
||||||
|
List(vec) => {
|
||||||
|
let mut new_vec = Vec::with_capacity(vec.len());
|
||||||
|
for v in vec {
|
||||||
|
new_vec.push(v.into_u8()?)
|
||||||
|
}
|
||||||
|
Ok(new_vec)
|
||||||
|
}
|
||||||
|
Byte(val) => Ok(vec![val]),
|
||||||
|
|
||||||
|
val => Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_u16_vec(self) -> TiffResult<Vec<u16>> {
|
||||||
|
match self {
|
||||||
|
List(vec) => {
|
||||||
|
let mut new_vec = Vec::with_capacity(vec.len());
|
||||||
|
for v in vec {
|
||||||
|
new_vec.push(v.into_u16()?)
|
||||||
|
}
|
||||||
|
Ok(new_vec)
|
||||||
|
}
|
||||||
|
Short(val) => Ok(vec![val]),
|
||||||
|
val => Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_i32_vec(self) -> TiffResult<Vec<i32>> {
|
pub fn into_i32_vec(self) -> TiffResult<Vec<i32>> {
|
||||||
match self {
|
match self {
|
||||||
List(vec) => {
|
List(vec) => {
|
||||||
@@ -112,13 +208,47 @@ impl Value {
|
|||||||
Signed(val) => Ok(vec![val]),
|
Signed(val) => Ok(vec![val]),
|
||||||
SignedBig(val) => Ok(vec![i32::try_from(val)?]),
|
SignedBig(val) => Ok(vec![i32::try_from(val)?]),
|
||||||
SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||||
SRationalBig(numerator, denominator) => Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?]),
|
SRationalBig(numerator, denominator) => {
|
||||||
|
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
|
||||||
|
}
|
||||||
val => Err(TiffError::FormatError(
|
val => Err(TiffError::FormatError(
|
||||||
TiffFormatError::SignedIntegerExpected(val),
|
TiffFormatError::SignedIntegerExpected(val),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_f32_vec(self) -> TiffResult<Vec<f32>> {
|
||||||
|
match self {
|
||||||
|
List(vec) => {
|
||||||
|
let mut new_vec = Vec::with_capacity(vec.len());
|
||||||
|
for v in vec {
|
||||||
|
new_vec.push(v.into_f32()?)
|
||||||
|
}
|
||||||
|
Ok(new_vec)
|
||||||
|
}
|
||||||
|
Float(val) => Ok(vec![val]),
|
||||||
|
val => Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_f64_vec(self) -> TiffResult<Vec<f64>> {
|
||||||
|
match self {
|
||||||
|
List(vec) => {
|
||||||
|
let mut new_vec = Vec::with_capacity(vec.len());
|
||||||
|
for v in vec {
|
||||||
|
new_vec.push(v.into_f64()?)
|
||||||
|
}
|
||||||
|
Ok(new_vec)
|
||||||
|
}
|
||||||
|
Double(val) => Ok(vec![val]),
|
||||||
|
val => Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_u64_vec(self) -> TiffResult<Vec<u64>> {
|
pub fn into_u64_vec(self) -> TiffResult<Vec<u64>> {
|
||||||
match self {
|
match self {
|
||||||
List(vec) => {
|
List(vec) => {
|
||||||
@@ -132,6 +262,8 @@ impl Value {
|
|||||||
UnsignedBig(val) => Ok(vec![val]),
|
UnsignedBig(val) => Ok(vec![val]),
|
||||||
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
||||||
RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||||
|
Ifd(val) => Ok(vec![val.into()]),
|
||||||
|
IfdBig(val) => Ok(vec![val]),
|
||||||
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
|
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
|
||||||
val => Err(TiffError::FormatError(
|
val => Err(TiffError::FormatError(
|
||||||
TiffFormatError::UnsignedIntegerExpected(val),
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
@@ -208,178 +340,288 @@ impl Entry {
|
|||||||
pub fn val<R: Read + Seek>(
|
pub fn val<R: Read + Seek>(
|
||||||
&self,
|
&self,
|
||||||
limits: &super::Limits,
|
limits: &super::Limits,
|
||||||
decoder: &mut super::Decoder<R>,
|
bigtiff: bool,
|
||||||
|
reader: &mut SmartReader<R>,
|
||||||
) -> TiffResult<Value> {
|
) -> TiffResult<Value> {
|
||||||
let bo = decoder.byte_order();
|
// Case 1: there are no values so we can return immediately.
|
||||||
let bigtiff_value = if decoder.bigtiff {
|
if self.count == 0 {
|
||||||
match (self.type_, self.count) {
|
return Ok(List(Vec::new()));
|
||||||
(Type::BYTE, 5) => Some(offset_to_bytes(5, self)?),
|
|
||||||
(Type::BYTE, 6) => Some(offset_to_bytes(6, self)?),
|
|
||||||
(Type::BYTE, 7) => Some(offset_to_bytes(7, self)?),
|
|
||||||
(Type::BYTE, 8) => Some(offset_to_bytes(8, self)?),
|
|
||||||
(Type::SBYTE, 5) => Some(offset_to_sbytes(5, self)?),
|
|
||||||
(Type::SBYTE, 6) => Some(offset_to_sbytes(6, self)?),
|
|
||||||
(Type::SBYTE, 7) => Some(offset_to_sbytes(7, self)?),
|
|
||||||
(Type::SBYTE, 8) => Some(offset_to_sbytes(8, self)?),
|
|
||||||
(Type::SHORT, 3) => {
|
|
||||||
let mut r = self.r(bo);
|
|
||||||
Some(List(vec![
|
|
||||||
Unsigned(u32::from(r.read_u16()?)),
|
|
||||||
Unsigned(u32::from(r.read_u16()?)),
|
|
||||||
Unsigned(u32::from(r.read_u16()?)),
|
|
||||||
]))
|
|
||||||
}
|
}
|
||||||
(Type::SSHORT, 3) => {
|
|
||||||
let mut r = self.r(bo);
|
let bo = reader.byte_order();
|
||||||
Some(List(vec![
|
|
||||||
Signed(i32::from(r.read_i16()?)),
|
let tag_size = match self.type_ {
|
||||||
Signed(i32::from(r.read_i16()?)),
|
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
||||||
Signed(i32::from(r.read_i16()?)),
|
Type::SHORT | Type::SSHORT => 2,
|
||||||
]))
|
Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
|
||||||
}
|
Type::LONG8
|
||||||
(Type::SHORT, 4) => {
|
| Type::SLONG8
|
||||||
let mut r = self.r(bo);
|
| Type::DOUBLE
|
||||||
Some(List(vec![
|
| Type::RATIONAL
|
||||||
Unsigned(u32::from(r.read_u16()?)),
|
| Type::SRATIONAL
|
||||||
Unsigned(u32::from(r.read_u16()?)),
|
| Type::IFD8 => 8,
|
||||||
Unsigned(u32::from(r.read_u16()?)),
|
|
||||||
Unsigned(u32::from(r.read_u16()?)),
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
(Type::SSHORT, 4) => {
|
|
||||||
let mut r = self.r(bo);
|
|
||||||
Some(List(vec![
|
|
||||||
Signed(i32::from(r.read_i16()?)),
|
|
||||||
Signed(i32::from(r.read_i16()?)),
|
|
||||||
Signed(i32::from(r.read_i16()?)),
|
|
||||||
Signed(i32::from(r.read_i16()?)),
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
(Type::LONG, 2) => {
|
|
||||||
let mut r = self.r(bo);
|
|
||||||
Some(List(vec![
|
|
||||||
Unsigned(r.read_u32()?),
|
|
||||||
Unsigned(r.read_u32()?),
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
(Type::SLONG, 2) => {
|
|
||||||
let mut r = self.r(bo);
|
|
||||||
Some(List(vec![
|
|
||||||
Signed(r.read_i32()?),
|
|
||||||
Signed(r.read_i32()?),
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
(Type::RATIONAL, 1) => {
|
|
||||||
let mut r = self.r(bo);
|
|
||||||
let numerator = r.read_u32()?;
|
|
||||||
let denominator = r.read_u32()?;
|
|
||||||
Some(Rational(numerator, denominator))
|
|
||||||
}
|
|
||||||
(Type::SRATIONAL, 1) => {
|
|
||||||
let mut r = self.r(bo);
|
|
||||||
let numerator = r.read_i32()?;
|
|
||||||
let denominator = r.read_i32()?;
|
|
||||||
Some(SRational(numerator, denominator))
|
|
||||||
}
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(v) = bigtiff_value {
|
let value_bytes = match self.count.checked_mul(tag_size) {
|
||||||
Ok(v)
|
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 bigtiff && value_bytes > 4 && value_bytes <= 8 {
|
||||||
|
return Ok(match self.type_ {
|
||||||
|
Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?),
|
||||||
|
Type::SLONG8 => SignedBig(self.r(bo).read_i64()?),
|
||||||
|
Type::DOUBLE => Double(self.r(bo).read_f64()?),
|
||||||
|
Type::RATIONAL => {
|
||||||
|
let mut r = self.r(bo);
|
||||||
|
Rational(r.read_u32()?, r.read_u32()?)
|
||||||
|
}
|
||||||
|
Type::SRATIONAL => {
|
||||||
|
let mut r = self.r(bo);
|
||||||
|
SRational(r.read_i32()?, r.read_i32()?)
|
||||||
|
}
|
||||||
|
Type::IFD8 => IfdBig(self.r(bo).read_u64()?),
|
||||||
|
Type::BYTE
|
||||||
|
| Type::SBYTE
|
||||||
|
| Type::ASCII
|
||||||
|
| Type::UNDEFINED
|
||||||
|
| Type::SHORT
|
||||||
|
| Type::SSHORT
|
||||||
|
| Type::LONG
|
||||||
|
| Type::SLONG
|
||||||
|
| Type::FLOAT
|
||||||
|
| Type::IFD => 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::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::LONG => Unsigned(self.r(bo).read_u32()?),
|
||||||
|
Type::SLONG => Signed(self.r(bo).read_i32()?),
|
||||||
|
Type::FLOAT => Float(self.r(bo).read_f32()?),
|
||||||
|
Type::ASCII => {
|
||||||
|
if self.offset[0] == 0 {
|
||||||
|
Ascii("".to_string())
|
||||||
} else {
|
} else {
|
||||||
match (self.type_, self.count) {
|
return Err(TiffError::FormatError(TiffFormatError::InvalidTag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::LONG8 => {
|
||||||
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
|
UnsignedBig(reader.read_u64()?)
|
||||||
|
}
|
||||||
|
Type::SLONG8 => {
|
||||||
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
|
SignedBig(reader.read_i64()?)
|
||||||
|
}
|
||||||
|
Type::DOUBLE => {
|
||||||
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
|
Double(reader.read_f64()?)
|
||||||
|
}
|
||||||
|
Type::RATIONAL => {
|
||||||
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
|
Rational(reader.read_u32()?, reader.read_u32()?)
|
||||||
|
}
|
||||||
|
Type::SRATIONAL => {
|
||||||
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
|
SRational(reader.read_i32()?, reader.read_i32()?)
|
||||||
|
}
|
||||||
|
Type::IFD => Ifd(self.r(bo).read_u32()?),
|
||||||
|
Type::IFD8 => {
|
||||||
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
|
IfdBig(reader.read_u64()?)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 3: There is more than one value, but it fits in the offset field.
|
||||||
|
if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
|
||||||
|
match self.type_ {
|
||||||
|
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)?;
|
||||||
|
if buf.is_ascii() && buf.ends_with(&[0]) {
|
||||||
|
let v = str::from_utf8(&buf)?;
|
||||||
|
let v = v.trim_matches(char::from(0));
|
||||||
|
return Ok(Ascii(v.into()));
|
||||||
|
} else {
|
||||||
|
return Err(TiffError::FormatError(TiffFormatError::InvalidTag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::UNDEFINED => {
|
||||||
|
return Ok(List(
|
||||||
|
self.offset[0..self.count as usize]
|
||||||
|
.iter()
|
||||||
|
.map(|&b| Byte(b))
|
||||||
|
.collect(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Type::SHORT => {
|
||||||
|
let mut r = self.r(bo);
|
||||||
|
let mut v = Vec::new();
|
||||||
|
for _ in 0..self.count {
|
||||||
|
v.push(Short(r.read_u16()?));
|
||||||
|
}
|
||||||
|
return Ok(List(v));
|
||||||
|
}
|
||||||
|
Type::SSHORT => {
|
||||||
|
let mut r = self.r(bo);
|
||||||
|
let mut v = Vec::new();
|
||||||
|
for _ in 0..self.count {
|
||||||
|
v.push(Signed(i32::from(r.read_i16()?)));
|
||||||
|
}
|
||||||
|
return Ok(List(v));
|
||||||
|
}
|
||||||
|
Type::LONG => {
|
||||||
|
let mut r = self.r(bo);
|
||||||
|
let mut v = Vec::new();
|
||||||
|
for _ in 0..self.count {
|
||||||
|
v.push(Unsigned(r.read_u32()?));
|
||||||
|
}
|
||||||
|
return Ok(List(v));
|
||||||
|
}
|
||||||
|
Type::SLONG => {
|
||||||
|
let mut r = self.r(bo);
|
||||||
|
let mut v = Vec::new();
|
||||||
|
for _ in 0..self.count {
|
||||||
|
v.push(Signed(r.read_i32()?));
|
||||||
|
}
|
||||||
|
return Ok(List(v));
|
||||||
|
}
|
||||||
|
Type::FLOAT => {
|
||||||
|
let mut r = self.r(bo);
|
||||||
|
let mut v = Vec::new();
|
||||||
|
for _ in 0..self.count {
|
||||||
|
v.push(Float(r.read_f32()?));
|
||||||
|
}
|
||||||
|
return Ok(List(v));
|
||||||
|
}
|
||||||
|
Type::IFD => {
|
||||||
|
let mut r = self.r(bo);
|
||||||
|
let mut v = Vec::new();
|
||||||
|
for _ in 0..self.count {
|
||||||
|
v.push(Ifd(r.read_u32()?));
|
||||||
|
}
|
||||||
|
return Ok(List(v));
|
||||||
|
}
|
||||||
|
Type::LONG8
|
||||||
|
| Type::SLONG8
|
||||||
|
| Type::RATIONAL
|
||||||
|
| Type::SRATIONAL
|
||||||
|
| Type::DOUBLE
|
||||||
|
| Type::IFD8 => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 4: there is more than one value, and it doesn't fit in the offset field.
|
||||||
|
match self.type_ {
|
||||||
// TODO check if this could give wrong results
|
// TODO check if this could give wrong results
|
||||||
// at a different endianess of file/computer.
|
// at a different endianess of file/computer.
|
||||||
(Type::BYTE, 1) => Ok(Unsigned(u32::from(self.offset[0]))),
|
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
(Type::BYTE, 2) => offset_to_bytes(2, self),
|
let mut buf = [0; 1];
|
||||||
(Type::BYTE, 3) => offset_to_bytes(3, self),
|
reader.read_exact(&mut buf)?;
|
||||||
(Type::BYTE, 4) => offset_to_bytes(4, self),
|
Ok(UnsignedBig(u64::from(buf[0])))
|
||||||
(Type::BYTE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
|
||||||
Ok(UnsignedBig(u64::from(decoder.read_byte()?)))
|
|
||||||
}),
|
}),
|
||||||
(Type::SBYTE, 1) => Ok(Signed(i32::from(self.offset[0] as i8))),
|
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
(Type::SBYTE, 2) => offset_to_sbytes(2, self),
|
Ok(SignedBig(i64::from(reader.read_i8()? as i8)))
|
||||||
(Type::SBYTE, 3) => offset_to_sbytes(3, self),
|
|
||||||
(Type::SBYTE, 4) => offset_to_sbytes(4, self),
|
|
||||||
(Type::SBYTE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
|
||||||
Ok(SignedBig(i64::from(decoder.read_byte()? as i8)))
|
|
||||||
}),
|
}),
|
||||||
(Type::SHORT, 1) => Ok(Unsigned(u32::from(self.r(bo).read_u16()?))),
|
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
(Type::SSHORT, 1) => Ok(Signed(i32::from(self.r(bo).read_i16()?))),
|
Ok(UnsignedBig(u64::from(reader.read_u16()?)))
|
||||||
(Type::SHORT, 2) => {
|
}),
|
||||||
let mut r = self.r(bo);
|
Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
Ok(List(vec![
|
Ok(SignedBig(i64::from(reader.read_i16()?)))
|
||||||
Unsigned(u32::from(r.read_u16()?)),
|
}),
|
||||||
Unsigned(u32::from(r.read_u16()?)),
|
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, bigtiff, limits, reader, |reader| {
|
||||||
|
Ok(Signed(reader.read_i32()?))
|
||||||
|
}),
|
||||||
|
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, bigtiff, limits, reader, |reader| {
|
||||||
|
Ok(Double(reader.read_f64()?))
|
||||||
|
}),
|
||||||
|
Type::RATIONAL => {
|
||||||
|
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
|
Ok(Rational(reader.read_u32()?, reader.read_u32()?))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
(Type::SSHORT, 2) => {
|
Type::SRATIONAL => {
|
||||||
let mut r = self.r(bo);
|
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
Ok(List(vec![
|
Ok(SRational(reader.read_i32()?, reader.read_i32()?))
|
||||||
Signed(i32::from(r.read_i16()?)),
|
})
|
||||||
Signed(i32::from(r.read_i16()?)),
|
|
||||||
]))
|
|
||||||
}
|
}
|
||||||
(Type::SHORT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
Ok(UnsignedBig(u64::from(decoder.read_short()?)))
|
Ok(UnsignedBig(reader.read_u64()?))
|
||||||
}),
|
}),
|
||||||
(Type::SSHORT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
Ok(SignedBig(i64::from(decoder.read_sshort()?)))
|
Ok(SignedBig(reader.read_i64()?))
|
||||||
}),
|
}),
|
||||||
(Type::LONG, 1) => Ok(Unsigned(self.r(bo).read_u32()?)),
|
Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
(Type::SLONG, 1) => Ok(Signed(self.r(bo).read_i32()?)),
|
Ok(Ifd(reader.read_u32()?))
|
||||||
(Type::LONG, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
|
||||||
Ok(Unsigned(decoder.read_long()?))
|
|
||||||
}),
|
}),
|
||||||
(Type::SLONG, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
Ok(Signed(decoder.read_slong()?))
|
Ok(IfdBig(reader.read_u64()?))
|
||||||
}),
|
}),
|
||||||
(Type::RATIONAL, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
Type::UNDEFINED => {
|
||||||
Ok(Rational(decoder.read_long()?, decoder.read_long()?))
|
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
}),
|
let mut buf = [0; 1];
|
||||||
(Type::SRATIONAL, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
reader.read_exact(&mut buf)?;
|
||||||
Ok(SRational(decoder.read_slong()?, decoder.read_slong()?))
|
Ok(Byte(buf[0]))
|
||||||
}),
|
})
|
||||||
(Type::LONG8, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
}
|
||||||
Ok(UnsignedBig(decoder.read_long8()?))
|
Type::ASCII => {
|
||||||
}),
|
let n = usize::try_from(self.count)?;
|
||||||
(Type::ASCII, n) => {
|
|
||||||
let n = usize::try_from(n)?;
|
|
||||||
if n > limits.decoding_buffer_size {
|
if n > limits.decoding_buffer_size {
|
||||||
return Err(TiffError::LimitsExceeded);
|
return Err(TiffError::LimitsExceeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n <= 4 && !decoder.bigtiff) || (n <= 8 && decoder.bigtiff) {
|
if bigtiff {
|
||||||
let mut buf = vec![0; n];
|
reader.goto_offset(self.r(bo).read_u64()?)?
|
||||||
self.r(bo).read_exact(&mut buf )?;
|
|
||||||
let v = String::from_utf8(buf)?;
|
|
||||||
let v = v.trim_matches(char::from(0));
|
|
||||||
Ok(Ascii(v.into()))
|
|
||||||
} else {
|
} else {
|
||||||
if decoder.bigtiff {
|
reader.goto_offset(self.r(bo).read_u32()?.into())?
|
||||||
decoder.goto_offset_u64(self.r(bo).read_u64()?)?
|
|
||||||
} else {
|
|
||||||
decoder.goto_offset(self.r(bo).read_u32()?)?
|
|
||||||
}
|
}
|
||||||
let string = decoder.read_string(n)?;
|
|
||||||
Ok(Ascii(string))
|
let mut out = vec![0; n];
|
||||||
|
reader.read_exact(&mut out)?;
|
||||||
|
// Strings may be null-terminated, so we trim anything downstream of the null byte
|
||||||
|
if let Some(first) = out.iter().position(|&b| b == 0) {
|
||||||
|
out.truncate(first);
|
||||||
}
|
}
|
||||||
}
|
Ok(Ascii(String::from_utf8(out)?))
|
||||||
_ => Err(TiffError::UnsupportedError(
|
|
||||||
TiffUnsupportedError::UnsupportedDataType,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn decode_offset<R, F>(&self, value_count: u64, bo: ByteOrder, limits: &super::Limits, decoder: &mut super::Decoder<R>, decode_fn: F) -> TiffResult<Value>
|
fn decode_offset<R, F>(
|
||||||
|
&self,
|
||||||
|
value_count: u64,
|
||||||
|
bo: ByteOrder,
|
||||||
|
bigtiff: bool,
|
||||||
|
limits: &super::Limits,
|
||||||
|
reader: &mut SmartReader<R>,
|
||||||
|
decode_fn: F,
|
||||||
|
) -> TiffResult<Value>
|
||||||
where
|
where
|
||||||
R: Read + Seek,
|
R: Read + Seek,
|
||||||
F: Fn(&mut super::Decoder<R>) -> TiffResult<Value>,
|
F: Fn(&mut SmartReader<R>) -> TiffResult<Value>,
|
||||||
{
|
{
|
||||||
let value_count = usize::try_from(value_count)?;
|
let value_count = usize::try_from(value_count)?;
|
||||||
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
|
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
|
||||||
@@ -387,13 +629,16 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut v = Vec::with_capacity(value_count);
|
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 {
|
} else {
|
||||||
decoder.goto_offset(self.r(bo).read_u32()?)?
|
self.r(bo).read_u32()?.into()
|
||||||
}
|
};
|
||||||
|
reader.goto_offset(offset)?;
|
||||||
|
|
||||||
for _ in 0..value_count {
|
for _ in 0..value_count {
|
||||||
v.push(decode_fn(decoder)?)
|
v.push(decode_fn(reader)?)
|
||||||
}
|
}
|
||||||
Ok(List(v))
|
Ok(List(v))
|
||||||
}
|
}
|
||||||
@@ -406,7 +651,7 @@ fn offset_to_bytes(n: usize, entry: &Entry) -> TiffResult<Value> {
|
|||||||
entry.offset[0..n]
|
entry.offset[0..n]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&e| Unsigned(u32::from(e)))
|
.map(|&e| Unsigned(u32::from(e)))
|
||||||
.collect()
|
.collect(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,7 +662,7 @@ fn offset_to_sbytes(n: usize, entry: &Entry) -> TiffResult<Value> {
|
|||||||
entry.offset[0..n]
|
entry.offset[0..n]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&e| Signed(i32::from(e as i8)))
|
.map(|&e| Signed(i32::from(e as i8)))
|
||||||
.collect()
|
.collect(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,601 @@
|
|||||||
|
use super::ifd::{Directory, Value};
|
||||||
|
use super::stream::{ByteOrder, DeflateReader, JpegReader, LZWReader, PackBitsReader};
|
||||||
|
use super::tag_reader::TagReader;
|
||||||
|
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
|
||||||
|
use super::{stream::SmartReader, ChunkType};
|
||||||
|
use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag};
|
||||||
|
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
use std::io::{self, Cursor, Read, Seek};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct StripDecodeState {
|
||||||
|
pub rows_per_strip: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Computed values useful for tile decoding
|
||||||
|
pub(crate) struct TileAttributes {
|
||||||
|
pub image_width: usize,
|
||||||
|
pub image_height: usize,
|
||||||
|
|
||||||
|
pub tile_width: usize,
|
||||||
|
pub tile_length: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileAttributes {
|
||||||
|
pub fn tiles_across(&self) -> usize {
|
||||||
|
(self.image_width + self.tile_width - 1) / self.tile_width
|
||||||
|
}
|
||||||
|
pub fn tiles_down(&self) -> usize {
|
||||||
|
(self.image_height + self.tile_length - 1) / self.tile_length
|
||||||
|
}
|
||||||
|
fn padding_right(&self) -> usize {
|
||||||
|
(self.tile_width - self.image_width % self.tile_width) % self.tile_width
|
||||||
|
}
|
||||||
|
fn padding_down(&self) -> usize {
|
||||||
|
(self.tile_length - self.image_height % self.tile_length) % self.tile_length
|
||||||
|
}
|
||||||
|
pub fn get_padding(&self, tile: usize) -> (usize, usize) {
|
||||||
|
let row = tile / self.tiles_across();
|
||||||
|
let column = tile % self.tiles_across();
|
||||||
|
|
||||||
|
let padding_right = if column == self.tiles_across() - 1 {
|
||||||
|
self.padding_right()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let padding_down = if row == self.tiles_down() - 1 {
|
||||||
|
self.padding_down()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
(padding_right, padding_down)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Image {
|
||||||
|
pub ifd: Option<Directory>,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub bits_per_sample: Vec<u8>,
|
||||||
|
#[allow(unused)]
|
||||||
|
pub samples: u8,
|
||||||
|
pub sample_format: Vec<SampleFormat>,
|
||||||
|
pub photometric_interpretation: PhotometricInterpretation,
|
||||||
|
pub compression_method: CompressionMethod,
|
||||||
|
pub predictor: Predictor,
|
||||||
|
pub jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||||
|
pub chunk_type: ChunkType,
|
||||||
|
pub strip_decoder: Option<StripDecodeState>,
|
||||||
|
pub tile_attributes: Option<TileAttributes>,
|
||||||
|
pub chunk_offsets: Vec<u64>,
|
||||||
|
pub chunk_bytes: Vec<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
pub fn from_reader<R: Read + Seek>(
|
||||||
|
reader: &mut SmartReader<R>,
|
||||||
|
ifd: Directory,
|
||||||
|
limits: &Limits,
|
||||||
|
bigtiff: bool,
|
||||||
|
) -> TiffResult<Image> {
|
||||||
|
let mut tag_reader = TagReader {
|
||||||
|
reader,
|
||||||
|
limits,
|
||||||
|
ifd: &ifd,
|
||||||
|
bigtiff,
|
||||||
|
};
|
||||||
|
|
||||||
|
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
||||||
|
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
|
||||||
|
width, height,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let photometric_interpretation = tag_reader
|
||||||
|
.find_tag(Tag::PhotometricInterpretation)?
|
||||||
|
.map(Value::into_u16)
|
||||||
|
.transpose()?
|
||||||
|
.and_then(PhotometricInterpretation::from_u16)
|
||||||
|
.ok_or(TiffUnsupportedError::UnknownInterpretation)?;
|
||||||
|
|
||||||
|
// Try to parse both the compression method and the number, format, and bits of the included samples.
|
||||||
|
// If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
|
||||||
|
let compression_method = match tag_reader.find_tag(Tag::Compression)? {
|
||||||
|
Some(val) => CompressionMethod::from_u16(val.into_u16()?)
|
||||||
|
.ok_or(TiffUnsupportedError::UnknownCompressionMethod)?,
|
||||||
|
None => CompressionMethod::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
|
||||||
|
&& ifd.contains_key(&Tag::JPEGTables)
|
||||||
|
{
|
||||||
|
let vec = tag_reader
|
||||||
|
.find_tag(Tag::JPEGTables)?
|
||||||
|
.unwrap()
|
||||||
|
.into_u8_vec()?;
|
||||||
|
if vec.len() < 2 {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Arc::new(vec))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let samples = tag_reader
|
||||||
|
.find_tag(Tag::SamplesPerPixel)?
|
||||||
|
.map(Value::into_u16)
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(1)
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
|
let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
|
||||||
|
Some(vals) => {
|
||||||
|
let sample_format: Vec<_> = vals
|
||||||
|
.into_iter()
|
||||||
|
.map(SampleFormat::from_u16_exhaustive)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// TODO: for now, only homogenous formats across samples are supported.
|
||||||
|
if !sample_format.windows(2).all(|s| s[0] == s[1]) {
|
||||||
|
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
sample_format
|
||||||
|
}
|
||||||
|
None => vec![SampleFormat::Uint],
|
||||||
|
};
|
||||||
|
|
||||||
|
let bits_per_sample = match samples {
|
||||||
|
1 | 3 | 4 => tag_reader
|
||||||
|
.find_tag_uint_vec(Tag::BitsPerSample)?
|
||||||
|
.unwrap_or_else(|| vec![1]),
|
||||||
|
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(samples).into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let predictor = tag_reader
|
||||||
|
.find_tag(Tag::Predictor)?
|
||||||
|
.map(Value::into_u16)
|
||||||
|
.transpose()?
|
||||||
|
.map(|p| {
|
||||||
|
Predictor::from_u16(p)
|
||||||
|
.ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p)))
|
||||||
|
})
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(Predictor::None);
|
||||||
|
|
||||||
|
let chunk_type;
|
||||||
|
let chunk_offsets;
|
||||||
|
let chunk_bytes;
|
||||||
|
let strip_decoder;
|
||||||
|
let tile_attributes;
|
||||||
|
match (
|
||||||
|
ifd.contains_key(&Tag::StripByteCounts),
|
||||||
|
ifd.contains_key(&Tag::StripOffsets),
|
||||||
|
ifd.contains_key(&Tag::TileByteCounts),
|
||||||
|
ifd.contains_key(&Tag::TileOffsets),
|
||||||
|
) {
|
||||||
|
(true, true, false, false) => {
|
||||||
|
chunk_type = ChunkType::Strip;
|
||||||
|
|
||||||
|
chunk_offsets = tag_reader
|
||||||
|
.find_tag(Tag::StripOffsets)?
|
||||||
|
.unwrap()
|
||||||
|
.into_u64_vec()?;
|
||||||
|
chunk_bytes = tag_reader
|
||||||
|
.find_tag(Tag::StripByteCounts)?
|
||||||
|
.unwrap()
|
||||||
|
.into_u64_vec()?;
|
||||||
|
let rows_per_strip = tag_reader
|
||||||
|
.find_tag(Tag::RowsPerStrip)?
|
||||||
|
.map(Value::into_u32)
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or(height);
|
||||||
|
strip_decoder = Some(StripDecodeState { rows_per_strip });
|
||||||
|
tile_attributes = None;
|
||||||
|
|
||||||
|
if chunk_offsets.len() != chunk_bytes.len()
|
||||||
|
|| rows_per_strip == 0
|
||||||
|
|| u32::try_from(chunk_offsets.len())?
|
||||||
|
!= height.saturating_sub(1) / rows_per_strip + 1
|
||||||
|
{
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(false, false, true, true) => {
|
||||||
|
chunk_type = ChunkType::Tile;
|
||||||
|
|
||||||
|
let tile_width =
|
||||||
|
usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?;
|
||||||
|
let tile_length =
|
||||||
|
usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?;
|
||||||
|
|
||||||
|
if tile_width == 0 {
|
||||||
|
return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into());
|
||||||
|
} else if tile_length == 0 {
|
||||||
|
return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
strip_decoder = None;
|
||||||
|
tile_attributes = Some(TileAttributes {
|
||||||
|
image_width: usize::try_from(width)?,
|
||||||
|
image_height: usize::try_from(height)?,
|
||||||
|
tile_width,
|
||||||
|
tile_length,
|
||||||
|
});
|
||||||
|
chunk_offsets = tag_reader
|
||||||
|
.find_tag(Tag::TileOffsets)?
|
||||||
|
.unwrap()
|
||||||
|
.into_u64_vec()?;
|
||||||
|
chunk_bytes = tag_reader
|
||||||
|
.find_tag(Tag::TileByteCounts)?
|
||||||
|
.unwrap()
|
||||||
|
.into_u64_vec()?;
|
||||||
|
|
||||||
|
let tile = tile_attributes.as_ref().unwrap();
|
||||||
|
if chunk_offsets.len() != chunk_bytes.len()
|
||||||
|
|| chunk_offsets.len() != tile.tiles_down() * tile.tiles_across()
|
||||||
|
{
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, _, _, _) => {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::StripTileTagConflict,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Image {
|
||||||
|
ifd: Some(ifd),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
bits_per_sample,
|
||||||
|
samples,
|
||||||
|
sample_format,
|
||||||
|
photometric_interpretation,
|
||||||
|
compression_method,
|
||||||
|
jpeg_tables,
|
||||||
|
predictor,
|
||||||
|
chunk_type,
|
||||||
|
strip_decoder,
|
||||||
|
tile_attributes,
|
||||||
|
chunk_offsets,
|
||||||
|
chunk_bytes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
|
||||||
|
match self.photometric_interpretation {
|
||||||
|
PhotometricInterpretation::RGB => match self.bits_per_sample[..] {
|
||||||
|
[r, g, b] if [r, r] == [g, b] => Ok(ColorType::RGB(r)),
|
||||||
|
[r, g, b, a] if [r, r, r] == [g, b, a] => Ok(ColorType::RGBA(r)),
|
||||||
|
// FIXME: We should _ignore_ other components. In particular:
|
||||||
|
// > Beware of extra components. Some TIFF files may have more components per pixel
|
||||||
|
// than you think. A Baseline TIFF reader must skip over them gracefully,using the
|
||||||
|
// values of the SamplesPerPixel and BitsPerSample fields.
|
||||||
|
// > -- TIFF 6.0 Specification, Section 7, Additional Baseline requirements.
|
||||||
|
_ => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
self.bits_per_sample.clone(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
PhotometricInterpretation::CMYK => match self.bits_per_sample[..] {
|
||||||
|
[c, m, y, k] if [c, c, c] == [m, y, k] => Ok(ColorType::CMYK(c)),
|
||||||
|
_ => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
self.bits_per_sample.clone(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
PhotometricInterpretation::YCbCr => match self.bits_per_sample[..] {
|
||||||
|
[y, cb, cr] if [y, y] == [cb, cr] => Ok(ColorType::YCbCr(y)),
|
||||||
|
_ => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
self.bits_per_sample.clone(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
|
||||||
|
if self.bits_per_sample.len() == 1 =>
|
||||||
|
{
|
||||||
|
Ok(ColorType::Gray(self.bits_per_sample[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is bad we should not fail at this point
|
||||||
|
_ => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
self.bits_per_sample.clone(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_reader<'r, R: 'r + Read>(
|
||||||
|
reader: R,
|
||||||
|
photometric_interpretation: PhotometricInterpretation,
|
||||||
|
compression_method: CompressionMethod,
|
||||||
|
compressed_length: u64,
|
||||||
|
jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||||
|
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||||
|
Ok(match compression_method {
|
||||||
|
CompressionMethod::None => Box::new(reader),
|
||||||
|
CompressionMethod::LZW => {
|
||||||
|
Box::new(LZWReader::new(reader, usize::try_from(compressed_length)?))
|
||||||
|
}
|
||||||
|
CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)),
|
||||||
|
CompressionMethod::Deflate | CompressionMethod::OldDeflate => {
|
||||||
|
Box::new(DeflateReader::new(reader))
|
||||||
|
}
|
||||||
|
CompressionMethod::ModernJPEG => {
|
||||||
|
if jpeg_tables.is_some() && compressed_length < 2 {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?;
|
||||||
|
let mut decoder = jpeg::Decoder::new(jpeg_reader);
|
||||||
|
|
||||||
|
match photometric_interpretation {
|
||||||
|
PhotometricInterpretation::RGB => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::RGB)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::WhiteIsZero => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::BlackIsZero => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::TransparencyMask => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::CMYK => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::CMYK)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::YCbCr => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::YCbCr)
|
||||||
|
}
|
||||||
|
photometric_interpretation => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedInterpretation(
|
||||||
|
photometric_interpretation,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = decoder.decode()?;
|
||||||
|
|
||||||
|
Box::new(Cursor::new(data))
|
||||||
|
}
|
||||||
|
method => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> {
|
||||||
|
let file_offset = self
|
||||||
|
.chunk_offsets
|
||||||
|
.get(chunk as usize)
|
||||||
|
.ok_or(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let compressed_bytes =
|
||||||
|
self.chunk_bytes
|
||||||
|
.get(chunk as usize)
|
||||||
|
.ok_or(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
))?;
|
||||||
|
|
||||||
|
Ok((*file_offset, *compressed_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> {
|
||||||
|
match self.chunk_type {
|
||||||
|
ChunkType::Strip => {
|
||||||
|
let strip_attrs = self.strip_decoder.as_ref().unwrap();
|
||||||
|
Ok((self.width, strip_attrs.rows_per_strip))
|
||||||
|
}
|
||||||
|
ChunkType::Tile => {
|
||||||
|
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
||||||
|
Ok((
|
||||||
|
u32::try_from(tile_attrs.tile_width)?,
|
||||||
|
u32::try_from(tile_attrs.tile_length)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
|
||||||
|
let dims = self.chunk_dimensions()?;
|
||||||
|
|
||||||
|
match self.chunk_type {
|
||||||
|
ChunkType::Strip => {
|
||||||
|
let strip_height_without_padding = chunk_index
|
||||||
|
.checked_mul(dims.1)
|
||||||
|
.and_then(|x| self.height.checked_sub(x))
|
||||||
|
.ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex(
|
||||||
|
chunk_index,
|
||||||
|
)))?;
|
||||||
|
|
||||||
|
// Ignore potential vertical padding on the bottommost strip
|
||||||
|
let strip_height = dims.1.min(strip_height_without_padding);
|
||||||
|
|
||||||
|
Ok((dims.0, strip_height))
|
||||||
|
}
|
||||||
|
ChunkType::Tile => {
|
||||||
|
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
||||||
|
let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize);
|
||||||
|
|
||||||
|
let tile_width = tile_attrs.tile_width - padding_right;
|
||||||
|
let tile_length = tile_attrs.tile_length - padding_down;
|
||||||
|
|
||||||
|
Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expand_chunk(
|
||||||
|
&self,
|
||||||
|
reader: impl Read,
|
||||||
|
mut buffer: DecodingBuffer,
|
||||||
|
output_width: usize,
|
||||||
|
byte_order: ByteOrder,
|
||||||
|
chunk_index: u32,
|
||||||
|
) -> TiffResult<()> {
|
||||||
|
// Validate that the provided buffer is of the expected type.
|
||||||
|
let color_type = self.colortype()?;
|
||||||
|
match (color_type, &buffer) {
|
||||||
|
(ColorType::RGB(n), _)
|
||||||
|
| (ColorType::RGBA(n), _)
|
||||||
|
| (ColorType::CMYK(n), _)
|
||||||
|
| (ColorType::YCbCr(n), _)
|
||||||
|
| (ColorType::Gray(n), _)
|
||||||
|
if usize::from(n) == buffer.byte_len() * 8 => {}
|
||||||
|
(ColorType::Gray(n), DecodingBuffer::U8(_)) if n < 8 => match self.predictor {
|
||||||
|
Predictor::None => {}
|
||||||
|
Predictor::Horizontal => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::HorizontalPredictor(color_type),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Predictor::FloatingPoint => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::FloatingPointPredictor(color_type),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(type_, _) => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedColorType(type_),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that the predictor is supported for the sample type.
|
||||||
|
match (self.predictor, &buffer) {
|
||||||
|
(Predictor::Horizontal, DecodingBuffer::F32(_))
|
||||||
|
| (Predictor::Horizontal, DecodingBuffer::F64(_)) => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::HorizontalPredictor(color_type),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
(Predictor::FloatingPoint, DecodingBuffer::F32(_))
|
||||||
|
| (Predictor::FloatingPoint, DecodingBuffer::F64(_)) => {}
|
||||||
|
(Predictor::FloatingPoint, _) => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::FloatingPointPredictor(color_type),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let compressed_bytes =
|
||||||
|
self.chunk_bytes
|
||||||
|
.get(chunk_index as usize)
|
||||||
|
.ok_or(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let byte_len = buffer.byte_len();
|
||||||
|
let compression_method = self.compression_method;
|
||||||
|
let photometric_interpretation = self.photometric_interpretation;
|
||||||
|
let predictor = self.predictor;
|
||||||
|
let samples = self.bits_per_sample.len();
|
||||||
|
|
||||||
|
let chunk_dims = self.chunk_dimensions()?;
|
||||||
|
let data_dims = self.chunk_data_dimensions(chunk_index)?;
|
||||||
|
|
||||||
|
let padding_right = chunk_dims.0 - data_dims.0;
|
||||||
|
|
||||||
|
let jpeg_tables = self.jpeg_tables.clone();
|
||||||
|
let mut reader = Self::create_reader(
|
||||||
|
reader,
|
||||||
|
photometric_interpretation,
|
||||||
|
compression_method,
|
||||||
|
*compressed_bytes,
|
||||||
|
jpeg_tables,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if output_width == data_dims.0 as usize && padding_right == 0 {
|
||||||
|
let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples;
|
||||||
|
let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len];
|
||||||
|
reader.read_exact(tile)?;
|
||||||
|
|
||||||
|
for row in 0..data_dims.1 as usize {
|
||||||
|
let row_start = row as usize * output_width as usize * samples;
|
||||||
|
let row_end = (row + 1) * output_width as usize * samples;
|
||||||
|
let row = buffer.subrange(row_start..row_end);
|
||||||
|
super::fix_endianness_and_predict(row, samples, byte_order, predictor);
|
||||||
|
}
|
||||||
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
|
super::invert_colors(&mut buffer.subrange(0..total_samples), color_type);
|
||||||
|
}
|
||||||
|
} else if padding_right > 0 && self.predictor == Predictor::FloatingPoint {
|
||||||
|
// The floating point predictor shuffles the padding bytes into the encoded output, so
|
||||||
|
// this case is handled specially when needed.
|
||||||
|
let mut encoded = vec![0u8; chunk_dims.0 as usize * samples * byte_len];
|
||||||
|
|
||||||
|
for row in 0..data_dims.1 as usize {
|
||||||
|
let row_start = row * output_width as usize * samples;
|
||||||
|
let row_end = row_start + data_dims.0 as usize * samples;
|
||||||
|
|
||||||
|
reader.read_exact(&mut encoded)?;
|
||||||
|
match buffer.subrange(row_start..row_end) {
|
||||||
|
DecodingBuffer::F32(buf) => fp_predict_f32(&mut encoded, buf, samples),
|
||||||
|
DecodingBuffer::F64(buf) => fp_predict_f64(&mut encoded, buf, samples),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
|
super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for row in 0..data_dims.1 as usize {
|
||||||
|
let row_start = row * output_width as usize * samples;
|
||||||
|
let row_end = row_start + data_dims.0 as usize * samples;
|
||||||
|
|
||||||
|
let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)];
|
||||||
|
reader.read_exact(row)?;
|
||||||
|
|
||||||
|
// Skip horizontal padding
|
||||||
|
if padding_right > 0 {
|
||||||
|
let len = u64::try_from(padding_right as usize * samples * byte_len)?;
|
||||||
|
io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut row = buffer.subrange(row_start..row_end);
|
||||||
|
super::fix_endianness_and_predict(row.copy(), samples, byte_order, predictor);
|
||||||
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
|
super::invert_colors(&mut row, color_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+725
-458
File diff suppressed because it is too large.
Load diff
+297
-207
@@ -1,10 +1,8 @@
|
|||||||
//! All IO functionality needed for TIFF decoding
|
//! All IO functionality needed for TIFF decoding
|
||||||
|
|
||||||
use crate::error::{TiffError, TiffResult};
|
use std::convert::TryFrom;
|
||||||
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Take};
|
||||||
use lzw;
|
use std::sync::Arc;
|
||||||
use miniz_oxide::inflate;
|
|
||||||
use std::io::{self, Read, Seek};
|
|
||||||
|
|
||||||
/// Byte order of the TIFF file.
|
/// Byte order of the TIFF file.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -23,261 +21,346 @@ pub trait EndianReader: Read {
|
|||||||
/// Reads an u16
|
/// Reads an u16
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_u16(&mut self) -> Result<u16, io::Error> {
|
fn read_u16(&mut self) -> Result<u16, io::Error> {
|
||||||
match self.byte_order() {
|
let mut n = [0u8; 2];
|
||||||
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_u16::<LittleEndian>(self),
|
self.read_exact(&mut n)?;
|
||||||
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_u16::<BigEndian>(self),
|
Ok(match self.byte_order() {
|
||||||
}
|
ByteOrder::LittleEndian => u16::from_le_bytes(n),
|
||||||
|
ByteOrder::BigEndian => u16::from_be_bytes(n),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads an i8
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_u16_into(&mut self, buffer: &mut [u16]) -> Result<(), io::Error> {
|
fn read_i8(&mut self) -> Result<i8, io::Error> {
|
||||||
match self.byte_order() {
|
let mut n = [0u8; 1];
|
||||||
ByteOrder::LittleEndian => {
|
self.read_exact(&mut n)?;
|
||||||
<Self as ReadBytesExt>::read_u16_into::<LittleEndian>(self, buffer)
|
Ok(match self.byte_order() {
|
||||||
}
|
ByteOrder::LittleEndian => i8::from_le_bytes(n),
|
||||||
ByteOrder::BigEndian => {
|
ByteOrder::BigEndian => i8::from_be_bytes(n),
|
||||||
<Self as ReadBytesExt>::read_u16_into::<BigEndian>(self, buffer)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads an i16
|
/// Reads an i16
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_i16(&mut self) -> Result<i16, io::Error> {
|
fn read_i16(&mut self) -> Result<i16, io::Error> {
|
||||||
match self.byte_order() {
|
let mut n = [0u8; 2];
|
||||||
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_i16::<LittleEndian>(self),
|
self.read_exact(&mut n)?;
|
||||||
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_i16::<BigEndian>(self),
|
Ok(match self.byte_order() {
|
||||||
}
|
ByteOrder::LittleEndian => i16::from_le_bytes(n),
|
||||||
|
ByteOrder::BigEndian => i16::from_be_bytes(n),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads an u32
|
/// Reads an u32
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_u32(&mut self) -> Result<u32, io::Error> {
|
fn read_u32(&mut self) -> Result<u32, io::Error> {
|
||||||
match self.byte_order() {
|
let mut n = [0u8; 4];
|
||||||
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_u32::<LittleEndian>(self),
|
self.read_exact(&mut n)?;
|
||||||
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_u32::<BigEndian>(self),
|
Ok(match self.byte_order() {
|
||||||
}
|
ByteOrder::LittleEndian => u32::from_le_bytes(n),
|
||||||
}
|
ByteOrder::BigEndian => u32::from_be_bytes(n),
|
||||||
|
})
|
||||||
#[inline(always)]
|
|
||||||
fn read_u32_into(&mut self, buffer: &mut [u32]) -> Result<(), io::Error> {
|
|
||||||
match self.byte_order() {
|
|
||||||
ByteOrder::LittleEndian => {
|
|
||||||
<Self as ReadBytesExt>::read_u32_into::<LittleEndian>(self, buffer)
|
|
||||||
}
|
|
||||||
ByteOrder::BigEndian => {
|
|
||||||
<Self as ReadBytesExt>::read_u32_into::<BigEndian>(self, buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads an u64
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_u64(&mut self) -> Result<u64, io::Error> {
|
|
||||||
match self.byte_order() {
|
|
||||||
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_u64::<LittleEndian>(self),
|
|
||||||
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_u64::<BigEndian>(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn read_u64_into(&mut self, buffer: &mut [u64]) -> Result<(), io::Error> {
|
|
||||||
match self.byte_order() {
|
|
||||||
ByteOrder::LittleEndian => {
|
|
||||||
<Self as ReadBytesExt>::read_u64_into::<LittleEndian>(self, buffer)
|
|
||||||
}
|
|
||||||
ByteOrder::BigEndian => {
|
|
||||||
<Self as ReadBytesExt>::read_u64_into::<BigEndian>(self, buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads an i32
|
/// Reads an i32
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn read_i32(&mut self) -> Result<i32, io::Error> {
|
fn read_i32(&mut self) -> Result<i32, io::Error> {
|
||||||
match self.byte_order() {
|
let mut n = [0u8; 4];
|
||||||
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_i32::<LittleEndian>(self),
|
self.read_exact(&mut n)?;
|
||||||
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_i32::<BigEndian>(self),
|
Ok(match self.byte_order() {
|
||||||
|
ByteOrder::LittleEndian => i32::from_le_bytes(n),
|
||||||
|
ByteOrder::BigEndian => i32::from_be_bytes(n),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads an u64
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_u64(&mut self) -> Result<u64, io::Error> {
|
||||||
|
let mut n = [0u8; 8];
|
||||||
|
self.read_exact(&mut n)?;
|
||||||
|
Ok(match self.byte_order() {
|
||||||
|
ByteOrder::LittleEndian => u64::from_le_bytes(n),
|
||||||
|
ByteOrder::BigEndian => u64::from_be_bytes(n),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads an i64
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_i64(&mut self) -> Result<i64, io::Error> {
|
||||||
|
let mut n = [0u8; 8];
|
||||||
|
self.read_exact(&mut n)?;
|
||||||
|
Ok(match self.byte_order() {
|
||||||
|
ByteOrder::LittleEndian => i64::from_le_bytes(n),
|
||||||
|
ByteOrder::BigEndian => i64::from_be_bytes(n),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads an f32
|
||||||
|
#[inline(always)]
|
||||||
|
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() {
|
||||||
|
ByteOrder::LittleEndian => u32::from_le_bytes(n),
|
||||||
|
ByteOrder::BigEndian => u32::from_be_bytes(n),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads an f64
|
||||||
|
#[inline(always)]
|
||||||
|
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() {
|
||||||
|
ByteOrder::LittleEndian => u64::from_le_bytes(n),
|
||||||
|
ByteOrder::BigEndian => u64::from_be_bytes(n),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reader that decompresses DEFLATE streams
|
///
|
||||||
pub struct DeflateReader {
|
/// # READERS
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
///
|
||||||
byte_order: ByteOrder,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeflateReader {
|
///
|
||||||
pub fn new<R: Read + Seek>(
|
/// ## Deflate Reader
|
||||||
reader: &mut SmartReader<R>,
|
///
|
||||||
max_uncompressed_length: usize,
|
|
||||||
) -> TiffResult<(usize, Self)> {
|
|
||||||
let byte_order = reader.byte_order;
|
|
||||||
let mut compressed = Vec::new();
|
|
||||||
reader.read_to_end(&mut compressed)?;
|
|
||||||
|
|
||||||
// TODO: Implement streaming compression, and remove this (temporary) and somewhat
|
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
||||||
// misleading workaround.
|
|
||||||
if compressed.len() > max_uncompressed_length {
|
|
||||||
return Err(TiffError::LimitsExceeded);
|
|
||||||
}
|
|
||||||
|
|
||||||
let uncompressed =
|
///
|
||||||
inflate::decompress_to_vec_zlib(&compressed).map_err(TiffError::from_inflate_status)?;
|
/// ## LZW Reader
|
||||||
|
///
|
||||||
Ok((
|
|
||||||
uncompressed.len(),
|
|
||||||
Self {
|
|
||||||
byte_order,
|
|
||||||
buffer: io::Cursor::new(uncompressed),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for DeflateReader {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.buffer.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EndianReader for DeflateReader {
|
|
||||||
fn byte_order(&self) -> ByteOrder {
|
|
||||||
self.byte_order
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reader that decompresses LZW streams
|
/// Reader that decompresses LZW streams
|
||||||
pub struct LZWReader {
|
pub struct LZWReader<R: Read> {
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
reader: BufReader<Take<R>>,
|
||||||
byte_order: ByteOrder,
|
decoder: weezl::decode::Decoder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LZWReader {
|
impl<R: Read> LZWReader<R> {
|
||||||
/// Wraps a reader
|
/// Wraps a reader
|
||||||
pub fn new<R>(
|
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
|
||||||
reader: &mut SmartReader<R>,
|
Self {
|
||||||
compressed_length: usize,
|
reader: BufReader::with_capacity(
|
||||||
max_uncompressed_length: usize,
|
(32 * 1024).min(compressed_length),
|
||||||
) -> io::Result<(usize, LZWReader)>
|
reader.take(u64::try_from(compressed_length).unwrap()),
|
||||||
where
|
),
|
||||||
R: Read + Seek,
|
decoder: weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8),
|
||||||
{
|
|
||||||
let order = reader.byte_order;
|
|
||||||
let mut compressed = vec![0; compressed_length as usize];
|
|
||||||
reader.read_exact(&mut compressed[..])?;
|
|
||||||
let mut uncompressed = Vec::with_capacity(max_uncompressed_length);
|
|
||||||
let mut decoder = lzw::DecoderEarlyChange::new(lzw::MsbReader::new(), 8);
|
|
||||||
let mut bytes_read = 0;
|
|
||||||
while bytes_read < compressed_length && uncompressed.len() < max_uncompressed_length {
|
|
||||||
let (len, bytes) = decoder.decode_bytes(&compressed[bytes_read..])?;
|
|
||||||
bytes_read += len;
|
|
||||||
uncompressed.extend_from_slice(bytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes = uncompressed.len();
|
|
||||||
Ok((
|
|
||||||
bytes,
|
|
||||||
LZWReader {
|
|
||||||
buffer: io::Cursor::new(uncompressed),
|
|
||||||
byte_order: order,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Read for LZWReader {
|
impl<R: Read> Read for LZWReader<R> {
|
||||||
#[inline]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
self.buffer.read(buf)
|
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) => {
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EndianReader for LZWReader {
|
///
|
||||||
#[inline(always)]
|
/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7))
|
||||||
fn byte_order(&self) -> ByteOrder {
|
///
|
||||||
self.byte_order
|
|
||||||
|
pub(crate) struct JpegReader {
|
||||||
|
jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||||
|
|
||||||
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
|
|
||||||
|
offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JpegReader {
|
||||||
|
/// Constructs new JpegReader wrapping a SmartReader.
|
||||||
|
/// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one
|
||||||
|
/// 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: Read>(
|
||||||
|
mut reader: R,
|
||||||
|
length: u64,
|
||||||
|
jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||||
|
) -> io::Result<JpegReader> {
|
||||||
|
// Read jpeg image data
|
||||||
|
let mut segment = vec![0; length as usize];
|
||||||
|
|
||||||
|
reader.read_exact(&mut segment[..])?;
|
||||||
|
|
||||||
|
match jpeg_tables {
|
||||||
|
Some(jpeg_tables) => {
|
||||||
|
assert!(
|
||||||
|
jpeg_tables.len() >= 2,
|
||||||
|
"jpeg_tables, if given, must be at least 2 bytes long. Got {:?}",
|
||||||
|
jpeg_tables
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
length >= 2,
|
||||||
|
"if jpeg_tables is given, length must be at least 2 bytes long, got {}",
|
||||||
|
length
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut buffer = io::Cursor::new(segment);
|
||||||
|
// Skip the first two bytes (marker bytes)
|
||||||
|
buffer.seek(SeekFrom::Start(2))?;
|
||||||
|
|
||||||
|
Ok(JpegReader {
|
||||||
|
buffer,
|
||||||
|
jpeg_tables: Some(jpeg_tables),
|
||||||
|
offset: 0,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
None => Ok(JpegReader {
|
||||||
|
buffer: io::Cursor::new(segment),
|
||||||
|
jpeg_tables: None,
|
||||||
|
offset: 0,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for JpegReader {
|
||||||
|
// #[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
let mut start = 0;
|
||||||
|
|
||||||
|
if let Some(jpeg_tables) = &self.jpeg_tables {
|
||||||
|
if jpeg_tables.len() - 2 > self.offset {
|
||||||
|
// Read (rest of) jpeg_tables to buf (without the last two bytes)
|
||||||
|
let size_remaining = jpeg_tables.len() - self.offset - 2;
|
||||||
|
let to_copy = size_remaining.min(buf.len());
|
||||||
|
|
||||||
|
buf[start..start + to_copy]
|
||||||
|
.copy_from_slice(&jpeg_tables[self.offset..self.offset + to_copy]);
|
||||||
|
|
||||||
|
self.offset += to_copy;
|
||||||
|
|
||||||
|
if to_copy == buf.len() {
|
||||||
|
return Ok(to_copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
start += to_copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let read = self.buffer.read(&mut buf[start..])?;
|
||||||
|
self.offset += read;
|
||||||
|
|
||||||
|
Ok(read + start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// ## PackBits Reader
|
||||||
|
///
|
||||||
|
|
||||||
|
enum PackBitsReaderState {
|
||||||
|
Header,
|
||||||
|
Literal,
|
||||||
|
Repeat { value: u8 },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reader that unpacks Apple's `PackBits` format
|
/// Reader that unpacks Apple's `PackBits` format
|
||||||
pub struct PackBitsReader {
|
pub struct PackBitsReader<R: Read> {
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
reader: Take<R>,
|
||||||
byte_order: ByteOrder,
|
state: PackBitsReaderState,
|
||||||
|
count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackBitsReader {
|
impl<R: Read> PackBitsReader<R> {
|
||||||
/// Wraps a reader
|
/// Wraps a reader
|
||||||
pub fn new<R: Read + Seek>(
|
pub fn new(reader: R, length: u64) -> Self {
|
||||||
mut reader: R,
|
Self {
|
||||||
byte_order: ByteOrder,
|
reader: reader.take(length),
|
||||||
length: usize,
|
state: PackBitsReaderState::Header,
|
||||||
) -> io::Result<(usize, PackBitsReader)> {
|
count: 0,
|
||||||
let mut buffer = Vec::new();
|
|
||||||
let mut read: usize = 0;
|
|
||||||
while read < length {
|
|
||||||
let lread = read_packbits_run(&mut reader, &mut buffer)?;
|
|
||||||
if lread == 0 {
|
|
||||||
return Err(io::ErrorKind::UnexpectedEof.into());
|
|
||||||
}
|
}
|
||||||
read += lread;
|
|
||||||
}
|
|
||||||
Ok((
|
|
||||||
buffer.len(),
|
|
||||||
PackBitsReader {
|
|
||||||
buffer: io::Cursor::new(buffer),
|
|
||||||
byte_order,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_packbits_run<R: Read + Seek>(reader: &mut R, buffer: &mut Vec<u8>) -> io::Result<usize> {
|
impl<R: Read> Read for PackBitsReader<R> {
|
||||||
let mut header: [u8; 1] = [0];
|
|
||||||
|
|
||||||
let bytes = reader.read(&mut header)?;
|
|
||||||
|
|
||||||
match bytes {
|
|
||||||
0 => Ok(0),
|
|
||||||
_ => match header[0] as i8 {
|
|
||||||
-128 => Ok(1),
|
|
||||||
h if h >= -127 && h <= -1 => {
|
|
||||||
let new_len = buffer.len() + (1 - h as isize) as usize;
|
|
||||||
reader.read_exact(&mut header)?;
|
|
||||||
buffer.resize(new_len, header[0]);
|
|
||||||
Ok(2)
|
|
||||||
}
|
|
||||||
h => {
|
|
||||||
let num_vals = h as usize + 1;
|
|
||||||
let start = buffer.len();
|
|
||||||
buffer.resize(start + num_vals, 0);
|
|
||||||
reader.read_exact(&mut buffer[start..])?;
|
|
||||||
Ok(num_vals + 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for PackBitsReader {
|
|
||||||
#[inline]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
self.buffer.read(buf)
|
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 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 {
|
||||||
|
self.state = PackBitsReaderState::Literal;
|
||||||
|
self.count = h as usize + 1;
|
||||||
|
} else {
|
||||||
|
// h = -128 is a no-op.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
length
|
||||||
|
}
|
||||||
|
PackBitsReaderState::Header => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.count -= actual;
|
||||||
|
if self.count == 0 {
|
||||||
|
self.state = PackBitsReaderState::Header;
|
||||||
|
}
|
||||||
|
return Ok(actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EndianReader for PackBitsReader {
|
///
|
||||||
#[inline(always)]
|
/// ## SmartReader Reader
|
||||||
fn byte_order(&self) -> ByteOrder {
|
///
|
||||||
self.byte_order
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reader that is aware of the byte order.
|
/// Reader that is aware of the byte order.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SmartReader<R>
|
pub struct SmartReader<R>
|
||||||
where
|
where
|
||||||
R: Read + Seek,
|
R: Read,
|
||||||
{
|
{
|
||||||
reader: R,
|
reader: R,
|
||||||
pub byte_order: ByteOrder,
|
pub byte_order: ByteOrder,
|
||||||
@@ -285,17 +368,25 @@ where
|
|||||||
|
|
||||||
impl<R> SmartReader<R>
|
impl<R> SmartReader<R>
|
||||||
where
|
where
|
||||||
R: Read + Seek,
|
R: Read,
|
||||||
{
|
{
|
||||||
/// Wraps a reader
|
/// Wraps a reader
|
||||||
pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> {
|
pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> {
|
||||||
SmartReader { reader, byte_order }
|
SmartReader { reader, byte_order }
|
||||||
}
|
}
|
||||||
|
pub fn into_inner(self) -> R {
|
||||||
|
self.reader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<R: Read + Seek> SmartReader<R> {
|
||||||
|
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
|
||||||
|
self.seek(io::SeekFrom::Start(offset)).map(|_| ())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> EndianReader for SmartReader<R>
|
impl<R> EndianReader for SmartReader<R>
|
||||||
where
|
where
|
||||||
R: Read + Seek,
|
R: Read,
|
||||||
{
|
{
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn byte_order(&self) -> ByteOrder {
|
fn byte_order(&self) -> ByteOrder {
|
||||||
@@ -303,7 +394,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read + Seek> Read for SmartReader<R> {
|
impl<R: Read> Read for SmartReader<R> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
self.reader.read(buf)
|
self.reader.read(buf)
|
||||||
@@ -330,8 +421,7 @@ mod test {
|
|||||||
let encoded_len = encoded.len();
|
let encoded_len = encoded.len();
|
||||||
|
|
||||||
let buff = io::Cursor::new(encoded);
|
let buff = io::Cursor::new(encoded);
|
||||||
let (_, mut decoder) =
|
let mut decoder = PackBitsReader::new(buff, encoded_len as u64);
|
||||||
PackBitsReader::new(buff, ByteOrder::LittleEndian, encoded_len).unwrap();
|
|
||||||
|
|
||||||
let mut decoded = Vec::new();
|
let mut decoded = Vec::new();
|
||||||
decoder.read_to_end(&mut decoded).unwrap();
|
decoder.read_to_end(&mut decoded).unwrap();
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
|
use crate::tags::Tag;
|
||||||
|
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||||
|
|
||||||
|
use super::ifd::{Directory, Value};
|
||||||
|
use super::stream::SmartReader;
|
||||||
|
use super::Limits;
|
||||||
|
|
||||||
|
pub(crate) struct TagReader<'a, R: Read + Seek> {
|
||||||
|
pub reader: &'a mut SmartReader<R>,
|
||||||
|
pub ifd: &'a Directory,
|
||||||
|
pub limits: &'a Limits,
|
||||||
|
pub bigtiff: bool,
|
||||||
|
}
|
||||||
|
impl<'a, R: Read + Seek> TagReader<'a, R> {
|
||||||
|
pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<Value>> {
|
||||||
|
Ok(match self.ifd.get(&tag) {
|
||||||
|
Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?),
|
||||||
|
None => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
|
||||||
|
match self.find_tag(tag)? {
|
||||||
|
Some(val) => Ok(val),
|
||||||
|
None => Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::RequiredTagNotFound(tag),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn find_tag_uint_vec<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<Vec<T>>> {
|
||||||
|
self.find_tag(tag)?
|
||||||
|
.map(|v| v.into_u64_vec())
|
||||||
|
.transpose()?
|
||||||
|
.map(|v| {
|
||||||
|
v.into_iter()
|
||||||
|
.map(|u| {
|
||||||
|
T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
}
|
||||||
+125
-3
@@ -1,4 +1,4 @@
|
|||||||
use tags::PhotometricInterpretation;
|
use crate::tags::{PhotometricInterpretation, SampleFormat};
|
||||||
|
|
||||||
/// Trait for different colortypes that can be encoded.
|
/// Trait for different colortypes that can be encoded.
|
||||||
pub trait ColorType {
|
pub trait ColorType {
|
||||||
@@ -8,6 +8,8 @@ pub trait ColorType {
|
|||||||
const TIFF_VALUE: PhotometricInterpretation;
|
const TIFF_VALUE: PhotometricInterpretation;
|
||||||
/// The value of the tiff tag `BitsPerSample`
|
/// The value of the tiff tag `BitsPerSample`
|
||||||
const BITS_PER_SAMPLE: &'static [u16];
|
const BITS_PER_SAMPLE: &'static [u16];
|
||||||
|
/// The value of the tiff tag `SampleFormat`
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Gray8;
|
pub struct Gray8;
|
||||||
@@ -15,6 +17,15 @@ impl ColorType for Gray8 {
|
|||||||
type Inner = u8;
|
type Inner = u8;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GrayI8;
|
||||||
|
impl ColorType for GrayI8 {
|
||||||
|
type Inner = i8;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Gray16;
|
pub struct Gray16;
|
||||||
@@ -22,6 +33,15 @@ impl ColorType for Gray16 {
|
|||||||
type Inner = u16;
|
type Inner = u16;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GrayI16;
|
||||||
|
impl ColorType for GrayI16 {
|
||||||
|
type Inner = i16;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Gray32;
|
pub struct Gray32;
|
||||||
@@ -29,6 +49,23 @@ impl ColorType for Gray32 {
|
|||||||
type Inner = u32;
|
type Inner = u32;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GrayI32;
|
||||||
|
impl ColorType for GrayI32 {
|
||||||
|
type Inner = i32;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Gray32Float;
|
||||||
|
impl ColorType for Gray32Float {
|
||||||
|
type Inner = f32;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Gray64;
|
pub struct Gray64;
|
||||||
@@ -36,6 +73,23 @@ impl ColorType for Gray64 {
|
|||||||
type Inner = u64;
|
type Inner = u64;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GrayI64;
|
||||||
|
impl ColorType for GrayI64 {
|
||||||
|
type Inner = i64;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Gray64Float;
|
||||||
|
impl ColorType for Gray64Float {
|
||||||
|
type Inner = f64;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGB8;
|
pub struct RGB8;
|
||||||
@@ -43,6 +97,7 @@ impl ColorType for RGB8 {
|
|||||||
type Inner = u8;
|
type Inner = u8;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGB16;
|
pub struct RGB16;
|
||||||
@@ -50,6 +105,7 @@ impl ColorType for RGB16 {
|
|||||||
type Inner = u16;
|
type Inner = u16;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
|
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGB32;
|
pub struct RGB32;
|
||||||
@@ -57,6 +113,15 @@ impl ColorType for RGB32 {
|
|||||||
type Inner = u32;
|
type Inner = u32;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RGB32Float;
|
||||||
|
impl ColorType for RGB32Float {
|
||||||
|
type Inner = f32;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGB64;
|
pub struct RGB64;
|
||||||
@@ -64,6 +129,15 @@ impl ColorType for RGB64 {
|
|||||||
type Inner = u64;
|
type Inner = u64;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RGB64Float;
|
||||||
|
impl ColorType for RGB64Float {
|
||||||
|
type Inner = f64;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGBA8;
|
pub struct RGBA8;
|
||||||
@@ -71,6 +145,7 @@ impl ColorType for RGBA8 {
|
|||||||
type Inner = u8;
|
type Inner = u8;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGBA16;
|
pub struct RGBA16;
|
||||||
@@ -78,20 +153,39 @@ impl ColorType for RGBA16 {
|
|||||||
type Inner = u16;
|
type Inner = u16;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGBA32;
|
pub struct RGBA32;
|
||||||
impl ColorType for RGBA32 {
|
impl ColorType for RGBA32 {
|
||||||
type Inner = u16;
|
type Inner = u32;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RGBA32Float;
|
||||||
|
impl ColorType for RGBA32Float {
|
||||||
|
type Inner = f32;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGBA64;
|
pub struct RGBA64;
|
||||||
impl ColorType for RGBA64 {
|
impl ColorType for RGBA64 {
|
||||||
type Inner = u16;
|
type Inner = u64;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RGBA64Float;
|
||||||
|
impl ColorType for RGBA64Float {
|
||||||
|
type Inner = f64;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CMYK8;
|
pub struct CMYK8;
|
||||||
@@ -99,6 +193,7 @@ impl ColorType for CMYK8 {
|
|||||||
type Inner = u8;
|
type Inner = u8;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CMYK16;
|
pub struct CMYK16;
|
||||||
@@ -106,6 +201,7 @@ impl ColorType for CMYK16 {
|
|||||||
type Inner = u16;
|
type Inner = u16;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CMYK32;
|
pub struct CMYK32;
|
||||||
@@ -113,6 +209,15 @@ impl ColorType for CMYK32 {
|
|||||||
type Inner = u32;
|
type Inner = u32;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CMYK32Float;
|
||||||
|
impl ColorType for CMYK32Float {
|
||||||
|
type Inner = f32;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CMYK64;
|
pub struct CMYK64;
|
||||||
@@ -120,4 +225,21 @@ impl ColorType for CMYK64 {
|
|||||||
type Inner = u64;
|
type Inner = u64;
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CMYK64Float;
|
||||||
|
impl ColorType for CMYK64Float {
|
||||||
|
type Inner = f64;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct YCbCr8;
|
||||||
|
impl ColorType for YCbCr8 {
|
||||||
|
type Inner = u8;
|
||||||
|
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr;
|
||||||
|
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
||||||
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||||
|
use flate2::{write::ZlibEncoder, Compression as FlateCompression};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/// The Deflate algorithm used to compress image data in TIFF files.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Deflate {
|
||||||
|
level: FlateCompression,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The level of compression used by the Deflate algorithm.
|
||||||
|
/// It allows trading compression ratio for compression speed.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum DeflateLevel {
|
||||||
|
/// The fastest possible compression mode.
|
||||||
|
Fast = 1,
|
||||||
|
/// The conserative choice between speed and ratio.
|
||||||
|
Balanced = 6,
|
||||||
|
/// The best compression available with Deflate.
|
||||||
|
Best = 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DeflateLevel {
|
||||||
|
fn default() -> Self {
|
||||||
|
DeflateLevel::Balanced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deflate {
|
||||||
|
/// Create a new deflate compressor with a specific level of compression.
|
||||||
|
pub fn with_level(level: DeflateLevel) -> Self {
|
||||||
|
Self {
|
||||||
|
level: FlateCompression::new(level as u32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Deflate {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::with_level(DeflateLevel::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Compression for Deflate {
|
||||||
|
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::Deflate;
|
||||||
|
|
||||||
|
fn get_algorithm(&self) -> Compressor {
|
||||||
|
Compressor::Deflate(self.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionAlgorithm for Deflate {
|
||||||
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
|
let mut encoder = ZlibEncoder::new(writer, self.level);
|
||||||
|
encoder.write_all(bytes)?;
|
||||||
|
encoder.try_finish()?;
|
||||||
|
Ok(encoder.total_out())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::encoder::compression::tests::TEST_DATA;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deflate() {
|
||||||
|
const EXPECTED_COMPRESSED_DATA: [u8; 64] = [
|
||||||
|
0x78, 0x9C, 0x15, 0xC7, 0xD1, 0x0D, 0x80, 0x20, 0x0C, 0x04, 0xD0, 0x55, 0x6E, 0x02,
|
||||||
|
0xA7, 0x71, 0x81, 0xA6, 0x41, 0xDA, 0x28, 0xD4, 0xF4, 0xD0, 0xF9, 0x81, 0xE4, 0xFD,
|
||||||
|
0xBC, 0xD3, 0x9C, 0x58, 0x04, 0x1C, 0xE9, 0xBD, 0xE2, 0x8A, 0x84, 0x5A, 0xD1, 0x7B,
|
||||||
|
0xE7, 0x97, 0xF4, 0xF8, 0x08, 0x8D, 0xF6, 0x66, 0x21, 0x3D, 0x3A, 0xE4, 0xA9, 0x91,
|
||||||
|
0x3E, 0xAC, 0xF1, 0x98, 0xB9, 0x70, 0x17, 0x13,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut compressed_data = Vec::<u8>::new();
|
||||||
|
let mut writer = Cursor::new(&mut compressed_data);
|
||||||
|
Deflate::default().write_to(&mut writer, TEST_DATA).unwrap();
|
||||||
|
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||||
|
use std::io::Write;
|
||||||
|
use weezl::encode::Encoder as LZWEncoder;
|
||||||
|
|
||||||
|
/// The LZW algorithm used to compress image data in TIFF files.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub struct Lzw;
|
||||||
|
|
||||||
|
impl Compression for Lzw {
|
||||||
|
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::LZW;
|
||||||
|
|
||||||
|
fn get_algorithm(&self) -> Compressor {
|
||||||
|
Compressor::Lzw(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionAlgorithm for Lzw {
|
||||||
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
|
let mut encoder = LZWEncoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
|
||||||
|
let result = encoder.into_stream(writer).encode_all(bytes);
|
||||||
|
let byte_count = result.bytes_written as u64;
|
||||||
|
result.status.map(|_| byte_count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::encoder::compression::tests::TEST_DATA;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lzw() {
|
||||||
|
const EXPECTED_COMPRESSED_DATA: [u8; 63] = [
|
||||||
|
0x80, 0x15, 0x0D, 0x06, 0x93, 0x98, 0x82, 0x08, 0x20, 0x30, 0x88, 0x0E, 0x67, 0x43,
|
||||||
|
0x91, 0xA4, 0xDC, 0x67, 0x10, 0x19, 0x8D, 0xE7, 0x21, 0x01, 0x8C, 0xD0, 0x65, 0x31,
|
||||||
|
0x9A, 0xE1, 0xD1, 0x03, 0xB1, 0x86, 0x1A, 0x6F, 0x3A, 0xC1, 0x4C, 0x66, 0xF3, 0x69,
|
||||||
|
0xC0, 0xE4, 0x65, 0x39, 0x9C, 0xCD, 0x26, 0xF3, 0x74, 0x20, 0xD8, 0x67, 0x89, 0x9A,
|
||||||
|
0x4E, 0x86, 0x83, 0x69, 0xCC, 0x5D, 0x01,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut compressed_data = Vec::<u8>::new();
|
||||||
|
let mut writer = Cursor::new(&mut compressed_data);
|
||||||
|
Lzw::default().write_to(&mut writer, TEST_DATA).unwrap();
|
||||||
|
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
use crate::tags::CompressionMethod;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
mod deflate;
|
||||||
|
mod lzw;
|
||||||
|
mod packbits;
|
||||||
|
mod uncompressed;
|
||||||
|
|
||||||
|
pub use self::deflate::{Deflate, DeflateLevel};
|
||||||
|
pub use self::lzw::Lzw;
|
||||||
|
pub use self::packbits::Packbits;
|
||||||
|
pub use self::uncompressed::Uncompressed;
|
||||||
|
|
||||||
|
/// An algorithm used for compression
|
||||||
|
pub trait CompressionAlgorithm {
|
||||||
|
/// The algorithm writes data directly into the writer.
|
||||||
|
/// It returns the total number of bytes written.
|
||||||
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An algorithm used for compression with associated enums and optional configurations.
|
||||||
|
pub trait Compression: CompressionAlgorithm {
|
||||||
|
/// The corresponding tag to the algorithm.
|
||||||
|
const COMPRESSION_METHOD: CompressionMethod;
|
||||||
|
|
||||||
|
/// Method to optain a type that can store each variant of comression algorithm.
|
||||||
|
fn get_algorithm(&self) -> Compressor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An enum to store each compression algorithm.
|
||||||
|
pub enum Compressor {
|
||||||
|
Uncompressed(Uncompressed),
|
||||||
|
Lzw(Lzw),
|
||||||
|
Deflate(Deflate),
|
||||||
|
Packbits(Packbits),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Compressor {
|
||||||
|
/// The default compression strategy does not apply any compression.
|
||||||
|
fn default() -> Self {
|
||||||
|
Compressor::Uncompressed(Uncompressed::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionAlgorithm for Compressor {
|
||||||
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
|
match self {
|
||||||
|
Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes),
|
||||||
|
Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes),
|
||||||
|
Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes),
|
||||||
|
Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
pub const TEST_DATA: &'static [u8] =
|
||||||
|
b"This is a string for checking various compression algorithms.";
|
||||||
|
}
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||||
|
use std::io::{BufWriter, Error, ErrorKind, Write};
|
||||||
|
|
||||||
|
/// Compressor that uses the Packbits[^note] algorithm to compress bytes.
|
||||||
|
///
|
||||||
|
/// [^note]: PackBits is often ineffective on continuous tone images,
|
||||||
|
/// including many grayscale images. In such cases, it is better
|
||||||
|
/// to leave the image uncompressed.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub struct Packbits;
|
||||||
|
|
||||||
|
impl Compression for Packbits {
|
||||||
|
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::PackBits;
|
||||||
|
|
||||||
|
fn get_algorithm(&self) -> Compressor {
|
||||||
|
Compressor::Packbits(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionAlgorithm for Packbits {
|
||||||
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
|
// Inspired by https://github.com/skirridsystems/packbits
|
||||||
|
|
||||||
|
const MIN_REPT: u8 = 3; // Minimum run to compress between differ blocks
|
||||||
|
const MAX_BYTES: u8 = 128; // Maximum number of bytes that can be encoded in a header byte
|
||||||
|
|
||||||
|
// Encoding for header byte based on number of bytes represented.
|
||||||
|
fn encode_diff(n: u8) -> u8 {
|
||||||
|
n - 1
|
||||||
|
}
|
||||||
|
fn encode_rept(n: u8) -> u8 {
|
||||||
|
let var = 256 - (n - 1) as u16;
|
||||||
|
var as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_u8<W: Write>(writer: &mut W, byte: u8) -> Result<u64, Error> {
|
||||||
|
writer.write(&[byte]).map(|byte_count| byte_count as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bufwriter = BufWriter::new(writer);
|
||||||
|
let mut bytes_written = 0u64; // The number of bytes written into the writer
|
||||||
|
let mut offset: Option<u64> = None; // The index of the first byte written into the writer
|
||||||
|
|
||||||
|
let mut src_index: usize = 0; // Index of the current byte
|
||||||
|
let mut src_count = bytes.len(); //The number of bytes remaining to be compressed
|
||||||
|
|
||||||
|
let mut in_run = false; // Indication whether counting of similar bytes is performed
|
||||||
|
let mut run_index = 0u8; // Distance into pending bytes that a run starts
|
||||||
|
|
||||||
|
let mut bytes_pending = 0u8; // Bytes looked at but not yet output
|
||||||
|
let mut pending_index = 0usize; // Index of the first pending byte
|
||||||
|
|
||||||
|
let mut curr_byte: u8; // Byte currently being considered
|
||||||
|
let mut last_byte: u8; // Previous byte
|
||||||
|
|
||||||
|
// Need at least one byte to compress
|
||||||
|
if src_count == 0 {
|
||||||
|
return Err(Error::new(ErrorKind::WriteZero, "write zero"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prime compressor with first character.
|
||||||
|
last_byte = bytes[src_index];
|
||||||
|
src_index += 1;
|
||||||
|
bytes_pending += 1;
|
||||||
|
|
||||||
|
while src_count - 1 != 0 {
|
||||||
|
src_count -= 1;
|
||||||
|
curr_byte = bytes[src_index];
|
||||||
|
src_index += 1;
|
||||||
|
bytes_pending += 1;
|
||||||
|
|
||||||
|
if in_run {
|
||||||
|
if (curr_byte != last_byte) || (bytes_pending > MAX_BYTES) {
|
||||||
|
offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending - 1))?);
|
||||||
|
write_u8(&mut bufwriter, last_byte)?;
|
||||||
|
bytes_written += 2;
|
||||||
|
|
||||||
|
bytes_pending = 1;
|
||||||
|
pending_index = src_index - 1;
|
||||||
|
run_index = 0;
|
||||||
|
in_run = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if bytes_pending > MAX_BYTES {
|
||||||
|
// We have as much differing data as we can output in one chunk.
|
||||||
|
// Output MAX_BYTES leaving one byte.
|
||||||
|
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(MAX_BYTES))?);
|
||||||
|
bufwriter.write(&bytes[pending_index..pending_index + MAX_BYTES as usize])?;
|
||||||
|
bytes_written += 1 + MAX_BYTES as u64;
|
||||||
|
|
||||||
|
pending_index += MAX_BYTES as usize;
|
||||||
|
bytes_pending -= MAX_BYTES;
|
||||||
|
run_index = bytes_pending - 1; // A run could start here
|
||||||
|
} else if curr_byte == last_byte {
|
||||||
|
if (bytes_pending - run_index >= MIN_REPT) || (run_index == 0) {
|
||||||
|
// This is a worthwhile run
|
||||||
|
if run_index != 0 {
|
||||||
|
// Flush differing data out of input buffer
|
||||||
|
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(run_index))?);
|
||||||
|
bufwriter
|
||||||
|
.write(&bytes[pending_index..pending_index + run_index as usize])?;
|
||||||
|
bytes_written += 1 + run_index as u64;
|
||||||
|
}
|
||||||
|
bytes_pending -= run_index; // Length of run
|
||||||
|
in_run = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
run_index = bytes_pending - 1; // A run could start here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_byte = curr_byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the remainder
|
||||||
|
if in_run {
|
||||||
|
bytes_written += 2;
|
||||||
|
offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending))?);
|
||||||
|
write_u8(&mut bufwriter, last_byte)?;
|
||||||
|
} else {
|
||||||
|
bytes_written += 1 + bytes_pending as u64;
|
||||||
|
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(bytes_pending))?);
|
||||||
|
bufwriter.write(&bytes[pending_index..pending_index + bytes_pending as usize])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufwriter.flush()?;
|
||||||
|
Ok(bytes_written)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::encoder::compression::tests::TEST_DATA;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_packbits_single_byte() {
|
||||||
|
// compress single byte
|
||||||
|
const UNCOMPRESSED_DATA: [u8; 1] = [0x3F];
|
||||||
|
const EXPECTED_COMPRESSED_DATA: [u8; 2] = [0x00, 0x3F];
|
||||||
|
|
||||||
|
let mut compressed_data = Vec::<u8>::new();
|
||||||
|
let mut writer = Cursor::new(&mut compressed_data);
|
||||||
|
Packbits::default()
|
||||||
|
.write_to(&mut writer, &UNCOMPRESSED_DATA)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_packbits_rept() {
|
||||||
|
// compress buffer with repetitive sequence
|
||||||
|
const UNCOMPRESSED_DATA: &'static [u8] =
|
||||||
|
b"This strrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrring hangs.";
|
||||||
|
const EXPECTED_COMPRESSED_DATA: &'static [u8] = b"\x06This st\xD1r\x09ing hangs.";
|
||||||
|
|
||||||
|
let mut compressed_data = Vec::<u8>::new();
|
||||||
|
let mut writer = Cursor::new(&mut compressed_data);
|
||||||
|
Packbits::default()
|
||||||
|
.write_to(&mut writer, UNCOMPRESSED_DATA)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_packbits_large_rept_nonrept() {
|
||||||
|
// compress buffer with large repetitive and non-repetitive sequence
|
||||||
|
let mut data = b"This st".to_vec();
|
||||||
|
for _i in 0..158 {
|
||||||
|
data.push(b'r');
|
||||||
|
}
|
||||||
|
data.extend_from_slice(b"ing hangs.");
|
||||||
|
for i in 0..158 {
|
||||||
|
data.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EXPECTED_COMPRESSED_DATA: [u8; 182] = [
|
||||||
|
0x06, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x74, 0x81, 0x72, 0xE3, 0x72, 0x7F, 0x69,
|
||||||
|
0x6E, 0x67, 0x20, 0x68, 0x61, 0x6E, 0x67, 0x73, 0x2E, 0x00, 0x01, 0x02, 0x03, 0x04,
|
||||||
|
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
|
||||||
|
0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
|
||||||
|
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
|
||||||
|
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
|
||||||
|
0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
|
||||||
|
0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
|
||||||
|
0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
|
||||||
|
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
|
||||||
|
0x75, 0x27, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81,
|
||||||
|
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||||
|
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut compressed_data = Vec::<u8>::new();
|
||||||
|
let mut writer = Cursor::new(&mut compressed_data);
|
||||||
|
Packbits::default()
|
||||||
|
.write_to(&mut writer, data.as_slice())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_packbits() {
|
||||||
|
// compress teststring
|
||||||
|
const EXPECTED_COMPRESSED_DATA: &'static [u8] =
|
||||||
|
b"\x3CThis is a string for checking various compression algorithms.";
|
||||||
|
|
||||||
|
let mut compressed_data = Vec::<u8>::new();
|
||||||
|
let mut writer = Cursor::new(&mut compressed_data);
|
||||||
|
Packbits::default()
|
||||||
|
.write_to(&mut writer, TEST_DATA)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
/// The default algorithm which does not compress at all.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub struct Uncompressed;
|
||||||
|
|
||||||
|
impl Compression for Uncompressed {
|
||||||
|
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::None;
|
||||||
|
|
||||||
|
fn get_algorithm(&self) -> Compressor {
|
||||||
|
Compressor::Uncompressed(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionAlgorithm for Uncompressed {
|
||||||
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
|
writer.write(bytes).map(|byte_count| byte_count as u64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::encoder::compression::tests::TEST_DATA;
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_compression() {
|
||||||
|
let mut compressed_data = Vec::<u8>::new();
|
||||||
|
let mut writer = Cursor::new(&mut compressed_data);
|
||||||
|
Uncompressed::default()
|
||||||
|
.write_to(&mut writer, TEST_DATA)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(TEST_DATA, compressed_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
+374
-411
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,523 @@
|
|||||||
|
use std::{borrow::Cow, io::Write, slice::from_ref};
|
||||||
|
|
||||||
|
use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult};
|
||||||
|
|
||||||
|
use super::writer::TiffWriter;
|
||||||
|
|
||||||
|
/// Trait for types that can be encoded in a tiff file
|
||||||
|
pub trait TiffValue {
|
||||||
|
const BYTE_LEN: u8;
|
||||||
|
const FIELD_TYPE: Type;
|
||||||
|
fn count(&self) -> usize;
|
||||||
|
fn bytes(&self) -> usize {
|
||||||
|
self.count() * usize::from(Self::BYTE_LEN)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access this value as an contiguous sequence of bytes.
|
||||||
|
/// If their is no trivial representation, allocate it on the heap.
|
||||||
|
fn data(&self) -> Cow<[u8]>;
|
||||||
|
|
||||||
|
/// Write this value to a TiffWriter.
|
||||||
|
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
||||||
|
/// The written bytes of any custom implementation MUST be the same as yielded by `self.data()`.
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_bytes(&self.data())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for [u8] {
|
||||||
|
const BYTE_LEN: u8 = 1;
|
||||||
|
const FIELD_TYPE: Type = Type::BYTE;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for [i8] {
|
||||||
|
const BYTE_LEN: u8 = 1;
|
||||||
|
const FIELD_TYPE: Type = Type::SBYTE;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for [u16] {
|
||||||
|
const BYTE_LEN: u8 = 2;
|
||||||
|
const FIELD_TYPE: Type = Type::SHORT;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for [i16] {
|
||||||
|
const BYTE_LEN: u8 = 2;
|
||||||
|
const FIELD_TYPE: Type = Type::SSHORT;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for [u32] {
|
||||||
|
const BYTE_LEN: u8 = 4;
|
||||||
|
const FIELD_TYPE: Type = Type::LONG;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for [i32] {
|
||||||
|
const BYTE_LEN: u8 = 4;
|
||||||
|
const FIELD_TYPE: Type = Type::SLONG;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for [u64] {
|
||||||
|
const BYTE_LEN: u8 = 8;
|
||||||
|
const FIELD_TYPE: Type = Type::LONG8;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for [i64] {
|
||||||
|
const BYTE_LEN: u8 = 8;
|
||||||
|
const FIELD_TYPE: Type = Type::SLONG8;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for [f32] {
|
||||||
|
const BYTE_LEN: u8 = 4;
|
||||||
|
const FIELD_TYPE: Type = Type::FLOAT;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
// We write using native endian so this should be safe
|
||||||
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for [f64] {
|
||||||
|
const BYTE_LEN: u8 = 8;
|
||||||
|
const FIELD_TYPE: Type = Type::DOUBLE;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
// We write using native endian so this should be safe
|
||||||
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for u8 {
|
||||||
|
const BYTE_LEN: u8 = 1;
|
||||||
|
const FIELD_TYPE: Type = Type::BYTE;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_u8(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(from_ref(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for i8 {
|
||||||
|
const BYTE_LEN: u8 = 1;
|
||||||
|
const FIELD_TYPE: Type = Type::SBYTE;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_i8(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for u16 {
|
||||||
|
const BYTE_LEN: u8 = 2;
|
||||||
|
const FIELD_TYPE: Type = Type::SHORT;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_u16(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for i16 {
|
||||||
|
const BYTE_LEN: u8 = 2;
|
||||||
|
const FIELD_TYPE: Type = Type::SSHORT;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_i16(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for u32 {
|
||||||
|
const BYTE_LEN: u8 = 4;
|
||||||
|
const FIELD_TYPE: Type = Type::LONG;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_u32(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for i32 {
|
||||||
|
const BYTE_LEN: u8 = 4;
|
||||||
|
const FIELD_TYPE: Type = Type::SLONG;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_i32(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for u64 {
|
||||||
|
const BYTE_LEN: u8 = 8;
|
||||||
|
const FIELD_TYPE: Type = Type::LONG8;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_u64(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for i64 {
|
||||||
|
const BYTE_LEN: u8 = 8;
|
||||||
|
const FIELD_TYPE: Type = Type::SLONG8;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_i64(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for f32 {
|
||||||
|
const BYTE_LEN: u8 = 4;
|
||||||
|
const FIELD_TYPE: Type = Type::FLOAT;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_f32(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for f64 {
|
||||||
|
const BYTE_LEN: u8 = 8;
|
||||||
|
const FIELD_TYPE: Type = Type::DOUBLE;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_f64(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for Ifd {
|
||||||
|
const BYTE_LEN: u8 = 4;
|
||||||
|
const FIELD_TYPE: Type = Type::IFD;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_u32(self.0)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for Ifd8 {
|
||||||
|
const BYTE_LEN: u8 = 8;
|
||||||
|
const FIELD_TYPE: Type = Type::IFD8;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_u64(self.0)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for Rational {
|
||||||
|
const BYTE_LEN: u8 = 8;
|
||||||
|
const FIELD_TYPE: Type = Type::RATIONAL;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_u32(self.n)?;
|
||||||
|
writer.write_u32(self.d)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Owned({
|
||||||
|
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
||||||
|
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
||||||
|
[first_dword, second_dword].concat()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for SRational {
|
||||||
|
const BYTE_LEN: u8 = 8;
|
||||||
|
const FIELD_TYPE: Type = Type::SRATIONAL;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.write_i32(self.n)?;
|
||||||
|
writer.write_i32(self.d)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Owned({
|
||||||
|
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
||||||
|
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
||||||
|
[first_dword, second_dword].concat()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TiffValue for str {
|
||||||
|
const BYTE_LEN: u8 = 1;
|
||||||
|
const FIELD_TYPE: Type = Type::ASCII;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||||
|
writer.write_bytes(self.as_bytes())?;
|
||||||
|
writer.write_u8(0)?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(TiffError::FormatError(TiffFormatError::InvalidTag))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
Cow::Owned({
|
||||||
|
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||||
|
let bytes: &[u8] = self.as_bytes();
|
||||||
|
[bytes, &[0]].concat()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
||||||
|
const BYTE_LEN: u8 = T::BYTE_LEN;
|
||||||
|
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
(*self).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
(*self).write(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
T::data(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_tiff_value_for_contiguous_sequence {
|
||||||
|
($inner_type:ty; $bytes:expr; $field_type:expr) => {
|
||||||
|
impl $crate::encoder::TiffValue for [$inner_type] {
|
||||||
|
const BYTE_LEN: u8 = $bytes;
|
||||||
|
const FIELD_TYPE: Type = $field_type;
|
||||||
|
|
||||||
|
fn count(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
for x in self {
|
||||||
|
x.write(writer)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self) -> Cow<[u8]> {
|
||||||
|
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
||||||
|
for x in self {
|
||||||
|
buf.extend_from_slice(&x.data());
|
||||||
|
}
|
||||||
|
Cow::Owned(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_tiff_value_for_contiguous_sequence!(Ifd; 4; Type::IFD);
|
||||||
|
impl_tiff_value_for_contiguous_sequence!(Ifd8; 8; Type::IFD8);
|
||||||
|
impl_tiff_value_for_contiguous_sequence!(Rational; 8; Type::RATIONAL);
|
||||||
|
impl_tiff_value_for_contiguous_sequence!(SRational; 8; Type::SRATIONAL);
|
||||||
|
|
||||||
|
/// Type to represent tiff values of type `IFD`
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Ifd(pub u32);
|
||||||
|
|
||||||
|
/// Type to represent tiff values of type `IFD8`
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Ifd8(pub u64);
|
||||||
|
|
||||||
|
/// Type to represent tiff values of type `RATIONAL`
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Rational {
|
||||||
|
pub n: u32,
|
||||||
|
pub d: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type to represent tiff values of type `SRATIONAL`
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SRational {
|
||||||
|
pub n: i32,
|
||||||
|
pub d: i32,
|
||||||
|
}
|
||||||
+107
-33
@@ -1,94 +1,168 @@
|
|||||||
use error::TiffResult;
|
use crate::encoder::compression::*;
|
||||||
use byteorder::{BigEndian, ByteOrder, LittleEndian, NativeEndian, WriteBytesExt};
|
use crate::error::TiffResult;
|
||||||
use std::io::{self, Seek, SeekFrom, Write};
|
use std::io::{self, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
pub trait TiffByteOrder: ByteOrder {
|
pub fn write_tiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()>;
|
#[cfg(target_endian = "little")]
|
||||||
}
|
let boi: u8 = 0x49;
|
||||||
|
#[cfg(not(target_endian = "little"))]
|
||||||
|
let boi: u8 = 0x4d;
|
||||||
|
|
||||||
impl TiffByteOrder for LittleEndian {
|
writer.writer.write_all(&[boi, boi])?;
|
||||||
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
writer.writer.write_all(&42u16.to_ne_bytes())?;
|
||||||
writer.writer.write_u16::<LittleEndian>(0x4949)?;
|
|
||||||
writer.writer.write_u16::<LittleEndian>(42)?;
|
|
||||||
writer.offset += 4;
|
writer.offset += 4;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TiffByteOrder for BigEndian {
|
/// Writes a BigTiff header, excluding the IFD offset field.
|
||||||
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
///
|
||||||
writer.writer.write_u16::<BigEndian>(0x4d4d)?;
|
/// Writes the byte order, version number, offset byte size, and zero constant fields. Does
|
||||||
writer.writer.write_u16::<BigEndian>(42)?;
|
// _not_ write the offset to the first IFD, this should be done by the caller.
|
||||||
writer.offset += 4;
|
pub fn write_bigtiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
#[cfg(target_endian = "little")]
|
||||||
|
let boi: u8 = 0x49;
|
||||||
|
#[cfg(not(target_endian = "little"))]
|
||||||
|
let boi: u8 = 0x4d;
|
||||||
|
|
||||||
|
// byte order indication
|
||||||
|
writer.writer.write_all(&[boi, boi])?;
|
||||||
|
// version number
|
||||||
|
writer.writer.write_all(&43u16.to_ne_bytes())?;
|
||||||
|
// bytesize of offsets (pointer size)
|
||||||
|
writer.writer.write_all(&8u16.to_ne_bytes())?;
|
||||||
|
// always 0
|
||||||
|
writer.writer.write_all(&0u16.to_ne_bytes())?;
|
||||||
|
|
||||||
|
// we wrote 8 bytes, so set the internal offset accordingly
|
||||||
|
writer.offset += 8;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TiffWriter<W> {
|
pub struct TiffWriter<W> {
|
||||||
writer: W,
|
writer: W,
|
||||||
offset: u64,
|
offset: u64,
|
||||||
|
byte_count: u64,
|
||||||
|
compressor: Compressor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write> TiffWriter<W> {
|
impl<W: Write> TiffWriter<W> {
|
||||||
pub fn new(writer: W) -> Self {
|
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 {
|
pub fn offset(&self) -> u64 {
|
||||||
self.offset
|
self.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn last_written(&self) -> u64 {
|
||||||
|
self.byte_count
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> {
|
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> {
|
||||||
self.writer.write_all(bytes)?;
|
self.byte_count = self.compressor.write_to(&mut self.writer, bytes)?;
|
||||||
self.offset += bytes.len() as u64;
|
self.offset += self.byte_count;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> {
|
pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> {
|
||||||
self.writer.write_u8(n)?;
|
self.byte_count = self
|
||||||
self.offset += 1;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> {
|
pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> {
|
||||||
self.writer.write_i8(n)?;
|
self.byte_count = self
|
||||||
self.offset += 1;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> {
|
pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> {
|
||||||
self.writer.write_u16::<NativeEndian>(n)?;
|
self.byte_count = self
|
||||||
self.offset += 2;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> {
|
pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> {
|
||||||
self.writer.write_i16::<NativeEndian>(n)?;
|
self.byte_count = self
|
||||||
self.offset += 2;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> {
|
pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> {
|
||||||
self.writer.write_u32::<NativeEndian>(n)?;
|
self.byte_count = self
|
||||||
self.offset += 4;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> {
|
pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> {
|
||||||
self.writer.write_i32::<NativeEndian>(n)?;
|
self.byte_count = self
|
||||||
self.offset += 4;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> {
|
pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> {
|
||||||
self.writer.write_u64::<NativeEndian>(n)?;
|
self.byte_count = self
|
||||||
self.offset += 8;
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> {
|
||||||
|
self.byte_count = self
|
||||||
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &n.to_ne_bytes())?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> {
|
||||||
|
self.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.byte_count = self
|
||||||
|
.compressor
|
||||||
|
.write_to(&mut self.writer, &u64::to_ne_bytes(n.to_bits()))?;
|
||||||
|
self.offset += self.byte_count;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+171
-35
@@ -1,12 +1,20 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::str;
|
||||||
use std::string;
|
use std::string;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use decoder::ifd::{Value};
|
use jpeg::UnsupportedFeature;
|
||||||
use tags::{CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Tag};
|
|
||||||
use miniz_oxide::inflate::TINFLStatus;
|
use crate::decoder::{ifd::Value, ChunkType};
|
||||||
use ColorType;
|
use crate::tags::{
|
||||||
|
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
|
||||||
|
};
|
||||||
|
use crate::ColorType;
|
||||||
|
|
||||||
|
use crate::weezl::LzwError;
|
||||||
|
|
||||||
/// Tiff error kinds.
|
/// Tiff error kinds.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -26,6 +34,9 @@ pub enum TiffError {
|
|||||||
/// An integer conversion to or from a platform size failed, either due to
|
/// An integer conversion to or from a platform size failed, either due to
|
||||||
/// limits of the platform size or limits of the format.
|
/// limits of the platform size or limits of the format.
|
||||||
IntSizeError,
|
IntSizeError,
|
||||||
|
|
||||||
|
/// The image does not support the requested operation
|
||||||
|
UsageError(UsageError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The image is not formatted properly.
|
/// The image is not formatted properly.
|
||||||
@@ -35,22 +46,34 @@ pub enum TiffError {
|
|||||||
///
|
///
|
||||||
/// The list of variants may grow to incorporate errors of future features. Matching against this
|
/// The list of variants may grow to incorporate errors of future features. Matching against this
|
||||||
/// exhaustively is not covered by interface stability guarantees.
|
/// exhaustively is not covered by interface stability guarantees.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum TiffFormatError {
|
pub enum TiffFormatError {
|
||||||
TiffSignatureNotFound,
|
TiffSignatureNotFound,
|
||||||
TiffSignatureInvalid,
|
TiffSignatureInvalid,
|
||||||
ImageFileDirectoryNotFound,
|
ImageFileDirectoryNotFound,
|
||||||
InconsistentSizesEncountered,
|
InconsistentSizesEncountered,
|
||||||
|
UnexpectedCompressedData {
|
||||||
|
actual_bytes: usize,
|
||||||
|
required_bytes: usize,
|
||||||
|
},
|
||||||
|
InconsistentStripSamples {
|
||||||
|
actual_samples: usize,
|
||||||
|
required_samples: usize,
|
||||||
|
},
|
||||||
|
InvalidDimensions(u32, u32),
|
||||||
InvalidTag,
|
InvalidTag,
|
||||||
InvalidTagValueType(Tag),
|
InvalidTagValueType(Tag),
|
||||||
RequiredTagNotFound(Tag),
|
RequiredTagNotFound(Tag),
|
||||||
UnknownPredictor(u16),
|
UnknownPredictor(u16),
|
||||||
|
ByteExpected(Value),
|
||||||
UnsignedIntegerExpected(Value),
|
UnsignedIntegerExpected(Value),
|
||||||
SignedIntegerExpected(Value),
|
SignedIntegerExpected(Value),
|
||||||
InflateError(InflateError),
|
Format(String),
|
||||||
#[doc(hidden)]
|
RequiredTagEmpty(Tag),
|
||||||
/// Do not match against this variant. It may get removed.
|
StripTileTagConflict,
|
||||||
__NonExhaustive,
|
CycleInOffsets,
|
||||||
|
JpegDecoder(JpegDecoderError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TiffFormatError {
|
impl fmt::Display for TiffFormatError {
|
||||||
@@ -61,43 +84,51 @@ impl fmt::Display for TiffFormatError {
|
|||||||
TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."),
|
TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."),
|
||||||
ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
|
ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
|
||||||
InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."),
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
InvalidDimensions(width, height) => write!(fmt, "Invalid dimensions: {}x{}.", width, height),
|
||||||
InvalidTag => write!(fmt, "Image contains invalid tag."),
|
InvalidTag => write!(fmt, "Image contains invalid tag."),
|
||||||
InvalidTagValueType(ref tag) =>
|
InvalidTagValueType(ref tag) => {
|
||||||
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag),
|
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
|
||||||
|
}
|
||||||
RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag),
|
RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag),
|
||||||
UnknownPredictor(ref predictor) => {
|
UnknownPredictor(ref predictor) => {
|
||||||
write!(fmt, "Unknown predictor “{}” encountered", predictor)
|
write!(fmt, "Unknown predictor “{}” encountered", predictor)
|
||||||
}
|
}
|
||||||
|
ByteExpected(ref val) => write!(fmt, "Expected byte, {:?} found.", val),
|
||||||
UnsignedIntegerExpected(ref val) => {
|
UnsignedIntegerExpected(ref val) => {
|
||||||
write!(fmt, "Expected unsigned integer, {:?} found.", val)
|
write!(fmt, "Expected unsigned integer, {:?} found.", val)
|
||||||
}
|
}
|
||||||
SignedIntegerExpected(ref val) => {
|
SignedIntegerExpected(ref val) => {
|
||||||
write!(fmt, "Expected signed integer, {:?} found.", val)
|
write!(fmt, "Expected signed integer, {:?} found.", val)
|
||||||
}
|
}
|
||||||
InflateError(_) => write!(fmt, "Failed to decode inflate data."),
|
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
|
||||||
__NonExhaustive => unreachable!(),
|
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
|
||||||
|
StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
|
||||||
|
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
|
||||||
|
JpegDecoder(ref error) => write!(fmt, "{}", error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decompression failed due to faulty compressed data.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct InflateError {
|
|
||||||
status: TINFLStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InflateError {
|
|
||||||
pub(crate) fn new(status: TINFLStatus) -> Self {
|
|
||||||
Self { status }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TiffError {
|
|
||||||
pub(crate) fn from_inflate_status(status: TINFLStatus) -> Self {
|
|
||||||
TiffError::FormatError(TiffFormatError::InflateError(InflateError::new(status)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The Decoder does not support features required by the image.
|
/// 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
|
/// This only captures known failures for which the standard either does not require support or an
|
||||||
@@ -107,26 +138,33 @@ impl TiffError {
|
|||||||
/// The list of variants may grow. Matching against this exhaustively is not covered by interface
|
/// The list of variants may grow. Matching against this exhaustively is not covered by interface
|
||||||
/// stability guarantees.
|
/// stability guarantees.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum TiffUnsupportedError {
|
pub enum TiffUnsupportedError {
|
||||||
|
FloatingPointPredictor(ColorType),
|
||||||
HorizontalPredictor(ColorType),
|
HorizontalPredictor(ColorType),
|
||||||
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
|
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
|
||||||
UnknownInterpretation,
|
UnknownInterpretation,
|
||||||
UnknownCompressionMethod,
|
UnknownCompressionMethod,
|
||||||
UnsupportedCompressionMethod(CompressionMethod),
|
UnsupportedCompressionMethod(CompressionMethod),
|
||||||
UnsupportedSampleDepth(u8),
|
UnsupportedSampleDepth(u8),
|
||||||
|
UnsupportedSampleFormat(Vec<SampleFormat>),
|
||||||
UnsupportedColorType(ColorType),
|
UnsupportedColorType(ColorType),
|
||||||
UnsupportedBitsPerChannel(u8),
|
UnsupportedBitsPerChannel(u8),
|
||||||
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
|
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
|
||||||
UnsupportedDataType,
|
UnsupportedDataType,
|
||||||
#[doc(hidden)]
|
UnsupportedInterpretation(PhotometricInterpretation),
|
||||||
/// Do not match against this variant. It may get removed.
|
UnsupportedJpegFeature(UnsupportedFeature),
|
||||||
__NonExhaustive,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TiffUnsupportedError {
|
impl fmt::Display for TiffUnsupportedError {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
use self::TiffUnsupportedError::*;
|
use self::TiffUnsupportedError::*;
|
||||||
match *self {
|
match *self {
|
||||||
|
FloatingPointPredictor(color_type) => write!(
|
||||||
|
fmt,
|
||||||
|
"Floating point predictor for {:?} is unsupported.",
|
||||||
|
color_type
|
||||||
|
),
|
||||||
HorizontalPredictor(color_type) => write!(
|
HorizontalPredictor(color_type) => write!(
|
||||||
fmt,
|
fmt,
|
||||||
"Horizontal predictor for {:?} is unsupported.",
|
"Horizontal predictor for {:?} is unsupported.",
|
||||||
@@ -148,6 +186,9 @@ impl fmt::Display for TiffUnsupportedError {
|
|||||||
UnsupportedSampleDepth(samples) => {
|
UnsupportedSampleDepth(samples) => {
|
||||||
write!(fmt, "{} samples per pixel is unsupported.", samples)
|
write!(fmt, "{} samples per pixel is unsupported.", samples)
|
||||||
}
|
}
|
||||||
|
UnsupportedSampleFormat(ref formats) => {
|
||||||
|
write!(fmt, "Sample format {:?} is unsupported.", formats)
|
||||||
|
}
|
||||||
UnsupportedColorType(color_type) => {
|
UnsupportedColorType(color_type) => {
|
||||||
write!(fmt, "Color type {:?} is unsupported", color_type)
|
write!(fmt, "Color type {:?} is unsupported", color_type)
|
||||||
}
|
}
|
||||||
@@ -158,7 +199,41 @@ impl fmt::Display for TiffUnsupportedError {
|
|||||||
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
||||||
}
|
}
|
||||||
UnsupportedDataType => write!(fmt, "Unsupported data type."),
|
UnsupportedDataType => write!(fmt, "Unsupported data type."),
|
||||||
__NonExhaustive => unreachable!(),
|
UnsupportedInterpretation(interpretation) => {
|
||||||
|
write!(
|
||||||
|
fmt,
|
||||||
|
"Unsupported photometric interpretation \"{:?}\".",
|
||||||
|
interpretation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
UnsupportedJpegFeature(ref unsupported_feature) => {
|
||||||
|
write!(fmt, "Unsupported JPEG feature {:?}", unsupported_feature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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),
|
||||||
|
InvalidChunkIndex(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => write!(fmt, "Image chunk index ({}) requested.", index),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,6 +251,7 @@ impl fmt::Display for TiffError {
|
|||||||
TiffError::IoError(ref e) => e.fmt(fmt),
|
TiffError::IoError(ref e) => e.fmt(fmt),
|
||||||
TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"),
|
TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"),
|
||||||
TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"),
|
TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"),
|
||||||
|
TiffError::UsageError(ref e) => write!(fmt, "Usage error: {}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,6 +264,7 @@ impl Error for TiffError {
|
|||||||
TiffError::IoError(..) => "IO error",
|
TiffError::IoError(..) => "IO error",
|
||||||
TiffError::LimitsExceeded => "Decoder limits exceeded",
|
TiffError::LimitsExceeded => "Decoder limits exceeded",
|
||||||
TiffError::IntSizeError => "Platform or format size limits exceeded",
|
TiffError::IntSizeError => "Platform or format size limits exceeded",
|
||||||
|
TiffError::UsageError(..) => "Invalid usage",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +282,12 @@ impl From<io::Error> for TiffError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<str::Utf8Error> for TiffError {
|
||||||
|
fn from(_err: str::Utf8Error) -> TiffError {
|
||||||
|
TiffError::FormatError(TiffFormatError::InvalidTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<string::FromUtf8Error> for TiffError {
|
impl From<string::FromUtf8Error> for TiffError {
|
||||||
fn from(_err: string::FromUtf8Error) -> TiffError {
|
fn from(_err: string::FromUtf8Error) -> TiffError {
|
||||||
TiffError::FormatError(TiffFormatError::InvalidTag)
|
TiffError::FormatError(TiffFormatError::InvalidTag)
|
||||||
@@ -223,11 +306,64 @@ impl From<TiffUnsupportedError> for TiffError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<UsageError> for TiffError {
|
||||||
|
fn from(err: UsageError) -> TiffError {
|
||||||
|
TiffError::UsageError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<std::num::TryFromIntError> for TiffError {
|
impl From<std::num::TryFromIntError> for TiffError {
|
||||||
fn from(_err: std::num::TryFromIntError) -> TiffError {
|
fn from(_err: std::num::TryFromIntError) -> TiffError {
|
||||||
TiffError::IntSizeError
|
TiffError::IntSizeError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<LzwError> for TiffError {
|
||||||
|
fn from(err: LzwError) -> TiffError {
|
||||||
|
match err {
|
||||||
|
LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from(
|
||||||
|
"LZW compressed data corrupted",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JpegDecoderError {
|
||||||
|
inner: Arc<jpeg::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JpegDecoderError {
|
||||||
|
fn new(error: jpeg::Error) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for JpegDecoderError {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
Arc::ptr_eq(&self.inner, &other.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for JpegDecoderError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.inner.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JpegDecoderError> for TiffError {
|
||||||
|
fn from(error: JpegDecoderError) -> Self {
|
||||||
|
TiffError::FormatError(TiffFormatError::JpegDecoder(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<jpeg::Error> for TiffError {
|
||||||
|
fn from(error: jpeg::Error) -> Self {
|
||||||
|
JpegDecoderError::new(error).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Result of an image decoding/encoding process
|
/// Result of an image decoding/encoding process
|
||||||
pub type TiffResult<T> = Result<T, TiffError>;
|
pub type TiffResult<T> = Result<T, TiffError>;
|
||||||
+8
-5
@@ -4,18 +4,18 @@
|
|||||||
//! lossless and lossy compression.
|
//! lossless and lossy compression.
|
||||||
//!
|
//!
|
||||||
//! # Related Links
|
//! # Related Links
|
||||||
//! * <https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
//! * <https://web.archive.org/web/20210108073850/https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
||||||
|
|
||||||
extern crate byteorder;
|
extern crate jpeg;
|
||||||
extern crate lzw;
|
extern crate weezl;
|
||||||
extern crate miniz_oxide;
|
|
||||||
|
|
||||||
|
mod bytecast;
|
||||||
pub mod decoder;
|
pub mod decoder;
|
||||||
pub mod encoder;
|
pub mod encoder;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
|
|
||||||
pub use self::error::{InflateError, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||||
|
|
||||||
/// An enumeration over supported color types and their bit depths
|
/// An enumeration over supported color types and their bit depths
|
||||||
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
|
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
|
||||||
@@ -37,4 +37,7 @@ pub enum ColorType {
|
|||||||
|
|
||||||
/// Pixel is CMYK
|
/// Pixel is CMYK
|
||||||
CMYK(u8),
|
CMYK(u8),
|
||||||
|
|
||||||
|
/// Pixel is YCbCr
|
||||||
|
YCbCr(u8),
|
||||||
}
|
}
|
||||||
+61
-15
@@ -9,11 +9,9 @@ macro_rules! tags {
|
|||||||
} => {
|
} => {
|
||||||
$( #[$enum_attr] )*
|
$( #[$enum_attr] )*
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum $name {
|
pub enum $name {
|
||||||
$($(#[$ident_attr])* $tag,)*
|
$($(#[$ident_attr])* $tag,)*
|
||||||
// FIXME: switch to non_exhaustive once stabilized and compiler requirement new enough
|
|
||||||
#[doc(hidden)]
|
|
||||||
__NonExhaustive,
|
|
||||||
$(
|
$(
|
||||||
#[doc = $unknown_doc]
|
#[doc = $unknown_doc]
|
||||||
Unknown($ty),
|
Unknown($ty),
|
||||||
@@ -34,21 +32,28 @@ macro_rules! tags {
|
|||||||
match *self {
|
match *self {
|
||||||
$( $name::$tag => $val, )*
|
$( $name::$tag => $val, )*
|
||||||
$( $name::Unknown(n) => { $unknown_doc; n }, )*
|
$( $name::Unknown(n) => { $unknown_doc; n }, )*
|
||||||
$name::__NonExhaustive => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tags!($name, $ty);
|
tags!($name, $ty, $($unknown_doc)*);
|
||||||
};
|
};
|
||||||
// For u16 tags, provide direct inherent primitive conversion methods.
|
// For u16 tags, provide direct inherent primitive conversion methods.
|
||||||
($name:tt, u16) => {
|
($name:tt, u16, $($unknown_doc:literal)*) => {
|
||||||
impl $name {
|
impl $name {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn from_u16(val: u16) -> Option<Self> {
|
pub fn from_u16(val: u16) -> Option<Self> {
|
||||||
Self::__from_inner_type(val).ok()
|
Self::__from_inner_type(val).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_u16_exhaustive(val: u16) -> Self {
|
||||||
|
$unknown_doc;
|
||||||
|
Self::__from_inner_type(val).unwrap_or_else(|_| $name::Unknown(val))
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn to_u16(&self) -> u16 {
|
pub fn to_u16(&self) -> u16 {
|
||||||
Self::__to_inner_type(self)
|
Self::__to_inner_type(self)
|
||||||
@@ -57,7 +62,7 @@ macro_rules! tags {
|
|||||||
};
|
};
|
||||||
// For other tag types, do nothing for now. With concat_idents one could
|
// For other tag types, do nothing for now. With concat_idents one could
|
||||||
// provide inherent conversion methods for all types.
|
// provide inherent conversion methods for all types.
|
||||||
($name:tt, $ty:tt) => {};
|
($name:tt, $ty:tt, $($unknown_doc:literal)*) => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
||||||
@@ -109,35 +114,64 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
|||||||
TileLength = 323,
|
TileLength = 323,
|
||||||
TileOffsets = 324,
|
TileOffsets = 324,
|
||||||
TileByteCounts = 325,
|
TileByteCounts = 325,
|
||||||
|
// Data Sample Format
|
||||||
SampleFormat = 339,
|
SampleFormat = 339,
|
||||||
|
SMinSampleValue = 340, // TODO add support
|
||||||
|
SMaxSampleValue = 341, // TODO add support
|
||||||
|
// JPEG
|
||||||
|
JPEGTables = 347,
|
||||||
|
// GeoTIFF
|
||||||
|
ModelPixelScaleTag = 33550, // (SoftDesk)
|
||||||
|
ModelTransformationTag = 34264, // (JPL Carto Group)
|
||||||
|
ModelTiepointTag = 33922, // (Intergraph)
|
||||||
|
GeoKeyDirectoryTag = 34735, // (SPOT)
|
||||||
|
GeoDoubleParamsTag = 34736, // (SPOT)
|
||||||
|
GeoAsciiParamsTag = 34737, // (SPOT)
|
||||||
|
GdalNodata = 42113, // Contains areas with missing data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tag {
|
|
||||||
pub fn from_u16_exhaustive(val: u16) -> Self {
|
|
||||||
Self::from_u16(val).unwrap_or_else(|| Tag::Unknown(val))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
/// The type of an IFD entry (a 2 byte field).
|
/// The type of an IFD entry (a 2 byte field).
|
||||||
pub enum Type(u16) {
|
pub enum Type(u16) {
|
||||||
|
/// 8-bit unsigned integer
|
||||||
BYTE = 1,
|
BYTE = 1,
|
||||||
|
/// 8-bit byte that contains a 7-bit ASCII code; the last byte must be zero
|
||||||
ASCII = 2,
|
ASCII = 2,
|
||||||
|
/// 16-bit unsigned integer
|
||||||
SHORT = 3,
|
SHORT = 3,
|
||||||
|
/// 32-bit unsigned integer
|
||||||
LONG = 4,
|
LONG = 4,
|
||||||
|
/// Fraction stored as two 32-bit unsigned integers
|
||||||
RATIONAL = 5,
|
RATIONAL = 5,
|
||||||
|
/// 8-bit signed integer
|
||||||
SBYTE = 6,
|
SBYTE = 6,
|
||||||
|
/// 8-bit byte that may contain anything, depending on the field
|
||||||
|
UNDEFINED = 7,
|
||||||
|
/// 16-bit signed integer
|
||||||
SSHORT = 8,
|
SSHORT = 8,
|
||||||
|
/// 32-bit signed integer
|
||||||
SLONG = 9,
|
SLONG = 9,
|
||||||
|
/// Fraction stored as two 32-bit signed integers
|
||||||
SRATIONAL = 10,
|
SRATIONAL = 10,
|
||||||
|
/// 32-bit IEEE floating point
|
||||||
|
FLOAT = 11,
|
||||||
|
/// 64-bit IEEE floating point
|
||||||
|
DOUBLE = 12,
|
||||||
|
/// 32-bit unsigned integer (offset)
|
||||||
|
IFD = 13,
|
||||||
/// BigTIFF 64-bit unsigned integer
|
/// BigTIFF 64-bit unsigned integer
|
||||||
LONG8 = 16,
|
LONG8 = 16,
|
||||||
|
/// BigTIFF 64-bit signed integer
|
||||||
|
SLONG8 = 17,
|
||||||
|
/// BigTIFF 64-bit unsigned integer (offset)
|
||||||
|
IFD8 = 18,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
|
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||||
|
/// for reference.
|
||||||
pub enum CompressionMethod(u16) {
|
pub enum CompressionMethod(u16) {
|
||||||
None = 1,
|
None = 1,
|
||||||
Huffman = 2,
|
Huffman = 2,
|
||||||
@@ -145,6 +179,8 @@ pub enum CompressionMethod(u16) {
|
|||||||
Fax4 = 4,
|
Fax4 = 4,
|
||||||
LZW = 5,
|
LZW = 5,
|
||||||
JPEG = 6,
|
JPEG = 6,
|
||||||
|
// "Extended JPEG" or "new JPEG" style
|
||||||
|
ModernJPEG = 7,
|
||||||
Deflate = 8,
|
Deflate = 8,
|
||||||
OldDeflate = 0x80B2,
|
OldDeflate = 0x80B2,
|
||||||
PackBits = 0x8005,
|
PackBits = 0x8005,
|
||||||
@@ -175,10 +211,11 @@ tags! {
|
|||||||
pub enum Predictor(u16) {
|
pub enum Predictor(u16) {
|
||||||
None = 1,
|
None = 1,
|
||||||
Horizontal = 2,
|
Horizontal = 2,
|
||||||
|
FloatingPoint = 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tags!{
|
tags! {
|
||||||
/// Type to represent resolution units
|
/// Type to represent resolution units
|
||||||
pub enum ResolutionUnit(u16) {
|
pub enum ResolutionUnit(u16) {
|
||||||
None = 1,
|
None = 1,
|
||||||
@@ -186,3 +223,12 @@ pub enum ResolutionUnit(u16) {
|
|||||||
Centimeter = 3,
|
Centimeter = 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tags! {
|
||||||
|
pub enum SampleFormat(u16) unknown("An unknown extension sample format") {
|
||||||
|
Uint = 1,
|
||||||
|
Int = 2,
|
||||||
|
IEEEFP = 3,
|
||||||
|
Void = 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Copyrights:
|
||||||
|
|
||||||
|
kodim*.png: Eastman Kodak Company, released for unrestricted use
|
||||||
|
Transparency.png: Public Domain, according to Wikimedia
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+314
-2
@@ -21,7 +21,7 @@ macro_rules! test_image_sum {
|
|||||||
DecodingResult::$buffer(res) => {
|
DecodingResult::$buffer(res) => {
|
||||||
let sum: $sum_ty = res.into_iter().map(<$sum_ty>::from).sum();
|
let sum: $sum_ty = res.into_iter().map(<$sum_ty>::from).sum();
|
||||||
assert_eq!(sum, expected_sum);
|
assert_eq!(sum, expected_sum);
|
||||||
},
|
}
|
||||||
_ => panic!("Wrong bit depth"),
|
_ => panic!("Wrong bit depth"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,9 +29,13 @@ macro_rules! test_image_sum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test_image_sum!(test_image_sum_u8, U8, u64);
|
test_image_sum!(test_image_sum_u8, U8, u64);
|
||||||
|
test_image_sum!(test_image_sum_i8, I8, i64);
|
||||||
test_image_sum!(test_image_sum_u16, U16, u64);
|
test_image_sum!(test_image_sum_u16, U16, u64);
|
||||||
|
test_image_sum!(test_image_sum_i16, I16, i64);
|
||||||
test_image_sum!(test_image_sum_u32, U32, u64);
|
test_image_sum!(test_image_sum_u32, U32, u64);
|
||||||
test_image_sum!(test_image_sum_u64, U64, u64);
|
test_image_sum!(test_image_sum_u64, U64, u64);
|
||||||
|
test_image_sum!(test_image_sum_f32, F32, f32);
|
||||||
|
test_image_sum!(test_image_sum_f64, F64, f64);
|
||||||
|
|
||||||
/// Tests that a decoder can be constructed for an image and the color type
|
/// Tests that a decoder can be constructed for an image and the color type
|
||||||
/// read from the IFD and is of the appropriate type, but the type is
|
/// read from the IFD and is of the appropriate type, but the type is
|
||||||
@@ -42,7 +46,9 @@ fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
|
|||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
assert!(match decoder.read_image() {
|
assert!(match decoder.read_image() {
|
||||||
Err(tiff::TiffError::UnsupportedError(tiff::TiffUnsupportedError::UnsupportedColorType(_))) => true,
|
Err(tiff::TiffError::UnsupportedError(
|
||||||
|
tiff::TiffUnsupportedError::UnsupportedColorType(_),
|
||||||
|
)) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -57,6 +63,11 @@ fn test_cmyk_u16() {
|
|||||||
test_image_sum_u16("cmyk-3c-16b.tiff", ColorType::CMYK(16), 2181426827);
|
test_image_sum_u16("cmyk-3c-16b.tiff", ColorType::CMYK(16), 2181426827);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cmyk_f32() {
|
||||||
|
test_image_sum_f32("cmyk-3c-32b-float.tiff", ColorType::CMYK(32), 496.0405);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gray_u8() {
|
fn test_gray_u8() {
|
||||||
test_image_sum_u8("minisblack-1c-8b.tiff", ColorType::Gray(8), 2840893);
|
test_image_sum_u8("minisblack-1c-8b.tiff", ColorType::Gray(8), 2840893);
|
||||||
@@ -82,6 +93,20 @@ fn test_gray_u64() {
|
|||||||
test_image_sum_u64("gradient-1c-64b.tiff", ColorType::Gray(64), 549892913787);
|
test_image_sum_u64("gradient-1c-64b.tiff", ColorType::Gray(64), 549892913787);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gray_f32() {
|
||||||
|
test_image_sum_f32("gradient-1c-32b-float.tiff", ColorType::Gray(32), 128.03194);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gray_f64() {
|
||||||
|
test_image_sum_f64(
|
||||||
|
"gradient-1c-64b-float.tiff",
|
||||||
|
ColorType::Gray(64),
|
||||||
|
128.0319210877642,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rgb_u8() {
|
fn test_rgb_u8() {
|
||||||
test_image_sum_u8("rgb-3c-8b.tiff", ColorType::RGB(8), 7842108);
|
test_image_sum_u8("rgb-3c-8b.tiff", ColorType::RGB(8), 7842108);
|
||||||
@@ -107,6 +132,31 @@ fn test_rgb_u64() {
|
|||||||
test_image_sum_u64("gradient-3c-64b.tiff", ColorType::RGB(64), 2030834111716);
|
test_image_sum_u64("gradient-3c-64b.tiff", ColorType::RGB(64), 2030834111716);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rgb_f32() {
|
||||||
|
test_image_sum_f32("gradient-3c-32b-float.tiff", ColorType::RGB(32), 472.8405);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_int8() {
|
||||||
|
test_image_sum_i8("int8.tif", ColorType::Gray(8), 3111)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_int8_rgb() {
|
||||||
|
test_image_sum_i8("int8_rgb.tif", ColorType::RGB(8), -10344)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_int16() {
|
||||||
|
test_image_sum_i16("int16.tif", ColorType::Gray(16), 354396);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_int16_rgb() {
|
||||||
|
test_image_sum_i16("int16_rgb.tif", ColorType::RGB(16), 1063188);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_string_tags() {
|
fn test_string_tags() {
|
||||||
// these files have null-terminated strings for their Software tag. One has extra bytes after
|
// these files have null-terminated strings for their Software tag. One has extra bytes after
|
||||||
@@ -165,3 +215,265 @@ fn issue_69() {
|
|||||||
//let img_res = decoder.read_image();
|
//let img_res = decoder.read_image();
|
||||||
//assert!(img_res.is_ok());
|
//assert!(img_res.is_ok());
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tiled_rgb_u8() {
|
||||||
|
test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tiled_rect_rgb_u8() {
|
||||||
|
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #[test]
|
||||||
|
fn test_tiled_jpeg_rgb_u8() {
|
||||||
|
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
|
||||||
|
} */
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tiled_oversize_gray_i8() {
|
||||||
|
test_image_sum_i8("tiled-oversize-gray-i8.tif", ColorType::Gray(8), 1214996);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tiled_cmyk_i8() {
|
||||||
|
test_image_sum_i8("tiled-cmyk-i8.tif", ColorType::CMYK(8), 1759101);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tiled_incremental() {
|
||||||
|
let file = "tiled-rgb-u8.tif";
|
||||||
|
let expected_type = ColorType::RGB(8);
|
||||||
|
let sums = [
|
||||||
|
188760, 195639, 108148, 81986, 665088, 366140, 705317, 423366, 172033, 324455, 244102,
|
||||||
|
81853, 181258, 247971, 129486, 55600, 565625, 422102, 730888, 379271, 232142, 292549,
|
||||||
|
244045, 86866, 188141, 115036, 150785, 84389, 353170, 459325, 719619, 329594, 278663,
|
||||||
|
220474, 243048, 113563, 189152, 109684, 179391, 122188, 279651, 622093, 724682, 302459,
|
||||||
|
268428, 204499, 224255, 124674, 170668, 121868, 192768, 183367, 378029, 585651, 657712,
|
||||||
|
296790, 241444, 197083, 198429, 134869, 182318, 86034, 203655, 182338, 297255, 601284,
|
||||||
|
633813, 242531, 228578, 206441, 193552, 125412, 181527, 165439, 202531, 159538, 268388,
|
||||||
|
565790, 611382, 272967, 236497, 215154, 158881, 90806, 106114, 182342, 191824, 186138,
|
||||||
|
215174, 393193, 701228, 198866, 227944, 193830, 166330, 49008, 55719, 122820, 197316,
|
||||||
|
161969, 203152, 170986, 624427, 188605, 186187, 111064, 115192, 39538, 48626, 163929,
|
||||||
|
144682, 135796, 194141, 154198, 584125, 180255, 153524, 121433, 132641, 35743, 47798,
|
||||||
|
152343, 162874, 167664, 160175, 133038, 659882, 138339, 166470, 124173, 118929, 51317,
|
||||||
|
45267, 155776, 161331, 161006, 130052, 137618, 337291, 106481, 161999, 127343, 87724,
|
||||||
|
59540, 63907, 155677, 140668, 141523, 108061, 168657, 186482, 98599, 147614, 139963, 90444,
|
||||||
|
56602, 92547, 125644, 134212, 126569, 144153, 179800, 174516, 133969, 129399, 117681,
|
||||||
|
83305, 55075, 110737, 115108, 128572, 128911, 130922, 179986, 143288, 145884, 155856,
|
||||||
|
96683, 94057, 56238, 79649, 71651, 70182, 75010, 77009, 98855, 78979, 74341, 83482, 53403,
|
||||||
|
59842, 30305,
|
||||||
|
];
|
||||||
|
|
||||||
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
|
|
||||||
|
let tiles = decoder.tile_count().unwrap();
|
||||||
|
assert_eq!(tiles as usize, sums.len());
|
||||||
|
|
||||||
|
for tile in 0..tiles {
|
||||||
|
match decoder.read_chunk(tile).unwrap() {
|
||||||
|
DecodingResult::U8(res) => {
|
||||||
|
let sum: u64 = res.into_iter().map(<u64>::from).sum();
|
||||||
|
assert_eq!(sum, sums[tile as usize]);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong bit depth"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_div_zero() {
|
||||||
|
use tiff::{TiffError, TiffFormatError};
|
||||||
|
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
||||||
|
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 39, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 158, 0, 0, 251, 67, 1, 3, 0,
|
||||||
|
1, 0, 0, 0, 40, 0, 0, 0, 66, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 178, 178, 178, 178,
|
||||||
|
178, 178, 178,
|
||||||
|
];
|
||||||
|
|
||||||
|
let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_too_many_value_bytes() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 43, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 8, 0, 0, 0,
|
||||||
|
23, 0, 12, 0, 0, 65, 4, 0, 1, 6, 0, 0, 1, 16, 0, 1, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 59, 73, 84, 186, 202, 83, 240, 66, 1, 53, 22, 56, 47,
|
||||||
|
0, 0, 0, 0, 0, 0, 1, 222, 4, 0, 58, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 4, 0, 0, 100, 0,
|
||||||
|
0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18,
|
||||||
|
];
|
||||||
|
|
||||||
|
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_testcase5() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0,
|
||||||
|
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 246, 16, 0, 0, 22, 1, 4, 0, 1,
|
||||||
|
0, 0, 0, 40, 0, 251, 255, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 178, 178, 178, 178,
|
||||||
|
178, 178, 178,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
|
let _ = decoder.read_image().unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_testcase1() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1,
|
||||||
|
0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 17, 1, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0,
|
||||||
|
22, 1, 4, 0, 1, 0, 0, 0, 44, 0, 0, 0, 23, 1, 4, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 1, 0, 178,
|
||||||
|
178,
|
||||||
|
];
|
||||||
|
|
||||||
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stripped_image_overflow() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0,
|
||||||
|
0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, 22,
|
||||||
|
1, 4, 0, 1, 0, 0, 0, 44, 0, 248, 255, 23, 1, 4, 0, 1, 0, 0, 0, 178, 178, 178, 0, 1, 178,
|
||||||
|
178, 178,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
|
let err = decoder.read_image().unwrap_err();
|
||||||
|
|
||||||
|
match err {
|
||||||
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
|
unexpected => panic!("Unexpected error {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn oom() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
||||||
|
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0,
|
||||||
|
17, 1, 4, 0, 1, 0, 0, 0, 3, 77, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
|
||||||
|
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178,
|
||||||
|
];
|
||||||
|
|
||||||
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_testcase4() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
||||||
|
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
|
||||||
|
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13,
|
||||||
|
];
|
||||||
|
|
||||||
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_testcase2() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0,
|
||||||
|
0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0,
|
||||||
|
73, 42, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 158, 0, 0, 251, 3, 1,
|
||||||
|
3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 4, 61, 1, 18, 0, 1, 0, 0, 0, 202, 0, 0, 0, 17,
|
||||||
|
1, 100, 0, 129, 0, 0, 0, 0, 0, 0, 0, 232, 254, 252, 255, 254, 255, 255, 255, 1, 29, 0, 0,
|
||||||
|
22, 1, 3, 0, 1, 0, 0, 0, 16, 0, 0, 0, 23, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 123, 73, 254, 0,
|
||||||
|
73,
|
||||||
|
];
|
||||||
|
|
||||||
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_jpeg_tag_2() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 242, 0, 1, 4, 0, 1, 0,
|
||||||
|
0, 0, 0, 129, 16, 0, 1, 1, 4, 0, 1, 0, 0, 0, 214, 0, 0, 248, 253, 1, 3, 0, 1, 0, 0, 0, 64,
|
||||||
|
0, 0, 0, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 64, 14, 1, 0,
|
||||||
|
2, 0, 0, 148, 0, 206, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||||
|
1, 0, 0, 0, 22, 1, 4, 0, 17, 0, 0, 201, 1, 0, 0, 0, 23, 1, 2, 0, 20, 0, 0, 0, 194, 0, 0, 0,
|
||||||
|
91, 1, 7, 0, 5, 0, 0, 0, 64, 0, 0, 0, 237, 254, 65, 255, 255, 255, 255, 255, 1, 0, 0, 0,
|
||||||
|
22, 1, 4, 0, 1, 0, 0, 0, 42, 0, 0, 0, 23, 1, 255, 255, 255, 255, 255, 36, 36, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fuzzer_testcase3() {
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22,
|
||||||
|
128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 3, 0, 1, 0, 0, 0, 17, 1, 0, 231, 22, 1, 1, 0, 1, 0, 0, 0, 130, 0, 0, 0, 23, 1, 4,
|
||||||
|
0, 14, 0, 0, 0, 0, 0, 0, 0, 133, 133, 133, 77, 77, 77, 0, 0, 22, 128, 0, 255, 255, 255,
|
||||||
|
255, 255,
|
||||||
|
];
|
||||||
|
|
||||||
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn timeout() {
|
||||||
|
use tiff::{TiffError, TiffFormatError};
|
||||||
|
|
||||||
|
let image = [
|
||||||
|
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 68, 0, 0, 0, 2, 0, 32, 254, 252, 0, 109,
|
||||||
|
0, 129, 0, 0, 0, 32, 0, 58, 0, 1, 4, 0, 1, 0, 6, 0, 0, 0, 8, 0, 0, 1, 73, 73, 42, 0, 8, 0,
|
||||||
|
0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 21, 0, 0, 0, 61, 1, 255, 128, 9, 0, 0, 8, 0, 1, 113, 2,
|
||||||
|
3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 112, 0, 0, 36, 0, 0,
|
||||||
|
0, 112, 56, 200, 0, 5, 0, 0, 64, 0, 0, 1, 0, 4, 0, 0, 0, 2, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 4, 17, 1, 1, 0, 93, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 3, 6, 0, 231, 22, 1,
|
||||||
|
1, 0, 1, 0, 0, 0, 2, 64, 118, 36, 23, 1, 1, 0, 43, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 8,
|
||||||
|
0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32,
|
||||||
|
];
|
||||||
|
|
||||||
|
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
|
match error {
|
||||||
|
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
||||||
|
e => panic!("Unexpected error {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_rows_per_strip() {
|
||||||
|
test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_predictor_3_rgb_f32() {
|
||||||
|
test_image_sum_f32("predictor-3-rgb-f32.tif", ColorType::RGB(32), 54004.33);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_predictor_3_gray_f32() {
|
||||||
|
test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275);
|
||||||
|
}
|
||||||
+310
-47
@@ -1,9 +1,9 @@
|
|||||||
extern crate tiff;
|
extern crate tiff;
|
||||||
|
|
||||||
use tiff::decoder::{Decoder, DecodingResult, ifd};
|
use tiff::decoder::{ifd, Decoder, DecodingResult};
|
||||||
use tiff::encoder::{colortype, TiffEncoder, SRational};
|
use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder};
|
||||||
use tiff::ColorType;
|
|
||||||
use tiff::tags::Tag;
|
use tiff::tags::Tag;
|
||||||
|
use tiff::ColorType;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Cursor, Seek, SeekFrom};
|
use std::io::{Cursor, Seek, SeekFrom};
|
||||||
@@ -24,18 +24,22 @@ fn encode_decode() {
|
|||||||
{
|
{
|
||||||
let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
||||||
|
|
||||||
let mut image = tiff.new_image::<colortype::RGB8>(100, 100)
|
let mut image = tiff.new_image::<colortype::RGB8>(100, 100).unwrap();
|
||||||
|
image
|
||||||
|
.encoder()
|
||||||
|
.write_tag(Tag::Artist, "Image-tiff")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
image.encoder().write_tag(Tag::Artist, "Image-tiff").unwrap();
|
|
||||||
image.write_data(&image_data).unwrap();
|
image.write_data(&image_data).unwrap();
|
||||||
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||||
assert_eq!(decoder.get_tag(Tag::Artist).unwrap(), ifd::Value::Ascii("Image-tiff".into()));
|
assert_eq!(
|
||||||
|
decoder.get_tag(Tag::Artist).unwrap(),
|
||||||
|
ifd::Value::Ascii("Image-tiff".into())
|
||||||
|
);
|
||||||
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
||||||
assert_eq!(image_data, img_res);
|
assert_eq!(image_data, img_res);
|
||||||
} else {
|
} else {
|
||||||
@@ -44,6 +48,93 @@ fn encode_decode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_decode_big() {
|
||||||
|
let mut image_data = Vec::new();
|
||||||
|
for x in 0..100 {
|
||||||
|
for y in 0..100u8 {
|
||||||
|
let val = x + y;
|
||||||
|
image_data.push(val);
|
||||||
|
image_data.push(val);
|
||||||
|
image_data.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut file = Cursor::new(Vec::new());
|
||||||
|
{
|
||||||
|
let mut tiff = TiffEncoder::new_big(&mut file).unwrap();
|
||||||
|
|
||||||
|
let mut image = tiff.new_image::<colortype::RGB8>(100, 100).unwrap();
|
||||||
|
image
|
||||||
|
.encoder()
|
||||||
|
.write_tag(Tag::Artist, "Image-tiff")
|
||||||
|
.unwrap();
|
||||||
|
image.write_data(&image_data).unwrap();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||||
|
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||||
|
assert_eq!(
|
||||||
|
decoder.get_tag(Tag::Artist).unwrap(),
|
||||||
|
ifd::Value::Ascii("Image-tiff".into())
|
||||||
|
);
|
||||||
|
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
||||||
|
assert_eq!(image_data, img_res);
|
||||||
|
} else {
|
||||||
|
panic!("Wrong data type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_ifd() {
|
||||||
|
let mut data = Cursor::new(Vec::new());
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
||||||
|
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
||||||
|
image_encoder.write_strip(&[1]).unwrap();
|
||||||
|
let encoder = image_encoder.encoder();
|
||||||
|
|
||||||
|
// Use the "reusable" tags section as per the TIFF6 spec
|
||||||
|
encoder.write_tag(Tag::Unknown(65000), Ifd(42u32)).unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65001), &[Ifd(100u32)][..])
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65002), &[Ifd(1u32), Ifd(2u32), Ifd(3u32)][..])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
encoder.write_tag(Tag::Unknown(65010), Ifd8(43u64)).unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65011), &[Ifd8(100u64)][..])
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(
|
||||||
|
Tag::Unknown(65012),
|
||||||
|
&[Ifd8(1u64), Ifd8(2u64), Ifd8(3u64)][..],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewind the cursor for reading
|
||||||
|
data.set_position(0);
|
||||||
|
{
|
||||||
|
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(decoder.assert_tag_u32(65000), 42);
|
||||||
|
assert_eq!(decoder.assert_tag_u32_vec(65000), [42]);
|
||||||
|
assert_eq!(decoder.assert_tag_u32_vec(65001), [100]);
|
||||||
|
assert_eq!(decoder.assert_tag_u32_vec(65002), [1, 2, 3]);
|
||||||
|
|
||||||
|
assert_eq!(decoder.assert_tag_u64(65010), 43);
|
||||||
|
assert_eq!(decoder.assert_tag_u64_vec(65010), [43]);
|
||||||
|
assert_eq!(decoder.assert_tag_u64_vec(65011), [100]);
|
||||||
|
assert_eq!(decoder.assert_tag_u64_vec(65012), [1, 2, 3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
/// Test that attempting to encode when the input buffer is undersized returns
|
/// Test that attempting to encode when the input buffer is undersized returns
|
||||||
/// an error rather than panicking.
|
/// an error rather than panicking.
|
||||||
@@ -62,7 +153,10 @@ const TEST_IMAGE_DIR: &str = "./tests/images/";
|
|||||||
|
|
||||||
macro_rules! test_roundtrip {
|
macro_rules! test_roundtrip {
|
||||||
($name:ident, $buffer:ident, $buffer_ty:ty) => {
|
($name:ident, $buffer:ident, $buffer_ty:ty) => {
|
||||||
fn $name<C: colortype::ColorType<Inner=$buffer_ty>>(file: &str, expected_type: ColorType) {
|
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
|
||||||
|
file: &str,
|
||||||
|
expected_type: ColorType,
|
||||||
|
) {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
@@ -78,8 +172,7 @@ macro_rules! test_roundtrip {
|
|||||||
let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
let mut tiff = TiffEncoder::new(&mut file).unwrap();
|
||||||
|
|
||||||
let (width, height) = decoder.dimensions().unwrap();
|
let (width, height) = decoder.dimensions().unwrap();
|
||||||
tiff.write_image::<C>(width, height, &image_data)
|
tiff.write_image::<C>(width, height, &image_data).unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
{
|
{
|
||||||
@@ -98,6 +191,8 @@ test_roundtrip!(test_u8_roundtrip, U8, u8);
|
|||||||
test_roundtrip!(test_u16_roundtrip, U16, u16);
|
test_roundtrip!(test_u16_roundtrip, U16, u16);
|
||||||
test_roundtrip!(test_u32_roundtrip, U32, u32);
|
test_roundtrip!(test_u32_roundtrip, U32, u32);
|
||||||
test_roundtrip!(test_u64_roundtrip, U64, u64);
|
test_roundtrip!(test_u64_roundtrip, U64, u64);
|
||||||
|
test_roundtrip!(test_f32_roundtrip, F32, f32);
|
||||||
|
test_roundtrip!(test_f64_roundtrip, F64, f64);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gray_u8_roundtrip() {
|
fn test_gray_u8_roundtrip() {
|
||||||
@@ -149,6 +244,81 @@ fn test_rgb_u64_roundtrip() {
|
|||||||
test_u64_roundtrip::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
|
test_u64_roundtrip::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gray_f32_roundtrip() {
|
||||||
|
test_f32_roundtrip::<colortype::Gray32Float>("gradient-1c-32b-float.tiff", ColorType::Gray(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rgb_f32_roundtrip() {
|
||||||
|
test_f32_roundtrip::<colortype::RGB32Float>("gradient-3c-32b-float.tiff", ColorType::RGB(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cmyk_f32_roundtrip() {
|
||||||
|
test_f32_roundtrip::<colortype::CMYK32Float>("cmyk-3c-32b-float.tiff", ColorType::CMYK(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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>;
|
||||||
|
fn assert_tag_i32(&mut self, tag: u16) -> i32;
|
||||||
|
fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec<i32>;
|
||||||
|
fn assert_tag_u64(&mut self, tag: u16) -> u64;
|
||||||
|
fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec<u64>;
|
||||||
|
fn assert_tag_i64(&mut self, tag: u16) -> i64;
|
||||||
|
fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec<i64>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: std::io::Read + std::io::Seek> AssertDecode for Decoder<R> {
|
||||||
|
fn assert_tag_u32(&mut self, tag: u16) -> u32 {
|
||||||
|
self.get_tag(Tag::Unknown(tag)).unwrap().into_u32().unwrap()
|
||||||
|
}
|
||||||
|
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32> {
|
||||||
|
self.get_tag(Tag::Unknown(tag))
|
||||||
|
.unwrap()
|
||||||
|
.into_u32_vec()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
fn assert_tag_i32(&mut self, tag: u16) -> i32 {
|
||||||
|
self.get_tag(Tag::Unknown(tag)).unwrap().into_i32().unwrap()
|
||||||
|
}
|
||||||
|
fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec<i32> {
|
||||||
|
self.get_tag(Tag::Unknown(tag))
|
||||||
|
.unwrap()
|
||||||
|
.into_i32_vec()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
fn assert_tag_u64(&mut self, tag: u16) -> u64 {
|
||||||
|
self.get_tag(Tag::Unknown(tag)).unwrap().into_u64().unwrap()
|
||||||
|
}
|
||||||
|
fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec<u64> {
|
||||||
|
self.get_tag(Tag::Unknown(tag))
|
||||||
|
.unwrap()
|
||||||
|
.into_u64_vec()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
fn assert_tag_i64(&mut self, tag: u16) -> i64 {
|
||||||
|
self.get_tag(Tag::Unknown(tag)).unwrap().into_i64().unwrap()
|
||||||
|
}
|
||||||
|
fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec<i64> {
|
||||||
|
self.get_tag(Tag::Unknown(tag))
|
||||||
|
.unwrap()
|
||||||
|
.into_i64_vec()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_byte() {
|
fn test_multiple_byte() {
|
||||||
let mut data = Cursor::new(Vec::new());
|
let mut data = Cursor::new(Vec::new());
|
||||||
@@ -156,24 +326,33 @@ fn test_multiple_byte() {
|
|||||||
{
|
{
|
||||||
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
||||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
||||||
|
image_encoder.write_strip(&[1]).unwrap();
|
||||||
let encoder = image_encoder.encoder();
|
let encoder = image_encoder.encoder();
|
||||||
|
|
||||||
encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]).unwrap();
|
encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]).unwrap();
|
||||||
encoder.write_tag(Tag::Unknown(65001), &[1_u8, 2][..]).unwrap();
|
encoder
|
||||||
encoder.write_tag(Tag::Unknown(65002), &[1_u8, 2, 3][..]).unwrap();
|
.write_tag(Tag::Unknown(65001), &[1_u8, 2][..])
|
||||||
encoder.write_tag(Tag::Unknown(65003), &[1_u8, 2, 3, 4][..]).unwrap();
|
.unwrap();
|
||||||
encoder.write_tag(Tag::Unknown(65004), &[1_u8, 2, 3, 4, 5][..]).unwrap();
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65002), &[1_u8, 2, 3][..])
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65003), &[1_u8, 2, 3, 4][..])
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65004), &[1_u8, 2, 3, 4, 5][..])
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
data.set_position(0);
|
data.set_position(0);
|
||||||
{
|
{
|
||||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||||
|
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65000)).unwrap().into_u32_vec().unwrap(), [1]);
|
assert_eq!(decoder.assert_tag_u32_vec(65000), [1]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65001)).unwrap().into_u32_vec().unwrap(), [1, 2]);
|
assert_eq!(decoder.assert_tag_u32_vec(65001), [1, 2]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65002)).unwrap().into_u32_vec().unwrap(), [1, 2, 3]);
|
assert_eq!(decoder.assert_tag_u32_vec(65002), [1, 2, 3]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65003)).unwrap().into_u32_vec().unwrap(), [1, 2, 3, 4]);
|
assert_eq!(decoder.assert_tag_u32_vec(65003), [1, 2, 3, 4]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65004)).unwrap().into_u32_vec().unwrap(), [1, 2, 3, 4, 5]);
|
assert_eq!(decoder.assert_tag_u32_vec(65004), [1, 2, 3, 4, 5]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,31 +360,68 @@ fn test_multiple_byte() {
|
|||||||
/// Test writing signed tags from TIFF 6.0
|
/// Test writing signed tags from TIFF 6.0
|
||||||
fn test_signed() {
|
fn test_signed() {
|
||||||
let mut data = Cursor::new(Vec::new());
|
let mut data = Cursor::new(Vec::new());
|
||||||
|
fn make_srational(i: i32) -> SRational {
|
||||||
|
SRational { n: i, d: 100 }
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
||||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
||||||
|
image_encoder.write_strip(&[1]).unwrap();
|
||||||
let encoder = image_encoder.encoder();
|
let encoder = image_encoder.encoder();
|
||||||
|
|
||||||
//Use the "reusable" tags section as per the TIFF6 spec
|
//Use the "reusable" tags section as per the TIFF6 spec
|
||||||
encoder.write_tag(Tag::Unknown(65000), -1_i8).unwrap();
|
encoder.write_tag(Tag::Unknown(65000), -1_i8).unwrap();
|
||||||
encoder.write_tag(Tag::Unknown(65001), &[-1_i8][..]).unwrap();
|
encoder
|
||||||
encoder.write_tag(Tag::Unknown(65002), &[-1_i8, 2][..]).unwrap();
|
.write_tag(Tag::Unknown(65001), &[-1_i8][..])
|
||||||
encoder.write_tag(Tag::Unknown(65003), &[-1_i8, 2, -3][..]).unwrap();
|
.unwrap();
|
||||||
encoder.write_tag(Tag::Unknown(65004), &[-1_i8, 2, -3, 4][..]).unwrap();
|
encoder
|
||||||
encoder.write_tag(Tag::Unknown(65005), &[-1_i8, 2, -3, 4, -5][..]).unwrap();
|
.write_tag(Tag::Unknown(65002), &[-1_i8, 2][..])
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65003), &[-1_i8, 2, -3][..])
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65004), &[-1_i8, 2, -3, 4][..])
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65005), &[-1_i8, 2, -3, 4, -5][..])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
encoder.write_tag(Tag::Unknown(65010), -1_i16).unwrap();
|
encoder.write_tag(Tag::Unknown(65010), -1_i16).unwrap();
|
||||||
encoder.write_tag(Tag::Unknown(65011), -1_i16).unwrap();
|
encoder.write_tag(Tag::Unknown(65011), -1_i16).unwrap();
|
||||||
encoder.write_tag(Tag::Unknown(65012), &[-1_i16, 2][..]).unwrap();
|
encoder
|
||||||
encoder.write_tag(Tag::Unknown(65013), &[-1_i16, 2, -3][..]).unwrap();
|
.write_tag(Tag::Unknown(65012), &[-1_i16, 2][..])
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65013), &[-1_i16, 2, -3][..])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
encoder.write_tag(Tag::Unknown(65020), -1_i32).unwrap();
|
encoder.write_tag(Tag::Unknown(65020), -1_i32).unwrap();
|
||||||
encoder.write_tag(Tag::Unknown(65021), &[-1_i32][..]).unwrap();
|
encoder
|
||||||
encoder.write_tag(Tag::Unknown(65022), &[-1_i32, 2][..]).unwrap();
|
.write_tag(Tag::Unknown(65021), &[-1_i32][..])
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65022), &[-1_i32, 2][..])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
encoder.write_tag(Tag::Unknown(65030), SRational { n: -1, d: 100 }).unwrap();
|
encoder.write_tag(Tag::Unknown(65030), -1_i64).unwrap();
|
||||||
encoder.write_tag(Tag::Unknown(65031), &[SRational { n: -1, d: 100 }, SRational { n: 2, d: 100 }][..]).unwrap();
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65031), &[-1_i64][..])
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65032), &[-1_i64, 2][..])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
encoder
|
||||||
|
.write_tag(Tag::Unknown(65040), make_srational(-1))
|
||||||
|
.unwrap();
|
||||||
|
encoder
|
||||||
|
.write_tag(
|
||||||
|
Tag::Unknown(65041),
|
||||||
|
&[make_srational(-1), make_srational(2)][..],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Rewind the cursor for reading
|
//Rewind the cursor for reading
|
||||||
@@ -213,24 +429,28 @@ fn test_signed() {
|
|||||||
{
|
{
|
||||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||||
|
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65000)).unwrap().into_i32().unwrap(), -1, );
|
assert_eq!(decoder.assert_tag_i32(65000), -1);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65001)).unwrap().into_i32_vec().unwrap(), [-1]);
|
assert_eq!(decoder.assert_tag_i32_vec(65001), [-1]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65002)).unwrap().into_i32_vec().unwrap(), [-1, 2]);
|
assert_eq!(decoder.assert_tag_i32_vec(65002), [-1, 2]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65003)).unwrap().into_i32_vec().unwrap(), [-1, 2, -3]);
|
assert_eq!(decoder.assert_tag_i32_vec(65003), [-1, 2, -3]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65004)).unwrap().into_i32_vec().unwrap(), [-1, 2, -3, 4]);
|
assert_eq!(decoder.assert_tag_i32_vec(65004), [-1, 2, -3, 4]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65005)).unwrap().into_i32_vec().unwrap(), [-1, 2, -3, 4, -5], );
|
assert_eq!(decoder.assert_tag_i32_vec(65005), [-1, 2, -3, 4, -5],);
|
||||||
|
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65010)).unwrap().into_i32().unwrap(), -1);
|
assert_eq!(decoder.assert_tag_i32(65010), -1);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65011)).unwrap().into_i32_vec().unwrap(), [-1]);
|
assert_eq!(decoder.assert_tag_i32_vec(65011), [-1]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65012)).unwrap().into_i32_vec().unwrap(), [-1, 2]);
|
assert_eq!(decoder.assert_tag_i32_vec(65012), [-1, 2]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65013)).unwrap().into_i32_vec().unwrap(), [-1, 2, -3]);
|
assert_eq!(decoder.assert_tag_i32_vec(65013), [-1, 2, -3]);
|
||||||
|
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65020)).unwrap().into_i32().unwrap(), -1);
|
assert_eq!(decoder.assert_tag_i32(65020), -1);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65021)).unwrap().into_i32_vec().unwrap(), [-1]);
|
assert_eq!(decoder.assert_tag_i32_vec(65021), [-1]);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65022)).unwrap().into_i32_vec().unwrap(), [-1, 2]);
|
assert_eq!(decoder.assert_tag_i32_vec(65022), [-1, 2]);
|
||||||
|
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65030)).unwrap().into_i32_vec().unwrap(), [-1, 100]);
|
assert_eq!(decoder.assert_tag_i64(65030), -1);
|
||||||
assert_eq!(decoder.get_tag(Tag::Unknown(65031)).unwrap().into_i32_vec().unwrap(), [-1_i32, 100, 2, 100]);
|
assert_eq!(decoder.assert_tag_i64_vec(65031), [-1]);
|
||||||
|
assert_eq!(decoder.assert_tag_i64_vec(65032), [-1, 2]);
|
||||||
|
|
||||||
|
assert_eq!(decoder.assert_tag_i32_vec(65040), [-1, 100]);
|
||||||
|
assert_eq!(decoder.assert_tag_i32_vec(65041), [-1_i32, 100, 2, 100]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,10 +465,14 @@ fn test_multipage_image() {
|
|||||||
|
|
||||||
// write first grayscale image (2x2 16-bit)
|
// write first grayscale image (2x2 16-bit)
|
||||||
let img1: Vec<u16> = [1, 2, 3, 4].to_vec();
|
let img1: Vec<u16> = [1, 2, 3, 4].to_vec();
|
||||||
img_encoder.write_image::<colortype::Gray16>(2, 2, &img1[..]).unwrap();
|
img_encoder
|
||||||
|
.write_image::<colortype::Gray16>(2, 2, &img1[..])
|
||||||
|
.unwrap();
|
||||||
// write second grayscale image (3x3 8-bit)
|
// write second grayscale image (3x3 8-bit)
|
||||||
let img2: Vec<u8> = [9, 8, 7, 6, 5, 4, 3, 2, 1].to_vec();
|
let img2: Vec<u8> = [9, 8, 7, 6, 5, 4, 3, 2, 1].to_vec();
|
||||||
img_encoder.write_image::<colortype::Gray8>(3, 3, &img2[..]).unwrap();
|
img_encoder
|
||||||
|
.write_image::<colortype::Gray8>(3, 3, &img2[..])
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// seek to the beginning of the file, so that it can be decoded
|
// seek to the beginning of the file, so that it can be decoded
|
||||||
@@ -264,3 +488,42 @@ fn test_multipage_image() {
|
|||||||
assert_eq!(img_decoder.dimensions().unwrap(), (3, 3));
|
assert_eq!(img_decoder.dimensions().unwrap(), (3, 3));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// verify rows per strip setting
|
||||||
|
fn test_rows_per_strip() {
|
||||||
|
let mut file = Cursor::new(Vec::new());
|
||||||
|
{
|
||||||
|
let mut img_encoder = TiffEncoder::new(&mut file).unwrap();
|
||||||
|
|
||||||
|
let mut image = img_encoder.new_image::<colortype::Gray8>(100, 100).unwrap();
|
||||||
|
assert_eq!(image.next_strip_sample_count(), 100 * 100);
|
||||||
|
image.rows_per_strip(2).unwrap();
|
||||||
|
assert_eq!(image.next_strip_sample_count(), 2 * 100);
|
||||||
|
|
||||||
|
let img2: Vec<u8> = vec![0; 2 * 100];
|
||||||
|
image.write_strip(&img2[..]).unwrap();
|
||||||
|
assert!(image.rows_per_strip(5).is_err());
|
||||||
|
for i in 1..50 {
|
||||||
|
let img2: Vec<u8> = vec![i; 2 * 100];
|
||||||
|
image.write_strip(&img2[..]).unwrap();
|
||||||
|
}
|
||||||
|
assert!(image.write_strip(&img2[..]).is_err());
|
||||||
|
image.finish().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
|
{
|
||||||
|
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||||
|
assert_eq!(decoder.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2);
|
||||||
|
assert_eq!(decoder.strip_count().unwrap(), 50);
|
||||||
|
|
||||||
|
for i in 0..50 {
|
||||||
|
let img2 = [i; 2 * 100];
|
||||||
|
match decoder.read_chunk(i as u32).unwrap() {
|
||||||
|
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
|
||||||
|
other => panic!("Incorrect strip type {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
extern crate tiff;
|
||||||
|
|
||||||
|
use std::io::{Cursor, Seek, Write};
|
||||||
|
use tiff::{
|
||||||
|
decoder::{Decoder, DecodingResult},
|
||||||
|
encoder::{
|
||||||
|
colortype::{self, ColorType},
|
||||||
|
compression::*,
|
||||||
|
TiffEncoder, TiffValue,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>::Inner>> {
|
||||||
|
const WIDTH: u32;
|
||||||
|
const HEIGHT: u32;
|
||||||
|
type Color: ColorType;
|
||||||
|
|
||||||
|
fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
|
||||||
|
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
|
||||||
|
|
||||||
|
fn compress<C: Compression, W: Write + Seek>(
|
||||||
|
&self,
|
||||||
|
encoder: &mut TiffEncoder<W>,
|
||||||
|
compression: C,
|
||||||
|
) where
|
||||||
|
[<Self::Color as ColorType>::Inner]: TiffValue,
|
||||||
|
{
|
||||||
|
let image = encoder
|
||||||
|
.new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression)
|
||||||
|
.unwrap();
|
||||||
|
image.write_data(self.reference_data()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate() -> Self {
|
||||||
|
assert_eq!(
|
||||||
|
Self::Color::BITS_PER_SAMPLE.len(),
|
||||||
|
NUM_CHANNELS,
|
||||||
|
"Incompatible color type"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut data = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * NUM_CHANNELS);
|
||||||
|
for x in 0..Self::WIDTH {
|
||||||
|
for y in 0..Self::HEIGHT {
|
||||||
|
data.extend(IntoIterator::into_iter(Self::generate_pixel(x, y)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::from(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestImageColor(Vec<u16>);
|
||||||
|
|
||||||
|
impl From<Vec<u16>> for TestImageColor {
|
||||||
|
fn from(value: Vec<u16>) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestImage<3> for TestImageColor {
|
||||||
|
const WIDTH: u32 = 1;
|
||||||
|
const HEIGHT: u32 = 7;
|
||||||
|
type Color = colortype::RGB16;
|
||||||
|
|
||||||
|
fn reference_data(&self) -> &[u16] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 3] {
|
||||||
|
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
|
||||||
|
[val as <Self::Color as ColorType>::Inner; 3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestImageGrayscale(Vec<u8>);
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for TestImageGrayscale {
|
||||||
|
fn from(value: Vec<u8>) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestImage<1> for TestImageGrayscale {
|
||||||
|
const WIDTH: u32 = 21;
|
||||||
|
const HEIGHT: u32 = 10;
|
||||||
|
type Color = colortype::Gray8;
|
||||||
|
|
||||||
|
fn reference_data(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 1] {
|
||||||
|
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
|
||||||
|
[val as <Self::Color as ColorType>::Inner]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
|
||||||
|
let mut data = Cursor::new(Vec::new());
|
||||||
|
|
||||||
|
let image_rgb = TestImageColor::generate();
|
||||||
|
let image_grayscale = TestImageGrayscale::generate();
|
||||||
|
|
||||||
|
// Encode tiff with compression
|
||||||
|
{
|
||||||
|
// Create a multipage image with 2 images
|
||||||
|
let mut encoder = TiffEncoder::new(&mut data).unwrap();
|
||||||
|
image_rgb.compress(&mut encoder, compression.clone());
|
||||||
|
image_grayscale.compress(&mut encoder, compression);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode tiff
|
||||||
|
data.set_position(0);
|
||||||
|
{
|
||||||
|
let mut decoder = Decoder::new(data).unwrap();
|
||||||
|
|
||||||
|
// Check the RGB image
|
||||||
|
assert_eq!(
|
||||||
|
match decoder.read_image() {
|
||||||
|
Ok(DecodingResult::U16(image_data)) => image_data,
|
||||||
|
unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
|
||||||
|
},
|
||||||
|
image_rgb.reference_data()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check the grayscale image
|
||||||
|
decoder.next_image().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
match decoder.read_image() {
|
||||||
|
Ok(DecodingResult::U8(image_data)) => image_data,
|
||||||
|
unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
|
||||||
|
},
|
||||||
|
image_grayscale.reference_data()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_decode_without_compression() {
|
||||||
|
encode_decode_with_compression(Uncompressed::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_decode_with_lzw() {
|
||||||
|
encode_decode_with_compression(Lzw::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_decode_with_deflate() {
|
||||||
|
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast));
|
||||||
|
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced));
|
||||||
|
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn encode_decode_with_packbits() {
|
||||||
|
encode_decode_with_compression(Packbits::default());
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user