Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ed3d90f19 |
No files matched your search
@@ -1,94 +0,0 @@
|
|||||||
name: Rust CI
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
rust: ["1.74.0", stable, beta, nightly]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
|
||||||
if: ${{ matrix.rust == '1.74.0' }}
|
|
||||||
- name: Generate Cargo.lock with minimal-version dependencies
|
|
||||||
if: ${{ matrix.rust == '1.74.0' }}
|
|
||||||
run: cargo -Zminimal-versions generate-lockfile
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: ${{ matrix.rust }}
|
|
||||||
- name: Cache Cargo Dependencies
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
cache-on-failure: true
|
|
||||||
- name: build
|
|
||||||
run: cargo build -v
|
|
||||||
- name: test
|
|
||||||
if: ${{ matrix.rust != '1.74.0' }}
|
|
||||||
run: cargo test -v && cargo doc -v
|
|
||||||
|
|
||||||
features:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
feature: ["", deflate, jpeg, lzw, zstd, default, "default,zstd"]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
components: clippy
|
|
||||||
- name: Cargo check
|
|
||||||
run: cargo check --features "${{ matrix.feature }}" --no-default-features
|
|
||||||
|
|
||||||
- name: Cargo clippy
|
|
||||||
if: ${{ matrix.feature != '' }}
|
|
||||||
run: cargo clippy --no-deps --all-features --all-targets -- -D warnings
|
|
||||||
|
|
||||||
other_archs:
|
|
||||||
# github actions does not support 32-bit or big endian systems directly, but
|
|
||||||
# it does support QEMU. so we install qemu, then build and run the tests on
|
|
||||||
# emulated systems. NOTE: you can also use this approach to test locally.
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
arch: [powerpc-unknown-linux-gnu, i686-unknown-linux-gnu]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Install or use cached cross-rs/cross
|
|
||||||
uses: baptiste0928/cargo-install@v2
|
|
||||||
with:
|
|
||||||
crate: cross
|
|
||||||
- name: Cache Cargo Dependencies
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
cache-on-failure: true
|
|
||||||
key: ${{ matrix.arch }}
|
|
||||||
- name: Start Docker (required for cross-rs)
|
|
||||||
run: sudo systemctl start docker
|
|
||||||
- name: Cross-Run Tests using QEMU
|
|
||||||
run: cross test --target ${{ matrix.arch }}
|
|
||||||
|
|
||||||
rustfmt:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
components: rustfmt
|
|
||||||
- name: Run rustfmt check
|
|
||||||
run: cargo fmt -- --check
|
|
||||||
|
|
||||||
clippy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
with:
|
|
||||||
components: clippy
|
|
||||||
- run: cargo clippy --all-features # TODO: enable "-- -D warnings" once all outstanding clippy lints are fixed
|
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
language: rust
|
||||||
|
rust:
|
||||||
|
- 1.34.2
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
- nightly
|
||||||
|
|
||||||
|
script:
|
||||||
|
- if [[ "$TRAVIS_RUST_VERSION" == *stable* ]]; then rustup component add rustfmt; cargo fmt --version; cargo fmt -- --check || (echo "Please reformat your code with 'cargo fmt' (version $(cargo fmt --version))"; false); fi
|
||||||
|
- cargo build -v
|
||||||
|
- cargo test -v
|
||||||
-265
@@ -1,268 +1,3 @@
|
|||||||
# Version 0.10.4 (unreleased)
|
|
||||||
|
|
||||||
- `Directory` now implements `FromIterator<(Tag, Value)>`.
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- The decoder now interprets the `ExtraSamples` tag. The sample count must now
|
|
||||||
more strict match the expected value with alpha channels only allowing for
|
|
||||||
explicitly denoted unassociated or associated alpha. This effects the
|
|
||||||
indicated color type when decoding images with additional samples indicated
|
|
||||||
as unspecified relation. Previously, these may have been interpreted as
|
|
||||||
alpha by the total sample count (e.g. RgbA if 4 samples under a photometric
|
|
||||||
interpretation of RGB).
|
|
||||||
|
|
||||||
# Version 0.10.3
|
|
||||||
|
|
||||||
New features:
|
|
||||||
- Group 4 `fax` is now recognized as a compression type and decoded.
|
|
||||||
- A crate feature `fax` controls related dependencies, on by default.
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- The presence of PhotometricInterpretation::WhiteIsZero now returns an error
|
|
||||||
if the color type can not be inverted by the library where they were
|
|
||||||
previously silently ignored. Please report any missing color types.
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
- The PhotometricInterpretation::WhiteIsZero is now also applied to 1-bit
|
|
||||||
images, as well as 2-bit and 4-bit sample types where previously the presence
|
|
||||||
of this tag was ignored.
|
|
||||||
|
|
||||||
# Version 0.10.2
|
|
||||||
|
|
||||||
Republished 0.10.1, fixing an accidentally removed method (`Decoder::find_tag_unsigned_vec`).
|
|
||||||
|
|
||||||
# Version 0.10.1
|
|
||||||
|
|
||||||
New features:
|
|
||||||
- Added a dedicated `Directory` type (#277)
|
|
||||||
- Expose methods related to `Directory` publicly (#278)
|
|
||||||
- Implement writing directories (#281, #283)
|
|
||||||
- Add ICC (color profile) to the set of known tags (#287)
|
|
||||||
- Added `Decoder.inner()` to access the underlying reader (#289)
|
|
||||||
- Added methods for reading into a pre-allocated byte buffer (#285)
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- No longer perform color conversion when decoding embedded JPEG. This fixes decoding for JPEG images with alpha channel. (#274)
|
|
||||||
- More lenient LZW decoding to match libtiff (#231)
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
- Fixed seeking multiple times in a row (#279)
|
|
||||||
|
|
||||||
# Version 0.10.0
|
|
||||||
|
|
||||||
New features:
|
|
||||||
- Added `ColorType::Multiband` and `ColorType::CMYKA`.
|
|
||||||
- Added `Value::Byte` and `Value::SignedByte` to represent `u8` and `i8` tags.
|
|
||||||
- Encoding can now use horizontal predictor.
|
|
||||||
- The `Decoder::tag_iter` method allows iterating over all tags in the image.
|
|
||||||
- Half precision floating point values are now supported during decoding.
|
|
||||||
- ZStandard compression has been added as an additional compression method
|
|
||||||
during decoding, but requires the `zstd` feature flag.
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- Switched `u16` and `i16` values to be intepreted as `Value::Short` and
|
|
||||||
`Value::SignedShort` respectively.
|
|
||||||
- Changed JPEG compression to use the `zune-jpeg` crate instead of
|
|
||||||
`jpeg-decoder`.
|
|
||||||
- The `deflate`, `jpeg`, and `lzw` compression methods are now controlled by
|
|
||||||
(enabled by default) feature flags.
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
- Fixed a bug in 1 bit-per-pixel decoding.
|
|
||||||
|
|
||||||
# Version 0.9.1
|
|
||||||
|
|
||||||
New features:
|
|
||||||
- Basic support for planar configuration.
|
|
||||||
- Allow arbitrary number of samples as long as all have the same bit depth.
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
- Don't panic when parsing metadata when custom compression is used.
|
|
||||||
|
|
||||||
# Version 0.9.0
|
|
||||||
|
|
||||||
New features:
|
|
||||||
* Added support for photometric interpretation `YCbCr` and added related
|
|
||||||
`ColorType`.
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* Decoding tiled images calculates padding correctly when image width or height
|
|
||||||
is a multiple of tile size. It could previously corrupt the last tile per row
|
|
||||||
by skipping over data.
|
|
||||||
|
|
||||||
# Version 0.8.1
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
* The jpeg decoder gained to ability to utilize the Photometric Interpretation
|
|
||||||
directly instead of relying on a custom APP segment.
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* A spurious error within the PackBits decoder lead to the incorrect results
|
|
||||||
(wrong bits or errors), based on the maximum size of reads from the
|
|
||||||
underlying reader.
|
|
||||||
* Removed a panic path in jpeg decoding, when a feature such as photometric
|
|
||||||
interpretation is not supported. An error is returned instead.
|
|
||||||
|
|
||||||
# Version 0.8.0
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
* The minimum supported rust version is now indicated in `Cargo.toml`.
|
|
||||||
* The enums `TiffFormatError` and `TiffUnsupportedError` are now
|
|
||||||
marked with the `#[non_exhaustive]` attribute.
|
|
||||||
* Additionally, tag related enums `Value`, `Tags`, `Type`, `CompressionMethod`,
|
|
||||||
`PhotometricInterpretation`, `PlanarConfiguration`, `Predictor`,
|
|
||||||
`ResolutionUnit`, `SampleFormat` are also changed.
|
|
||||||
|
|
||||||
Removals:
|
|
||||||
* Removed deprecated methods of `Decoder`: `init`, `read_jpeg`,
|
|
||||||
`read_strip_to_buffer`, `read_strip`, `read_tile`. The implicit chunk (row or
|
|
||||||
tile) index order could not be easily tracked by the caller. New separate
|
|
||||||
utility interfaces may be introduced at a later point, for now callers are
|
|
||||||
obligated to choose their own.
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* Update to `jpeg_decoder = 0.3`.
|
|
||||||
|
|
||||||
# Version 0.7.4
|
|
||||||
|
|
||||||
New features:
|
|
||||||
* Creating an encoder for invalid, zero-sized images is now rejected.
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* Fix panic, in a case where decoding jpeg encoded images did not expect the
|
|
||||||
jpeg decoder to return an error.
|
|
||||||
* Fix panic by validating `rows_per_strip` better, fixing a division-by-zero.
|
|
||||||
|
|
||||||
# Version 0.7.3
|
|
||||||
|
|
||||||
New features:
|
|
||||||
* Allow decoder to access specific tiles by index.
|
|
||||||
* Add support for floating point predictor.
|
|
||||||
* Tiled jpeg file support.
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
* Various refactoring and performance improvements.
|
|
||||||
|
|
||||||
# Version 0.7.2
|
|
||||||
|
|
||||||
New features:
|
|
||||||
* Encoding with `ImageEncoder` now takes an optional compressor argument,
|
|
||||||
allowing compressed encoding. See the methods
|
|
||||||
`TiffEncoder::{new_image,write_image}_with_compression`.
|
|
||||||
* `jpeg_decoder` has been upgraded, now supports lossless JPEG.
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
* Decoding now more consistently reads and interprets the initial IFD, instead
|
|
||||||
of performing _some_ interpretation lazily. (This change prepares fully lazy
|
|
||||||
and backwards seeking.)
|
|
||||||
|
|
||||||
# Version 0.7.1
|
|
||||||
|
|
||||||
New features:
|
|
||||||
* Encoding signed integer formats is now supported.
|
|
||||||
* Extensive fuzzing with `cargo fuzz`.
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
* Tile decoding should be a little faster, requires one less intermediate buffer.
|
|
||||||
* Images whose IFDs form a cycle due to their offsets will now raise an error
|
|
||||||
when the cycle would be entered (jumping back should still be supported).
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
* Fixed a regression that caused conflict between strips and tile images,
|
|
||||||
causing errors in decoding some images.
|
|
||||||
* Use checked integer arithmetic in limit calculations, fixes overflows.
|
|
||||||
* IFD Tags are now always cleared between images.
|
|
||||||
* Found by fuzzing: Several memory limit overflows; JPEG now correctly
|
|
||||||
validates offsets and a minimum size of its table; Check upper limit of strip
|
|
||||||
byte size correctly;
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
Our CI has warned that this version no longer builds on `1.34.2` out of the
|
|
||||||
box. We're still committed to the MSRV on this major version yet one
|
|
||||||
dependency—`flate2`—has already bumped it in a SemVer compatible version of its
|
|
||||||
own. This is out-of-our-control (cargo's dependency resolution does not allow
|
|
||||||
us to address this in a reasonable manner).
|
|
||||||
|
|
||||||
This can be address this by pinning the version of `flate2` to `1.0.21` in your
|
|
||||||
own files. However, you should understand that this puts you in considerable
|
|
||||||
maintenance debt as you will no longer receive any updates for this dependency
|
|
||||||
and any package that _requires_ a new version of the `1.0` series would be
|
|
||||||
incompatible with this requirement (cargo might yell at you very loudly).
|
|
||||||
|
|
||||||
# Version 0.7.0
|
|
||||||
|
|
||||||
New features:
|
|
||||||
* 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
|
|
||||||
* 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
|
|
||||||
* 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 handling after null terminator in ASCII data
|
|
||||||
* Recognize tile and sample format tags
|
|
||||||
|
|
||||||
# Version 0.5.0
|
|
||||||
|
|
||||||
* Added support for 32-bit and 64-bit decoded values.
|
|
||||||
* Added CMYK(16|32|64) color type support.
|
|
||||||
* Check many internal integer conversions to increase stability. This should
|
|
||||||
only lead to images being reported as faulty that would previously silently
|
|
||||||
break platform limits. If there are any false positives, please report them.
|
|
||||||
* Remove an erroneous check of decoded length in lzw compressed images.
|
|
||||||
|
|
||||||
# Version 0.4.0
|
# Version 0.4.0
|
||||||
|
|
||||||
* Several enumerations are now non_exhaustive for future extensions.
|
* Several enumerations are now non_exhaustive for future extensions.
|
||||||
|
|||||||
+14
-39
@@ -1,47 +1,22 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.10.3"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
authors = [
|
||||||
resolver = "2"
|
"ccgn",
|
||||||
|
"bvssvni <bvssvni@gmail.com>",
|
||||||
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
"nwin",
|
||||||
rust-version = "1.74.0"
|
"TyOverby <ty@pre-alpha.com>",
|
||||||
|
"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]
|
||||||
half = { version = "2.4.1" }
|
byteorder = "1.2"
|
||||||
quick-error = "2.0.1"
|
lzw = "0.10"
|
||||||
|
miniz_oxide = "0.3"
|
||||||
# Rename to avoid confusion with the feature name.
|
|
||||||
fax34 = { package = "fax", version = "0.2.6", optional = true }
|
|
||||||
flate2 = { version = "1.0.20", optional = true }
|
|
||||||
weezl = { version = "0.1.10", optional = true }
|
|
||||||
zstd = { version = "0.13", optional = true }
|
|
||||||
zune-jpeg = { version = "0.5.1", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
criterion = "0.3.1"
|
|
||||||
# We may already depend on this via flate2
|
|
||||||
crc32fast = "1.5"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["deflate", "fax", "jpeg", "lzw"]
|
|
||||||
|
|
||||||
# Compression algorithms
|
|
||||||
deflate = ["dep:flate2"]
|
|
||||||
fax = ["dep:fax34"]
|
|
||||||
jpeg = ["dep:zune-jpeg"]
|
|
||||||
lzw = ["dep:weezl"]
|
|
||||||
zstd = ["dep:zstd"]
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "lzw"
|
|
||||||
harness = false
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# image-tiff
|
# image-tiff
|
||||||
[](https://github.com/image-rs/image-tiff/actions)
|
[](https://travis-ci.org/image-rs/image-tiff)
|
||||||
[](https://docs.rs/tiff)
|
[](https://docs.rs/tiff)
|
||||||
[](https://crates.io/crates/tiff)
|
[](https://crates.io/crates/tiff)
|
||||||
|
|
||||||
@@ -10,35 +10,30 @@ 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) | Gray(32\|64) |
|
| `WhiteIsZero` | Gray(8\|16\|32\|64) | |
|
||||||
| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
|
| `BlackIsZero` | Gray(8\|16\|32\|64) | |
|
||||||
| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) |
|
| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | |
|
||||||
| `RGBPalette` | | |
|
| `RGBPalette` | | |
|
||||||
| `Mask` | | |
|
| `Mask` | | |
|
||||||
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
|
| `CMYK` | CMYK(8\|16\|32\|64) | |
|
||||||
| `CMYKA` | CMYKA(8) | |
|
| `YCbCr` | | |
|
||||||
| `YCbCr` | | |
|
| `CIELab` | | |
|
||||||
| `CIELab` | | |
|
|
||||||
|
|
||||||
### Compressions
|
### Compressions
|
||||||
|
|
||||||
| | Decoding | Encoding |
|
| | Decoding | Encoding |
|
||||||
| -------- | -------- | -------- |
|
|-|-|-|
|
||||||
| None | ✓ | ✓ |
|
| None | ✓ | ✓ |
|
||||||
| LZW | ✓ | ✓ |
|
| LZW | ✓ | |
|
||||||
| Deflate | ✓ | ✓ |
|
| Deflate | ✓ | |
|
||||||
| PackBits | ✓ | ✓ |
|
| PackBits | ✓ | |
|
||||||
| Fax4 | ✓ | not yet |
|
|
||||||
| JPEG | ✓ | not yet |
|
|
||||||
| ZSTD | ✓ | not yet |
|
|
||||||
|
|
||||||
|
|
||||||
## Not yet supported
|
## Not yet supported
|
||||||
@@ -48,11 +43,4 @@ 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`.
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
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,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
use tiff::decoder::{BufferLayoutPreference, Decoder, DecodingBuffer, DecodingResult};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let Some(image) = std::env::args_os().nth(1) else {
|
|
||||||
eprintln!("Usage: decode FILE");
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let file = std::fs::File::open(image)?;
|
|
||||||
let io = std::io::BufReader::new(file);
|
|
||||||
let mut reader = Decoder::new(io)?;
|
|
||||||
|
|
||||||
let mut data = DecodingResult::I8(vec![]);
|
|
||||||
|
|
||||||
for i in 0u32.. {
|
|
||||||
let colortype = reader.colortype()?;
|
|
||||||
let dimensions = reader.dimensions()?;
|
|
||||||
let layout = reader.read_image_to_buffer(&mut data)?;
|
|
||||||
|
|
||||||
debug_planes(i, &mut data, &layout, &colortype, dimensions);
|
|
||||||
|
|
||||||
if !reader.more_images() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.next_image()?
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug_planes(
|
|
||||||
index: u32,
|
|
||||||
data: &mut DecodingResult,
|
|
||||||
layout: &BufferLayoutPreference,
|
|
||||||
colortype: &tiff::ColorType,
|
|
||||||
(width, height): (u32, u32),
|
|
||||||
) {
|
|
||||||
let (depth, mut tupltype) = match colortype {
|
|
||||||
tiff::ColorType::Gray(_) => (1, "GRAYSCALE"),
|
|
||||||
tiff::ColorType::RGB(_) => (3, "RGB"),
|
|
||||||
tiff::ColorType::RGBA(_) => (4, "RGB_ALPHA"),
|
|
||||||
tiff::ColorType::Palette(_) => (1, "PALETTE"),
|
|
||||||
tiff::ColorType::CMYK(_) => (4, "CMYK"),
|
|
||||||
tiff::ColorType::Multiband { num_samples: 2, .. } => (2, "GRAYSCALE_ALPHA"),
|
|
||||||
_ => {
|
|
||||||
eprintln!("Unsupported color type for PAM output: {:?}", colortype);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let maxval = match data.as_buffer(0) {
|
|
||||||
DecodingBuffer::U8(_) => u64::from(u8::MAX),
|
|
||||||
DecodingBuffer::U16(_) => u64::from(u16::MAX),
|
|
||||||
DecodingBuffer::U32(_) => u64::from(u32::MAX),
|
|
||||||
DecodingBuffer::U64(_) => u64::MAX,
|
|
||||||
// Eh, this is a good effort I guess.
|
|
||||||
DecodingBuffer::I8(items) => {
|
|
||||||
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
|
||||||
u64::from(i8::MAX as u8)
|
|
||||||
}
|
|
||||||
DecodingBuffer::I16(items) => {
|
|
||||||
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
|
||||||
u64::from(i16::MAX as u16)
|
|
||||||
}
|
|
||||||
DecodingBuffer::I32(items) => {
|
|
||||||
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
|
||||||
u64::from(i32::MAX as u32)
|
|
||||||
}
|
|
||||||
DecodingBuffer::I64(items) => {
|
|
||||||
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
|
||||||
i64::MAX as u64
|
|
||||||
}
|
|
||||||
// Not supported by PAM.
|
|
||||||
DecodingBuffer::F16(_) | DecodingBuffer::F32(_) | DecodingBuffer::F64(_) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let byte_len = if layout.planes > 1 {
|
|
||||||
tupltype = "GRAYSCALE";
|
|
||||||
layout.plane_stride.unwrap().get()
|
|
||||||
} else {
|
|
||||||
data.as_buffer(0).byte_len()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write out this image's planes to PAM files.
|
|
||||||
let header = format!("P7\nWIDTH {width}\nHEIGHT {height}\nDEPTH {depth}\nMAXVAL {maxval}\nTUPLTYPE {tupltype}\nENDHDR\n");
|
|
||||||
|
|
||||||
for (plane, plane_data) in data
|
|
||||||
.as_buffer(0)
|
|
||||||
.as_bytes_mut()
|
|
||||||
.chunks_exact(byte_len)
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
let filename = format!("image_{index:03}_plane_{plane:03}.pam");
|
|
||||||
let mut file = std::fs::File::create(filename).expect("Failed to create output file");
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
file.write_all(header.as_bytes())
|
|
||||||
.expect("Failed to write PAM header");
|
|
||||||
file.write_all(plane_data)
|
|
||||||
.expect("Failed to write PAM data");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
target
|
|
||||||
corpus
|
|
||||||
artifacts
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
[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
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#![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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
//! 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.
|
|
||||||
// Until we implement predictors for f16, we don't need f16_as_ne_bytes. (Due to the macro we do
|
|
||||||
// not apply this directly to the functions). And rust-version is not new enough for `expect`.
|
|
||||||
#![allow(dead_code)]
|
|
||||||
use std::{mem, slice};
|
|
||||||
|
|
||||||
use half::f16;
|
|
||||||
|
|
||||||
macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => {
|
|
||||||
pub(crate) fn $const(slice: &[$int]) -> &[u8] {
|
|
||||||
assert!(mem::align_of::<$int>() <= mem::size_of::<$int>());
|
|
||||||
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!(f16, f16_as_ne_bytes, f16_as_ne_mut_bytes);
|
|
||||||
integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes);
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
use crate::{tags::IfdPointer, TiffError, TiffFormatError};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
/// The IFD structure of a TIFF file should be limited to a forest of entries, and a tree when we
|
|
||||||
/// only consider having a primary IFD and its children. There is up to one primary child of a node
|
|
||||||
/// that is the `next` entry in an IFD itself. Since we offer iteration over this chain we want to
|
|
||||||
/// detect when a new edge would introduce a cycle, such that callers can be guaranteed to
|
|
||||||
/// terminate iteration at some point even in malicious images.
|
|
||||||
///
|
|
||||||
/// However, we are not necessarily visiting the IFDs in their topological order.
|
|
||||||
///
|
|
||||||
/// Since we only consider one child, we run union find to assign each pointer to a known chain.
|
|
||||||
/// Then, when we insert a new edge we check if they belong to the same component.
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct IfdCycles {
|
|
||||||
/// The root of each component union.
|
|
||||||
component_union: HashMap<ComponentId, ComponentId>,
|
|
||||||
links: HashMap<IfdPointer, u64>,
|
|
||||||
chains: HashMap<IfdPointer, ComponentId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
||||||
struct ComponentId(u64);
|
|
||||||
|
|
||||||
impl IfdCycles {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
IfdCycles::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_next(
|
|
||||||
&mut self,
|
|
||||||
from: IfdPointer,
|
|
||||||
to: Option<IfdPointer>,
|
|
||||||
) -> Result<bool, TiffError> {
|
|
||||||
let to_offset = to.map_or(0, |p| p.0);
|
|
||||||
|
|
||||||
// For some reason we got
|
|
||||||
match self.links.get(&from) {
|
|
||||||
Some(existing) if *existing == to_offset => return Ok(false),
|
|
||||||
// We got here twice with two different reads of the same IFD. That is .. unusual and
|
|
||||||
// we interpret it as a cycle.
|
|
||||||
Some(_) => return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)),
|
|
||||||
None => self.links.insert(from, to_offset),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.ensure_node(from);
|
|
||||||
|
|
||||||
if let Some(to) = to {
|
|
||||||
self.ensure_node(to);
|
|
||||||
|
|
||||||
let parent = self.nominal_component(from);
|
|
||||||
let child = self.nominal_component(to);
|
|
||||||
|
|
||||||
if parent == child {
|
|
||||||
return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.component_union.insert(child, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_node(&mut self, ifd: IfdPointer) {
|
|
||||||
self.chains.entry(ifd).or_insert_with(|| {
|
|
||||||
let id = ComponentId(self.component_union.len() as u64);
|
|
||||||
self.component_union.insert(id, id);
|
|
||||||
id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nominal_component(&mut self, node: IfdPointer) -> ComponentId {
|
|
||||||
let id = self.chains[&node];
|
|
||||||
|
|
||||||
let nomimal = {
|
|
||||||
let mut iter = id;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let parent = self.component_union[&iter];
|
|
||||||
|
|
||||||
if parent == iter {
|
|
||||||
break parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
iter = parent;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if nomimal != id {
|
|
||||||
// Compress.
|
|
||||||
let mut iter = id;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let parent = self.component_union[&iter];
|
|
||||||
|
|
||||||
if parent == iter {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.component_union.insert(iter, nomimal);
|
|
||||||
iter = parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nomimal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn cycles_are_detected() {
|
|
||||||
let mut cycles = IfdCycles::new();
|
|
||||||
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x20), Some(IfdPointer(0x800)))
|
|
||||||
.expect("non-existing link is valid");
|
|
||||||
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x800), Some(IfdPointer(0x20)))
|
|
||||||
.expect_err("cycle must be detected");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn reflective_cycle() {
|
|
||||||
let mut cycles = IfdCycles::new();
|
|
||||||
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x20), Some(IfdPointer(0x20)))
|
|
||||||
.expect_err("self-referential cycle must be detected");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn late_cycle() {
|
|
||||||
let mut cycles = IfdCycles::new();
|
|
||||||
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x20), Some(IfdPointer(0x40)))
|
|
||||||
.expect("non-existing link is valid");
|
|
||||||
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x60), Some(IfdPointer(0x80)))
|
|
||||||
.expect("non-existing link is valid");
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x80), Some(IfdPointer(0x20)))
|
|
||||||
.expect("non-existing link is valid");
|
|
||||||
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x40), Some(IfdPointer(0x60)))
|
|
||||||
.expect_err("non-existing link is valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn odd_cycle() {
|
|
||||||
let mut cycles = IfdCycles::new();
|
|
||||||
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x20), Some(IfdPointer(0x40)))
|
|
||||||
.expect("non-existing link is valid");
|
|
||||||
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x60), Some(IfdPointer(0x80)))
|
|
||||||
.expect("non-existing link is valid");
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x80), Some(IfdPointer(0x20)))
|
|
||||||
.expect("non-existing link is valid");
|
|
||||||
|
|
||||||
cycles
|
|
||||||
.insert_next(IfdPointer(0x40), Some(IfdPointer(0x80)))
|
|
||||||
.expect_err("non-existing link is valid");
|
|
||||||
}
|
|
||||||
+119
-686
@@ -1,158 +1,46 @@
|
|||||||
//! Function for reading TIFF tags
|
//! Function for reading TIFF tags
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryFrom;
|
||||||
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};
|
use super::stream::{ByteOrder, EndianReader, SmartReader};
|
||||||
use crate::tags::{IfdPointer, Tag, Type};
|
use tags::{Tag, Type};
|
||||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
use {TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
||||||
|
|
||||||
|
use self::Value::{Ascii, List, Rational, Unsigned, Signed, SRational};
|
||||||
|
|
||||||
use self::Value::{
|
|
||||||
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
|
|
||||||
Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(unused_qualifications)]
|
#[allow(unused_qualifications)]
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
Byte(u8),
|
|
||||||
Short(u16),
|
|
||||||
SignedByte(i8),
|
|
||||||
SignedShort(i16),
|
|
||||||
Signed(i32),
|
Signed(i32),
|
||||||
SignedBig(i64),
|
|
||||||
Unsigned(u32),
|
Unsigned(u32),
|
||||||
UnsignedBig(u64),
|
|
||||||
Float(f32),
|
|
||||||
Double(f64),
|
|
||||||
List(Vec<Value>),
|
List(Vec<Value>),
|
||||||
Rational(u32, u32),
|
Rational(u32, u32),
|
||||||
RationalBig(u64, u64),
|
|
||||||
SRational(i32, i32),
|
SRational(i32, i32),
|
||||||
SRationalBig(i64, i64),
|
|
||||||
Ascii(String),
|
Ascii(String),
|
||||||
Ifd(u32),
|
#[doc(hidden)] // Do not match against this.
|
||||||
IfdBig(u64),
|
__NonExhaustive,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
pub fn into_u8(self) -> TiffResult<u8> {
|
|
||||||
match self {
|
|
||||||
Byte(val) => Ok(val),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn into_i8(self) -> TiffResult<i8> {
|
|
||||||
match self {
|
|
||||||
SignedByte(val) => Ok(val),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_u16(self) -> TiffResult<u16> {
|
|
||||||
match self {
|
|
||||||
Byte(val) => Ok(val.into()),
|
|
||||||
Short(val) => Ok(val),
|
|
||||||
Unsigned(val) => Ok(u16::try_from(val)?),
|
|
||||||
UnsignedBig(val) => Ok(u16::try_from(val)?),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_i16(self) -> TiffResult<i16> {
|
|
||||||
match self {
|
|
||||||
SignedByte(val) => Ok(val.into()),
|
|
||||||
SignedShort(val) => Ok(val),
|
|
||||||
Signed(val) => Ok(i16::try_from(val)?),
|
|
||||||
SignedBig(val) => Ok(i16::try_from(val)?),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_u32(self) -> TiffResult<u32> {
|
pub fn into_u32(self) -> TiffResult<u32> {
|
||||||
match self {
|
match self {
|
||||||
Byte(val) => Ok(val.into()),
|
|
||||||
Short(val) => Ok(val.into()),
|
|
||||||
Unsigned(val) => Ok(val),
|
Unsigned(val) => Ok(val),
|
||||||
UnsignedBig(val) => Ok(u32::try_from(val)?),
|
val => Err(TiffError::FormatError(
|
||||||
Ifd(val) => Ok(val),
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
IfdBig(val) => Ok(u32::try_from(val)?),
|
)),
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_i32(self) -> TiffResult<i32> {
|
pub fn into_i32(self) -> TiffResult<i32> {
|
||||||
match self {
|
match self {
|
||||||
SignedByte(val) => Ok(val.into()),
|
|
||||||
SignedShort(val) => Ok(val.into()),
|
|
||||||
Signed(val) => Ok(val),
|
Signed(val) => Ok(val),
|
||||||
SignedBig(val) => Ok(i32::try_from(val)?),
|
val => Err(TiffError::FormatError(
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
TiffFormatError::SignedIntegerExpected(val),
|
||||||
}
|
)),
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_u64(self) -> TiffResult<u64> {
|
|
||||||
match self {
|
|
||||||
Byte(val) => Ok(val.into()),
|
|
||||||
Short(val) => Ok(val.into()),
|
|
||||||
Unsigned(val) => Ok(val.into()),
|
|
||||||
UnsignedBig(val) => Ok(val),
|
|
||||||
Ifd(val) => Ok(val.into()),
|
|
||||||
IfdBig(val) => Ok(val),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_i64(self) -> TiffResult<i64> {
|
|
||||||
match self {
|
|
||||||
SignedByte(val) => Ok(val.into()),
|
|
||||||
SignedShort(val) => Ok(val.into()),
|
|
||||||
Signed(val) => Ok(val.into()),
|
|
||||||
SignedBig(val) => Ok(val),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_f32(self) -> TiffResult<f32> {
|
|
||||||
match self {
|
|
||||||
Float(val) => Ok(val),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_f64(self) -> TiffResult<f64> {
|
|
||||||
match self {
|
|
||||||
Double(val) => Ok(val),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Turn this value into an `IfdPointer`.
|
|
||||||
///
|
|
||||||
/// Notice that this does not take an argument, a 64-bit IFD is always allowed. If the
|
|
||||||
/// difference is crucial and you do not want to be permissive you're expected to filter this
|
|
||||||
/// out before.
|
|
||||||
///
|
|
||||||
/// For compatibility the smaller sized tags should always be allowed i.e. you might use a
|
|
||||||
/// non-bigtiff's directory and its tag types and move it straight to a bigtiff. For instance
|
|
||||||
/// the SubIFD tag is defined as `LONG or IFD`:
|
|
||||||
///
|
|
||||||
/// <https://web.archive.org/web/20181105221012/https://www.awaresystems.be/imaging/tiff/tifftags/subifds.html>
|
|
||||||
pub fn into_ifd_pointer(self) -> TiffResult<IfdPointer> {
|
|
||||||
match self {
|
|
||||||
Unsigned(val) | Ifd(val) => Ok(IfdPointer(val.into())),
|
|
||||||
IfdBig(val) => Ok(IfdPointer(val)),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_string(self) -> TiffResult<String> {
|
|
||||||
match self {
|
|
||||||
Ascii(val) => Ok(val),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,48 +53,12 @@ impl Value {
|
|||||||
}
|
}
|
||||||
Ok(new_vec)
|
Ok(new_vec)
|
||||||
}
|
}
|
||||||
Byte(val) => Ok(vec![val.into()]),
|
|
||||||
Short(val) => Ok(vec![val.into()]),
|
|
||||||
Unsigned(val) => Ok(vec![val]),
|
Unsigned(val) => Ok(vec![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)?])
|
|
||||||
}
|
|
||||||
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()),
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
val => Err(TiffError::FormatError(
|
||||||
}
|
TiffFormatError::UnsignedIntegerExpected(val),
|
||||||
}
|
)),
|
||||||
|
|
||||||
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]),
|
|
||||||
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
Byte(val) => Ok(vec![val.into()]),
|
|
||||||
Short(val) => Ok(vec![val]),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,129 +72,25 @@ impl Value {
|
|||||||
new_vec.push(numerator);
|
new_vec.push(numerator);
|
||||||
new_vec.push(denominator);
|
new_vec.push(denominator);
|
||||||
}
|
}
|
||||||
SRationalBig(numerator, denominator) => {
|
_ => new_vec.push(v.into_i32()?)
|
||||||
new_vec.push(i32::try_from(numerator)?);
|
|
||||||
new_vec.push(i32::try_from(denominator)?);
|
|
||||||
}
|
|
||||||
_ => new_vec.push(v.into_i32()?),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(new_vec)
|
Ok(new_vec)
|
||||||
}
|
}
|
||||||
SignedByte(val) => Ok(vec![val.into()]),
|
|
||||||
SignedShort(val) => Ok(vec![val.into()]),
|
|
||||||
Signed(val) => Ok(vec![val]),
|
Signed(val) => Ok(vec![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) => {
|
val => Err(TiffError::FormatError(
|
||||||
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
|
TiffFormatError::SignedIntegerExpected(val),
|
||||||
}
|
)),
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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]),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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]),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_u64_vec(self) -> TiffResult<Vec<u64>> {
|
|
||||||
match self {
|
|
||||||
List(vec) => {
|
|
||||||
let mut new_vec = Vec::with_capacity(vec.len());
|
|
||||||
for v in vec {
|
|
||||||
new_vec.push(v.into_u64()?)
|
|
||||||
}
|
|
||||||
Ok(new_vec)
|
|
||||||
}
|
|
||||||
Byte(val) => Ok(vec![val.into()]),
|
|
||||||
Short(val) => Ok(vec![val.into()]),
|
|
||||||
Unsigned(val) => Ok(vec![val.into()]),
|
|
||||||
UnsignedBig(val) => Ok(vec![val]),
|
|
||||||
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
|
||||||
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()),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_i64_vec(self) -> TiffResult<Vec<i64>> {
|
|
||||||
match self {
|
|
||||||
List(vec) => {
|
|
||||||
let mut new_vec = Vec::with_capacity(vec.len());
|
|
||||||
for v in vec {
|
|
||||||
match v {
|
|
||||||
SRational(numerator, denominator) => {
|
|
||||||
new_vec.push(numerator.into());
|
|
||||||
new_vec.push(denominator.into());
|
|
||||||
}
|
|
||||||
SRationalBig(numerator, denominator) => {
|
|
||||||
new_vec.push(numerator);
|
|
||||||
new_vec.push(denominator);
|
|
||||||
}
|
|
||||||
_ => new_vec.push(v.into_i64()?),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(new_vec)
|
|
||||||
}
|
|
||||||
SignedByte(val) => Ok(vec![val.into()]),
|
|
||||||
SignedShort(val) => Ok(vec![val.into()]),
|
|
||||||
Signed(val) => Ok(vec![val.into()]),
|
|
||||||
SignedBig(val) => Ok(vec![val]),
|
|
||||||
SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
|
||||||
SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_ifd_vec(self) -> TiffResult<Vec<IfdPointer>> {
|
|
||||||
let vec = match self {
|
|
||||||
Unsigned(val) | Ifd(val) => return Ok(vec![IfdPointer(val.into())]),
|
|
||||||
IfdBig(val) => return Ok(vec![IfdPointer(val)]),
|
|
||||||
List(vec) => vec,
|
|
||||||
_ => return Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
|
||||||
};
|
|
||||||
|
|
||||||
vec.into_iter().map(Self::into_ifd_pointer).collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A combination of type, count, and offset.
|
|
||||||
///
|
|
||||||
/// In a TIFF the data offset portion of an entry is used for inline data in case the length of the
|
|
||||||
/// encoded value does not exceed the size of the offset field. Since the size of the offset field
|
|
||||||
/// depends on the file kind (4 bytes for standard TIFF, 8 bytes for BigTIFF) the interpretation of
|
|
||||||
/// this struct is only complete in combination with file metadata.
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Entry {
|
pub struct Entry {
|
||||||
type_: Type,
|
type_: Type,
|
||||||
count: u64,
|
count: u32,
|
||||||
offset: [u8; 8],
|
offset: [u8; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::std::fmt::Debug for Entry {
|
impl ::std::fmt::Debug for Entry {
|
||||||
@@ -355,15 +103,7 @@ impl ::std::fmt::Debug for Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
/// Create a new entry fit to be added to a standard TIFF IFD.
|
|
||||||
pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry {
|
pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry {
|
||||||
let mut entry_off = [0u8; 8];
|
|
||||||
entry_off[..4].copy_from_slice(&offset);
|
|
||||||
Entry::new_u64(type_, count.into(), entry_off)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new entry with data for a Big TIFF IFD.
|
|
||||||
pub fn new_u64(type_: Type, count: u64, offset: [u8; 8]) -> Entry {
|
|
||||||
Entry {
|
Entry {
|
||||||
type_,
|
type_,
|
||||||
count,
|
count,
|
||||||
@@ -371,425 +111,120 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn field_type(&self) -> Type {
|
|
||||||
self.type_
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn count(&self) -> u64 {
|
|
||||||
self.count
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn offset(&self) -> &[u8] {
|
|
||||||
&self.offset
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mem_reader for the offset/value field
|
/// Returns a mem_reader for the offset/value field
|
||||||
pub(crate) fn offset_field_reader(
|
fn r(&self, byte_order: ByteOrder) -> SmartReader<io::Cursor<Vec<u8>>> {
|
||||||
&self,
|
SmartReader::wrap(io::Cursor::new(self.offset.to_vec()), byte_order)
|
||||||
byte_order: ByteOrder,
|
|
||||||
) -> EndianReader<io::Cursor<Vec<u8>>> {
|
|
||||||
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn val<R: Read + Seek>(
|
pub fn val<R: Read + Seek>(
|
||||||
&self,
|
&self,
|
||||||
limits: &super::Limits,
|
limits: &super::Limits,
|
||||||
bigtiff: bool,
|
decoder: &mut super::Decoder<R>,
|
||||||
reader: &mut EndianReader<R>,
|
|
||||||
) -> TiffResult<Value> {
|
) -> TiffResult<Value> {
|
||||||
// Case 1: there are no values so we can return immediately.
|
let bo = decoder.byte_order();
|
||||||
if self.count == 0 {
|
match (self.type_, self.count) {
|
||||||
return Ok(List(Vec::new()));
|
// TODO check if this could give wrong results
|
||||||
}
|
// at a different endianess of file/computer.
|
||||||
|
(Type::BYTE, 1) => Ok(Unsigned(u32::from(self.offset[0]))),
|
||||||
let bo = reader.byte_order;
|
(Type::BYTE, 2) => offset_to_bytes(2, self),
|
||||||
let value_bytes = self.type_.value_bytes(self.count)?;
|
(Type::BYTE, 3) => offset_to_bytes(3, self),
|
||||||
|
(Type::BYTE, 4) => offset_to_bytes(4, self),
|
||||||
// Case 2: there is one value.
|
(Type::BYTE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||||
if self.count == 1 {
|
Ok(Unsigned(u32::from(decoder.read_byte()?)))
|
||||||
// 2a: the value is 5-8 bytes and we're in BigTiff mode.
|
}),
|
||||||
if bigtiff && value_bytes > 4 && value_bytes <= 8 {
|
(Type::SBYTE, 1) => Ok(Signed(i32::from(self.offset[0] as i8))),
|
||||||
return Ok(match self.type_ {
|
(Type::SBYTE, 2) => offset_to_sbytes(2, self),
|
||||||
Type::LONG8 => UnsignedBig(self.offset_field_reader(bo).read_u64()?),
|
(Type::SBYTE, 3) => offset_to_sbytes(3, self),
|
||||||
Type::SLONG8 => SignedBig(self.offset_field_reader(bo).read_i64()?),
|
(Type::SBYTE, 4) => offset_to_sbytes(4, self),
|
||||||
Type::DOUBLE => Double(self.offset_field_reader(bo).read_f64()?),
|
(Type::SBYTE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||||
Type::RATIONAL => {
|
Ok(Signed(i32::from(decoder.read_byte()? as i8)))
|
||||||
let mut r = self.offset_field_reader(bo);
|
}),
|
||||||
Rational(r.read_u32()?, r.read_u32()?)
|
(Type::SHORT, 1) => Ok(Unsigned(u32::from(self.r(bo).read_u16()?))),
|
||||||
}
|
(Type::SSHORT, 1) => Ok(Signed(i32::from(self.r(bo).read_i16()?))),
|
||||||
Type::SRATIONAL => {
|
(Type::SHORT, 2) => {
|
||||||
let mut r = self.offset_field_reader(bo);
|
let mut r = self.r(bo);
|
||||||
SRational(r.read_i32()?, r.read_i32()?)
|
Ok(List(vec![
|
||||||
}
|
Unsigned(u32::from(r.read_u16()?)),
|
||||||
Type::IFD8 => IfdBig(self.offset_field_reader(bo).read_u64()?),
|
Unsigned(u32::from(r.read_u16()?)),
|
||||||
Type::BYTE
|
]))
|
||||||
| Type::SBYTE
|
|
||||||
| Type::ASCII
|
|
||||||
| Type::UNDEFINED
|
|
||||||
| Type::SHORT
|
|
||||||
| Type::SSHORT
|
|
||||||
| Type::LONG
|
|
||||||
| Type::SLONG
|
|
||||||
| Type::FLOAT
|
|
||||||
| Type::IFD => unreachable!(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
(Type::SSHORT, 2) => {
|
||||||
// 2b: the value is at most 4 bytes or doesn't fit in the offset field.
|
let mut r = self.r(bo);
|
||||||
return Ok(match self.type_ {
|
Ok(List(vec![
|
||||||
Type::BYTE => Byte(self.offset[0]),
|
Signed(i32::from(r.read_i16()?)),
|
||||||
Type::SBYTE => SignedByte(self.offset[0] as i8),
|
Signed(i32::from(r.read_i16()?)),
|
||||||
Type::UNDEFINED => Byte(self.offset[0]),
|
]))
|
||||||
Type::SHORT => Short(self.offset_field_reader(bo).read_u16()?),
|
|
||||||
Type::SSHORT => SignedShort(self.offset_field_reader(bo).read_i16()?),
|
|
||||||
Type::LONG => Unsigned(self.offset_field_reader(bo).read_u32()?),
|
|
||||||
Type::SLONG => Signed(self.offset_field_reader(bo).read_i32()?),
|
|
||||||
Type::FLOAT => Float(self.offset_field_reader(bo).read_f32()?),
|
|
||||||
Type::ASCII => {
|
|
||||||
if self.offset[0] == 0 {
|
|
||||||
Ascii("".to_string())
|
|
||||||
} else {
|
|
||||||
return Err(TiffError::FormatError(TiffFormatError::InvalidTag));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::LONG8 => {
|
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
|
||||||
UnsignedBig(reader.read_u64()?)
|
|
||||||
}
|
|
||||||
Type::SLONG8 => {
|
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
|
||||||
SignedBig(reader.read_i64()?)
|
|
||||||
}
|
|
||||||
Type::DOUBLE => {
|
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
|
||||||
Double(reader.read_f64()?)
|
|
||||||
}
|
|
||||||
Type::RATIONAL => {
|
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
|
||||||
Rational(reader.read_u32()?, reader.read_u32()?)
|
|
||||||
}
|
|
||||||
Type::SRATIONAL => {
|
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
|
||||||
SRational(reader.read_i32()?, reader.read_i32()?)
|
|
||||||
}
|
|
||||||
Type::IFD => Ifd(self.offset_field_reader(bo).read_u32()?),
|
|
||||||
Type::IFD8 => {
|
|
||||||
reader.goto_offset(self.offset_field_reader(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];
|
|
||||||
buf.copy_from_slice(&self.offset[..self.count as usize]);
|
|
||||||
if buf.is_ascii() && buf.ends_with(&[0]) {
|
|
||||||
let v = str::from_utf8(&buf)?;
|
|
||||||
let v = v.trim_matches(char::from(0));
|
|
||||||
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.offset_field_reader(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.offset_field_reader(bo);
|
|
||||||
let mut v = Vec::new();
|
|
||||||
for _ in 0..self.count {
|
|
||||||
v.push(SignedShort(r.read_i16()?));
|
|
||||||
}
|
|
||||||
return Ok(List(v));
|
|
||||||
}
|
|
||||||
Type::LONG => {
|
|
||||||
let mut r = self.offset_field_reader(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.offset_field_reader(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.offset_field_reader(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.offset_field_reader(bo);
|
|
||||||
let mut v = Vec::new();
|
|
||||||
for _ in 0..self.count {
|
|
||||||
v.push(Ifd(r.read_u32()?));
|
|
||||||
}
|
|
||||||
return Ok(List(v));
|
|
||||||
}
|
|
||||||
Type::LONG8
|
|
||||||
| Type::SLONG8
|
|
||||||
| Type::RATIONAL
|
|
||||||
| Type::SRATIONAL
|
|
||||||
| Type::DOUBLE
|
|
||||||
| Type::IFD8 => {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
(Type::SHORT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||||
|
Ok(Unsigned(u32::from(decoder.read_short()?)))
|
||||||
// Case 4: there is more than one value, and it doesn't fit in the offset field.
|
}),
|
||||||
let mut v;
|
(Type::SSHORT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||||
self.set_reader_offset(bo, bigtiff, reader)?;
|
Ok(Signed(i32::from(decoder.read_sshort()?)))
|
||||||
|
}),
|
||||||
match self.type_ {
|
(Type::LONG, 1) => Ok(Unsigned(self.r(bo).read_u32()?)),
|
||||||
Type::BYTE | Type::UNDEFINED => {
|
(Type::SLONG, 1) => Ok(Signed(self.r(bo).read_i32()?)),
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
(Type::LONG, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
Ok(Unsigned(decoder.read_long()?))
|
||||||
v.extend(bytes.iter().copied().map(Byte))
|
}),
|
||||||
})
|
(Type::SLONG, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||||
|
Ok(Signed(decoder.read_slong()?))
|
||||||
|
}),
|
||||||
|
(Type::RATIONAL, 1) => {
|
||||||
|
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||||
|
let numerator = decoder.read_long()?;
|
||||||
|
let denominator = decoder.read_long()?;
|
||||||
|
Ok(Rational(numerator, denominator))
|
||||||
}
|
}
|
||||||
Type::SBYTE => {
|
(Type::SRATIONAL, 1) => {
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
let numerator = decoder.read_slong()?;
|
||||||
v.extend(bytes.iter().copied().map(|v| SignedByte(v as i8)))
|
let denominator = decoder.read_slong()?;
|
||||||
})
|
Ok(SRational(numerator, denominator))
|
||||||
}
|
}
|
||||||
Type::SHORT => {
|
(Type::RATIONAL, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
Ok(Rational(
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
decoder.read_long()?,
|
||||||
v.extend(
|
decoder.read_long()?,
|
||||||
bytes
|
))
|
||||||
.chunks_exact(2)
|
}),
|
||||||
.map(|ch| Short(u16::from_ne_bytes(ch.try_into().unwrap()))),
|
(Type::SRATIONAL, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
|
||||||
)
|
Ok(SRational(
|
||||||
})
|
decoder.read_slong()?,
|
||||||
}
|
decoder.read_slong()?,
|
||||||
Type::SSHORT => {
|
))
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
}),
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
(Type::ASCII, n) => {
|
||||||
v.extend(
|
let n = usize::try_from(n)?;
|
||||||
bytes
|
|
||||||
.chunks_exact(2)
|
|
||||||
.map(|ch| SignedShort(i16::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::LONG => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|ch| Unsigned(u32::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::SLONG => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|ch| Signed(i32::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::FLOAT => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|ch| Float(f32::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::DOUBLE => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(8)
|
|
||||||
.map(|ch| Double(f64::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::RATIONAL => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(bytes.chunks_exact(8).map(|ch| {
|
|
||||||
Rational(
|
|
||||||
u32::from_ne_bytes(ch[..4].try_into().unwrap()),
|
|
||||||
u32::from_ne_bytes(ch[4..].try_into().unwrap()),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::SRATIONAL => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(bytes.chunks_exact(8).map(|ch| {
|
|
||||||
SRational(
|
|
||||||
i32::from_ne_bytes(ch[..4].try_into().unwrap()),
|
|
||||||
i32::from_ne_bytes(ch[4..].try_into().unwrap()),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::LONG8 => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(8)
|
|
||||||
.map(|ch| UnsignedBig(u64::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::SLONG8 => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(8)
|
|
||||||
.map(|ch| SignedBig(i64::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::IFD => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|ch| Ifd(u32::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::IFD8 => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(8)
|
|
||||||
.map(|ch| IfdBig(u64::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::ASCII => {
|
|
||||||
let n = usize::try_from(self.count)?;
|
|
||||||
|
|
||||||
if n > limits.decoding_buffer_size {
|
if n > limits.decoding_buffer_size {
|
||||||
return Err(dbg!(TiffError::LimitsExceeded));
|
return Err(TiffError::LimitsExceeded);
|
||||||
}
|
}
|
||||||
|
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||||
let mut out = vec![0; n];
|
let string = decoder.read_string(n)?;
|
||||||
reader.inner().read_exact(&mut out)?;
|
Ok(Ascii(string))
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(Ascii(String::from_utf8(out)?));
|
|
||||||
}
|
}
|
||||||
}?;
|
_ => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedDataType,
|
||||||
Ok(List(v))
|
)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn vec_with_capacity(
|
#[inline]
|
||||||
value_count: u64,
|
fn decode_offset<R, F>(&self, value_count: u32, bo: ByteOrder, limits: &super::Limits, decoder: &mut super::Decoder<R>, decode_fn: F) -> TiffResult<Value>
|
||||||
limits: &super::Limits,
|
where
|
||||||
) -> Result<Vec<Value>, TiffError> {
|
R: Read + Seek,
|
||||||
|
F: Fn(&mut super::Decoder<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>() {
|
||||||
return Err(TiffError::LimitsExceeded);
|
return Err(TiffError::LimitsExceeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Vec::with_capacity(value_count))
|
let mut v = Vec::with_capacity(value_count);
|
||||||
}
|
decoder.goto_offset(self.r(bo).read_u32()?)?;
|
||||||
|
for _ in 0..value_count {
|
||||||
fn set_reader_offset<R>(
|
v.push(decode_fn(decoder)?)
|
||||||
&self,
|
|
||||||
bo: ByteOrder,
|
|
||||||
bigtiff: bool,
|
|
||||||
reader: &mut EndianReader<R>,
|
|
||||||
) -> TiffResult<()>
|
|
||||||
where
|
|
||||||
R: Read + Seek,
|
|
||||||
{
|
|
||||||
let offset = if bigtiff {
|
|
||||||
self.offset_field_reader(bo).read_u64()?
|
|
||||||
} else {
|
|
||||||
self.offset_field_reader(bo).read_u32()?.into()
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.goto_offset(offset)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn decode_values<R, F>(
|
|
||||||
&self,
|
|
||||||
value_count: u64,
|
|
||||||
type_: Type,
|
|
||||||
reader: &mut EndianReader<R>,
|
|
||||||
mut collect: F,
|
|
||||||
) -> TiffResult<()>
|
|
||||||
where
|
|
||||||
R: Read + Seek,
|
|
||||||
F: FnMut(&[u8]),
|
|
||||||
{
|
|
||||||
let mut total_bytes = type_.value_bytes(value_count)?;
|
|
||||||
let mut buffer = [0u8; 512];
|
|
||||||
|
|
||||||
let buf_unit = usize::from(type_.byte_len());
|
|
||||||
let mul_of_ty = buffer.len() / buf_unit * buf_unit;
|
|
||||||
|
|
||||||
let cls = type_.endian_bytes();
|
|
||||||
let native = ByteOrder::native();
|
|
||||||
|
|
||||||
while total_bytes > 0 {
|
|
||||||
// `now <= mul_of_ty < 512` so casting is mathematical
|
|
||||||
let now = total_bytes.min(mul_of_ty as u64);
|
|
||||||
total_bytes -= now;
|
|
||||||
|
|
||||||
let buffer = &mut buffer[..now as usize];
|
|
||||||
reader.inner().read_exact(buffer)?;
|
|
||||||
|
|
||||||
reader.byte_order.convert_endian_bytes(cls, buffer, native);
|
|
||||||
collect(buffer);
|
|
||||||
}
|
}
|
||||||
|
Ok(List(v))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -800,7 +235,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()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,11 +246,9 @@ 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()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type representing an Image File Directory
|
/// Type representing an Image File Directory
|
||||||
#[doc(hidden)]
|
|
||||||
#[deprecated = "Use struct `tiff::Directory` instead which contains all fields relevant to an Image File Directory, including the offset to the next directory"]
|
|
||||||
pub type Directory = HashMap<Tag, Entry>;
|
pub type Directory = HashMap<Tag, Entry>;
|
||||||
File diff suppressed because it is too large.
Load diff
+584
-1579
File diff suppressed because it is too large.
Load diff
+267
-303
@@ -1,356 +1,319 @@
|
|||||||
//! All IO functionality needed for TIFF decoding
|
//! All IO functionality needed for TIFF decoding
|
||||||
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
|
||||||
|
|
||||||
pub use crate::tags::ByteOrder;
|
use crate::error::{TiffError, TiffResult};
|
||||||
|
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
|
||||||
|
use lzw;
|
||||||
|
use miniz_oxide::inflate;
|
||||||
|
use std::io::{self, Read, Seek};
|
||||||
|
|
||||||
/// Reader that is aware of the byte order.
|
/// Byte order of the TIFF file.
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct EndianReader<R> {
|
pub enum ByteOrder {
|
||||||
reader: R,
|
/// little endian byte order
|
||||||
pub(crate) byte_order: ByteOrder,
|
LittleEndian,
|
||||||
|
/// big endian byte order
|
||||||
|
BigEndian,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> EndianReader<R> {
|
/// Reader that is aware of the byte order.
|
||||||
pub fn new(reader: R, byte_order: ByteOrder) -> Self {
|
pub trait EndianReader: Read {
|
||||||
Self { reader, byte_order }
|
/// Byte order that should be adhered to
|
||||||
}
|
fn byte_order(&self) -> ByteOrder;
|
||||||
|
|
||||||
pub fn inner(&mut self) -> &mut R {
|
|
||||||
&mut self.reader
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()>
|
|
||||||
where
|
|
||||||
R: Seek,
|
|
||||||
{
|
|
||||||
self.reader.seek(io::SeekFrom::Start(offset))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads an u16
|
/// Reads an u16
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn read_u16(&mut self) -> Result<u16, io::Error> {
|
fn read_u16(&mut self) -> Result<u16, io::Error> {
|
||||||
let mut n = [0u8; 2];
|
match self.byte_order() {
|
||||||
self.reader.read_exact(&mut n)?;
|
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_u16::<LittleEndian>(self),
|
||||||
Ok(match self.byte_order {
|
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_u16::<BigEndian>(self),
|
||||||
ByteOrder::LittleEndian => u16::from_le_bytes(n),
|
}
|
||||||
ByteOrder::BigEndian => u16::from_be_bytes(n),
|
}
|
||||||
})
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_u16_into(&mut self, buffer: &mut [u16]) -> Result<(), io::Error> {
|
||||||
|
match self.byte_order() {
|
||||||
|
ByteOrder::LittleEndian => {
|
||||||
|
<Self as ReadBytesExt>::read_u16_into::<LittleEndian>(self, buffer)
|
||||||
|
}
|
||||||
|
ByteOrder::BigEndian => {
|
||||||
|
<Self as ReadBytesExt>::read_u16_into::<BigEndian>(self, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads an i16
|
/// Reads an i16
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn read_i16(&mut self) -> Result<i16, io::Error> {
|
fn read_i16(&mut self) -> Result<i16, io::Error> {
|
||||||
let mut n = [0u8; 2];
|
match self.byte_order() {
|
||||||
self.reader.read_exact(&mut n)?;
|
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_i16::<LittleEndian>(self),
|
||||||
Ok(match self.byte_order {
|
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_i16::<BigEndian>(self),
|
||||||
ByteOrder::LittleEndian => i16::from_le_bytes(n),
|
}
|
||||||
ByteOrder::BigEndian => i16::from_be_bytes(n),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads an u32
|
/// Reads an u32
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn read_u32(&mut self) -> Result<u32, io::Error> {
|
fn read_u32(&mut self) -> Result<u32, io::Error> {
|
||||||
let mut n = [0u8; 4];
|
match self.byte_order() {
|
||||||
self.reader.read_exact(&mut n)?;
|
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_u32::<LittleEndian>(self),
|
||||||
Ok(match self.byte_order {
|
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_u32::<BigEndian>(self),
|
||||||
ByteOrder::LittleEndian => u32::from_le_bytes(n),
|
}
|
||||||
ByteOrder::BigEndian => u32::from_be_bytes(n),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads an i32
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn read_i32(&mut self) -> Result<i32, io::Error> {
|
fn read_u32_into(&mut self, buffer: &mut [u32]) -> Result<(), io::Error> {
|
||||||
let mut n = [0u8; 4];
|
match self.byte_order() {
|
||||||
self.reader.read_exact(&mut n)?;
|
ByteOrder::LittleEndian => {
|
||||||
Ok(match self.byte_order {
|
<Self as ReadBytesExt>::read_u32_into::<LittleEndian>(self, buffer)
|
||||||
ByteOrder::LittleEndian => i32::from_le_bytes(n),
|
}
|
||||||
ByteOrder::BigEndian => i32::from_be_bytes(n),
|
ByteOrder::BigEndian => {
|
||||||
})
|
<Self as ReadBytesExt>::read_u32_into::<BigEndian>(self, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads an u64
|
/// Reads an u64
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn read_u64(&mut self) -> Result<u64, io::Error> {
|
fn read_u64(&mut self) -> Result<u64, io::Error> {
|
||||||
let mut n = [0u8; 8];
|
match self.byte_order() {
|
||||||
self.reader.read_exact(&mut n)?;
|
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_u64::<LittleEndian>(self),
|
||||||
Ok(match self.byte_order {
|
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_u64::<BigEndian>(self),
|
||||||
ByteOrder::LittleEndian => u64::from_le_bytes(n),
|
|
||||||
ByteOrder::BigEndian => u64::from_be_bytes(n),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads an i64
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_i64(&mut self) -> Result<i64, io::Error> {
|
|
||||||
let mut n = [0u8; 8];
|
|
||||||
self.reader.read_exact(&mut n)?;
|
|
||||||
Ok(match self.byte_order {
|
|
||||||
ByteOrder::LittleEndian => i64::from_le_bytes(n),
|
|
||||||
ByteOrder::BigEndian => i64::from_be_bytes(n),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads an f32
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_f32(&mut self) -> Result<f32, io::Error> {
|
|
||||||
let mut n = [0u8; 4];
|
|
||||||
self.reader.read_exact(&mut n)?;
|
|
||||||
Ok(f32::from_bits(match self.byte_order {
|
|
||||||
ByteOrder::LittleEndian => u32::from_le_bytes(n),
|
|
||||||
ByteOrder::BigEndian => u32::from_be_bytes(n),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads an f64
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read_f64(&mut self) -> Result<f64, io::Error> {
|
|
||||||
let mut n = [0u8; 8];
|
|
||||||
self.reader.read_exact(&mut n)?;
|
|
||||||
Ok(f64::from_bits(match self.byte_order {
|
|
||||||
ByteOrder::LittleEndian => u64::from_le_bytes(n),
|
|
||||||
ByteOrder::BigEndian => u64::from_be_bytes(n),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// # READERS
|
|
||||||
//
|
|
||||||
|
|
||||||
/// Type alias for the deflate Reader
|
|
||||||
#[cfg(feature = "deflate")]
|
|
||||||
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
|
||||||
|
|
||||||
//
|
|
||||||
// ## LZW Reader
|
|
||||||
//
|
|
||||||
|
|
||||||
/// Reader that decompresses LZW streams
|
|
||||||
#[cfg(feature = "lzw")]
|
|
||||||
pub struct LZWReader<R: Read> {
|
|
||||||
reader: BufReader<Take<R>>,
|
|
||||||
decoder: weezl::decode::Decoder,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "lzw")]
|
|
||||||
impl<R: Read> LZWReader<R> {
|
|
||||||
/// Wraps a reader
|
|
||||||
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
|
|
||||||
let configuration =
|
|
||||||
weezl::decode::Configuration::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
|
|
||||||
.with_yield_on_full_buffer(true);
|
|
||||||
Self {
|
|
||||||
reader: BufReader::with_capacity(
|
|
||||||
(32 * 1024).min(compressed_length),
|
|
||||||
reader.take(u64::try_from(compressed_length).unwrap()),
|
|
||||||
),
|
|
||||||
decoder: configuration.build(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "lzw")]
|
#[inline(always)]
|
||||||
impl<R: Read> Read for LZWReader<R> {
|
fn read_u64_into(&mut self, buffer: &mut [u64]) -> Result<(), io::Error> {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
match self.byte_order() {
|
||||||
loop {
|
ByteOrder::LittleEndian => {
|
||||||
let result = self.decoder.decode_bytes(self.reader.fill_buf()?, buf);
|
<Self as ReadBytesExt>::read_u64_into::<LittleEndian>(self, buffer)
|
||||||
self.reader.consume(result.consumed_in);
|
}
|
||||||
|
ByteOrder::BigEndian => {
|
||||||
match result.status {
|
<Self as ReadBytesExt>::read_u64_into::<BigEndian>(self, buffer)
|
||||||
Ok(weezl::LzwStatus::Ok) => {
|
|
||||||
if result.consumed_out == 0 {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
return Ok(result.consumed_out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(weezl::LzwStatus::NoProgress) => {
|
|
||||||
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)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads an i32
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_i32(&mut self) -> Result<i32, io::Error> {
|
||||||
|
match self.byte_order() {
|
||||||
|
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_i32::<LittleEndian>(self),
|
||||||
|
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_i32::<BigEndian>(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
/// Reader that decompresses DEFLATE streams
|
||||||
// ## PackBits Reader
|
pub struct DeflateReader {
|
||||||
//
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
|
byte_order: ByteOrder,
|
||||||
|
}
|
||||||
|
|
||||||
/// Internal state machine for the PackBitsReader.
|
impl DeflateReader {
|
||||||
enum PackBitsReaderState {
|
pub fn new<R: Read + Seek>(
|
||||||
Header,
|
reader: &mut SmartReader<R>,
|
||||||
Literal,
|
max_uncompressed_length: usize,
|
||||||
Repeat { value: u8 },
|
) -> TiffResult<(usize, Self)> {
|
||||||
|
let byte_order = reader.byte_order;
|
||||||
|
let mut compressed = Vec::new();
|
||||||
|
reader.read_to_end(&mut compressed)?;
|
||||||
|
|
||||||
|
// TODO: Implement streaming compression, and remove this (temporary) and somewhat
|
||||||
|
// misleading workaround.
|
||||||
|
if compressed.len() > max_uncompressed_length {
|
||||||
|
return Err(TiffError::LimitsExceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
let uncompressed =
|
||||||
|
inflate::decompress_to_vec_zlib(&compressed).map_err(TiffError::from_inflate_status)?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
uncompressed.len(),
|
||||||
|
Self {
|
||||||
|
byte_order,
|
||||||
|
buffer: io::Cursor::new(uncompressed),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for DeflateReader {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.buffer.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EndianReader for DeflateReader {
|
||||||
|
fn byte_order(&self) -> ByteOrder {
|
||||||
|
self.byte_order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reader that decompresses LZW streams
|
||||||
|
pub struct LZWReader {
|
||||||
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
|
byte_order: ByteOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LZWReader {
|
||||||
|
/// Wraps a reader
|
||||||
|
pub fn new<R>(
|
||||||
|
reader: &mut SmartReader<R>,
|
||||||
|
compressed_length: usize,
|
||||||
|
max_uncompressed_length: usize,
|
||||||
|
) -> io::Result<(usize, LZWReader)>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
let 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 {
|
||||||
|
#[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.buffer.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EndianReader for LZWReader {
|
||||||
|
#[inline(always)]
|
||||||
|
fn byte_order(&self) -> ByteOrder {
|
||||||
|
self.byte_order
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reader that unpacks Apple's `PackBits` format
|
/// Reader that unpacks Apple's `PackBits` format
|
||||||
pub struct PackBitsReader<R: Read> {
|
pub struct PackBitsReader {
|
||||||
reader: Take<R>,
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
state: PackBitsReaderState,
|
byte_order: ByteOrder,
|
||||||
count: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> PackBitsReader<R> {
|
impl PackBitsReader {
|
||||||
/// Wraps a reader
|
/// Wraps a reader
|
||||||
pub fn new(reader: R, length: u64) -> Self {
|
pub fn new<R: Read + Seek>(
|
||||||
Self {
|
mut reader: R,
|
||||||
reader: reader.take(length),
|
byte_order: ByteOrder,
|
||||||
state: PackBitsReaderState::Header,
|
length: usize,
|
||||||
count: 0,
|
) -> io::Result<(usize, PackBitsReader)> {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
let mut read: usize = 0;
|
||||||
|
while read < length {
|
||||||
|
let lread = read_packbits_run(&mut reader, &mut buffer)?;
|
||||||
|
if lread == 0 {
|
||||||
|
return Err(io::ErrorKind::UnexpectedEof.into());
|
||||||
|
}
|
||||||
|
read += lread;
|
||||||
}
|
}
|
||||||
|
Ok((
|
||||||
|
buffer.len(),
|
||||||
|
PackBitsReader {
|
||||||
|
buffer: io::Cursor::new(buffer),
|
||||||
|
byte_order,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> Read for PackBitsReader<R> {
|
fn read_packbits_run<R: Read + Seek>(reader: &mut R, buffer: &mut Vec<u8>) -> io::Result<usize> {
|
||||||
|
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> {
|
||||||
while let PackBitsReaderState::Header = self.state {
|
self.buffer.read(buf)
|
||||||
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 (-127..=-1).contains(&h) {
|
|
||||||
let mut data: [u8; 1] = [0];
|
|
||||||
self.reader.read_exact(&mut data)?;
|
|
||||||
self.state = PackBitsReaderState::Repeat { value: data[0] };
|
|
||||||
self.count = (1 - h as isize) as usize;
|
|
||||||
} else if h >= 0 {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
Ok(actual)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "fax")]
|
impl EndianReader for PackBitsReader {
|
||||||
pub struct Group4Reader<R: Read> {
|
#[inline(always)]
|
||||||
decoder: fax34::decoder::Group4Decoder<io::Bytes<io::BufReader<io::Take<R>>>>,
|
fn byte_order(&self) -> ByteOrder {
|
||||||
line_buf: io::Cursor<Vec<u8>>,
|
self.byte_order
|
||||||
height: u16,
|
|
||||||
width: u16,
|
|
||||||
y: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "fax")]
|
|
||||||
impl<R: Read> Group4Reader<R> {
|
|
||||||
pub fn new(
|
|
||||||
dimensions: (u32, u32),
|
|
||||||
reader: R,
|
|
||||||
compressed_length: u64,
|
|
||||||
) -> crate::TiffResult<Self> {
|
|
||||||
let width = u16::try_from(dimensions.0)?;
|
|
||||||
let height = u16::try_from(dimensions.1)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
decoder: fax34::decoder::Group4Decoder::new(
|
|
||||||
io::BufReader::new(reader.take(compressed_length)).bytes(),
|
|
||||||
width,
|
|
||||||
)?,
|
|
||||||
line_buf: io::Cursor::new(Vec::with_capacity(width.into())),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
y: 0,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "fax")]
|
/// Reader that is aware of the byte order.
|
||||||
impl<R: Read> Read for Group4Reader<R> {
|
#[derive(Debug)]
|
||||||
|
pub struct SmartReader<R>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
reader: R,
|
||||||
|
pub byte_order: ByteOrder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> SmartReader<R>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
/// Wraps a reader
|
||||||
|
pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> {
|
||||||
|
SmartReader { reader, byte_order }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> EndianReader for SmartReader<R>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
#[inline(always)]
|
||||||
|
fn byte_order(&self) -> ByteOrder {
|
||||||
|
self.byte_order
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read + Seek> Read for SmartReader<R> {
|
||||||
|
#[inline]
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
// Either we have not read any line or we are at the end of a line.
|
self.reader.read(buf)
|
||||||
if self.line_buf.position() as usize == self.line_buf.get_ref().len()
|
}
|
||||||
&& self.y < self.height
|
}
|
||||||
{
|
|
||||||
let next = self.decoder.advance().map_err(std::io::Error::other)?;
|
|
||||||
|
|
||||||
match next {
|
impl<R: Read + Seek> Seek for SmartReader<R> {
|
||||||
fax34::decoder::DecodeStatus::End => (),
|
#[inline]
|
||||||
fax34::decoder::DecodeStatus::Incomplete => {
|
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||||
self.y += 1;
|
self.reader.seek(pos)
|
||||||
|
|
||||||
// We known `transitions` yields exactly `self.width` items (per doc).
|
|
||||||
// FIXME: performance. We do not need an individual pixel iterator, filling
|
|
||||||
// memory especially for long runs can be much quicker. The `transitions` are
|
|
||||||
// the positions at which each run-length ends, i.e. the prefix sum of run
|
|
||||||
// lengths. Runs in fax4 start with white.
|
|
||||||
let transitions = fax34::decoder::pels(self.decoder.transition(), self.width);
|
|
||||||
|
|
||||||
let buffer = self.line_buf.get_mut();
|
|
||||||
buffer.resize(usize::from(self.width).div_ceil(8), 0u8);
|
|
||||||
|
|
||||||
let target = &mut buffer[..];
|
|
||||||
|
|
||||||
// Note: it may seem strange to treat black as 0b1 and white as 0b0 despite all
|
|
||||||
// our streams by default decoding as-if PhotometricInterpretation::BlackIsMin.
|
|
||||||
// This is however consistent with libtiff. It seems that fax4's "White"
|
|
||||||
// differs from what libtiff thinks of as "White". For content, a line of data
|
|
||||||
// is in runlength encoding of white-black-white-black always starting with
|
|
||||||
// white. In libtiff, the loop always does both colors in one go and the
|
|
||||||
// structure is:
|
|
||||||
//
|
|
||||||
// ```
|
|
||||||
// for (; runs < erun; runs += 2)
|
|
||||||
// // white run
|
|
||||||
// do { *lp++ = 0L; } while (…)
|
|
||||||
// // black run
|
|
||||||
// do { *lp++ = -1L; } while (…)
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// So indeed the Fax4::White run is implemented by filling with zeros.
|
|
||||||
let mut bits = transitions.map(|c| match c {
|
|
||||||
fax34::Color::Black => true,
|
|
||||||
fax34::Color::White => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assemble bits in MSB as per our library representation for buffer.
|
|
||||||
for byte in target {
|
|
||||||
let mut val = 0;
|
|
||||||
|
|
||||||
for (idx, bit) in bits.by_ref().take(8).enumerate() {
|
|
||||||
val |= u8::from(bit) << (7 - idx % 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
*byte = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.line_buf.set_position(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.line_buf.read(buf)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +330,8 @@ 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 = PackBitsReader::new(buff, encoded_len as u64);
|
let (_, mut decoder) =
|
||||||
|
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();
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
use std::io::{Read, Seek};
|
|
||||||
|
|
||||||
use crate::{tags::Tag, Directory};
|
|
||||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
|
||||||
|
|
||||||
use super::ifd::{Entry, Value};
|
|
||||||
use super::ValueReader;
|
|
||||||
|
|
||||||
pub(crate) struct TagReader<'lt, R: EntryDecoder + ?Sized> {
|
|
||||||
pub(crate) decoder: &'lt mut R,
|
|
||||||
pub(crate) ifd: &'lt Directory,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) trait EntryDecoder {
|
|
||||||
/// Turn an entry into a value by fetching the required bytes in the stream.
|
|
||||||
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Seek + Read> EntryDecoder for ValueReader<R> {
|
|
||||||
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value> {
|
|
||||||
entry.val(&self.limits, self.bigtiff, &mut self.reader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, R: EntryDecoder + ?Sized> TagReader<'a, R> {
|
|
||||||
pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<Value>> {
|
|
||||||
let entry = match self.ifd.get(tag) {
|
|
||||||
None => return Ok(None),
|
|
||||||
Some(entry) => entry.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.decoder.entry_val(&entry).map(Some)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
|
|
||||||
match self.find_tag(tag)? {
|
|
||||||
Some(val) => Ok(val),
|
|
||||||
None => Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::RequiredTagNotFound(tag),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_tag_uint_vec<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<Vec<T>>> {
|
|
||||||
self.find_tag(tag)?
|
|
||||||
.map(|v| v.into_u64_vec())
|
|
||||||
.transpose()?
|
|
||||||
.map(|v| {
|
|
||||||
v.into_iter()
|
|
||||||
.map(|u| {
|
|
||||||
T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.transpose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
use core::fmt;
|
|
||||||
use std::{collections::BTreeMap, num::NonZeroU64};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
decoder::ifd::Entry,
|
|
||||||
tags::{IfdPointer, Tag},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// An Image File Directory (IFD).
|
|
||||||
///
|
|
||||||
/// A directory is a map of [`Tag`]s to [`Value`](crate::decoder::ifd::Value)s. The values are
|
|
||||||
/// stored anywhere in the file, with the directory containing the offsets and length of the
|
|
||||||
/// associated values for each tag present in the directory.
|
|
||||||
///
|
|
||||||
/// A directory can be created with with
|
|
||||||
/// [`Decoder::read_directory`](crate::decoder::Decoder::read_directory) or as an empty directory
|
|
||||||
/// to be extended with entries. A directory may be used with
|
|
||||||
/// [`Decoder::read_directory_tags`](crate::decoder::Decoder::read_directory_tags) to read the
|
|
||||||
/// values associated with tags from an underlying file.
|
|
||||||
#[doc(alias = "IFD")]
|
|
||||||
pub struct Directory {
|
|
||||||
/// There are at most `u16::MAX` entries in any single directory, the count is stored as a
|
|
||||||
/// 2-byte value. The order in the file is implied to be ascending by tag value (the decoder
|
|
||||||
/// does not mind unordered entries).
|
|
||||||
pub(crate) entries: BTreeMap<u16, Entry>,
|
|
||||||
pub(crate) next_ifd: Option<NonZeroU64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Directory {
|
|
||||||
/// Create a directory in an initial state without entries. Note that an empty directory can
|
|
||||||
/// not be encoded in a file, it must contain at least one entry.
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Directory {
|
|
||||||
entries: BTreeMap::new(),
|
|
||||||
next_ifd: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve the value associated with a tag.
|
|
||||||
pub fn get(&self, tag: Tag) -> Option<&Entry> {
|
|
||||||
self.entries.get(&tag.to_u16())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the directory contains a specified tag.
|
|
||||||
pub fn contains(&self, tag: Tag) -> bool {
|
|
||||||
self.entries.contains_key(&tag.to_u16())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all known and unknown tags in this directory.
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (Tag, &Entry)> + '_ {
|
|
||||||
self.entries
|
|
||||||
.iter()
|
|
||||||
.map(|(k, v)| (Tag::from_u16_exhaustive(*k), v))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert additional entries into the directory.
|
|
||||||
///
|
|
||||||
/// Note that a directory can contain at most `u16::MAX` values. There may be one entry that
|
|
||||||
/// does not fit into the directory. This entry is silently ignored (please check [`Self::len`]
|
|
||||||
/// to detect the condition). Providing a tag multiple times or a tag that already exists
|
|
||||||
/// within this directory overwrites the entry.
|
|
||||||
pub fn extend(&mut self, iter: impl IntoIterator<Item = (Tag, Entry)>) {
|
|
||||||
// Code size conscious extension, avoid monomorphic extensions with the assumption of these
|
|
||||||
// not being performance sensitive in practice. (Maybe we have a polymorphic interface for
|
|
||||||
// the crate usage in the future.
|
|
||||||
self.extend_inner(iter.into_iter().by_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the number of entries.
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
// The keys are `u16`. Since IFDs are required to have at least one entry this would have
|
|
||||||
// been a trivial thing to do in the specification by storing it minus one but alas. In
|
|
||||||
// BigTIFF the count is stored as 8-bit anyways.
|
|
||||||
self.entries.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if there are any entries in this directory. Note that an empty directory can not be
|
|
||||||
/// encoded in the file, it must contain at least one entry.
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.entries.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the pointer to the next IFD, if it was defined.
|
|
||||||
pub fn next(&self) -> Option<IfdPointer> {
|
|
||||||
self.next_ifd.map(|n| IfdPointer(n.get()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_next(&mut self, next: Option<IfdPointer>) {
|
|
||||||
self.next_ifd = next.and_then(|n| NonZeroU64::new(n.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extend_inner(&mut self, iter: &mut dyn Iterator<Item = (Tag, Entry)>) {
|
|
||||||
for (tag, entry) in iter {
|
|
||||||
// If the tag is already present, it will be overwritten.
|
|
||||||
let map_entry = self.entries.entry(tag.to_u16());
|
|
||||||
|
|
||||||
match map_entry {
|
|
||||||
std::collections::btree_map::Entry::Vacant(vacant_entry) => {
|
|
||||||
vacant_entry.insert(entry);
|
|
||||||
}
|
|
||||||
std::collections::btree_map::Entry::Occupied(mut occupied_entry) => {
|
|
||||||
occupied_entry.insert(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn encoded_len<K: crate::encoder::TiffKind>(&self) -> u64 {
|
|
||||||
let (len_count, len_offset);
|
|
||||||
|
|
||||||
match std::mem::size_of::<K::OffsetType>() {
|
|
||||||
offset @ 4 => {
|
|
||||||
// non-BigTiff uses a `u16` here.
|
|
||||||
len_count = 2;
|
|
||||||
len_offset = offset as u64;
|
|
||||||
}
|
|
||||||
offset => {
|
|
||||||
let offset = offset as u64;
|
|
||||||
// assuming BigTIFF
|
|
||||||
len_count = offset;
|
|
||||||
len_offset = offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each entry is 12 bytes in classic TIFF, 20 bytes in BigTIFF.
|
|
||||||
let entry_bytes = 4 + 2 * len_offset;
|
|
||||||
len_count + entry_bytes * self.entries.len() as u64 + len_offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Directory {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("Directory")
|
|
||||||
.field(
|
|
||||||
"entries",
|
|
||||||
&self.entries.iter().map(|(k, v)| (Tag::from_u16(*k), v)),
|
|
||||||
)
|
|
||||||
.field("next_ifd", &self.next_ifd)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::iter::FromIterator<(Tag, Entry)> for Directory {
|
|
||||||
fn from_iter<T: IntoIterator<Item = (Tag, Entry)>>(iter: T) -> Self {
|
|
||||||
let mut dir = Directory::empty();
|
|
||||||
dir.extend(iter);
|
|
||||||
dir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Directory;
|
|
||||||
use crate::{decoder::ifd::Entry, tags::Tag};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn directory_multiple_entries() {
|
|
||||||
let mut dir = Directory::empty();
|
|
||||||
assert_eq!(dir.len(), 0);
|
|
||||||
|
|
||||||
dir.extend((0..=u16::MAX).map(|i| {
|
|
||||||
let tag = Tag::Unknown(1);
|
|
||||||
let entry = Entry::new_u64(crate::tags::Type::BYTE, i.into(), [0; 8]);
|
|
||||||
(tag, entry)
|
|
||||||
}));
|
|
||||||
|
|
||||||
assert_eq!(dir.len(), 1, "Only one tag was ever modified");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
dir.get(Tag::Unknown(1))
|
|
||||||
.expect("tag 1 should be present after this chain")
|
|
||||||
.count(),
|
|
||||||
u16::MAX.into()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn iteration_order() {
|
|
||||||
let mut dir = Directory::empty();
|
|
||||||
assert_eq!(dir.len(), 0);
|
|
||||||
|
|
||||||
let fake_entry = Entry::new_u64(crate::tags::Type::BYTE, 0, [0; 8]);
|
|
||||||
dir.extend((0..32).map(|i| {
|
|
||||||
let tag = Tag::Unknown(i);
|
|
||||||
let entry = fake_entry.clone();
|
|
||||||
(tag, entry)
|
|
||||||
}));
|
|
||||||
|
|
||||||
let iter_order: Vec<u16> = dir.iter().map(|(tag, _e)| tag.to_u16()).collect();
|
|
||||||
assert_eq!(
|
|
||||||
iter_order,
|
|
||||||
(0..32).collect::<Vec<_>>(),
|
|
||||||
"Tags must be in ascending order according to the specification"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-233
@@ -1,30 +1,4 @@
|
|||||||
use crate::tags::{PhotometricInterpretation, SampleFormat};
|
use tags::PhotometricInterpretation;
|
||||||
|
|
||||||
macro_rules! integer_horizontal_predict {
|
|
||||||
() => {
|
|
||||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
|
|
||||||
let sample_size = Self::SAMPLE_FORMAT.len();
|
|
||||||
|
|
||||||
if row.len() < sample_size {
|
|
||||||
debug_assert!(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (start, rest) = row.split_at(sample_size);
|
|
||||||
|
|
||||||
result.extend_from_slice(start);
|
|
||||||
if result.capacity() - result.len() < rest.len() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.extend(
|
|
||||||
row.into_iter()
|
|
||||||
.zip(rest)
|
|
||||||
.map(|(prev, current)| current.wrapping_sub(*prev)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Trait for different colortypes that can be encoded.
|
/// Trait for different colortypes that can be encoded.
|
||||||
pub trait ColorType {
|
pub trait ColorType {
|
||||||
@@ -34,10 +8,6 @@ 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];
|
|
||||||
|
|
||||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Gray8;
|
pub struct Gray8;
|
||||||
@@ -45,19 +15,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GrayI8;
|
|
||||||
impl ColorType for GrayI8 {
|
|
||||||
type Inner = i8;
|
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Gray16;
|
pub struct Gray16;
|
||||||
@@ -65,19 +22,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GrayI16;
|
|
||||||
impl ColorType for GrayI16 {
|
|
||||||
type Inner = i16;
|
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Gray32;
|
pub struct Gray32;
|
||||||
@@ -85,31 +29,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GrayI32;
|
|
||||||
impl ColorType for GrayI32 {
|
|
||||||
type Inner = i32;
|
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Gray32Float;
|
|
||||||
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];
|
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Gray64;
|
pub struct Gray64;
|
||||||
@@ -117,31 +36,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GrayI64;
|
|
||||||
impl ColorType for GrayI64 {
|
|
||||||
type Inner = i64;
|
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Gray64Float;
|
|
||||||
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];
|
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGB8;
|
pub struct RGB8;
|
||||||
@@ -149,9 +43,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGB16;
|
pub struct RGB16;
|
||||||
@@ -159,9 +50,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGB32;
|
pub struct RGB32;
|
||||||
@@ -169,20 +57,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGB64;
|
pub struct RGB64;
|
||||||
@@ -190,20 +64,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGBA8;
|
pub struct RGBA8;
|
||||||
@@ -211,9 +71,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGBA16;
|
pub struct RGBA16;
|
||||||
@@ -221,51 +78,20 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGBA32;
|
pub struct RGBA32;
|
||||||
impl ColorType for RGBA32 {
|
impl ColorType for RGBA32 {
|
||||||
type Inner = u32;
|
type Inner = u16;
|
||||||
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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGBA64;
|
pub struct RGBA64;
|
||||||
impl ColorType for RGBA64 {
|
impl ColorType for RGBA64 {
|
||||||
type Inner = u64;
|
type Inner = u16;
|
||||||
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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CMYK8;
|
pub struct CMYK8;
|
||||||
@@ -273,9 +99,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CMYK16;
|
pub struct CMYK16;
|
||||||
@@ -283,9 +106,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CMYK32;
|
pub struct CMYK32;
|
||||||
@@ -293,21 +113,6 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CMYK64;
|
pub struct CMYK64;
|
||||||
@@ -315,39 +120,4 @@ 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];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
|
|
||||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct YCbCr8;
|
|
||||||
impl ColorType for YCbCr8 {
|
|
||||||
type Inner = u8;
|
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr;
|
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CMYKA8;
|
|
||||||
impl ColorType for CMYKA8 {
|
|
||||||
type Inner = u8;
|
|
||||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
|
||||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8, 8];
|
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 5];
|
|
||||||
|
|
||||||
integer_horizontal_predict!();
|
|
||||||
}
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
use crate::encoder::compression::*;
|
|
||||||
use flate2::{write::ZlibEncoder, Compression as FlateCompression};
|
|
||||||
|
|
||||||
/// The Deflate algorithm used to compress image data in TIFF files.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct Deflate {
|
|
||||||
level: FlateCompression,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The level of compression used by the Deflate algorithm.
|
|
||||||
/// It allows trading compression ratio for compression speed.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[derive(Default)]
|
|
||||||
pub enum DeflateLevel {
|
|
||||||
/// The fastest possible compression mode.
|
|
||||||
Fast = 1,
|
|
||||||
/// The conserative choice between speed and ratio.
|
|
||||||
#[default]
|
|
||||||
Balanced = 6,
|
|
||||||
/// The best compression available with Deflate.
|
|
||||||
Best = 9,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deflate {
|
|
||||||
/// Create a new deflate compressor with a specific level of compression.
|
|
||||||
pub fn with_level(level: DeflateLevel) -> Self {
|
|
||||||
Self {
|
|
||||||
level: FlateCompression::new(level as u32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Deflate {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::with_level(DeflateLevel::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compression for Deflate {
|
|
||||||
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::Deflate;
|
|
||||||
|
|
||||||
fn get_algorithm(&self) -> Compressor {
|
|
||||||
Compressor::Deflate(*self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompressionAlgorithm for Deflate {
|
|
||||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
|
||||||
let mut encoder = ZlibEncoder::new(writer, self.level);
|
|
||||||
encoder.write_all(bytes)?;
|
|
||||||
encoder.try_finish()?;
|
|
||||||
Ok(encoder.total_out())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::encoder::compression::tests::TEST_DATA;
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_deflate() {
|
|
||||||
const EXPECTED_COMPRESSED_DATA: [u8; 64] = [
|
|
||||||
0x78, 0x9C, 0x15, 0xC7, 0xD1, 0x0D, 0x80, 0x20, 0x0C, 0x04, 0xD0, 0x55, 0x6E, 0x02,
|
|
||||||
0xA7, 0x71, 0x81, 0xA6, 0x41, 0xDA, 0x28, 0xD4, 0xF4, 0xD0, 0xF9, 0x81, 0xE4, 0xFD,
|
|
||||||
0xBC, 0xD3, 0x9C, 0x58, 0x04, 0x1C, 0xE9, 0xBD, 0xE2, 0x8A, 0x84, 0x5A, 0xD1, 0x7B,
|
|
||||||
0xE7, 0x97, 0xF4, 0xF8, 0x08, 0x8D, 0xF6, 0x66, 0x21, 0x3D, 0x3A, 0xE4, 0xA9, 0x91,
|
|
||||||
0x3E, 0xAC, 0xF1, 0x98, 0xB9, 0x70, 0x17, 0x13,
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut compressed_data = Vec::<u8>::new();
|
|
||||||
let mut writer = Cursor::new(&mut compressed_data);
|
|
||||||
Deflate::default().write_to(&mut writer, TEST_DATA).unwrap();
|
|
||||||
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
use crate::encoder::compression::*;
|
|
||||||
use weezl::encode::Encoder as LZWEncoder;
|
|
||||||
|
|
||||||
/// The LZW algorithm used to compress image data in TIFF files.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
||||||
pub struct Lzw;
|
|
||||||
|
|
||||||
impl Compression for Lzw {
|
|
||||||
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::LZW;
|
|
||||||
|
|
||||||
fn get_algorithm(&self) -> Compressor {
|
|
||||||
Compressor::Lzw(*self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompressionAlgorithm for Lzw {
|
|
||||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
|
||||||
let mut encoder = LZWEncoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
|
|
||||||
let result = encoder.into_stream(writer).encode_all(bytes);
|
|
||||||
let byte_count = result.bytes_written as u64;
|
|
||||||
result.status.map(|_| byte_count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::encoder::compression::tests::TEST_DATA;
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_lzw() {
|
|
||||||
const EXPECTED_COMPRESSED_DATA: [u8; 63] = [
|
|
||||||
0x80, 0x15, 0x0D, 0x06, 0x93, 0x98, 0x82, 0x08, 0x20, 0x30, 0x88, 0x0E, 0x67, 0x43,
|
|
||||||
0x91, 0xA4, 0xDC, 0x67, 0x10, 0x19, 0x8D, 0xE7, 0x21, 0x01, 0x8C, 0xD0, 0x65, 0x31,
|
|
||||||
0x9A, 0xE1, 0xD1, 0x03, 0xB1, 0x86, 0x1A, 0x6F, 0x3A, 0xC1, 0x4C, 0x66, 0xF3, 0x69,
|
|
||||||
0xC0, 0xE4, 0x65, 0x39, 0x9C, 0xCD, 0x26, 0xF3, 0x74, 0x20, 0xD8, 0x67, 0x89, 0x9A,
|
|
||||||
0x4E, 0x86, 0x83, 0x69, 0xCC, 0x5D, 0x01,
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut compressed_data = Vec::<u8>::new();
|
|
||||||
let mut writer = Cursor::new(&mut compressed_data);
|
|
||||||
Lzw.write_to(&mut writer, TEST_DATA).unwrap();
|
|
||||||
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
use crate::tags::CompressionMethod;
|
|
||||||
use std::io::{self, Write};
|
|
||||||
|
|
||||||
#[cfg(feature = "deflate")]
|
|
||||||
mod deflate;
|
|
||||||
#[cfg(feature = "lzw")]
|
|
||||||
mod lzw;
|
|
||||||
mod packbits;
|
|
||||||
mod uncompressed;
|
|
||||||
|
|
||||||
#[cfg(feature = "deflate")]
|
|
||||||
pub use self::deflate::{Deflate, DeflateLevel};
|
|
||||||
|
|
||||||
#[cfg(feature = "lzw")]
|
|
||||||
pub use self::lzw::Lzw;
|
|
||||||
|
|
||||||
pub use self::packbits::Packbits;
|
|
||||||
pub use self::uncompressed::Uncompressed;
|
|
||||||
|
|
||||||
/// An algorithm used for compression
|
|
||||||
pub trait CompressionAlgorithm {
|
|
||||||
/// The algorithm writes data directly into the writer.
|
|
||||||
/// It returns the total number of bytes written.
|
|
||||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An algorithm used for compression with associated enums and optional configurations.
|
|
||||||
pub trait Compression: CompressionAlgorithm {
|
|
||||||
/// The corresponding tag to the algorithm.
|
|
||||||
const COMPRESSION_METHOD: CompressionMethod;
|
|
||||||
|
|
||||||
/// Method to optain a type that can store each variant of comression algorithm.
|
|
||||||
fn get_algorithm(&self) -> Compressor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An enum to store each compression algorithm.
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum Compressor {
|
|
||||||
Uncompressed(Uncompressed),
|
|
||||||
#[cfg(feature = "lzw")]
|
|
||||||
Lzw(Lzw),
|
|
||||||
#[cfg(feature = "deflate")]
|
|
||||||
Deflate(Deflate),
|
|
||||||
Packbits(Packbits),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Compressor {
|
|
||||||
/// The default compression strategy does not apply any compression.
|
|
||||||
fn default() -> Self {
|
|
||||||
Compressor::Uncompressed(Uncompressed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompressionAlgorithm for Compressor {
|
|
||||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
|
||||||
match self {
|
|
||||||
Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes),
|
|
||||||
#[cfg(feature = "lzw")]
|
|
||||||
Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes),
|
|
||||||
#[cfg(feature = "deflate")]
|
|
||||||
Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes),
|
|
||||||
Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
pub const TEST_DATA: &[u8] = b"This is a string for checking various compression algorithms.";
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
use crate::encoder::compression::*;
|
|
||||||
use std::io::{BufWriter, Error, ErrorKind};
|
|
||||||
|
|
||||||
/// Compressor that uses the Packbits[^note] algorithm to compress bytes.
|
|
||||||
///
|
|
||||||
/// [^note]: PackBits is often ineffective on continuous tone images,
|
|
||||||
/// including many grayscale images. In such cases, it is better
|
|
||||||
/// to leave the image uncompressed.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
||||||
pub struct Packbits;
|
|
||||||
|
|
||||||
impl Compression for Packbits {
|
|
||||||
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::PackBits;
|
|
||||||
|
|
||||||
fn get_algorithm(&self) -> Compressor {
|
|
||||||
Compressor::Packbits(*self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompressionAlgorithm for Packbits {
|
|
||||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
|
||||||
// Inspired by https://github.com/skirridsystems/packbits
|
|
||||||
|
|
||||||
const MIN_REPT: u8 = 3; // Minimum run to compress between differ blocks
|
|
||||||
const MAX_BYTES: u8 = 128; // Maximum number of bytes that can be encoded in a header byte
|
|
||||||
|
|
||||||
// Encoding for header byte based on number of bytes represented.
|
|
||||||
fn encode_diff(n: u8) -> u8 {
|
|
||||||
n - 1
|
|
||||||
}
|
|
||||||
fn encode_rept(n: u8) -> u8 {
|
|
||||||
let var = 256 - (n - 1) as u16;
|
|
||||||
var as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_u8<W: Write>(writer: &mut W, byte: u8) -> Result<u64, Error> {
|
|
||||||
writer.write(&[byte]).map(|byte_count| byte_count as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut bufwriter = BufWriter::new(writer);
|
|
||||||
let mut bytes_written = 0u64; // The number of bytes written into the writer
|
|
||||||
let mut offset: Option<u64> = None; // The index of the first byte written into the writer
|
|
||||||
|
|
||||||
let mut src_index: usize = 0; // Index of the current byte
|
|
||||||
let mut src_count = bytes.len(); //The number of bytes remaining to be compressed
|
|
||||||
|
|
||||||
let mut in_run = false; // Indication whether counting of similar bytes is performed
|
|
||||||
let mut run_index = 0u8; // Distance into pending bytes that a run starts
|
|
||||||
|
|
||||||
let mut bytes_pending = 0u8; // Bytes looked at but not yet output
|
|
||||||
let mut pending_index = 0usize; // Index of the first pending byte
|
|
||||||
|
|
||||||
let mut curr_byte: u8; // Byte currently being considered
|
|
||||||
let mut last_byte: u8; // Previous byte
|
|
||||||
|
|
||||||
// Need at least one byte to compress
|
|
||||||
if src_count == 0 {
|
|
||||||
return Err(Error::new(ErrorKind::WriteZero, "write zero"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prime compressor with first character.
|
|
||||||
last_byte = bytes[src_index];
|
|
||||||
src_index += 1;
|
|
||||||
bytes_pending += 1;
|
|
||||||
|
|
||||||
while src_count - 1 != 0 {
|
|
||||||
src_count -= 1;
|
|
||||||
curr_byte = bytes[src_index];
|
|
||||||
src_index += 1;
|
|
||||||
bytes_pending += 1;
|
|
||||||
|
|
||||||
if in_run {
|
|
||||||
if (curr_byte != last_byte) || (bytes_pending > MAX_BYTES) {
|
|
||||||
offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending - 1))?);
|
|
||||||
write_u8(&mut bufwriter, last_byte)?;
|
|
||||||
bytes_written += 2;
|
|
||||||
|
|
||||||
bytes_pending = 1;
|
|
||||||
pending_index = src_index - 1;
|
|
||||||
run_index = 0;
|
|
||||||
in_run = false;
|
|
||||||
}
|
|
||||||
} else if bytes_pending > MAX_BYTES {
|
|
||||||
// We have as much differing data as we can output in one chunk.
|
|
||||||
// Output MAX_BYTES leaving one byte.
|
|
||||||
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(MAX_BYTES))?);
|
|
||||||
bufwriter.write_all(&bytes[pending_index..pending_index + MAX_BYTES as usize])?;
|
|
||||||
bytes_written += 1 + MAX_BYTES as u64;
|
|
||||||
|
|
||||||
pending_index += MAX_BYTES as usize;
|
|
||||||
bytes_pending -= MAX_BYTES;
|
|
||||||
run_index = bytes_pending - 1; // A run could start here
|
|
||||||
} else if curr_byte == last_byte {
|
|
||||||
if (bytes_pending - run_index >= MIN_REPT) || (run_index == 0) {
|
|
||||||
// This is a worthwhile run
|
|
||||||
if run_index != 0 {
|
|
||||||
// Flush differing data out of input buffer
|
|
||||||
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(run_index))?);
|
|
||||||
bufwriter
|
|
||||||
.write_all(&bytes[pending_index..pending_index + run_index as usize])?;
|
|
||||||
bytes_written += 1 + run_index as u64;
|
|
||||||
}
|
|
||||||
bytes_pending -= run_index; // Length of run
|
|
||||||
in_run = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
run_index = bytes_pending - 1; // A run could start here
|
|
||||||
}
|
|
||||||
last_byte = curr_byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output the remainder
|
|
||||||
if in_run {
|
|
||||||
bytes_written += 2;
|
|
||||||
offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending))?);
|
|
||||||
write_u8(&mut bufwriter, last_byte)?;
|
|
||||||
} else {
|
|
||||||
bytes_written += 1 + bytes_pending as u64;
|
|
||||||
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(bytes_pending))?);
|
|
||||||
bufwriter.write_all(&bytes[pending_index..pending_index + bytes_pending as usize])?;
|
|
||||||
}
|
|
||||||
|
|
||||||
bufwriter.flush()?;
|
|
||||||
Ok(bytes_written)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::encoder::compression::tests::TEST_DATA;
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_packbits_single_byte() {
|
|
||||||
// compress single byte
|
|
||||||
const UNCOMPRESSED_DATA: [u8; 1] = [0x3F];
|
|
||||||
const EXPECTED_COMPRESSED_DATA: [u8; 2] = [0x00, 0x3F];
|
|
||||||
|
|
||||||
let mut compressed_data = Vec::<u8>::new();
|
|
||||||
let mut writer = Cursor::new(&mut compressed_data);
|
|
||||||
Packbits.write_to(&mut writer, &UNCOMPRESSED_DATA).unwrap();
|
|
||||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_packbits_rept() {
|
|
||||||
// compress buffer with repetitive sequence
|
|
||||||
const UNCOMPRESSED_DATA: &[u8] =
|
|
||||||
b"This strrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrring hangs.";
|
|
||||||
const EXPECTED_COMPRESSED_DATA: &[u8] = b"\x06This st\xD1r\x09ing hangs.";
|
|
||||||
|
|
||||||
let mut compressed_data = Vec::<u8>::new();
|
|
||||||
let mut writer = Cursor::new(&mut compressed_data);
|
|
||||||
Packbits.write_to(&mut writer, UNCOMPRESSED_DATA).unwrap();
|
|
||||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_packbits_large_rept_nonrept() {
|
|
||||||
// compress buffer with large repetitive and non-repetitive sequence
|
|
||||||
let mut data = b"This st".to_vec();
|
|
||||||
for _i in 0..158 {
|
|
||||||
data.push(b'r');
|
|
||||||
}
|
|
||||||
data.extend_from_slice(b"ing hangs.");
|
|
||||||
for i in 0..158 {
|
|
||||||
data.push(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXPECTED_COMPRESSED_DATA: [u8; 182] = [
|
|
||||||
0x06, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x74, 0x81, 0x72, 0xE3, 0x72, 0x7F, 0x69,
|
|
||||||
0x6E, 0x67, 0x20, 0x68, 0x61, 0x6E, 0x67, 0x73, 0x2E, 0x00, 0x01, 0x02, 0x03, 0x04,
|
|
||||||
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
|
|
||||||
0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
|
|
||||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
|
|
||||||
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
|
|
||||||
0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
|
|
||||||
0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
|
|
||||||
0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
|
|
||||||
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
|
|
||||||
0x75, 0x27, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81,
|
|
||||||
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
|
||||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut compressed_data = Vec::<u8>::new();
|
|
||||||
let mut writer = Cursor::new(&mut compressed_data);
|
|
||||||
Packbits.write_to(&mut writer, data.as_slice()).unwrap();
|
|
||||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_packbits() {
|
|
||||||
// compress teststring
|
|
||||||
const EXPECTED_COMPRESSED_DATA: &[u8] =
|
|
||||||
b"\x3CThis is a string for checking various compression algorithms.";
|
|
||||||
|
|
||||||
let mut compressed_data = Vec::<u8>::new();
|
|
||||||
let mut writer = Cursor::new(&mut compressed_data);
|
|
||||||
Packbits.write_to(&mut writer, TEST_DATA).unwrap();
|
|
||||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
use crate::encoder::compression::*;
|
|
||||||
|
|
||||||
/// The default algorithm which does not compress at all.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
||||||
pub struct Uncompressed;
|
|
||||||
|
|
||||||
impl Compression for Uncompressed {
|
|
||||||
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::None;
|
|
||||||
|
|
||||||
fn get_algorithm(&self) -> Compressor {
|
|
||||||
Compressor::Uncompressed(*self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CompressionAlgorithm for Uncompressed {
|
|
||||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
|
||||||
writer.write_all(bytes).map(|()| bytes.len() as u64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::encoder::compression::tests::TEST_DATA;
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_no_compression() {
|
|
||||||
let mut compressed_data = Vec::<u8>::new();
|
|
||||||
let mut writer = Cursor::new(&mut compressed_data);
|
|
||||||
Uncompressed.write_to(&mut writer, TEST_DATA).unwrap();
|
|
||||||
assert_eq!(TEST_DATA, compressed_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+463
-787
File diff suppressed because it is too large.
Load diff
@@ -1,523 +0,0 @@
|
|||||||
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<T: TiffValue + ?Sized> TiffValue for &'_ 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,
|
|
||||||
}
|
|
||||||
+37
-176
@@ -1,172 +1,94 @@
|
|||||||
use crate::encoder::compression::*;
|
use error::TiffResult;
|
||||||
use crate::error::TiffResult;
|
use byteorder::{BigEndian, ByteOrder, LittleEndian, NativeEndian, WriteBytesExt};
|
||||||
use std::io::{self, Seek, SeekFrom, Write};
|
use std::io::{self, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
pub fn write_tiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
pub trait TiffByteOrder: ByteOrder {
|
||||||
#[cfg(target_endian = "little")]
|
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()>;
|
||||||
let boi: u8 = 0x49;
|
|
||||||
#[cfg(not(target_endian = "little"))]
|
|
||||||
let boi: u8 = 0x4d;
|
|
||||||
|
|
||||||
writer.writer.write_all(&[boi, boi])?;
|
|
||||||
writer.writer.write_all(&42u16.to_ne_bytes())?;
|
|
||||||
writer.offset += 4;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes a BigTiff header, excluding the IFD offset field.
|
impl TiffByteOrder for LittleEndian {
|
||||||
///
|
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
/// Writes the byte order, version number, offset byte size, and zero constant fields. Does
|
writer.writer.write_u16::<LittleEndian>(0x4949)?;
|
||||||
// _not_ write the offset to the first IFD, this should be done by the caller.
|
writer.writer.write_u16::<LittleEndian>(42)?;
|
||||||
pub fn write_bigtiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
writer.offset += 4;
|
||||||
#[cfg(target_endian = "little")]
|
|
||||||
let boi: u8 = 0x49;
|
|
||||||
#[cfg(not(target_endian = "little"))]
|
|
||||||
let boi: u8 = 0x4d;
|
|
||||||
|
|
||||||
// byte order indication
|
Ok(())
|
||||||
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
|
impl TiffByteOrder for BigEndian {
|
||||||
writer.offset += 8;
|
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
||||||
|
writer.writer.write_u16::<BigEndian>(0x4d4d)?;
|
||||||
|
writer.writer.write_u16::<BigEndian>(42)?;
|
||||||
|
writer.offset += 4;
|
||||||
|
|
||||||
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 {
|
Self { writer, offset: 0 }
|
||||||
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(crate) fn previous_ifd_pointer<K: super::TiffKind>(&self) -> u64 {
|
|
||||||
self.offset() - core::mem::size_of::<K::OffsetType>() as u64
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn last_written(&self) -> u64 {
|
|
||||||
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.byte_count = self.compressor.write_to(&mut self.writer, bytes)?;
|
self.writer.write_all(bytes)?;
|
||||||
self.offset += self.byte_count;
|
self.offset += bytes.len() as u64;
|
||||||
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.byte_count = self
|
self.writer.write_u8(n)?;
|
||||||
.compressor
|
self.offset += 1;
|
||||||
.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.byte_count = self
|
self.writer.write_i8(n)?;
|
||||||
.compressor
|
self.offset += 1;
|
||||||
.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.byte_count = self
|
self.writer.write_u16::<NativeEndian>(n)?;
|
||||||
.compressor
|
self.offset += 2;
|
||||||
.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.byte_count = self
|
self.writer.write_i16::<NativeEndian>(n)?;
|
||||||
.compressor
|
self.offset += 2;
|
||||||
.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.byte_count = self
|
self.writer.write_u32::<NativeEndian>(n)?;
|
||||||
.compressor
|
self.offset += 4;
|
||||||
.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.byte_count = self
|
self.writer.write_i32::<NativeEndian>(n)?;
|
||||||
.compressor
|
self.offset += 4;
|
||||||
.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.byte_count = self
|
self.writer.write_u64::<NativeEndian>(n)?;
|
||||||
.compressor
|
self.offset += 8;
|
||||||
.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(())
|
||||||
}
|
}
|
||||||
@@ -186,68 +108,7 @@ impl<W: Write> TiffWriter<W> {
|
|||||||
impl<W: Seek> TiffWriter<W> {
|
impl<W: Seek> TiffWriter<W> {
|
||||||
pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> {
|
pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> {
|
||||||
self.offset = offset;
|
self.offset = offset;
|
||||||
self.writer.seek(SeekFrom::Start(offset))?;
|
self.writer.seek(SeekFrom::Start(offset as u64))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encoder_on_short_writes() {
|
|
||||||
struct ShortWriter<T> {
|
|
||||||
inner: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: std::io::Write> std::io::Write for ShortWriter<T> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
||||||
let lower_bound = buf.len().min(1);
|
|
||||||
let lower = buf.len().saturating_sub(1).max(lower_bound);
|
|
||||||
|
|
||||||
self.inner.write(&buf[..lower])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
|
||||||
self.inner.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: std::io::Seek> std::io::Seek for ShortWriter<T> {
|
|
||||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
|
||||||
self.inner.seek(pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut write = ShortWriter {
|
|
||||||
inner: std::io::Cursor::new(vec![0u8; 0]),
|
|
||||||
};
|
|
||||||
|
|
||||||
let imgdata = vec![42u8; 100 * 100];
|
|
||||||
let mut data = crate::decoder::DecodingResult::U8(vec![]);
|
|
||||||
|
|
||||||
for comp in [
|
|
||||||
super::Compression::Uncompressed,
|
|
||||||
super::Compression::Packbits,
|
|
||||||
] {
|
|
||||||
write.inner.set_position(0);
|
|
||||||
write.inner.get_mut().clear();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut tiff_writer = super::TiffEncoder::new(&mut write)
|
|
||||||
.unwrap()
|
|
||||||
.with_compression(comp);
|
|
||||||
|
|
||||||
tiff_writer
|
|
||||||
.write_image::<super::Gray8>(100, 100, &imgdata)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
write.inner.set_position(0);
|
|
||||||
let mut tiff_reader = crate::decoder::Decoder::new(&mut write.inner).unwrap();
|
|
||||||
let _layout = tiff_reader.read_image_to_buffer(&mut data).unwrap();
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
matches!(&data, crate::decoder::DecodingResult::U8(v) if *v == imgdata),
|
|
||||||
"{data:?} @ {}",
|
|
||||||
data.as_buffer(0).byte_len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+202
-210
@@ -1,213 +1,207 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::str;
|
|
||||||
use std::string;
|
use std::string;
|
||||||
|
|
||||||
use quick_error::quick_error;
|
use decoder::ifd::{Value};
|
||||||
|
use tags::{CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Tag};
|
||||||
|
use miniz_oxide::inflate::TINFLStatus;
|
||||||
|
use ColorType;
|
||||||
|
|
||||||
use crate::decoder::ChunkType;
|
/// Tiff error kinds.
|
||||||
use crate::tags::{
|
#[derive(Debug)]
|
||||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
|
pub enum TiffError {
|
||||||
};
|
/// The Image is not formatted properly.
|
||||||
use crate::ColorType;
|
FormatError(TiffFormatError),
|
||||||
|
|
||||||
quick_error! {
|
|
||||||
/// Tiff error kinds.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum TiffError {
|
|
||||||
/// The Image is not formatted properly.
|
|
||||||
FormatError(err: TiffFormatError) {
|
|
||||||
display("format error: {err}")
|
|
||||||
from()
|
|
||||||
source(err)
|
|
||||||
}
|
|
||||||
/// The Decoder does not support features required by the image.
|
|
||||||
UnsupportedError(err: TiffUnsupportedError) {
|
|
||||||
display("unsupported error: {err}")
|
|
||||||
from()
|
|
||||||
source(err)
|
|
||||||
}
|
|
||||||
/// An I/O Error occurred while decoding the image.
|
|
||||||
IoError(err: io::Error) {
|
|
||||||
display("{err}")
|
|
||||||
from()
|
|
||||||
source(err)
|
|
||||||
}
|
|
||||||
/// The Limits of the Decoder is exceeded.
|
|
||||||
LimitsExceeded {
|
|
||||||
display("decoder limits exceeded")
|
|
||||||
}
|
|
||||||
/// An integer conversion to or from a platform size failed.
|
|
||||||
IntSizeError {
|
|
||||||
display("platform or format size limits exceeded")
|
|
||||||
}
|
|
||||||
/// The image does not support the requested operation
|
|
||||||
UsageError(err: UsageError) {
|
|
||||||
display("usage error: {err}")
|
|
||||||
from()
|
|
||||||
source(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quick_error! {
|
|
||||||
/// The image is not formatted properly.
|
|
||||||
///
|
|
||||||
/// This indicates that the encoder producing the image might behave incorrectly or that the
|
|
||||||
/// input file has been corrupted.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum TiffFormatError {
|
|
||||||
TiffSignatureNotFound {
|
|
||||||
display("TIFF signature not found")
|
|
||||||
}
|
|
||||||
TiffSignatureInvalid {
|
|
||||||
display("TIFF signature invalid")
|
|
||||||
}
|
|
||||||
ImageFileDirectoryNotFound {
|
|
||||||
display("image file directory not found")
|
|
||||||
}
|
|
||||||
InconsistentSizesEncountered {
|
|
||||||
display("inconsistent sizes encountered")
|
|
||||||
}
|
|
||||||
InvalidDimensions(width: u32, height: u32) {
|
|
||||||
display("invalid dimensions: {width}x{height}")
|
|
||||||
}
|
|
||||||
InvalidTag {
|
|
||||||
display("image contains invalid tag")
|
|
||||||
}
|
|
||||||
InvalidTagValueType(tag: Tag) {
|
|
||||||
display("tag `{tag:?}` did not have the expected value type")
|
|
||||||
}
|
|
||||||
RequiredTagNotFound(tag: Tag) {
|
|
||||||
display("required tag `{tag:?}` not found")
|
|
||||||
}
|
|
||||||
UnknownPredictor(pred: u16) {
|
|
||||||
display("unknown predictor “{pred}” encountered")
|
|
||||||
}
|
|
||||||
UnknownPlanarConfiguration(cfg: u16) {
|
|
||||||
display("unknown planar configuration “{cfg}”")
|
|
||||||
}
|
|
||||||
InvalidTypeForTag {
|
|
||||||
display("tag has invalid type")
|
|
||||||
}
|
|
||||||
StripTileTagConflict {
|
|
||||||
display("file should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found")
|
|
||||||
}
|
|
||||||
CycleInOffsets {
|
|
||||||
display("file contained a cycle in the list of IFDs")
|
|
||||||
}
|
|
||||||
SamplesPerPixelIsZero {
|
|
||||||
display("samples per pixel is zero")
|
|
||||||
}
|
|
||||||
CompressedDataCorrupt(message: String) {
|
|
||||||
display("compressed data is corrupt: {message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
quick_error! {
|
|
||||||
/// The Decoder does not support features required by the image.
|
/// The Decoder does not support features required by the image.
|
||||||
///
|
UnsupportedError(TiffUnsupportedError),
|
||||||
/// This only captures known failures for which the standard either does not require support or an
|
|
||||||
/// implementation has been planned but not yet completed. Some variants may become unused over
|
/// An I/O Error occurred while decoding the image.
|
||||||
/// time and will then get deprecated before being removed.
|
IoError(io::Error),
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
#[non_exhaustive]
|
/// The Limits of the Decoder is exceeded.
|
||||||
pub enum TiffUnsupportedError {
|
LimitsExceeded,
|
||||||
FloatingPointPredictor(color_type: ColorType) {
|
|
||||||
display("floating point predictor for {color_type:?} is unsupported")
|
/// An integer conversion to or from a platform size failed, either due to
|
||||||
}
|
/// limits of the platform size or limits of the format.
|
||||||
HorizontalPredictor(color_type: ColorType) {
|
IntSizeError,
|
||||||
display("horizontal predictor for {color_type:?} is unsupported")
|
}
|
||||||
}
|
|
||||||
InconsistentBitsPerSample(bits_per_sample: Vec<u8>) {
|
/// The image is not formatted properly.
|
||||||
display("inconsistent bits per sample: {bits_per_sample:?}")
|
///
|
||||||
}
|
/// This indicates that the encoder producing the image might behave incorrectly or that the input
|
||||||
InterpretationWithBits(interpretation: PhotometricInterpretation, bits_per_sample: Vec<u8>) {
|
/// file has been corrupted.
|
||||||
display("Photometric interpretation {interpretation:?} with bits per sample {bits_per_sample:?} is unsupported")
|
///
|
||||||
}
|
/// The list of variants may grow to incorporate errors of future features. Matching against this
|
||||||
UnknownInterpretation {
|
/// exhaustively is not covered by interface stability guarantees.
|
||||||
display("unknown photometric interpretation")
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
}
|
pub enum TiffFormatError {
|
||||||
UnknownCompressionMethod {
|
TiffSignatureNotFound,
|
||||||
display("unknown compression method")
|
TiffSignatureInvalid,
|
||||||
}
|
ImageFileDirectoryNotFound,
|
||||||
UnsupportedCompressionMethod(method: CompressionMethod) {
|
InconsistentSizesEncountered,
|
||||||
display("compression method {method:?} is unsupported")
|
InvalidTag,
|
||||||
}
|
InvalidTagValueType(Tag),
|
||||||
UnsupportedSampleDepth(depth: u8) {
|
RequiredTagNotFound(Tag),
|
||||||
display("{depth} samples per pixel is unsupported")
|
UnknownPredictor(u16),
|
||||||
}
|
UnsignedIntegerExpected(Value),
|
||||||
UnsupportedSampleFormat(sample_format: Vec<SampleFormat>) {
|
SignedIntegerExpected(Value),
|
||||||
display("sample format {sample_format:?} is unsupported")
|
InflateError(InflateError),
|
||||||
}
|
#[doc(hidden)]
|
||||||
UnsupportedColorType(color_type: ColorType) {
|
/// Do not match against this variant. It may get removed.
|
||||||
display("color type {color_type:?} is unsupported")
|
__NonExhaustive,
|
||||||
}
|
}
|
||||||
UnsupportedBitsPerChannel(bits_per_channel: u8) {
|
|
||||||
display("{bits_per_channel} bits per channel not supported")
|
impl fmt::Display for TiffFormatError {
|
||||||
}
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
UnsupportedPlanarConfig(planar: Option<PlanarConfiguration>) {
|
use self::TiffFormatError::*;
|
||||||
display("unsupported planar configuration “{planar:?}”")
|
match *self {
|
||||||
}
|
TiffSignatureNotFound => write!(fmt, "TIFF signature not found."),
|
||||||
UnsupportedDataType {
|
TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."),
|
||||||
display("unsupported data type.")
|
ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
|
||||||
}
|
InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."),
|
||||||
UnsupportedInterpretation(interpretation: PhotometricInterpretation) {
|
InvalidTag => write!(fmt, "Image contains invalid tag."),
|
||||||
display("unsupported photometric interpretation \"{interpretation:?}\"")
|
InvalidTagValueType(ref tag) =>
|
||||||
}
|
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag),
|
||||||
MisalignedTileBoundaries {
|
RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag),
|
||||||
display("tile rows are not aligned to byte boundaries")
|
UnknownPredictor(ref predictor) => {
|
||||||
|
write!(fmt, "Unknown predictor “{}” encountered", predictor)
|
||||||
|
}
|
||||||
|
UnsignedIntegerExpected(ref val) => {
|
||||||
|
write!(fmt, "Expected unsigned integer, {:?} found.", val)
|
||||||
|
}
|
||||||
|
SignedIntegerExpected(ref val) => {
|
||||||
|
write!(fmt, "Expected signed integer, {:?} found.", val)
|
||||||
|
}
|
||||||
|
InflateError(_) => write!(fmt, "Failed to decode inflate data."),
|
||||||
|
__NonExhaustive => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
quick_error! {
|
/// Decompression failed due to faulty compressed data.
|
||||||
/// User attempted to use the Decoder in a way that is incompatible with a specific image.
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
///
|
pub struct InflateError {
|
||||||
/// For example: attempting to read a tile from a stripped image.
|
status: TINFLStatus,
|
||||||
#[derive(Debug)]
|
}
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum UsageError {
|
impl InflateError {
|
||||||
InvalidChunkType(expected: ChunkType, actual: ChunkType) {
|
pub(crate) fn new(status: TINFLStatus) -> Self {
|
||||||
display("requested operation is only valid for images with chunk encoding of type {expected:?} but got {actual:?}")
|
Self { status }
|
||||||
}
|
}
|
||||||
InvalidChunkIndex(index: u32) {
|
}
|
||||||
display("invalid chunk index ({index}) requested")
|
|
||||||
}
|
impl TiffError {
|
||||||
InvalidPlaneIndex(index: u16) {
|
pub(crate) fn from_inflate_status(status: TINFLStatus) -> Self {
|
||||||
display("invalid plane index ({index}) requested")
|
TiffError::FormatError(TiffFormatError::InflateError(InflateError::new(status)))
|
||||||
}
|
}
|
||||||
InvalidCodingUnit(index: u32, have: u32) {
|
}
|
||||||
display("out of bounds coding unit ({index}) requested, only {have} available")
|
|
||||||
}
|
/// The Decoder does not support features required by the image.
|
||||||
PredictorCompressionMismatch {
|
///
|
||||||
display("requested predictor is not compatible with the requested compression")
|
/// This only captures known failures for which the standard either does not require support or an
|
||||||
}
|
/// implementation has been planned but not yet completed. Some variants may become unused over
|
||||||
PredictorIncompatible {
|
/// time and will then get deprecated before being removed.
|
||||||
display("the requested predictor is not compatible with the image's format")
|
///
|
||||||
}
|
/// The list of variants may grow. Matching against this exhaustively is not covered by interface
|
||||||
PredictorUnavailable {
|
/// stability guarantees.
|
||||||
display("the requested predictor is not available")
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
}
|
pub enum TiffUnsupportedError {
|
||||||
InsufficientOutputBufferSize { needed: usize, provided: usize } {
|
HorizontalPredictor(ColorType),
|
||||||
display("the borrowed output buffer is not large enough for the decoded data, needed {needed} but have {provided}")
|
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
|
||||||
}
|
UnknownInterpretation,
|
||||||
InsufficientOutputRowStride { needed: usize, requested: usize } {
|
UnknownCompressionMethod,
|
||||||
display("the provided output row stride would alias rows of decoded data, needed {needed} but have {requested}")
|
UnsupportedCompressionMethod(CompressionMethod),
|
||||||
}
|
UnsupportedSampleDepth(u8),
|
||||||
ZeroIfdPointer {
|
UnsupportedColorType(ColorType),
|
||||||
display("the offset 0 can not point to a valid IFD")
|
UnsupportedBitsPerChannel(u8),
|
||||||
}
|
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
|
||||||
ReconfiguredAfterImageWrite {
|
UnsupportedDataType,
|
||||||
display("attempted to reconfigure the encoder after image writing has started")
|
#[doc(hidden)]
|
||||||
|
/// Do not match against this variant. It may get removed.
|
||||||
|
__NonExhaustive,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TiffUnsupportedError {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
use self::TiffUnsupportedError::*;
|
||||||
|
match *self {
|
||||||
|
HorizontalPredictor(color_type) => write!(
|
||||||
|
fmt,
|
||||||
|
"Horizontal predictor for {:?} is unsupported.",
|
||||||
|
color_type
|
||||||
|
),
|
||||||
|
InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => write!(
|
||||||
|
fmt,
|
||||||
|
"{:?} with {:?} bits per sample is unsupported",
|
||||||
|
photometric_interpretation, bits_per_sample
|
||||||
|
),
|
||||||
|
UnknownInterpretation => write!(
|
||||||
|
fmt,
|
||||||
|
"The image is using an unknown photometric interpretation."
|
||||||
|
),
|
||||||
|
UnknownCompressionMethod => write!(fmt, "Unknown compression method."),
|
||||||
|
UnsupportedCompressionMethod(method) => {
|
||||||
|
write!(fmt, "Compression method {:?} is unsupported", method)
|
||||||
|
}
|
||||||
|
UnsupportedSampleDepth(samples) => {
|
||||||
|
write!(fmt, "{} samples per pixel is unsupported.", samples)
|
||||||
|
}
|
||||||
|
UnsupportedColorType(color_type) => {
|
||||||
|
write!(fmt, "Color type {:?} is unsupported", color_type)
|
||||||
|
}
|
||||||
|
UnsupportedBitsPerChannel(bits) => {
|
||||||
|
write!(fmt, "{} bits per channel not supported", bits)
|
||||||
|
}
|
||||||
|
UnsupportedPlanarConfig(config) => {
|
||||||
|
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
||||||
|
}
|
||||||
|
UnsupportedDataType => write!(fmt, "Unsupported data type."),
|
||||||
|
__NonExhaustive => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<str::Utf8Error> for TiffError {
|
impl fmt::Display for TiffError {
|
||||||
fn from(_err: str::Utf8Error) -> TiffError {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
TiffError::FormatError(TiffFormatError::InvalidTag)
|
match *self {
|
||||||
|
TiffError::FormatError(ref e) => write!(fmt, "Format error: {}", e),
|
||||||
|
TiffError::UnsupportedError(ref f) => write!(
|
||||||
|
fmt,
|
||||||
|
"The Decoder does not support the \
|
||||||
|
image format `{}`",
|
||||||
|
f
|
||||||
|
),
|
||||||
|
TiffError::IoError(ref e) => e.fmt(fmt),
|
||||||
|
TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"),
|
||||||
|
TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for TiffError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
TiffError::FormatError(..) => "Format error",
|
||||||
|
TiffError::UnsupportedError(..) => "Unsupported error",
|
||||||
|
TiffError::IoError(..) => "IO error",
|
||||||
|
TiffError::LimitsExceeded => "Decoder limits exceeded",
|
||||||
|
TiffError::IntSizeError => "Platform or format size limits exceeded",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&dyn Error> {
|
||||||
|
match *self {
|
||||||
|
TiffError::IoError(ref e) => Some(e),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for TiffError {
|
||||||
|
fn from(err: io::Error) -> TiffError {
|
||||||
|
TiffError::IoError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,25 +211,23 @@ impl From<string::FromUtf8Error> for TiffError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<TiffFormatError> for TiffError {
|
||||||
|
fn from(err: TiffFormatError) -> TiffError {
|
||||||
|
TiffError::FormatError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TiffUnsupportedError> for TiffError {
|
||||||
|
fn from(err: TiffUnsupportedError) -> TiffError {
|
||||||
|
TiffError::UnsupportedError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<std::num::TryFromIntError> for TiffError {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "lzw")]
|
|
||||||
impl From<weezl::LzwError> for TiffError {
|
|
||||||
fn from(err: weezl::LzwError) -> TiffError {
|
|
||||||
TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "jpeg")]
|
|
||||||
impl From<zune_jpeg::errors::DecodeErrors> for TiffError {
|
|
||||||
fn from(err: zune_jpeg::errors::DecodeErrors) -> Self {
|
|
||||||
TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Result of an image decoding/encoding process
|
/// Result of an image decoding/encoding process
|
||||||
pub type TiffResult<T> = Result<T, TiffError>;
|
pub type TiffResult<T> = Result<T, TiffError>;
|
||||||
+6
-44
@@ -4,21 +4,21 @@
|
|||||||
//! lossless and lossy compression.
|
//! lossless and lossy compression.
|
||||||
//!
|
//!
|
||||||
//! # Related Links
|
//! # Related Links
|
||||||
//! * <https://web.archive.org/web/20210108073850/https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
//! * <http://partners.adobe.com/public/developer/tiff/index.html> - The TIFF specification
|
||||||
|
|
||||||
|
extern crate byteorder;
|
||||||
|
extern crate lzw;
|
||||||
|
extern crate miniz_oxide;
|
||||||
|
|
||||||
mod bytecast;
|
|
||||||
pub mod decoder;
|
pub mod decoder;
|
||||||
mod directory;
|
|
||||||
pub mod encoder;
|
pub mod encoder;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
|
|
||||||
pub use self::directory::Directory;
|
pub use self::error::{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)]
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum ColorType {
|
pub enum ColorType {
|
||||||
/// Pixel is grayscale
|
/// Pixel is grayscale
|
||||||
Gray(u8),
|
Gray(u8),
|
||||||
@@ -37,42 +37,4 @@ pub enum ColorType {
|
|||||||
|
|
||||||
/// Pixel is CMYK
|
/// Pixel is CMYK
|
||||||
CMYK(u8),
|
CMYK(u8),
|
||||||
|
|
||||||
/// Pixel is CMYK with an alpha channel
|
|
||||||
CMYKA(u8),
|
|
||||||
|
|
||||||
/// Pixel is YCbCr
|
|
||||||
YCbCr(u8),
|
|
||||||
|
|
||||||
/// Pixel has multiple bands/channels
|
|
||||||
Multiband { bit_depth: u8, num_samples: u16 },
|
|
||||||
}
|
|
||||||
impl ColorType {
|
|
||||||
fn bit_depth(&self) -> u8 {
|
|
||||||
match *self {
|
|
||||||
ColorType::Gray(b)
|
|
||||||
| ColorType::RGB(b)
|
|
||||||
| ColorType::Palette(b)
|
|
||||||
| ColorType::GrayA(b)
|
|
||||||
| ColorType::RGBA(b)
|
|
||||||
| ColorType::CMYK(b)
|
|
||||||
| ColorType::CMYKA(b)
|
|
||||||
| ColorType::YCbCr(b)
|
|
||||||
| ColorType::Multiband { bit_depth: b, .. } => b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn num_samples(&self) -> u16 {
|
|
||||||
match *self {
|
|
||||||
ColorType::Gray(_) => 1,
|
|
||||||
ColorType::RGB(_) => 3,
|
|
||||||
ColorType::Palette(_) => 1,
|
|
||||||
ColorType::GrayA(_) => 2,
|
|
||||||
ColorType::RGBA(_) => 4,
|
|
||||||
ColorType::CMYK(_) => 4,
|
|
||||||
ColorType::CMYKA(_) => 5,
|
|
||||||
ColorType::YCbCr(_) => 3,
|
|
||||||
ColorType::Multiband { num_samples, .. } => num_samples,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+20
-236
@@ -2,18 +2,20 @@ macro_rules! tags {
|
|||||||
{
|
{
|
||||||
// Permit arbitrary meta items, which include documentation.
|
// Permit arbitrary meta items, which include documentation.
|
||||||
$( #[$enum_attr:meta] )*
|
$( #[$enum_attr:meta] )*
|
||||||
$vis:vis enum $name:ident($ty:tt) $(unknown(#[$unknown_meta:meta] $unknown_doc:ident))* {
|
$vis:vis enum $name:ident($ty:tt) $(unknown($unknown_doc:literal))* {
|
||||||
// Each of the `Name = Val,` permitting documentation.
|
// Each of the `Name = Val,` permitting documentation.
|
||||||
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
|
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
|
||||||
}
|
}
|
||||||
} => {
|
} => {
|
||||||
$( #[$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,
|
||||||
$(
|
$(
|
||||||
#[$unknown_meta]
|
#[doc = $unknown_doc]
|
||||||
Unknown($ty),
|
Unknown($ty),
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
@@ -31,28 +33,22 @@ macro_rules! tags {
|
|||||||
fn __to_inner_type(&self) -> $ty {
|
fn __to_inner_type(&self) -> $ty {
|
||||||
match *self {
|
match *self {
|
||||||
$( $name::$tag => $val, )*
|
$( $name::$tag => $val, )*
|
||||||
$( $name::Unknown($unknown_doc) => { $unknown_doc }, )*
|
$( $name::Unknown(n) => { $unknown_doc; n }, )*
|
||||||
|
$name::__NonExhaustive => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tags!($name, $ty, $($unknown_doc)*);
|
tags!($name, $ty);
|
||||||
};
|
};
|
||||||
// For u16 tags, provide direct inherent primitive conversion methods.
|
// For u16 tags, provide direct inherent primitive conversion methods.
|
||||||
($name:tt, u16, $($unknown_doc:ident)*) => {
|
($name:tt, u16) => {
|
||||||
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($unknown_doc: u16) -> Self {
|
|
||||||
Self::__from_inner_type($unknown_doc).unwrap_or_else(|_| $name::Unknown($unknown_doc))
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
|
|
||||||
#[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)
|
||||||
@@ -61,16 +57,13 @@ 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, $($unknown_doc:literal)*) => {};
|
($name:tt, $ty:tt) => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
||||||
tags! {
|
tags! {
|
||||||
/// TIFF tags
|
/// TIFF tags
|
||||||
pub enum Tag(u16) unknown(
|
pub enum Tag(u16) unknown("A private or extension tag") {
|
||||||
/// A private or extension tag
|
|
||||||
unknown
|
|
||||||
) {
|
|
||||||
// Baseline tags:
|
// Baseline tags:
|
||||||
Artist = 315,
|
Artist = 315,
|
||||||
// grayscale images PhotometricInterpretation 1 or 3
|
// grayscale images PhotometricInterpretation 1 or 3
|
||||||
@@ -80,6 +73,7 @@ pub enum Tag(u16) unknown(
|
|||||||
// palette-color images (PhotometricInterpretation 3)
|
// palette-color images (PhotometricInterpretation 3)
|
||||||
ColorMap = 320, // TODO add support
|
ColorMap = 320, // TODO add support
|
||||||
Compression = 259, // TODO add support for 2 and 32773
|
Compression = 259, // TODO add support for 2 and 32773
|
||||||
|
Copyright = 33_432,
|
||||||
DateTime = 306,
|
DateTime = 306,
|
||||||
ExtraSamples = 338, // TODO add support
|
ExtraSamples = 338, // TODO add support
|
||||||
FillOrder = 266, // TODO add support
|
FillOrder = 266, // TODO add support
|
||||||
@@ -111,148 +105,44 @@ pub enum Tag(u16) unknown(
|
|||||||
YResolution = 283,
|
YResolution = 283,
|
||||||
// Advanced tags
|
// Advanced tags
|
||||||
Predictor = 317,
|
Predictor = 317,
|
||||||
TileWidth = 322,
|
|
||||||
TileLength = 323,
|
|
||||||
TileOffsets = 324,
|
|
||||||
TileByteCounts = 325,
|
|
||||||
SubIfd = 330,
|
|
||||||
// Data Sample Format
|
|
||||||
SampleFormat = 339,
|
|
||||||
SMinSampleValue = 340, // TODO add support
|
|
||||||
SMaxSampleValue = 341, // TODO add support
|
|
||||||
// JPEG
|
|
||||||
JPEGTables = 347,
|
|
||||||
// GeoTIFF
|
|
||||||
ModelPixelScaleTag = 33550, // (SoftDesk)
|
|
||||||
ModelTransformationTag = 34264, // (JPL Carto Group)
|
|
||||||
ModelTiepointTag = 33922, // (Intergraph)
|
|
||||||
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
|
||||||
// *Do note its typo in the Decimal id*
|
|
||||||
Copyright = 33_432,
|
|
||||||
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
|
||||||
ExifDirectory = 0x8769,
|
|
||||||
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
|
||||||
GpsDirectory = 0x8825,
|
|
||||||
// <https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf>
|
|
||||||
IccProfile = 34675,
|
|
||||||
GeoKeyDirectoryTag = 34735, // (SPOT)
|
|
||||||
GeoDoubleParamsTag = 34736, // (SPOT)
|
|
||||||
GeoAsciiParamsTag = 34737, // (SPOT)
|
|
||||||
ExifVersion = 0x9000,
|
|
||||||
GdalNodata = 42113, // Contains areas with missing data
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifies the offset of an IFD.
|
impl Tag {
|
||||||
///
|
pub fn from_u16_exhaustive(val: u16) -> Self {
|
||||||
/// This is represented as a 64-bit integer but only BigTIFF can utilize the bits. It is encoded
|
Self::from_u16(val).unwrap_or_else(|| Tag::Unknown(val))
|
||||||
/// as 32-bit unsigned value ([`Type::LONG`]) in regular TIFF files and as 64-bit unsigned value
|
}
|
||||||
/// ([`Type::IFD8`]) in BigTIFF files.
|
}
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
|
||||||
// We could be using `NonZeroU64` here but I find that this complicates semantics. This type
|
|
||||||
// represents the integer value stored as a value in a tag. The semantics of treating `0` as an end
|
|
||||||
// marker are imposed by the IFD. (It's unclear if Pointer tags such as Exif would allow `0` but in
|
|
||||||
// practice it just returns garbage and the validity does not matter greatly to us).
|
|
||||||
pub struct IfdPointer(pub u64);
|
|
||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
/// The type of an IFD entry (a 2 byte field).
|
/// The type of an IFD entry (a 2 byte field).
|
||||||
pub enum Type(u16) {
|
pub enum Type(u16) {
|
||||||
/// 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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Type {
|
|
||||||
pub(crate) fn byte_len(&self) -> u8 {
|
|
||||||
match *self {
|
|
||||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
|
||||||
Type::SHORT | Type::SSHORT => 2,
|
|
||||||
Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
|
|
||||||
Type::LONG8
|
|
||||||
| Type::SLONG8
|
|
||||||
| Type::DOUBLE
|
|
||||||
| Type::RATIONAL
|
|
||||||
| Type::SRATIONAL
|
|
||||||
| Type::IFD8 => 8,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn value_bytes(&self, count: u64) -> Result<u64, crate::error::TiffError> {
|
|
||||||
let tag_size = u64::from(self.byte_len());
|
|
||||||
|
|
||||||
match count.checked_mul(tag_size) {
|
|
||||||
Some(n) => Ok(n),
|
|
||||||
None => Err(crate::error::TiffError::LimitsExceeded),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn endian_bytes(self) -> EndianBytes {
|
|
||||||
match self {
|
|
||||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => EndianBytes::One,
|
|
||||||
Type::SHORT | Type::SSHORT => EndianBytes::Two,
|
|
||||||
Type::LONG
|
|
||||||
| Type::SLONG
|
|
||||||
| Type::FLOAT
|
|
||||||
| Type::IFD
|
|
||||||
| Type::RATIONAL
|
|
||||||
| Type::SRATIONAL => EndianBytes::Four,
|
|
||||||
Type::LONG8 | Type::SLONG8 | Type::DOUBLE | Type::IFD8 => EndianBytes::Eight,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
pub enum CompressionMethod(u16) {
|
||||||
/// for reference.
|
|
||||||
pub enum CompressionMethod(u16) unknown(
|
|
||||||
/// A custom compression method
|
|
||||||
unknown
|
|
||||||
) {
|
|
||||||
None = 1,
|
None = 1,
|
||||||
Huffman = 2,
|
Huffman = 2,
|
||||||
Fax3 = 3,
|
Fax3 = 3,
|
||||||
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,
|
||||||
|
|
||||||
// Self-assigned by libtiff
|
|
||||||
ZSTD = 0xC350,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,122 +168,16 @@ pub enum PlanarConfiguration(u16) {
|
|||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
pub enum Predictor(u16) {
|
pub enum Predictor(u16) {
|
||||||
/// No changes were made to the data
|
|
||||||
None = 1,
|
None = 1,
|
||||||
/// The images' rows were processed to contain the difference of each pixel from the previous one.
|
|
||||||
///
|
|
||||||
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
|
|
||||||
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
|
|
||||||
Horizontal = 2,
|
Horizontal = 2,
|
||||||
/// Not currently supported
|
|
||||||
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,
|
||||||
Inch = 2,
|
Inch = 2,
|
||||||
Centimeter = 3,
|
Centimeter = 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tags! {
|
|
||||||
pub enum SampleFormat(u16) unknown(
|
|
||||||
/// An unknown extension sample format
|
|
||||||
unknown
|
|
||||||
) {
|
|
||||||
Uint = 1,
|
|
||||||
Int = 2,
|
|
||||||
IEEEFP = 3,
|
|
||||||
Void = 4,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tags! {
|
|
||||||
pub enum ExtraSamples(u16) {
|
|
||||||
/// There is no specified association between the sample and the image.
|
|
||||||
Unspecified = 0,
|
|
||||||
/// The sample is associated alpha, i.e. pre-multiplied color.
|
|
||||||
AssociatedAlpha = 1,
|
|
||||||
/// The sample is unassociated alpha such as a mask. There might be more than one such sample.
|
|
||||||
UnassociatedAlpha = 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Byte order of the TIFF file.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ByteOrder {
|
|
||||||
/// little endian byte order
|
|
||||||
LittleEndian,
|
|
||||||
/// big endian byte order
|
|
||||||
BigEndian,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ByteOrder {
|
|
||||||
/// Get the byte order representing the running target.
|
|
||||||
///
|
|
||||||
/// The infallibility of this method represents the fact that only little and big endian
|
|
||||||
/// systems are supported by the library. No mixed endian and no other weird stuff. (Note: as
|
|
||||||
/// of Rust 1.90 this is a tautology as Rust itself only has those two kinds).
|
|
||||||
pub const fn native() -> Self {
|
|
||||||
match () {
|
|
||||||
#[cfg(target_endian = "little")]
|
|
||||||
() => ByteOrder::LittleEndian,
|
|
||||||
#[cfg(target_endian = "big")]
|
|
||||||
() => ByteOrder::BigEndian,
|
|
||||||
#[cfg(not(any(target_endian = "big", target_endian = "little")))]
|
|
||||||
() => compile_error!("Unsupported target"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given a typed buffer, convert its contents to the specified byte order in-place.
|
|
||||||
///
|
|
||||||
/// The buffer is assumed to represent an array of the given type. If the length of the buffer
|
|
||||||
/// is not divisible into an integer number of values, the behavior for the remaining bytes it
|
|
||||||
/// not specified.
|
|
||||||
pub fn convert(self, ty: Type, buffer: &mut [u8], to: ByteOrder) {
|
|
||||||
self.convert_endian_bytes(ty.endian_bytes(), buffer, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn convert_endian_bytes(self, cls: EndianBytes, buffer: &mut [u8], to: ByteOrder) {
|
|
||||||
if self == to {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: at MSRV 1.89 or higher use `slice::as_chunks_mut`.
|
|
||||||
match cls {
|
|
||||||
EndianBytes::One => {
|
|
||||||
// No change needed
|
|
||||||
}
|
|
||||||
EndianBytes::Two => {
|
|
||||||
for chunk in buffer.chunks_exact_mut(2) {
|
|
||||||
let chunk: &mut [u8; 2] = chunk.try_into().unwrap();
|
|
||||||
*chunk = u16::from_be_bytes(*chunk).to_le_bytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EndianBytes::Four => {
|
|
||||||
for chunk in buffer.chunks_exact_mut(4) {
|
|
||||||
let chunk: &mut [u8; 4] = chunk.try_into().unwrap();
|
|
||||||
*chunk = u32::from_be_bytes(*chunk).to_le_bytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EndianBytes::Eight => {
|
|
||||||
for chunk in buffer.chunks_exact_mut(8) {
|
|
||||||
let chunk: &mut [u8; 8] = chunk.try_into().unwrap();
|
|
||||||
*chunk = u64::from_be_bytes(*chunk).to_le_bytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The size of individual byte-order corrected elements.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub(crate) enum EndianBytes {
|
|
||||||
One,
|
|
||||||
Two,
|
|
||||||
Four,
|
|
||||||
Eight,
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
subsubifd.tif:
|
|
||||||
url: https://data.kitware.com/api/v1/file/hashsum/sha512/372ca32735c8a8fdbe2286dabead9e779da63f10ba81eda84625e5273f76d74ca1a47a978f67e9c00c12f7f72009c7b2c07a641e643bb0c463812f4ae7f15d6e/download
|
|
||||||
credit: https://github.com/DigitalSlideArchive/tifftools/commit/4d00c70dbce828262f86c1431f7c66b1748965ac
|
|
||||||
license: CC0
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
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.
@@ -1,46 +0,0 @@
|
|||||||
extern crate tiff;
|
|
||||||
|
|
||||||
use tiff::decoder::Decoder;
|
|
||||||
use tiff::tags::Tag;
|
|
||||||
use tiff::ColorType;
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
const TEST_IMAGE_DIR: &str = "./tests/images/bigtiff";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_big_tiff() {
|
|
||||||
let filenames = ["BigTIFF.tif", "BigTIFFMotorola.tif", "BigTIFFLong.tif"];
|
|
||||||
for filename in filenames.iter() {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
assert_eq!(
|
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
|
||||||
(64, 64)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder.colortype().expect("Cannot get colortype"),
|
|
||||||
ColorType::RGB(8)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder
|
|
||||||
.get_tag_u64(Tag::StripOffsets)
|
|
||||||
.expect("Cannot get StripOffsets"),
|
|
||||||
16
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder
|
|
||||||
.get_tag_u64(Tag::RowsPerStrip)
|
|
||||||
.expect("Cannot get RowsPerStrip"),
|
|
||||||
64
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder
|
|
||||||
.get_tag_u64(Tag::StripByteCounts)
|
|
||||||
.expect("Cannot get StripByteCounts"),
|
|
||||||
12288
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
extern crate tiff;
|
|
||||||
|
|
||||||
use tiff::decoder::{Decoder, DecodingResult};
|
|
||||||
use tiff::ColorType;
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
|
||||||
|
|
||||||
/// Test a basic all white image
|
|
||||||
#[test]
|
|
||||||
fn test_white_ieee_fp16() {
|
|
||||||
let filenames = ["white-fp16.tiff"];
|
|
||||||
|
|
||||||
for filename in filenames.iter() {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
assert_eq!(
|
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
|
||||||
(256, 256)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder.colortype().expect("Cannot get colortype"),
|
|
||||||
ColorType::Gray(16)
|
|
||||||
);
|
|
||||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
|
||||||
for p in img {
|
|
||||||
assert!(p == half::f16::from_f32_const(1.0));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("Wrong data type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test a single black pixel, to make sure scaling is ok
|
|
||||||
#[test]
|
|
||||||
fn test_one_black_pixel_ieee_fp16() {
|
|
||||||
let filenames = ["single-black-fp16.tiff"];
|
|
||||||
|
|
||||||
for filename in filenames.iter() {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
assert_eq!(
|
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
|
||||||
(256, 256)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder.colortype().expect("Cannot get colortype"),
|
|
||||||
ColorType::Gray(16)
|
|
||||||
);
|
|
||||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
|
||||||
for (i, p) in img.iter().enumerate() {
|
|
||||||
if i == 0 {
|
|
||||||
assert!(p < &half::f16::from_f32_const(0.001));
|
|
||||||
} else {
|
|
||||||
assert!(p == &half::f16::from_f32_const(1.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("Wrong data type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test white with horizontal differencing predictor
|
|
||||||
#[test]
|
|
||||||
fn test_pattern_horizontal_differencing_ieee_fp16() {
|
|
||||||
let filenames = ["white-fp16-pred2.tiff"];
|
|
||||||
|
|
||||||
for filename in filenames.iter() {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
assert_eq!(
|
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
|
||||||
(256, 256)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder.colortype().expect("Cannot get colortype"),
|
|
||||||
ColorType::Gray(16)
|
|
||||||
);
|
|
||||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
|
||||||
// 0, 2, 5, 8, 12, 16, 255 are black
|
|
||||||
let black = [0, 2, 5, 8, 12, 16, 255];
|
|
||||||
for (i, p) in img.iter().enumerate() {
|
|
||||||
if black.contains(&i) {
|
|
||||||
assert!(p < &half::f16::from_f32_const(0.001));
|
|
||||||
} else {
|
|
||||||
assert!(p == &half::f16::from_f32_const(1.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("Wrong data type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test white with floating point predictor
|
|
||||||
#[test]
|
|
||||||
fn test_pattern_predictor_ieee_fp16() {
|
|
||||||
let filenames = ["white-fp16-pred3.tiff"];
|
|
||||||
|
|
||||||
for filename in filenames.iter() {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
assert_eq!(
|
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
|
||||||
(256, 256)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder.colortype().expect("Cannot get colortype"),
|
|
||||||
ColorType::Gray(16)
|
|
||||||
);
|
|
||||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
|
||||||
// 0, 2, 5, 8, 12, 16, 255 are black
|
|
||||||
let black = [0, 2, 5, 8, 12, 16, 255];
|
|
||||||
for (i, p) in img.iter().enumerate() {
|
|
||||||
if black.contains(&i) {
|
|
||||||
assert!(p < &half::f16::from_f32_const(0.001));
|
|
||||||
} else {
|
|
||||||
assert!(p == &half::f16::from_f32_const(1.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("Wrong data type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test several random images
|
|
||||||
/// we'rell compare against a pnm file, that scales from 0 (for 0.0) to 65767 (for 1.0)
|
|
||||||
#[test]
|
|
||||||
fn test_predictor_ieee_fp16() {
|
|
||||||
// first parse pnm, skip the first 4 \n
|
|
||||||
let pnm_path = PathBuf::from(TEST_IMAGE_DIR).join("random-fp16.pgm");
|
|
||||||
let pnm_bytes = std::fs::read(pnm_path).expect("Failed to read expected PNM file");
|
|
||||||
|
|
||||||
// PGM looks like this:
|
|
||||||
// ---
|
|
||||||
// P5
|
|
||||||
// #Created with GIMP
|
|
||||||
// 16 16
|
|
||||||
// 65535
|
|
||||||
// ... <big-endian bytes>
|
|
||||||
// ---
|
|
||||||
// get index of 4th \n
|
|
||||||
let byte_start = pnm_bytes
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_, &v)| v == b'\n')
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.nth(3)
|
|
||||||
.expect("Must be 4 \\n's");
|
|
||||||
|
|
||||||
let pnm_values: Vec<f32> = pnm_bytes[(byte_start + 1)..]
|
|
||||||
.chunks(2)
|
|
||||||
.map(|slice| {
|
|
||||||
let bts = [slice[0], slice[1]];
|
|
||||||
(u16::from_be_bytes(bts) as f32) / (u16::MAX as f32)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
assert!(pnm_values.len() == 256);
|
|
||||||
|
|
||||||
let filenames = [
|
|
||||||
"random-fp16-pred2.tiff",
|
|
||||||
"random-fp16-pred3.tiff",
|
|
||||||
"random-fp16.tiff",
|
|
||||||
];
|
|
||||||
|
|
||||||
for filename in filenames.iter() {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
assert_eq!(
|
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
|
||||||
(16, 16)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder.colortype().expect("Cannot get colortype"),
|
|
||||||
ColorType::Gray(16)
|
|
||||||
);
|
|
||||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
|
||||||
for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) {
|
|
||||||
assert!((exp - found.to_f32()).abs() < 0.0001);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("Wrong data type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
extern crate tiff;
|
|
||||||
|
|
||||||
use tiff::decoder::{Decoder, DecodingResult};
|
|
||||||
use tiff::tags::Tag;
|
|
||||||
use tiff::ColorType;
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
const TEST_IMAGE_DIR: &str = "./tests/images";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_geo_tiff() {
|
|
||||||
let filenames = ["geo-5b.tif"];
|
|
||||||
for filename in filenames.iter() {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
decoder = decoder.with_limits(tiff::decoder::Limits::unlimited());
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
decoder.dimensions().expect("Cannot get dimensions"),
|
|
||||||
(10, 10)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder.colortype().expect("Cannot get colortype"),
|
|
||||||
ColorType::Multiband {
|
|
||||||
bit_depth: 16,
|
|
||||||
num_samples: 5
|
|
||||||
}
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder
|
|
||||||
.get_tag_u64(Tag::StripOffsets)
|
|
||||||
.expect("Cannot get StripOffsets"),
|
|
||||||
418
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder
|
|
||||||
.get_tag_u64(Tag::RowsPerStrip)
|
|
||||||
.expect("Cannot get RowsPerStrip"),
|
|
||||||
10
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder
|
|
||||||
.get_tag_u64(Tag::StripByteCounts)
|
|
||||||
.expect("Cannot get StripByteCounts"),
|
|
||||||
1000
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
decoder
|
|
||||||
.get_tag(Tag::ModelPixelScaleTag)
|
|
||||||
.expect("Cannot get pixel scale")
|
|
||||||
.into_f64_vec()
|
|
||||||
.expect("Cannot get pixel scale"),
|
|
||||||
vec![60.0, 60.0, 0.0]
|
|
||||||
);
|
|
||||||
let DecodingResult::I16(data) = decoder.read_image().unwrap() else {
|
|
||||||
panic!("Cannot read band data")
|
|
||||||
};
|
|
||||||
assert_eq!(data.len(), 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+6
-567
@@ -4,7 +4,6 @@ use tiff::decoder::{ifd, Decoder, DecodingResult};
|
|||||||
use tiff::ColorType;
|
use tiff::ColorType;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Read, Seek};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
||||||
@@ -22,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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,13 +29,9 @@ 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
|
||||||
@@ -46,12 +41,10 @@ fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
|
|||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
assert!(matches!(
|
assert!(match decoder.read_image() {
|
||||||
decoder.read_image(),
|
Err(tiff::TiffError::UnsupportedError(tiff::TiffUnsupportedError::UnsupportedColorType(_))) => true,
|
||||||
Err(tiff::TiffError::UnsupportedError(
|
_ => false,
|
||||||
tiff::TiffUnsupportedError::UnsupportedColorType(_),
|
});
|
||||||
))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -64,11 +57,6 @@ 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);
|
||||||
@@ -94,20 +82,6 @@ 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);
|
||||||
@@ -133,31 +107,6 @@ 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
|
||||||
@@ -173,7 +122,7 @@ fn test_string_tags() {
|
|||||||
&s,
|
&s,
|
||||||
"GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/"
|
"GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/"
|
||||||
),
|
),
|
||||||
other => unreachable!("Expected Ascii tag from file, got {other:?}"),
|
_ => assert!(false),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,12 +149,6 @@ fn test_decode_data() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn issue_69() {
|
|
||||||
test_image_sum_u16("issue_69_lzw.tiff", ColorType::Gray(16), 1015486);
|
|
||||||
test_image_sum_u16("issue_69_packbits.tiff", ColorType::Gray(16), 1015486);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: GrayA support
|
// TODO: GrayA support
|
||||||
//#[test]
|
//#[test]
|
||||||
//fn test_gray_alpha_u8()
|
//fn test_gray_alpha_u8()
|
||||||
@@ -216,507 +159,3 @@ 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_gray_i1() {
|
|
||||||
test_image_sum_u8("tiled-gray-i1.tif", ColorType::Gray(1), 30531);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tiled_rgb_u8() {
|
|
||||||
test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tiled_rect_rgb_u8() {
|
|
||||||
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_inner_access() {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
|
||||||
|
|
||||||
let c = decoder
|
|
||||||
.get_tag(tiff::tags::Tag::Compression)
|
|
||||||
.unwrap()
|
|
||||||
.into_u16()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(c, tiff::tags::CompressionMethod::None.to_u16());
|
|
||||||
|
|
||||||
// Because the image is uncompressed, reading the first tile directly with the inner reader
|
|
||||||
// should yield the same result as reading it with the decoder's read_chunk method.
|
|
||||||
let first_offset = decoder
|
|
||||||
.get_tag_u32_vec(tiff::tags::Tag::TileOffsets)
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.unwrap();
|
|
||||||
let first_byte_count = decoder
|
|
||||||
.get_tag_u32_vec(tiff::tags::Tag::TileByteCounts)
|
|
||||||
.unwrap()
|
|
||||||
.into_iter()
|
|
||||||
.next()
|
|
||||||
.unwrap();
|
|
||||||
decoder
|
|
||||||
.inner()
|
|
||||||
.seek(std::io::SeekFrom::Start(first_offset as u64))
|
|
||||||
.expect("Cannot seek to tile offset");
|
|
||||||
let mut buf = vec![0; first_byte_count as usize];
|
|
||||||
decoder
|
|
||||||
.inner()
|
|
||||||
.read_exact(&mut buf)
|
|
||||||
.expect("Cannot read tile data");
|
|
||||||
let raw_sum: u64 = buf.into_iter().map(<u64>::from).sum();
|
|
||||||
|
|
||||||
match decoder.read_chunk(0).unwrap() {
|
|
||||||
DecodingResult::U8(chunk) => {
|
|
||||||
let sum: u64 = chunk.into_iter().map(<u64>::from).sum();
|
|
||||||
assert_eq!(sum, raw_sum);
|
|
||||||
}
|
|
||||||
_ => panic!("Wrong bit depth"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* #[test]
|
|
||||||
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_planar_rgb_u8() {
|
|
||||||
test_image_sum_u8("planar-rgb-u8.tif", ColorType::RGB(8), 15417630);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_read_planar_bands() {
|
|
||||||
// gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
|
|
||||||
let file = "planar-rgb-u8.tif";
|
|
||||||
let expected_type = ColorType::RGB(8);
|
|
||||||
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
|
||||||
|
|
||||||
let chunks = decoder.strip_count().unwrap();
|
|
||||||
assert_eq!(chunks as usize, 72);
|
|
||||||
|
|
||||||
// convert -quiet planar-rgb-u8.tif[0] -crop 1x1+0+0 txt:
|
|
||||||
// 0,0: (73,51,30) #49331E srgb(73,51,30)
|
|
||||||
|
|
||||||
// 1st band (red)
|
|
||||||
match decoder.read_chunk(0).unwrap() {
|
|
||||||
DecodingResult::U8(chunk) => {
|
|
||||||
assert_eq!(chunk[0], 73);
|
|
||||||
}
|
|
||||||
_ => panic!("Wrong bit depth"),
|
|
||||||
}
|
|
||||||
// 2nd band (green)
|
|
||||||
match decoder.read_chunk(chunks / 3).unwrap() {
|
|
||||||
DecodingResult::U8(chunk) => {
|
|
||||||
assert_eq!(chunk[0], 51);
|
|
||||||
}
|
|
||||||
_ => panic!("Wrong bit depth"),
|
|
||||||
}
|
|
||||||
// 3rd band (blue)
|
|
||||||
match decoder.read_chunk(chunks / 3 * 2).unwrap() {
|
|
||||||
DecodingResult::U8(chunk) => {
|
|
||||||
assert_eq!(chunk[0], 30);
|
|
||||||
}
|
|
||||||
_ => panic!("Wrong bit depth"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_div_zero() {
|
|
||||||
use tiff::{TiffError, TiffFormatError};
|
|
||||||
|
|
||||||
let image = [
|
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
|
||||||
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 39, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 158, 0, 0, 251, 67, 1, 3, 0,
|
|
||||||
1, 0, 0, 0, 40, 0, 0, 0, 66, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 178, 178, 178, 178,
|
|
||||||
178, 178, 178,
|
|
||||||
];
|
|
||||||
|
|
||||||
let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
|
|
||||||
match err {
|
|
||||||
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
|
||||||
unexpected => panic!("Unexpected error {unexpected}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_too_many_value_bytes() {
|
|
||||||
let image = [
|
|
||||||
73, 73, 43, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 8, 0, 0, 0,
|
|
||||||
23, 0, 12, 0, 0, 65, 4, 0, 1, 6, 0, 0, 1, 16, 0, 1, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 59, 73, 84, 186, 202, 83, 240, 66, 1, 53, 22, 56, 47,
|
|
||||||
0, 0, 0, 0, 0, 0, 1, 222, 4, 0, 58, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 4, 0, 0, 100, 0,
|
|
||||||
0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18,
|
|
||||||
];
|
|
||||||
|
|
||||||
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
|
|
||||||
match error {
|
|
||||||
tiff::TiffError::LimitsExceeded => {}
|
|
||||||
unexpected => panic!("Unexpected error {unexpected}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fuzzer_testcase5() {
|
|
||||||
let image = [
|
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0,
|
|
||||||
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 246, 16, 0, 0, 22, 1, 4, 0, 1,
|
|
||||||
0, 0, 0, 40, 0, 251, 255, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 178, 178, 178, 178,
|
|
||||||
178, 178, 178,
|
|
||||||
];
|
|
||||||
|
|
||||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fuzzer_testcase1() {
|
|
||||||
let image = [
|
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1,
|
|
||||||
0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 17, 1, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0,
|
|
||||||
22, 1, 4, 0, 1, 0, 0, 0, 44, 0, 0, 0, 23, 1, 4, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 1, 0, 178,
|
|
||||||
178,
|
|
||||||
];
|
|
||||||
|
|
||||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fuzzer_testcase6() {
|
|
||||||
let image = [
|
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0,
|
|
||||||
0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, 22,
|
|
||||||
1, 4, 0, 1, 0, 0, 0, 44, 0, 248, 255, 23, 1, 4, 0, 1, 0, 0, 0, 178, 178, 178, 0, 1, 178,
|
|
||||||
178, 178,
|
|
||||||
];
|
|
||||||
|
|
||||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn oom() {
|
|
||||||
let image = [
|
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
|
||||||
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0,
|
|
||||||
17, 1, 4, 0, 1, 0, 0, 0, 3, 77, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
|
|
||||||
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178,
|
|
||||||
];
|
|
||||||
|
|
||||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fuzzer_testcase4() {
|
|
||||||
let image = [
|
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
|
||||||
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
|
|
||||||
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13,
|
|
||||||
];
|
|
||||||
|
|
||||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fuzzer_testcase2() {
|
|
||||||
let image = [
|
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0,
|
|
||||||
0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0,
|
|
||||||
73, 42, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 158, 0, 0, 251, 3, 1,
|
|
||||||
3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 4, 61, 1, 18, 0, 1, 0, 0, 0, 202, 0, 0, 0, 17,
|
|
||||||
1, 100, 0, 129, 0, 0, 0, 0, 0, 0, 0, 232, 254, 252, 255, 254, 255, 255, 255, 1, 29, 0, 0,
|
|
||||||
22, 1, 3, 0, 1, 0, 0, 0, 16, 0, 0, 0, 23, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 123, 73, 254, 0,
|
|
||||||
73,
|
|
||||||
];
|
|
||||||
|
|
||||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_jpeg_tag_2() {
|
|
||||||
let image = [
|
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 242, 0, 1, 4, 0, 1, 0,
|
|
||||||
0, 0, 0, 129, 16, 0, 1, 1, 4, 0, 1, 0, 0, 0, 214, 0, 0, 248, 253, 1, 3, 0, 1, 0, 0, 0, 64,
|
|
||||||
0, 0, 0, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 64, 14, 1, 0,
|
|
||||||
2, 0, 0, 148, 0, 206, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
|
||||||
1, 0, 0, 0, 22, 1, 4, 0, 17, 0, 0, 201, 1, 0, 0, 0, 23, 1, 2, 0, 20, 0, 0, 0, 194, 0, 0, 0,
|
|
||||||
91, 1, 7, 0, 5, 0, 0, 0, 64, 0, 0, 0, 237, 254, 65, 255, 255, 255, 255, 255, 1, 0, 0, 0,
|
|
||||||
22, 1, 4, 0, 1, 0, 0, 0, 42, 0, 0, 0, 23, 1, 255, 255, 255, 255, 255, 36, 36, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0,
|
|
||||||
];
|
|
||||||
|
|
||||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fuzzer_testcase3() {
|
|
||||||
let image = [
|
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22,
|
|
||||||
128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 1, 1, 3, 0, 1, 0, 0, 0, 17, 1, 0, 231, 22, 1, 1, 0, 1, 0, 0, 0, 130, 0, 0, 0, 23, 1, 4,
|
|
||||||
0, 14, 0, 0, 0, 0, 0, 0, 0, 133, 133, 133, 77, 77, 77, 0, 0, 22, 128, 0, 255, 255, 255,
|
|
||||||
255, 255,
|
|
||||||
];
|
|
||||||
|
|
||||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn timeout() {
|
|
||||||
use tiff::{TiffError, TiffFormatError};
|
|
||||||
|
|
||||||
let image = [
|
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 68, 0, 0, 0, 2, 0, 32, 254, 252, 0, 109,
|
|
||||||
0, 129, 0, 0, 0, 32, 0, 58, 0, 1, 4, 0, 1, 0, 6, 0, 0, 0, 8, 0, 0, 1, 73, 73, 42, 0, 8, 0,
|
|
||||||
0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 21, 0, 0, 0, 61, 1, 255, 128, 9, 0, 0, 8, 0, 1, 113, 2,
|
|
||||||
3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 112, 0, 0, 36, 0, 0,
|
|
||||||
0, 112, 56, 200, 0, 5, 0, 0, 64, 0, 0, 1, 0, 4, 0, 0, 0, 2, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0,
|
|
||||||
0, 0, 4, 17, 1, 1, 0, 93, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 3, 6, 0, 231, 22, 1,
|
|
||||||
1, 0, 1, 0, 0, 0, 2, 64, 118, 36, 23, 1, 1, 0, 43, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 8,
|
|
||||||
0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32,
|
|
||||||
];
|
|
||||||
|
|
||||||
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
|
||||||
|
|
||||||
match error {
|
|
||||||
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
|
||||||
e => panic!("Unexpected error {e:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_no_rows_per_strip() {
|
|
||||||
test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_predictor_3_rgb_f32() {
|
|
||||||
test_image_sum_f32("predictor-3-rgb-f32.tif", ColorType::RGB(32), 54004.33);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_predictor_3_gray_f32() {
|
|
||||||
test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "zstd")]
|
|
||||||
fn test_zstd_compression() {
|
|
||||||
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
|
|
||||||
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_image_bytes(
|
|
||||||
file: &str,
|
|
||||||
expected: &[(ColorType, u32)],
|
|
||||||
// Represent all samples as little endian, ensuring check sums on all platforms.
|
|
||||||
normalize_byte_order: fn(&mut [u8]),
|
|
||||||
) {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
|
|
||||||
let mut buffer = vec![0u8; 0];
|
|
||||||
for (idx, &(expected_type, expected_crc)) in expected.iter().enumerate() {
|
|
||||||
if idx > 0 {
|
|
||||||
decoder
|
|
||||||
.next_image()
|
|
||||||
.expect("mismatched number of directories");
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
|
||||||
let layout = decoder
|
|
||||||
.image_buffer_layout()
|
|
||||||
.expect("Cannot describe buffer layout");
|
|
||||||
|
|
||||||
buffer.resize(layout.len, 0u8);
|
|
||||||
decoder.read_image_bytes(&mut buffer).unwrap();
|
|
||||||
normalize_byte_order(&mut buffer);
|
|
||||||
|
|
||||||
// Well almost a crc..
|
|
||||||
assert_eq!(crc32fast::hash(&buffer), expected_crc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn byte_order_u16(bytes: &mut [u8]) {
|
|
||||||
for chunk in bytes.chunks_exact_mut(2) {
|
|
||||||
let n = u16::from_ne_bytes(chunk.try_into().unwrap());
|
|
||||||
chunk.copy_from_slice(&n.to_le_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn byte_order_u32(bytes: &mut [u8]) {
|
|
||||||
for chunk in bytes.chunks_exact_mut(4) {
|
|
||||||
let n = u32::from_ne_bytes(chunk.try_into().unwrap());
|
|
||||||
chunk.copy_from_slice(&n.to_le_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn byte_order_u64(bytes: &mut [u8]) {
|
|
||||||
for chunk in bytes.chunks_exact_mut(8) {
|
|
||||||
let n = u64::from_ne_bytes(chunk.try_into().unwrap());
|
|
||||||
chunk.copy_from_slice(&n.to_le_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bytes_cmyk_3c_8b() {
|
|
||||||
test_image_bytes(
|
|
||||||
"cmyk-3c-8b.tiff",
|
|
||||||
&[(ColorType::CMYK(8), 2935032230)],
|
|
||||||
|_| (),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bytes_gray_16b() {
|
|
||||||
test_image_bytes(
|
|
||||||
"minisblack-1c-16b.tiff",
|
|
||||||
&[(ColorType::Gray(16), 3524597615)],
|
|
||||||
byte_order_u16,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bytes_gray_32b() {
|
|
||||||
test_image_bytes(
|
|
||||||
"gradient-1c-32b.tiff",
|
|
||||||
&[(ColorType::Gray(32), 2257036530)],
|
|
||||||
byte_order_u32,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bytes_gray_u64() {
|
|
||||||
test_image_bytes(
|
|
||||||
"gradient-1c-64b.tiff",
|
|
||||||
&[(ColorType::Gray(64), 2156547113)],
|
|
||||||
byte_order_u64,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bytes_gray_f32() {
|
|
||||||
test_image_bytes(
|
|
||||||
"gradient-1c-32b-float.tiff",
|
|
||||||
&[(ColorType::Gray(32), 3232356730)],
|
|
||||||
byte_order_u32,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "fax")]
|
|
||||||
fn test_fax4() {
|
|
||||||
test_image_sum_u8("fax4.tiff", ColorType::Gray(1), 7802706);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "fax")]
|
|
||||||
fn test_fax4_white_is_min() {
|
|
||||||
test_image_sum_u8("imagemagick_group4.tiff", ColorType::Gray(1), 3742820);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn write_extra_bits_image() {
|
|
||||||
use tiff::{encoder, tags::ExtraSamples};
|
|
||||||
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join("extra_bits_rgb_8b.tiff");
|
|
||||||
let img_file = File::create(path).expect("Cannot find test image!");
|
|
||||||
|
|
||||||
let mut encoder = encoder::TiffEncoder::new(img_file).expect("Cannot create encoder");
|
|
||||||
let mut img = encoder.new_image::<encoder::colortype::RGB8>(8, 8).unwrap();
|
|
||||||
|
|
||||||
img.extra_samples(&[ExtraSamples::Unspecified]).unwrap();
|
|
||||||
|
|
||||||
// all pixels are supposed to be 0, the extra is 1
|
|
||||||
let data: [u8; 256] = core::array::from_fn(|i| (i as u8 % 4).saturating_sub(2));
|
|
||||||
|
|
||||||
img.write_data(&data)
|
|
||||||
.expect("correct amount of data to write");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn extra_bits_gray() {
|
|
||||||
test_image_sum_u8(
|
|
||||||
"extra_bits_gray_8b.tiff",
|
|
||||||
ColorType::Multiband {
|
|
||||||
bit_depth: 8,
|
|
||||||
num_samples: 2,
|
|
||||||
},
|
|
||||||
64,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn extra_bits_rgb() {
|
|
||||||
test_image_sum_u8("extra_bits_rgb_8b.tiff", ColorType::RGB(8), 0);
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
use tiff::decoder::{Decoder, TiffCodingUnit};
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_all_planes() {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join("planar-rgb-u8.tif");
|
|
||||||
let file = File::open(path).unwrap();
|
|
||||||
let io = std::io::BufReader::new(file);
|
|
||||||
|
|
||||||
let mut tif = Decoder::new(io).unwrap();
|
|
||||||
let layout = tif.image_buffer_layout().unwrap();
|
|
||||||
assert_eq!(layout.planes, 3);
|
|
||||||
let plane_stride = layout.plane_stride.map_or(0, |x| x.get());
|
|
||||||
|
|
||||||
eprintln!("{:?}", layout);
|
|
||||||
let mut buffer = vec![0u8; layout.complete_len];
|
|
||||||
tif.read_image_bytes(&mut buffer).unwrap();
|
|
||||||
|
|
||||||
// Mainly you can see: these are different and non-zero. Otherwise these magic constants just
|
|
||||||
// depend on the file itself that is being used.
|
|
||||||
const PLANE_EXCPECTED_SUMS: [u32; 3] = [15417630, 13007788, 11103530];
|
|
||||||
|
|
||||||
for (plane, expected) in (0..layout.planes).zip(PLANE_EXCPECTED_SUMS) {
|
|
||||||
let plane_data = &buffer[plane_stride * plane..][..layout.len];
|
|
||||||
let sum: u32 = plane_data.iter().copied().map(u32::from).sum();
|
|
||||||
assert_eq!(sum, expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_first_coding_unit() {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join("planar-rgb-u8.tif");
|
|
||||||
let file = File::open(path).unwrap();
|
|
||||||
let io = std::io::BufReader::new(file);
|
|
||||||
|
|
||||||
let mut tif = Decoder::new(io).unwrap();
|
|
||||||
let layout = tif.image_coding_unit_layout(TiffCodingUnit(0)).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(layout.planes, 3);
|
|
||||||
let plane_stride = layout.plane_stride.map_or(0, |x| x.get());
|
|
||||||
eprintln!("{:?}", layout);
|
|
||||||
let mut buffer = vec![0u8; layout.complete_len];
|
|
||||||
|
|
||||||
struct Expected {
|
|
||||||
plane_sums: [u32; 3],
|
|
||||||
unit: TiffCodingUnit,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mainly you can see: these are different and non-zero. Otherwise these magic constants just
|
|
||||||
// depend on the file itself that is being used. Also this varies by what unit we are reading.
|
|
||||||
const PLANE_EXCPECTED_SUMS: [Expected; 2] = [
|
|
||||||
Expected {
|
|
||||||
plane_sums: [856853, 767686, 688532],
|
|
||||||
unit: TiffCodingUnit(0),
|
|
||||||
},
|
|
||||||
Expected {
|
|
||||||
plane_sums: [883679, 797329, 711086],
|
|
||||||
unit: TiffCodingUnit(1),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for expected in PLANE_EXCPECTED_SUMS {
|
|
||||||
tif.read_coding_unit_bytes(expected.unit, &mut buffer)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
for (plane, expected) in (0..layout.planes).zip(expected.plane_sums) {
|
|
||||||
let plane_data = &buffer[plane_stride * plane..][..layout.len];
|
|
||||||
let sum: u32 = plane_data.iter().copied().map(u32::from).sum();
|
|
||||||
assert_eq!(sum, expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+48
-479
@@ -1,9 +1,9 @@
|
|||||||
extern crate tiff;
|
extern crate tiff;
|
||||||
|
|
||||||
use tiff::decoder::{ifd, Decoder, DecodingResult};
|
use tiff::decoder::{Decoder, DecodingResult};
|
||||||
use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder};
|
use tiff::encoder::{colortype, TiffEncoder, SRational};
|
||||||
use tiff::tags::Tag;
|
|
||||||
use tiff::ColorType;
|
use tiff::ColorType;
|
||||||
|
use tiff::tags::Tag;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{Cursor, Seek, SeekFrom};
|
use std::io::{Cursor, Seek, SeekFrom};
|
||||||
@@ -24,67 +24,15 @@ 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).unwrap();
|
tiff.write_image::<colortype::RGB8>(100, 100, &image_data)
|
||||||
image
|
|
||||||
.encoder()
|
|
||||||
.write_tag(Tag::Artist, "Image-tiff")
|
|
||||||
.unwrap();
|
.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));
|
||||||
|
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
||||||
let mut all_tags = decoder
|
|
||||||
.tag_iter()
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
all_tags.sort_by_key(|(t, _)| t.to_u16());
|
|
||||||
assert_eq!(
|
|
||||||
all_tags,
|
|
||||||
vec![
|
|
||||||
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
|
|
||||||
(Tag::ImageLength, ifd::Value::Unsigned(100)),
|
|
||||||
(
|
|
||||||
Tag::BitsPerSample,
|
|
||||||
ifd::Value::List(vec![
|
|
||||||
ifd::Value::Short(8),
|
|
||||||
ifd::Value::Short(8),
|
|
||||||
ifd::Value::Short(8)
|
|
||||||
])
|
|
||||||
),
|
|
||||||
(Tag::Compression, ifd::Value::Short(1)),
|
|
||||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
|
||||||
(Tag::StripOffsets, ifd::Value::Unsigned(35)),
|
|
||||||
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
|
|
||||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
|
||||||
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
|
|
||||||
(Tag::XResolution, ifd::Value::Rational(1, 1)),
|
|
||||||
(Tag::YResolution, ifd::Value::Rational(1, 1)),
|
|
||||||
(Tag::ResolutionUnit, ifd::Value::Short(1)),
|
|
||||||
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
|
|
||||||
(Tag::Predictor, ifd::Value::Short(1)),
|
|
||||||
(
|
|
||||||
Tag::SampleFormat,
|
|
||||||
ifd::Value::List(vec![
|
|
||||||
ifd::Value::Short(1),
|
|
||||||
ifd::Value::Short(1),
|
|
||||||
ifd::Value::Short(1)
|
|
||||||
])
|
|
||||||
),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
decoder.get_tag(Tag::Artist).unwrap(),
|
|
||||||
ifd::Value::Ascii("Image-tiff".into())
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
|
||||||
let _layout = decoder.read_image_to_buffer(&mut buffer).unwrap();
|
|
||||||
if let tiff::decoder::DecodingResult::U8(img_res) = buffer {
|
|
||||||
assert_eq!(image_data, img_res);
|
assert_eq!(image_data, img_res);
|
||||||
} else {
|
} else {
|
||||||
panic!("Wrong data type");
|
panic!("Wrong data type");
|
||||||
@@ -92,136 +40,6 @@ 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));
|
|
||||||
|
|
||||||
let mut all_tags = decoder
|
|
||||||
.tag_iter()
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
all_tags.sort_by_key(|(t, _)| t.to_u16());
|
|
||||||
assert_eq!(
|
|
||||||
all_tags,
|
|
||||||
vec![
|
|
||||||
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
|
|
||||||
(Tag::ImageLength, ifd::Value::Unsigned(100)),
|
|
||||||
(
|
|
||||||
Tag::BitsPerSample,
|
|
||||||
ifd::Value::List(vec![
|
|
||||||
ifd::Value::Short(8),
|
|
||||||
ifd::Value::Short(8),
|
|
||||||
ifd::Value::Short(8)
|
|
||||||
])
|
|
||||||
),
|
|
||||||
(Tag::Compression, ifd::Value::Short(1)),
|
|
||||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
|
||||||
(Tag::StripOffsets, ifd::Value::UnsignedBig(27)),
|
|
||||||
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
|
|
||||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
|
||||||
(Tag::StripByteCounts, ifd::Value::UnsignedBig(30000)),
|
|
||||||
(Tag::XResolution, ifd::Value::Rational(1, 1)),
|
|
||||||
(Tag::YResolution, ifd::Value::Rational(1, 1)),
|
|
||||||
(Tag::ResolutionUnit, ifd::Value::Short(1)),
|
|
||||||
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
|
|
||||||
(Tag::Predictor, ifd::Value::Short(1)),
|
|
||||||
(
|
|
||||||
Tag::SampleFormat,
|
|
||||||
ifd::Value::List(vec![
|
|
||||||
ifd::Value::Short(1),
|
|
||||||
ifd::Value::Short(1),
|
|
||||||
ifd::Value::Short(1)
|
|
||||||
])
|
|
||||||
),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
decoder.get_tag(Tag::Artist).unwrap(),
|
|
||||||
ifd::Value::Ascii("Image-tiff".into())
|
|
||||||
);
|
|
||||||
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
|
||||||
let _layout = decoder.read_image_to_buffer(&mut buffer).unwrap();
|
|
||||||
if let DecodingResult::U8(img_res) = buffer {
|
|
||||||
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.
|
||||||
@@ -240,20 +58,13 @@ 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) => {
|
||||||
#[track_caller]
|
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");
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
|
|
||||||
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
let image_data = match decoder.read_image().unwrap() {
|
||||||
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
|
||||||
|
|
||||||
let image_data = match buffer {
|
|
||||||
DecodingResult::$buffer(res) => res,
|
DecodingResult::$buffer(res) => res,
|
||||||
_ => panic!("Wrong data type"),
|
_ => panic!("Wrong data type"),
|
||||||
};
|
};
|
||||||
@@ -263,15 +74,14 @@ 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).unwrap();
|
tiff.write_image::<C>(width, height, &image_data)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
{
|
{
|
||||||
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
|
||||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||||
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
if let DecodingResult::$buffer(img_res) = decoder.read_image().unwrap() {
|
||||||
if let DecodingResult::$buffer(img_res) = buffer {
|
assert_eq!(image_data, img_res);
|
||||||
assert_eq!(*image_data, img_res);
|
|
||||||
} else {
|
} else {
|
||||||
panic!("Wrong data type");
|
panic!("Wrong data type");
|
||||||
}
|
}
|
||||||
@@ -281,24 +91,15 @@ macro_rules! test_roundtrip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test_roundtrip!(test_u8_roundtrip, U8, u8);
|
test_roundtrip!(test_u8_roundtrip, U8, u8);
|
||||||
test_roundtrip!(test_i8_roundtrip, I8, i8);
|
|
||||||
test_roundtrip!(test_u16_roundtrip, U16, u16);
|
test_roundtrip!(test_u16_roundtrip, U16, u16);
|
||||||
test_roundtrip!(test_i16_roundtrip, I16, i16);
|
|
||||||
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() {
|
||||||
test_u8_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
|
test_u8_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_i8_roundtrip() {
|
|
||||||
test_i8_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rgb_u8_roundtrip() {
|
fn test_rgb_u8_roundtrip() {
|
||||||
test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
|
test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
|
||||||
@@ -309,21 +110,11 @@ fn test_cmyk_u8_roundtrip() {
|
|||||||
test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
|
test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cmyka_u8_roundtrip() {
|
|
||||||
test_u8_roundtrip::<colortype::CMYKA8>("cmyk-4c-8b.tiff", ColorType::CMYKA(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gray_u16_roundtrip() {
|
fn test_gray_u16_roundtrip() {
|
||||||
test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
|
test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_i16_roundtrip() {
|
|
||||||
test_i16_roundtrip::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rgb_u16_roundtrip() {
|
fn test_rgb_u16_roundtrip() {
|
||||||
test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
|
test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
|
||||||
@@ -354,81 +145,6 @@ 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());
|
||||||
@@ -436,33 +152,24 @@ 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
|
encoder.write_tag(Tag::Unknown(65001), &[1_u8, 2][..]).unwrap();
|
||||||
.write_tag(Tag::Unknown(65001), &[1_u8, 2][..])
|
encoder.write_tag(Tag::Unknown(65002), &[1_u8, 2, 3][..]).unwrap();
|
||||||
.unwrap();
|
encoder.write_tag(Tag::Unknown(65003), &[1_u8, 2, 3, 4][..]).unwrap();
|
||||||
encoder
|
encoder.write_tag(Tag::Unknown(65004), &[1_u8, 2, 3, 4, 5][..]).unwrap();
|
||||||
.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.assert_tag_u32_vec(65000), [1]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65000)).unwrap().into_u32_vec().unwrap(), [1]);
|
||||||
assert_eq!(decoder.assert_tag_u32_vec(65001), [1, 2]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65001)).unwrap().into_u32_vec().unwrap(), [1, 2]);
|
||||||
assert_eq!(decoder.assert_tag_u32_vec(65002), [1, 2, 3]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65002)).unwrap().into_u32_vec().unwrap(), [1, 2, 3]);
|
||||||
assert_eq!(decoder.assert_tag_u32_vec(65003), [1, 2, 3, 4]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65003)).unwrap().into_u32_vec().unwrap(), [1, 2, 3, 4]);
|
||||||
assert_eq!(decoder.assert_tag_u32_vec(65004), [1, 2, 3, 4, 5]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65004)).unwrap().into_u32_vec().unwrap(), [1, 2, 3, 4, 5]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,68 +177,31 @@ 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
|
encoder.write_tag(Tag::Unknown(65001), &[-1_i8][..]).unwrap();
|
||||||
.write_tag(Tag::Unknown(65001), &[-1_i8][..])
|
encoder.write_tag(Tag::Unknown(65002), &[-1_i8, 2][..]).unwrap();
|
||||||
.unwrap();
|
encoder.write_tag(Tag::Unknown(65003), &[-1_i8, 2, -3][..]).unwrap();
|
||||||
encoder
|
encoder.write_tag(Tag::Unknown(65004), &[-1_i8, 2, -3, 4][..]).unwrap();
|
||||||
.write_tag(Tag::Unknown(65002), &[-1_i8, 2][..])
|
encoder.write_tag(Tag::Unknown(65005), &[-1_i8, 2, -3, 4, -5][..]).unwrap();
|
||||||
.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
|
encoder.write_tag(Tag::Unknown(65012), &[-1_i16, 2][..]).unwrap();
|
||||||
.write_tag(Tag::Unknown(65012), &[-1_i16, 2][..])
|
encoder.write_tag(Tag::Unknown(65013), &[-1_i16, 2, -3][..]).unwrap();
|
||||||
.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
|
encoder.write_tag(Tag::Unknown(65021), &[-1_i32][..]).unwrap();
|
||||||
.write_tag(Tag::Unknown(65021), &[-1_i32][..])
|
encoder.write_tag(Tag::Unknown(65022), &[-1_i32, 2][..]).unwrap();
|
||||||
.unwrap();
|
|
||||||
encoder
|
|
||||||
.write_tag(Tag::Unknown(65022), &[-1_i32, 2][..])
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
encoder.write_tag(Tag::Unknown(65030), -1_i64).unwrap();
|
encoder.write_tag(Tag::Unknown(65030), SRational { n: -1, d: 100 }).unwrap();
|
||||||
encoder
|
encoder.write_tag(Tag::Unknown(65031), &[SRational { n: -1, d: 100 }, SRational { n: 2, d: 100 }][..]).unwrap();
|
||||||
.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
|
||||||
@@ -539,28 +209,24 @@ fn test_signed() {
|
|||||||
{
|
{
|
||||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||||
|
|
||||||
assert_eq!(decoder.assert_tag_i32(65000), -1);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65000)).unwrap().into_i32().unwrap(), -1, );
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65001), [-1]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65001)).unwrap().into_i32_vec().unwrap(), [-1]);
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65002), [-1, 2]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65002)).unwrap().into_i32_vec().unwrap(), [-1, 2]);
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65003), [-1, 2, -3]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65003)).unwrap().into_i32_vec().unwrap(), [-1, 2, -3]);
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65004), [-1, 2, -3, 4]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65004)).unwrap().into_i32_vec().unwrap(), [-1, 2, -3, 4]);
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65005), [-1, 2, -3, 4, -5],);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65005)).unwrap().into_i32_vec().unwrap(), [-1, 2, -3, 4, -5], );
|
||||||
|
|
||||||
assert_eq!(decoder.assert_tag_i32(65010), -1);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65010)).unwrap().into_i32().unwrap(), -1);
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65011), [-1]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65011)).unwrap().into_i32_vec().unwrap(), [-1]);
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65012), [-1, 2]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65012)).unwrap().into_i32_vec().unwrap(), [-1, 2]);
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65013), [-1, 2, -3]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65013)).unwrap().into_i32_vec().unwrap(), [-1, 2, -3]);
|
||||||
|
|
||||||
assert_eq!(decoder.assert_tag_i32(65020), -1);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65020)).unwrap().into_i32().unwrap(), -1);
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65021), [-1]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65021)).unwrap().into_i32_vec().unwrap(), [-1]);
|
||||||
assert_eq!(decoder.assert_tag_i32_vec(65022), [-1, 2]);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65022)).unwrap().into_i32_vec().unwrap(), [-1, 2]);
|
||||||
|
|
||||||
assert_eq!(decoder.assert_tag_i64(65030), -1);
|
assert_eq!(decoder.get_tag(Tag::Unknown(65030)).unwrap().into_i32_vec().unwrap(), [-1, 100]);
|
||||||
assert_eq!(decoder.assert_tag_i64_vec(65031), [-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(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]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,14 +241,10 @@ 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
|
img_encoder.write_image::<colortype::Gray16>(2, 2, &img1[..]).unwrap();
|
||||||
.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
|
img_encoder.write_image::<colortype::Gray8>(3, 3, &img2[..]).unwrap();
|
||||||
.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
|
||||||
@@ -598,96 +260,3 @@ 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:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_auxiliary_directory() {
|
|
||||||
const ARTIST: &str = "Auxiliary-tiff";
|
|
||||||
|
|
||||||
let mut data = Cursor::new(Vec::new());
|
|
||||||
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
|
||||||
image_encoder.write_strip(&[1]).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let exif = {
|
|
||||||
let mut extra = tiff.extra_directory().unwrap();
|
|
||||||
extra.write_tag(Tag::Artist, ARTIST).unwrap();
|
|
||||||
extra.finish_with_offsets().unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(2, 1).unwrap();
|
|
||||||
image_encoder.write_strip(&[1, 2]).unwrap();
|
|
||||||
let encoder = image_encoder.encoder();
|
|
||||||
encoder.write_tag(Tag::ExifDirectory, exif.offset).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
data.set_position(0);
|
|
||||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
|
||||||
|
|
||||||
{
|
|
||||||
assert_eq!(decoder.dimensions().unwrap(), (1, 1));
|
|
||||||
let _ = decoder.read_image().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
decoder.next_image().unwrap();
|
|
||||||
assert_eq!(decoder.dimensions().unwrap(), (2, 1));
|
|
||||||
let _ = decoder.read_image().unwrap();
|
|
||||||
|
|
||||||
let exif_location = decoder
|
|
||||||
.get_tag(Tag::ExifDirectory)
|
|
||||||
.expect("second directory missing Exif")
|
|
||||||
.into_ifd_pointer()
|
|
||||||
.expect("exif directory is not an IFD pointer");
|
|
||||||
|
|
||||||
let exif = decoder.read_directory(exif_location).unwrap();
|
|
||||||
|
|
||||||
let artist = decoder
|
|
||||||
.read_directory_tags(&exif)
|
|
||||||
.get_tag_ascii_string(Tag::Artist)
|
|
||||||
.expect("EXIF directory missing artist tag");
|
|
||||||
assert_eq!(artist, ARTIST);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
extern crate tiff;
|
|
||||||
|
|
||||||
use std::io::{Cursor, Seek, Write};
|
|
||||||
use tiff::{
|
|
||||||
decoder::{Decoder, DecodingResult},
|
|
||||||
encoder::{
|
|
||||||
colortype::{self, ColorType},
|
|
||||||
compression::*,
|
|
||||||
Compression, TiffEncoder, TiffValue,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>::Inner>> {
|
|
||||||
const WIDTH: u32;
|
|
||||||
const HEIGHT: u32;
|
|
||||||
type Color: ColorType;
|
|
||||||
|
|
||||||
fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
|
|
||||||
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
|
|
||||||
|
|
||||||
fn compress<W: Write + Seek>(&self, encoder: &mut TiffEncoder<W>)
|
|
||||||
where
|
|
||||||
[<Self::Color as ColorType>::Inner]: TiffValue,
|
|
||||||
{
|
|
||||||
let image = encoder
|
|
||||||
.new_image::<Self::Color>(Self::WIDTH, Self::HEIGHT)
|
|
||||||
.unwrap();
|
|
||||||
image.write_data(self.reference_data()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate() -> Self {
|
|
||||||
assert_eq!(
|
|
||||||
Self::Color::BITS_PER_SAMPLE.len(),
|
|
||||||
NUM_CHANNELS,
|
|
||||||
"Incompatible color type"
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut data = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * NUM_CHANNELS);
|
|
||||||
for x in 0..Self::WIDTH {
|
|
||||||
for y in 0..Self::HEIGHT {
|
|
||||||
data.extend(IntoIterator::into_iter(Self::generate_pixel(x, y)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::from(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestImageColor(Vec<u16>);
|
|
||||||
|
|
||||||
impl From<Vec<u16>> for TestImageColor {
|
|
||||||
fn from(value: Vec<u16>) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestImage<3> for TestImageColor {
|
|
||||||
const WIDTH: u32 = 1;
|
|
||||||
const HEIGHT: u32 = 7;
|
|
||||||
type Color = colortype::RGB16;
|
|
||||||
|
|
||||||
fn reference_data(&self) -> &[u16] {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 3] {
|
|
||||||
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
|
|
||||||
[val as <Self::Color as ColorType>::Inner; 3]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestImageGrayscale(Vec<u8>);
|
|
||||||
|
|
||||||
impl From<Vec<u8>> for TestImageGrayscale {
|
|
||||||
fn from(value: Vec<u8>) -> Self {
|
|
||||||
Self(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TestImage<1> for TestImageGrayscale {
|
|
||||||
const WIDTH: u32 = 21;
|
|
||||||
const HEIGHT: u32 = 10;
|
|
||||||
type Color = colortype::Gray8;
|
|
||||||
|
|
||||||
fn reference_data(&self) -> &[u8] {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 1] {
|
|
||||||
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
|
|
||||||
[val as <Self::Color as ColorType>::Inner]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn encode_decode_with_compression(compression: Compression) {
|
|
||||||
let mut data = Cursor::new(Vec::new());
|
|
||||||
|
|
||||||
let image_rgb = TestImageColor::generate();
|
|
||||||
let image_grayscale = TestImageGrayscale::generate();
|
|
||||||
|
|
||||||
// Encode tiff with compression
|
|
||||||
{
|
|
||||||
// Create a multipage image with 2 images
|
|
||||||
let mut encoder = TiffEncoder::new(&mut data)
|
|
||||||
.unwrap()
|
|
||||||
.with_compression(compression);
|
|
||||||
image_rgb.compress(&mut encoder);
|
|
||||||
image_grayscale.compress(&mut encoder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode tiff
|
|
||||||
data.set_position(0);
|
|
||||||
{
|
|
||||||
let mut decoder = Decoder::new(data).unwrap();
|
|
||||||
|
|
||||||
// Check the RGB image
|
|
||||||
assert_eq!(
|
|
||||||
match decoder.read_image() {
|
|
||||||
Ok(DecodingResult::U16(image_data)) => image_data,
|
|
||||||
unexpected => panic!("Descoding RGB failed: {unexpected:?}"),
|
|
||||||
},
|
|
||||||
image_rgb.reference_data()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check the grayscale image
|
|
||||||
decoder.next_image().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
match decoder.read_image() {
|
|
||||||
Ok(DecodingResult::U8(image_data)) => image_data,
|
|
||||||
unexpected => panic!("Decoding grayscale failed: {unexpected:?}"),
|
|
||||||
},
|
|
||||||
image_grayscale.reference_data()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encode_decode_without_compression() {
|
|
||||||
encode_decode_with_compression(Compression::Uncompressed);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "lzw")]
|
|
||||||
fn encode_decode_with_lzw() {
|
|
||||||
encode_decode_with_compression(Compression::Lzw);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "deflate")]
|
|
||||||
fn encode_decode_with_deflate() {
|
|
||||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
|
|
||||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
|
|
||||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encode_decode_with_packbits() {
|
|
||||||
encode_decode_with_compression(Compression::Packbits);
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,231 +0,0 @@
|
|||||||
extern crate tiff;
|
|
||||||
|
|
||||||
use tiff::decoder::{Decoder, DecodingResult};
|
|
||||||
use tiff::encoder::{colortype, Predictor, TiffEncoder};
|
|
||||||
use tiff::ColorType;
|
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Cursor, Seek, SeekFrom};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
|
||||||
|
|
||||||
macro_rules! test_predict {
|
|
||||||
($name:ident, $buffer:ident, $buffer_ty:ty) => {
|
|
||||||
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
|
|
||||||
file: &str,
|
|
||||||
expected_type: ColorType,
|
|
||||||
) {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
|
||||||
let file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(file).expect("Cannot create decoder!");
|
|
||||||
|
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
|
||||||
let image_data = match decoder.read_image().unwrap() {
|
|
||||||
DecodingResult::$buffer(res) => res,
|
|
||||||
_ => panic!("Wrong data type"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut predicted = Vec::with_capacity(image_data.len());
|
|
||||||
C::horizontal_predict(&image_data, &mut predicted);
|
|
||||||
|
|
||||||
let sample_size = C::SAMPLE_FORMAT.len();
|
|
||||||
|
|
||||||
(0..sample_size).for_each(|i| {
|
|
||||||
assert_eq!(predicted[i], image_data[i]);
|
|
||||||
});
|
|
||||||
|
|
||||||
(sample_size..image_data.len()).for_each(|i| {
|
|
||||||
predicted[i] = predicted[i].wrapping_add(predicted[i - sample_size]);
|
|
||||||
assert_eq!(predicted[i], image_data[i]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test_predict!(test_u8_predict, U8, u8);
|
|
||||||
test_predict!(test_i8_predict, I8, i8);
|
|
||||||
test_predict!(test_u16_predict, U16, u16);
|
|
||||||
test_predict!(test_i16_predict, I16, i16);
|
|
||||||
test_predict!(test_u32_predict, U32, u32);
|
|
||||||
test_predict!(test_u64_predict, U64, u64);
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_u8_predict() {
|
|
||||||
test_u8_predict::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_i8_predict() {
|
|
||||||
test_i8_predict::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rgb_u8_predict() {
|
|
||||||
test_u8_predict::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cmyk_u8_predict() {
|
|
||||||
test_u8_predict::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_u16_predict() {
|
|
||||||
test_u16_predict::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_i16_predict() {
|
|
||||||
test_i16_predict::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rgb_u16_predict() {
|
|
||||||
test_u16_predict::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cmyk_u16_predict() {
|
|
||||||
test_u16_predict::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_u32_predict() {
|
|
||||||
test_u32_predict::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rgb_u32_predict() {
|
|
||||||
test_u32_predict::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_u64_predict() {
|
|
||||||
test_u64_predict::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rgb_u64_predict() {
|
|
||||||
test_u64_predict::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ycbcr_u8_predict() {
|
|
||||||
test_u8_predict::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! test_predict_roundtrip {
|
|
||||||
($name:ident, $buffer:ident, $buffer_ty:ty) => {
|
|
||||||
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
|
|
||||||
file: &str,
|
|
||||||
expected_type: ColorType,
|
|
||||||
) {
|
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
|
||||||
|
|
||||||
let image_data = match decoder.read_image().unwrap() {
|
|
||||||
DecodingResult::$buffer(res) => res,
|
|
||||||
_ => panic!("Wrong data type"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut file = Cursor::new(Vec::new());
|
|
||||||
{
|
|
||||||
let mut tiff = TiffEncoder::new(&mut file)
|
|
||||||
.unwrap()
|
|
||||||
.with_predictor(Predictor::Horizontal);
|
|
||||||
|
|
||||||
let (width, height) = decoder.dimensions().unwrap();
|
|
||||||
tiff.write_image::<C>(width, height, &image_data).unwrap();
|
|
||||||
}
|
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
|
||||||
{
|
|
||||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
|
||||||
if let DecodingResult::$buffer(img_res) =
|
|
||||||
decoder.read_image().expect("Decoding image failed")
|
|
||||||
{
|
|
||||||
assert_eq!(image_data, img_res);
|
|
||||||
} else {
|
|
||||||
panic!("Wrong data type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test_predict_roundtrip!(test_u8_predict_roundtrip, U8, u8);
|
|
||||||
test_predict_roundtrip!(test_i8_predict_roundtrip, I8, i8);
|
|
||||||
test_predict_roundtrip!(test_u16_predict_roundtrip, U16, u16);
|
|
||||||
test_predict_roundtrip!(test_i16_predict_roundtrip, I16, i16);
|
|
||||||
test_predict_roundtrip!(test_u32_predict_roundtrip, U32, u32);
|
|
||||||
test_predict_roundtrip!(test_u64_predict_roundtrip, U64, u64);
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_u8_predict_roundtrip() {
|
|
||||||
test_u8_predict_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_i8_predict_roundtrip() {
|
|
||||||
test_i8_predict_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rgb_u8_predict_roundtrip() {
|
|
||||||
test_u8_predict_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cmyk_u8_predict_roundtrip() {
|
|
||||||
test_u8_predict_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_u16_predict_roundtrip() {
|
|
||||||
test_u16_predict_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_i16_predict_roundtrip() {
|
|
||||||
test_i16_predict_roundtrip::<colortype::GrayI16>(
|
|
||||||
"minisblack-1c-i16b.tiff",
|
|
||||||
ColorType::Gray(16),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rgb_u16_predict_roundtrip() {
|
|
||||||
test_u16_predict_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cmyk_u16_predict_roundtrip() {
|
|
||||||
test_u16_predict_roundtrip::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_u32_predict_roundtrip() {
|
|
||||||
test_u32_predict_roundtrip::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rgb_u32_predict_roundtrip() {
|
|
||||||
test_u32_predict_roundtrip::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gray_u64_predict_roundtrip() {
|
|
||||||
test_u64_predict_roundtrip::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_rgb_u64_predict_roundtrip() {
|
|
||||||
test_u64_predict_roundtrip::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ycbcr_u8_predict_roundtrip() {
|
|
||||||
test_u8_predict_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
use tiff::decoder::Decoder;
|
|
||||||
|
|
||||||
// This file has the following IFD structure:
|
|
||||||
//
|
|
||||||
// ```
|
|
||||||
// IFD -> IFD -> IFD
|
|
||||||
// |-> IFD
|
|
||||||
// |-> IFD -> IFD
|
|
||||||
// | \-> IFD -> IFD
|
|
||||||
// \-> IFD
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// where the main chain of IFD has 3 images, the second of which has several sub-IFDs and some have
|
|
||||||
// more nested IFDs.
|
|
||||||
const TEST_IMAGE_SUBIFD: &str = "./tests/subsubifds.tif";
|
|
||||||
use std::fs::File;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn decode_seek_chain() {
|
|
||||||
let file = File::open(TEST_IMAGE_SUBIFD).expect("Cannot open test image");
|
|
||||||
let mut decoder = Decoder::new(file).expect("Invalid format to create decoder");
|
|
||||||
|
|
||||||
// Remember the offsets in the first iteration.
|
|
||||||
let offset0 = decoder.ifd_pointer().expect("First IFD pointer not found");
|
|
||||||
decoder.next_image().unwrap();
|
|
||||||
let offset1 = decoder.ifd_pointer().expect("Second IFD pointer not found");
|
|
||||||
decoder.next_image().unwrap();
|
|
||||||
let offset2 = decoder.ifd_pointer().expect("Third IFD pointer not found");
|
|
||||||
assert!(!decoder.more_images());
|
|
||||||
|
|
||||||
// Ensure we can seek, to do this again.
|
|
||||||
decoder
|
|
||||||
.seek_to_image(0)
|
|
||||||
.expect("Failed to seek to first image");
|
|
||||||
assert_eq!(Some(offset0), decoder.ifd_pointer());
|
|
||||||
decoder.next_image().unwrap();
|
|
||||||
assert_eq!(Some(offset1), decoder.ifd_pointer());
|
|
||||||
decoder.next_image().unwrap();
|
|
||||||
assert_eq!(Some(offset2), decoder.ifd_pointer());
|
|
||||||
assert!(!decoder.more_images());
|
|
||||||
}
|
|
||||||
Binary file not shown.
Reference in new issue
Block a user