From 1b6a04ca5504955c571d1c97504fb45ea0befee4 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Mon, 8 Jan 2024 01:21:28 +0400 Subject: Initial vendor packages Signed-off-by: Valentin Popov --- vendor/tiff/.cargo-checksum.json | 1 + vendor/tiff/CHANGES.md | 196 ++++ vendor/tiff/Cargo.toml | 48 + vendor/tiff/LICENSE | 21 + vendor/tiff/README.md | 54 + vendor/tiff/benches/lzw.rs | 82 ++ vendor/tiff/src/bytecast.rs | 34 + vendor/tiff/src/decoder/ifd.rs | 670 +++++++++++ vendor/tiff/src/decoder/image.rs | 601 ++++++++++ vendor/tiff/src/decoder/mod.rs | 1176 ++++++++++++++++++++ vendor/tiff/src/decoder/stream.rs | 435 ++++++++ vendor/tiff/src/decoder/tag_reader.rs | 45 + vendor/tiff/src/encoder/colortype.rs | 245 ++++ vendor/tiff/src/encoder/compression/deflate.rs | 83 ++ vendor/tiff/src/encoder/compression/lzw.rs | 47 + vendor/tiff/src/encoder/compression/mod.rs | 60 + vendor/tiff/src/encoder/compression/packbits.rs | 214 ++++ .../tiff/src/encoder/compression/uncompressed.rs | 37 + vendor/tiff/src/encoder/mod.rs | 681 ++++++++++++ vendor/tiff/src/encoder/tiff_value.rs | 523 +++++++++ vendor/tiff/src/encoder/writer.rs | 188 ++++ vendor/tiff/src/error.rs | 369 ++++++ vendor/tiff/src/lib.rs | 43 + vendor/tiff/src/tags.rs | 234 ++++ vendor/tiff/tests/benches/README.md | 4 + vendor/tiff/tests/benches/Transparency-lzw.tif | Bin 0 -> 6158 bytes vendor/tiff/tests/benches/kodim02-lzw.tif | Bin 0 -> 726638 bytes vendor/tiff/tests/benches/kodim07-lzw.tif | Bin 0 -> 643034 bytes vendor/tiff/tests/decode_bigtiff_images.rs | 46 + vendor/tiff/tests/decode_images.rs | 479 ++++++++ vendor/tiff/tests/decodedata-rgb-3c-8b.tiff | Bin 0 -> 30180 bytes vendor/tiff/tests/encode_images.rs | 529 +++++++++ .../tiff/tests/encode_images_with_compression.rs | 157 +++ vendor/tiff/tests/fuzz_tests.rs | 51 + 34 files changed, 7353 insertions(+) create mode 100644 vendor/tiff/.cargo-checksum.json create mode 100644 vendor/tiff/CHANGES.md create mode 100644 vendor/tiff/Cargo.toml create mode 100644 vendor/tiff/LICENSE create mode 100644 vendor/tiff/README.md create mode 100644 vendor/tiff/benches/lzw.rs create mode 100644 vendor/tiff/src/bytecast.rs create mode 100644 vendor/tiff/src/decoder/ifd.rs create mode 100644 vendor/tiff/src/decoder/image.rs create mode 100644 vendor/tiff/src/decoder/mod.rs create mode 100644 vendor/tiff/src/decoder/stream.rs create mode 100644 vendor/tiff/src/decoder/tag_reader.rs create mode 100644 vendor/tiff/src/encoder/colortype.rs create mode 100644 vendor/tiff/src/encoder/compression/deflate.rs create mode 100644 vendor/tiff/src/encoder/compression/lzw.rs create mode 100644 vendor/tiff/src/encoder/compression/mod.rs create mode 100644 vendor/tiff/src/encoder/compression/packbits.rs create mode 100644 vendor/tiff/src/encoder/compression/uncompressed.rs create mode 100644 vendor/tiff/src/encoder/mod.rs create mode 100644 vendor/tiff/src/encoder/tiff_value.rs create mode 100644 vendor/tiff/src/encoder/writer.rs create mode 100644 vendor/tiff/src/error.rs create mode 100644 vendor/tiff/src/lib.rs create mode 100644 vendor/tiff/src/tags.rs create mode 100644 vendor/tiff/tests/benches/README.md create mode 100644 vendor/tiff/tests/benches/Transparency-lzw.tif create mode 100644 vendor/tiff/tests/benches/kodim02-lzw.tif create mode 100644 vendor/tiff/tests/benches/kodim07-lzw.tif create mode 100644 vendor/tiff/tests/decode_bigtiff_images.rs create mode 100644 vendor/tiff/tests/decode_images.rs create mode 100644 vendor/tiff/tests/decodedata-rgb-3c-8b.tiff create mode 100644 vendor/tiff/tests/encode_images.rs create mode 100644 vendor/tiff/tests/encode_images_with_compression.rs create mode 100644 vendor/tiff/tests/fuzz_tests.rs (limited to 'vendor/tiff') diff --git a/vendor/tiff/.cargo-checksum.json b/vendor/tiff/.cargo-checksum.json new file mode 100644 index 0000000..2809616 --- /dev/null +++ b/vendor/tiff/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CHANGES.md":"f9bd3690a3ce0c64595af40b6baacc64aabd10d34468bc307d5d67250045063d","Cargo.toml":"519d88ebdcbce493d4cec3bf0b5c7add95529e9b6e462493f71109ae19519839","LICENSE":"20c097d0de27e2adc179140a3d542091951a030699c7e1c78655f2a2d08d4e3e","README.md":"1c17af2aab3dcd95685672aecc5e5b7d1d608d41be9a8b22d327d06ebad0b72a","benches/lzw.rs":"ae9bd8a35ad6826ac204bb03bc6c07fc6070eb69c8f1baf20ed3222e515ca0a5","src/bytecast.rs":"fe162944a473d48d2502eab08f2fdb56e5b546d11cb25833454dd8d6395396ae","src/decoder/ifd.rs":"688e32504179ff53ac28cc5951e114d77a8771f276ec9c5007a861fbe07a77a3","src/decoder/image.rs":"2e22caf9401c84768e2527753fa9d9813886aea158daf4dd53fdf5c142672d1a","src/decoder/mod.rs":"e7cd83c9f5fec43036b9f576748adb76d654fdda42eb0fa64b7e0dce1a907575","src/decoder/stream.rs":"538dca03462c5a8351b558509766e8b9581d9de06571df1dd1fa95ce45918952","src/decoder/tag_reader.rs":"e95db23244da757588f0d885ebce18e189fafc52ac841682fb39371197b6f9d3","src/encoder/colortype.rs":"801eb6111d5c9fece5537a171b4aa3300d3d3f0f6242610ee819595146ea6928","src/encoder/compression/deflate.rs":"634692eb4290659a38f69c8a3635619333779d308d4f1583b0cb9e5974901bc0","src/encoder/compression/lzw.rs":"73076a9f332b9a48be65d19bcb16d25089d1a2812a2bcfe197223f805c34ef5d","src/encoder/compression/mod.rs":"817aceeb78331464e41713d3da97e9b7aabd7f34b34fb8173e2942d7ee039e5b","src/encoder/compression/packbits.rs":"a3b2e93b18ab69d82011ed78c17da050a2271e2253ee6903ec34b0b196577824","src/encoder/compression/uncompressed.rs":"296b865315b9fb0a8927a2862bdff64fcd01f92b38f7120795c2f68e92187f29","src/encoder/mod.rs":"83f91088019d1318edae70d594dae288edbf7e2f1cd4e48d8bffc22ea1f786a7","src/encoder/tiff_value.rs":"df212aa92b53c2e8bdbe0638bce6e0ac477ade8d8b3494f287aceb4dda621ff1","src/encoder/writer.rs":"b9abdc9095ffb5160ac758534e5ff282d90c801fe4a6f4e157c081d646894ee6","src/error.rs":"c7a021b3d30930abfb03e021734f04acb43dc003d817d9301c03e9954875f0d3","src/lib.rs":"6ec15b1e8f824b346520ec94dddb0604ee1a636c20dd96ece326f31351fd0c3c","src/tags.rs":"847cdb026212b7e45d42840b26eb43713f1579d32f51fe3e22140a352e5f1758","tests/benches/README.md":"61c5580a4c667cfe0cb2e63597f84b84e34a3424202d6e3e53954d4c273189f7","tests/benches/Transparency-lzw.tif":"b15297094106a33374672a6971ed1aaa5d2fbf3f3731c61af1107ba3e88af156","tests/benches/kodim02-lzw.tif":"9a9e3cf1c53e13a38f9d6409b0aae3fe214fea1b004ac3e848766b08bde38f66","tests/benches/kodim07-lzw.tif":"068beb46a3d186ed2fa65d5afbdbf1b34a02287b8d6c9e9c858728295ab277eb","tests/decode_bigtiff_images.rs":"5460b4c0be1588a6281c00a23ec1cb2a9b4cf3a5df5434da464ec30b888f6cd2","tests/decode_images.rs":"c1f673e844d43c5a1be3159a1523308b2a665126478ae78ffa7c1313fe81b9ef","tests/decodedata-rgb-3c-8b.tiff":"88fb7908c4562ad3f3a1180c02115da71efc34a6d3b4f0573360113385cef33c","tests/encode_images.rs":"30e49f5aa2169fcb1ec59ffda95bcff4dd567ebb33eff3d81fae606d147cee59","tests/encode_images_with_compression.rs":"0a351c0096ee1db74214d12f1b1669b73419ddd915d4850400774ce2598c53cd","tests/fuzz_tests.rs":"742f4283cdc6c3f5d1ccf320ea6c0eb7f3f517cc831de801206c077a0518ec99"},"package":"6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211"} \ No newline at end of file diff --git a/vendor/tiff/CHANGES.md b/vendor/tiff/CHANGES.md new file mode 100644 index 0000000..fa0b993 --- /dev/null +++ b/vendor/tiff/CHANGES.md @@ -0,0 +1,196 @@ +# 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 + +* Several enumerations are now non_exhaustive for future extensions. + These are `Tag`, `Type`, `Value`, `PhotometricInterpretation`, + `CompressionMethod`, `Predictor`. +* Enums gained a dedicated method to convert to their TIFF variant value with + the specified type. Performing these conversions by casting the discriminant + with `as` is not guaranteed to be stable, except where documented explicitly. +* Removed the num-derive and num dependencies. +* Added support for decoding `deflate` compressed images. +* Make the decoder `Limits` customizable by exposing members. +* Fixed multi-page TIFF encoding writing incorrect offsets. diff --git a/vendor/tiff/Cargo.toml b/vendor/tiff/Cargo.toml new file mode 100644 index 0000000..7d7ed50 --- /dev/null +++ b/vendor/tiff/Cargo.toml @@ -0,0 +1,48 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +rust-version = "1.61.0" +name = "tiff" +version = "0.9.0" +authors = ["The image-rs Developers"] +exclude = [ + "tests/images/*", + "tests/fuzz_images/*", +] +description = "TIFF decoding and encoding library in pure Rust" +readme = "README.md" +categories = [ + "multimedia::images", + "multimedia::encoding", +] +license = "MIT" +repository = "https://github.com/image-rs/image-tiff" +resolver = "2" + +[[bench]] +name = "lzw" +harness = false + +[dependencies.flate2] +version = "1.0.20" + +[dependencies.jpeg] +version = "0.3.0" +default-features = false +package = "jpeg-decoder" + +[dependencies.weezl] +version = "0.1.0" + +[dev-dependencies.criterion] +version = "0.3.1" diff --git a/vendor/tiff/LICENSE b/vendor/tiff/LICENSE new file mode 100644 index 0000000..ad4dc76 --- /dev/null +++ b/vendor/tiff/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 PistonDevelopers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/tiff/README.md b/vendor/tiff/README.md new file mode 100644 index 0000000..f494a50 --- /dev/null +++ b/vendor/tiff/README.md @@ -0,0 +1,54 @@ +# image-tiff +[![Build Status](https://github.com/image-rs/image-tiff/workflows/Rust%20CI/badge.svg)](https://github.com/image-rs/image-tiff/actions) +[![Documentation](https://docs.rs/tiff/badge.svg)](https://docs.rs/tiff) +[![Further crate info](https://img.shields.io/crates/v/tiff.svg)](https://crates.io/crates/tiff) + +TIFF decoding and encoding library in pure Rust + +## Supported + +### Features +- Baseline spec (other than formats and tags listed below as not supported) +- Multipage +- BigTIFF +- Incremental decoding + +### Formats +This table lists photometric interpretations and sample formats which are supported for encoding and decoding. The entries are `ColorType` variants for which sample bit depths are supported. Only samples where all bit depths are equal are currently supported. For example, `RGB(8)` means that the bit depth [8, 8, 8] is supported and will be interpreted as an 8 bit per channel RGB color type. + +| `PhotometricInterpretation` | UINT Format | IEEEFP Format | +| --------------------------- | --------------------------------------- | ------------------------- | +| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | +| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | +| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) | +| `RGBPalette` | | | +| `Mask` | | | +| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) | +| `YCbCr` | | | +| `CIELab` | | | + +### Compressions + +| | Decoding | Encoding | +| -------- | -------- | -------- | +| None | ✓ | ✓ | +| LZW | ✓ | ✓ | +| Deflate | ✓ | ✓ | +| PackBits | ✓ | ✓ | + + +## Not yet supported + +Formats and interpretations not listed above or with empty entries are unsupported. + +- Baseline tags + - `ExtraSamples` +- Extension tags + +## Fuzzing + +This crate uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) in order to test the image parser. + +After installing it with `cargo install cargo-fuzz` on a nightly rustc, the +fuzzing harness can be run with recommended settings using +`cargo fuzz run decode_image -snone -- -timeout=5`. diff --git a/vendor/tiff/benches/lzw.rs b/vendor/tiff/benches/lzw.rs new file mode 100644 index 0000000..47f513b --- /dev/null +++ b/vendor/tiff/benches/lzw.rs @@ -0,0 +1,82 @@ +extern crate criterion; +extern crate tiff; + +use criterion::{ + black_box, measurement::Measurement, BenchmarkGroup, BenchmarkId, Criterion, Throughput, +}; +use tiff::decoder::Decoder; + +fn read_image(image: &[u8]) { + let image = std::io::Cursor::new(image); + let decoder = Decoder::new(black_box(image)); + let mut reader = decoder.unwrap(); + + while { + reader.read_image().unwrap(); + reader.more_images() + } {} +} + +fn main() { + struct BenchDef { + data: &'static [u8], + id: &'static str, + sample_size: usize, + } + + fn run_bench_def(group: &mut BenchmarkGroup, 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, + }, + ); +} diff --git a/vendor/tiff/src/bytecast.rs b/vendor/tiff/src/bytecast.rs new file mode 100644 index 0000000..6e9d762 --- /dev/null +++ b/vendor/tiff/src/bytecast.rs @@ -0,0 +1,34 @@ +//! Trivial, internal byte transmutation. +//! +//! A dependency like bytemuck would give us extra assurance of the safety but overall would not +//! reduce the amount of total unsafety. We don't use it in the interface where the traits would +//! really become useful. +//! +//! SAFETY: These are benign casts as we apply them to fixed size integer types only. All of them +//! are naturally aligned, valid for all bit patterns and their alignment is surely at most their +//! size (we assert the latter fact since it is 'implementation defined' if following the letter of +//! the unsafe code guidelines). +//! +//! TODO: Would like to use std-lib here. +use std::{mem, slice}; + +macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => { + pub(crate) fn $const(slice: &[$int]) -> &[u8] { + assert!(mem::align_of::<$int>() <= mem::size_of::<$int>()); + unsafe { slice::from_raw_parts(slice.as_ptr() as *const u8, mem::size_of_val(slice)) } + } + $(pub(crate) fn $mut(slice: &mut [$int]) -> &mut [u8] { + assert!(mem::align_of::<$int>() <= mem::size_of::<$int>()); + unsafe { slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, mem::size_of_val(slice)) } + })* +}} + +integral_slice_as_bytes!(i8, i8_as_ne_bytes, i8_as_ne_mut_bytes); +integral_slice_as_bytes!(u16, u16_as_ne_bytes, u16_as_ne_mut_bytes); +integral_slice_as_bytes!(i16, i16_as_ne_bytes, i16_as_ne_mut_bytes); +integral_slice_as_bytes!(u32, u32_as_ne_bytes, u32_as_ne_mut_bytes); +integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes); +integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes); +integral_slice_as_bytes!(i64, i64_as_ne_bytes, i64_as_ne_mut_bytes); +integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes); +integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes); diff --git a/vendor/tiff/src/decoder/ifd.rs b/vendor/tiff/src/decoder/ifd.rs new file mode 100644 index 0000000..b05513d --- /dev/null +++ b/vendor/tiff/src/decoder/ifd.rs @@ -0,0 +1,670 @@ +//! Function for reading TIFF tags + +use std::collections::HashMap; +use std::convert::{TryFrom, TryInto}; +use std::io::{self, Read, Seek}; +use std::mem; +use std::str; + +use super::stream::{ByteOrder, EndianReader, SmartReader}; +use crate::tags::{Tag, Type}; +use crate::{TiffError, TiffFormatError, TiffResult}; + +use self::Value::{ + Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig, + Short, Signed, SignedBig, Unsigned, UnsignedBig, +}; + +#[allow(unused_qualifications)] +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub enum Value { + Byte(u8), + Short(u16), + Signed(i32), + SignedBig(i64), + Unsigned(u32), + UnsignedBig(u64), + Float(f32), + Double(f64), + List(Vec), + Rational(u32, u32), + RationalBig(u64, u64), + SRational(i32, i32), + SRationalBig(i64, i64), + Ascii(String), + Ifd(u32), + IfdBig(u64), +} + +impl Value { + pub fn into_u8(self) -> TiffResult { + match self { + Byte(val) => Ok(val), + val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))), + } + } + + pub fn into_u16(self) -> TiffResult { + match self { + Short(val) => Ok(val), + Unsigned(val) => Ok(u16::try_from(val)?), + UnsignedBig(val) => Ok(u16::try_from(val)?), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_u32(self) -> TiffResult { + match self { + Short(val) => Ok(val.into()), + Unsigned(val) => Ok(val), + UnsignedBig(val) => Ok(u32::try_from(val)?), + Ifd(val) => Ok(val), + IfdBig(val) => Ok(u32::try_from(val)?), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_i32(self) -> TiffResult { + match self { + Signed(val) => Ok(val), + SignedBig(val) => Ok(i32::try_from(val)?), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_u64(self) -> TiffResult { + match self { + Short(val) => Ok(val.into()), + Unsigned(val) => Ok(val.into()), + UnsignedBig(val) => Ok(val), + Ifd(val) => Ok(val.into()), + IfdBig(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_i64(self) -> TiffResult { + match self { + Signed(val) => Ok(val.into()), + SignedBig(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_f32(self) -> TiffResult { + match self { + Float(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_f64(self) -> TiffResult { + match self { + Double(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_string(self) -> TiffResult { + match self { + Ascii(val) => Ok(val), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_u32_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_u32()?) + } + Ok(new_vec) + } + Unsigned(val) => Ok(vec![val]), + UnsignedBig(val) => Ok(vec![u32::try_from(val)?]), + 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()), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_u8_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_u8()?) + } + Ok(new_vec) + } + Byte(val) => Ok(vec![val]), + + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_u16_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_u16()?) + } + Ok(new_vec) + } + Short(val) => Ok(vec![val]), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_i32_vec(self) -> TiffResult> { + 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); + new_vec.push(denominator); + } + SRationalBig(numerator, denominator) => { + new_vec.push(i32::try_from(numerator)?); + new_vec.push(i32::try_from(denominator)?); + } + _ => new_vec.push(v.into_i32()?), + } + } + Ok(new_vec) + } + Signed(val) => Ok(vec![val]), + SignedBig(val) => Ok(vec![i32::try_from(val)?]), + SRational(numerator, denominator) => Ok(vec![numerator, denominator]), + SRationalBig(numerator, denominator) => { + Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?]) + } + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } + + pub fn into_f32_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_f32()?) + } + Ok(new_vec) + } + Float(val) => Ok(vec![val]), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_f64_vec(self) -> TiffResult> { + match self { + List(vec) => { + let mut new_vec = Vec::with_capacity(vec.len()); + for v in vec { + new_vec.push(v.into_f64()?) + } + Ok(new_vec) + } + Double(val) => Ok(vec![val]), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_u64_vec(self) -> TiffResult> { + 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) + } + 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()), + val => Err(TiffError::FormatError( + TiffFormatError::UnsignedIntegerExpected(val), + )), + } + } + + pub fn into_i64_vec(self) -> TiffResult> { + 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) + } + Signed(val) => Ok(vec![val.into()]), + SignedBig(val) => Ok(vec![val]), + SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]), + SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]), + val => Err(TiffError::FormatError( + TiffFormatError::SignedIntegerExpected(val), + )), + } + } +} + +#[derive(Clone)] +pub struct Entry { + type_: Type, + count: u64, + offset: [u8; 8], +} + +impl ::std::fmt::Debug for Entry { + fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { + fmt.write_str(&format!( + "Entry {{ type_: {:?}, count: {:?}, offset: {:?} }}", + self.type_, self.count, &self.offset + )) + } +} + +impl Entry { + pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry { + let mut offset = offset.to_vec(); + offset.append(&mut vec![0; 4]); + Entry::new_u64(type_, count.into(), offset[..].try_into().unwrap()) + } + + pub fn new_u64(type_: Type, count: u64, offset: [u8; 8]) -> Entry { + Entry { + type_, + count, + offset, + } + } + + /// Returns a mem_reader for the offset/value field + fn r(&self, byte_order: ByteOrder) -> SmartReader>> { + SmartReader::wrap(io::Cursor::new(self.offset.to_vec()), byte_order) + } + + pub fn val( + &self, + limits: &super::Limits, + bigtiff: bool, + reader: &mut SmartReader, + ) -> TiffResult { + // Case 1: there are no values so we can return immediately. + if self.count == 0 { + return Ok(List(Vec::new())); + } + + let bo = reader.byte_order(); + + let tag_size = match self.type_ { + Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, + Type::SHORT | Type::SSHORT => 2, + Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4, + Type::LONG8 + | Type::SLONG8 + | Type::DOUBLE + | Type::RATIONAL + | Type::SRATIONAL + | Type::IFD8 => 8, + }; + + let value_bytes = match self.count.checked_mul(tag_size) { + Some(n) => n, + None => { + return Err(TiffError::LimitsExceeded); + } + }; + + // Case 2: there is one value. + if self.count == 1 { + // 2a: the value is 5-8 bytes and we're in BigTiff mode. + if bigtiff && value_bytes > 4 && value_bytes <= 8 { + return Ok(match self.type_ { + Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?), + Type::SLONG8 => SignedBig(self.r(bo).read_i64()?), + Type::DOUBLE => Double(self.r(bo).read_f64()?), + Type::RATIONAL => { + let mut r = self.r(bo); + Rational(r.read_u32()?, r.read_u32()?) + } + Type::SRATIONAL => { + let mut r = self.r(bo); + SRational(r.read_i32()?, r.read_i32()?) + } + Type::IFD8 => IfdBig(self.r(bo).read_u64()?), + Type::BYTE + | Type::SBYTE + | Type::ASCII + | Type::UNDEFINED + | Type::SHORT + | Type::SSHORT + | Type::LONG + | Type::SLONG + | Type::FLOAT + | Type::IFD => unreachable!(), + }); + } + + // 2b: the value is at most 4 bytes or doesn't fit in the offset field. + return Ok(match self.type_ { + Type::BYTE => Unsigned(u32::from(self.offset[0])), + Type::SBYTE => Signed(i32::from(self.offset[0] as i8)), + Type::UNDEFINED => Byte(self.offset[0]), + Type::SHORT => Unsigned(u32::from(self.r(bo).read_u16()?)), + Type::SSHORT => Signed(i32::from(self.r(bo).read_i16()?)), + Type::LONG => Unsigned(self.r(bo).read_u32()?), + Type::SLONG => Signed(self.r(bo).read_i32()?), + Type::FLOAT => Float(self.r(bo).read_f32()?), + Type::ASCII => { + if self.offset[0] == 0 { + Ascii("".to_string()) + } else { + return Err(TiffError::FormatError(TiffFormatError::InvalidTag)); + } + } + Type::LONG8 => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + UnsignedBig(reader.read_u64()?) + } + Type::SLONG8 => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + SignedBig(reader.read_i64()?) + } + Type::DOUBLE => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + Double(reader.read_f64()?) + } + Type::RATIONAL => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + Rational(reader.read_u32()?, reader.read_u32()?) + } + Type::SRATIONAL => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + SRational(reader.read_i32()?, reader.read_i32()?) + } + Type::IFD => Ifd(self.r(bo).read_u32()?), + Type::IFD8 => { + reader.goto_offset(self.r(bo).read_u32()?.into())?; + IfdBig(reader.read_u64()?) + } + }); + } + + // Case 3: There is more than one value, but it fits in the offset field. + if value_bytes <= 4 || bigtiff && value_bytes <= 8 { + match self.type_ { + Type::BYTE => return offset_to_bytes(self.count as usize, self), + Type::SBYTE => return offset_to_sbytes(self.count as usize, self), + Type::ASCII => { + let mut buf = vec![0; self.count as usize]; + self.r(bo).read_exact(&mut buf)?; + if buf.is_ascii() && buf.ends_with(&[0]) { + let v = str::from_utf8(&buf)?; + let v = v.trim_matches(char::from(0)); + return Ok(Ascii(v.into())); + } else { + return Err(TiffError::FormatError(TiffFormatError::InvalidTag)); + } + } + Type::UNDEFINED => { + return Ok(List( + self.offset[0..self.count as usize] + .iter() + .map(|&b| Byte(b)) + .collect(), + )); + } + Type::SHORT => { + let mut r = self.r(bo); + let mut v = Vec::new(); + for _ in 0..self.count { + v.push(Short(r.read_u16()?)); + } + return Ok(List(v)); + } + Type::SSHORT => { + let mut r = self.r(bo); + let mut v = Vec::new(); + for _ in 0..self.count { + v.push(Signed(i32::from(r.read_i16()?))); + } + return Ok(List(v)); + } + Type::LONG => { + let mut r = self.r(bo); + let mut v = Vec::new(); + for _ in 0..self.count { + v.push(Unsigned(r.read_u32()?)); + } + return Ok(List(v)); + } + Type::SLONG => { + let mut r = self.r(bo); + let mut v = Vec::new(); + for _ in 0..self.count { + v.push(Signed(r.read_i32()?)); + } + return Ok(List(v)); + } + Type::FLOAT => { + let mut r = self.r(bo); + let mut v = Vec::new(); + for _ in 0..self.count { + v.push(Float(r.read_f32()?)); + } + return Ok(List(v)); + } + Type::IFD => { + let mut r = self.r(bo); + let mut v = Vec::new(); + for _ in 0..self.count { + v.push(Ifd(r.read_u32()?)); + } + return Ok(List(v)); + } + Type::LONG8 + | Type::SLONG8 + | Type::RATIONAL + | Type::SRATIONAL + | Type::DOUBLE + | Type::IFD8 => { + unreachable!() + } + } + } + + // Case 4: there is more than one value, and it doesn't fit in the offset field. + match self.type_ { + // TODO check if this could give wrong results + // at a different endianess of file/computer. + Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + let mut buf = [0; 1]; + reader.read_exact(&mut buf)?; + Ok(UnsignedBig(u64::from(buf[0]))) + }), + Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(SignedBig(i64::from(reader.read_i8()? as i8))) + }), + Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(UnsignedBig(u64::from(reader.read_u16()?))) + }), + Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(SignedBig(i64::from(reader.read_i16()?))) + }), + Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Unsigned(reader.read_u32()?)) + }), + Type::SLONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Signed(reader.read_i32()?)) + }), + Type::FLOAT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Float(reader.read_f32()?)) + }), + Type::DOUBLE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Double(reader.read_f64()?)) + }), + Type::RATIONAL => { + self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Rational(reader.read_u32()?, reader.read_u32()?)) + }) + } + Type::SRATIONAL => { + self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(SRational(reader.read_i32()?, reader.read_i32()?)) + }) + } + Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(UnsignedBig(reader.read_u64()?)) + }), + Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(SignedBig(reader.read_i64()?)) + }), + Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(Ifd(reader.read_u32()?)) + }), + Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + Ok(IfdBig(reader.read_u64()?)) + }), + Type::UNDEFINED => { + self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { + let mut buf = [0; 1]; + reader.read_exact(&mut buf)?; + Ok(Byte(buf[0])) + }) + } + Type::ASCII => { + let n = usize::try_from(self.count)?; + if n > limits.decoding_buffer_size { + return Err(TiffError::LimitsExceeded); + } + + if bigtiff { + reader.goto_offset(self.r(bo).read_u64()?)? + } else { + reader.goto_offset(self.r(bo).read_u32()?.into())? + } + + let mut out = vec![0; n]; + reader.read_exact(&mut out)?; + // Strings may be null-terminated, so we trim anything downstream of the null byte + if let Some(first) = out.iter().position(|&b| b == 0) { + out.truncate(first); + } + Ok(Ascii(String::from_utf8(out)?)) + } + } + } + + #[inline] + fn decode_offset( + &self, + value_count: u64, + bo: ByteOrder, + bigtiff: bool, + limits: &super::Limits, + reader: &mut SmartReader, + decode_fn: F, + ) -> TiffResult + where + R: Read + Seek, + F: Fn(&mut SmartReader) -> TiffResult, + { + let value_count = usize::try_from(value_count)?; + if value_count > limits.decoding_buffer_size / mem::size_of::() { + return Err(TiffError::LimitsExceeded); + } + + let mut v = Vec::with_capacity(value_count); + + let offset = if bigtiff { + self.r(bo).read_u64()? + } else { + self.r(bo).read_u32()?.into() + }; + reader.goto_offset(offset)?; + + for _ in 0..value_count { + v.push(decode_fn(reader)?) + } + Ok(List(v)) + } +} + +/// Extracts a list of BYTE tags stored in an offset +#[inline] +fn offset_to_bytes(n: usize, entry: &Entry) -> TiffResult { + Ok(List( + entry.offset[0..n] + .iter() + .map(|&e| Unsigned(u32::from(e))) + .collect(), + )) +} + +/// Extracts a list of SBYTE tags stored in an offset +#[inline] +fn offset_to_sbytes(n: usize, entry: &Entry) -> TiffResult { + Ok(List( + entry.offset[0..n] + .iter() + .map(|&e| Signed(i32::from(e as i8))) + .collect(), + )) +} + +/// Type representing an Image File Directory +pub type Directory = HashMap; diff --git a/vendor/tiff/src/decoder/image.rs b/vendor/tiff/src/decoder/image.rs new file mode 100644 index 0000000..c037e31 --- /dev/null +++ b/vendor/tiff/src/decoder/image.rs @@ -0,0 +1,601 @@ +use super::ifd::{Directory, Value}; +use super::stream::{ByteOrder, DeflateReader, JpegReader, LZWReader, PackBitsReader}; +use super::tag_reader::TagReader; +use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits}; +use super::{stream::SmartReader, ChunkType}; +use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag}; +use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError}; +use std::convert::{TryFrom, TryInto}; +use std::io::{self, Cursor, Read, Seek}; +use std::sync::Arc; + +#[derive(Debug)] +pub(crate) struct StripDecodeState { + pub rows_per_strip: u32, +} + +#[derive(Debug)] +/// Computed values useful for tile decoding +pub(crate) struct TileAttributes { + pub image_width: usize, + pub image_height: usize, + + pub tile_width: usize, + pub tile_length: usize, +} + +impl TileAttributes { + pub fn tiles_across(&self) -> usize { + (self.image_width + self.tile_width - 1) / self.tile_width + } + pub fn tiles_down(&self) -> usize { + (self.image_height + self.tile_length - 1) / self.tile_length + } + fn padding_right(&self) -> usize { + (self.tile_width - self.image_width % self.tile_width) % self.tile_width + } + fn padding_down(&self) -> usize { + (self.tile_length - self.image_height % self.tile_length) % self.tile_length + } + pub fn get_padding(&self, tile: usize) -> (usize, usize) { + let row = tile / self.tiles_across(); + let column = tile % self.tiles_across(); + + let padding_right = if column == self.tiles_across() - 1 { + self.padding_right() + } else { + 0 + }; + + let padding_down = if row == self.tiles_down() - 1 { + self.padding_down() + } else { + 0 + }; + + (padding_right, padding_down) + } +} + +#[derive(Debug)] +pub(crate) struct Image { + pub ifd: Option, + pub width: u32, + pub height: u32, + pub bits_per_sample: Vec, + #[allow(unused)] + pub samples: u8, + pub sample_format: Vec, + pub photometric_interpretation: PhotometricInterpretation, + pub compression_method: CompressionMethod, + pub predictor: Predictor, + pub jpeg_tables: Option>>, + pub chunk_type: ChunkType, + pub strip_decoder: Option, + pub tile_attributes: Option, + pub chunk_offsets: Vec, + pub chunk_bytes: Vec, +} + +impl Image { + pub fn from_reader( + reader: &mut SmartReader, + ifd: Directory, + limits: &Limits, + bigtiff: bool, + ) -> TiffResult { + let mut tag_reader = TagReader { + reader, + limits, + ifd: &ifd, + bigtiff, + }; + + let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?; + let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?; + if width == 0 || height == 0 { + return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions( + width, height, + ))); + } + + let photometric_interpretation = tag_reader + .find_tag(Tag::PhotometricInterpretation)? + .map(Value::into_u16) + .transpose()? + .and_then(PhotometricInterpretation::from_u16) + .ok_or(TiffUnsupportedError::UnknownInterpretation)?; + + // Try to parse both the compression method and the number, format, and bits of the included samples. + // If they are not explicitly specified, those tags are reset to their default values and not carried from previous images. + let compression_method = match tag_reader.find_tag(Tag::Compression)? { + Some(val) => CompressionMethod::from_u16(val.into_u16()?) + .ok_or(TiffUnsupportedError::UnknownCompressionMethod)?, + None => CompressionMethod::None, + }; + + let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG + && ifd.contains_key(&Tag::JPEGTables) + { + let vec = tag_reader + .find_tag(Tag::JPEGTables)? + .unwrap() + .into_u8_vec()?; + if vec.len() < 2 { + return Err(TiffError::FormatError( + TiffFormatError::InvalidTagValueType(Tag::JPEGTables), + )); + } + + Some(Arc::new(vec)) + } else { + None + }; + + let samples = tag_reader + .find_tag(Tag::SamplesPerPixel)? + .map(Value::into_u16) + .transpose()? + .unwrap_or(1) + .try_into()?; + + let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? { + Some(vals) => { + let sample_format: Vec<_> = vals + .into_iter() + .map(SampleFormat::from_u16_exhaustive) + .collect(); + + // TODO: for now, only homogenous formats across samples are supported. + if !sample_format.windows(2).all(|s| s[0] == s[1]) { + return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into()); + } + + sample_format + } + None => vec![SampleFormat::Uint], + }; + + let bits_per_sample = match samples { + 1 | 3 | 4 => tag_reader + .find_tag_uint_vec(Tag::BitsPerSample)? + .unwrap_or_else(|| vec![1]), + _ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(samples).into()), + }; + + let predictor = tag_reader + .find_tag(Tag::Predictor)? + .map(Value::into_u16) + .transpose()? + .map(|p| { + Predictor::from_u16(p) + .ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p))) + }) + .transpose()? + .unwrap_or(Predictor::None); + + let chunk_type; + let chunk_offsets; + let chunk_bytes; + let strip_decoder; + let tile_attributes; + match ( + ifd.contains_key(&Tag::StripByteCounts), + ifd.contains_key(&Tag::StripOffsets), + ifd.contains_key(&Tag::TileByteCounts), + ifd.contains_key(&Tag::TileOffsets), + ) { + (true, true, false, false) => { + chunk_type = ChunkType::Strip; + + chunk_offsets = tag_reader + .find_tag(Tag::StripOffsets)? + .unwrap() + .into_u64_vec()?; + chunk_bytes = tag_reader + .find_tag(Tag::StripByteCounts)? + .unwrap() + .into_u64_vec()?; + let rows_per_strip = tag_reader + .find_tag(Tag::RowsPerStrip)? + .map(Value::into_u32) + .transpose()? + .unwrap_or(height); + strip_decoder = Some(StripDecodeState { rows_per_strip }); + tile_attributes = None; + + if chunk_offsets.len() != chunk_bytes.len() + || rows_per_strip == 0 + || u32::try_from(chunk_offsets.len())? + != height.saturating_sub(1) / rows_per_strip + 1 + { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } + } + (false, false, true, true) => { + chunk_type = ChunkType::Tile; + + let tile_width = + usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?; + let tile_length = + usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?; + + if tile_width == 0 { + return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into()); + } else if tile_length == 0 { + return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into()); + } + + strip_decoder = None; + tile_attributes = Some(TileAttributes { + image_width: usize::try_from(width)?, + image_height: usize::try_from(height)?, + tile_width, + tile_length, + }); + chunk_offsets = tag_reader + .find_tag(Tag::TileOffsets)? + .unwrap() + .into_u64_vec()?; + chunk_bytes = tag_reader + .find_tag(Tag::TileByteCounts)? + .unwrap() + .into_u64_vec()?; + + let tile = tile_attributes.as_ref().unwrap(); + if chunk_offsets.len() != chunk_bytes.len() + || chunk_offsets.len() != tile.tiles_down() * tile.tiles_across() + { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } + } + (_, _, _, _) => { + return Err(TiffError::FormatError( + TiffFormatError::StripTileTagConflict, + )) + } + }; + + Ok(Image { + ifd: Some(ifd), + width, + height, + bits_per_sample, + samples, + sample_format, + photometric_interpretation, + compression_method, + jpeg_tables, + predictor, + chunk_type, + strip_decoder, + tile_attributes, + chunk_offsets, + chunk_bytes, + }) + } + + pub(crate) fn colortype(&self) -> TiffResult { + match self.photometric_interpretation { + PhotometricInterpretation::RGB => match self.bits_per_sample[..] { + [r, g, b] if [r, r] == [g, b] => Ok(ColorType::RGB(r)), + [r, g, b, a] if [r, r, r] == [g, b, a] => Ok(ColorType::RGBA(r)), + // FIXME: We should _ignore_ other components. In particular: + // > Beware of extra components. Some TIFF files may have more components per pixel + // than you think. A Baseline TIFF reader must skip over them gracefully,using the + // values of the SamplesPerPixel and BitsPerSample fields. + // > -- TIFF 6.0 Specification, Section 7, Additional Baseline requirements. + _ => Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + self.photometric_interpretation, + self.bits_per_sample.clone(), + ), + )), + }, + PhotometricInterpretation::CMYK => match self.bits_per_sample[..] { + [c, m, y, k] if [c, c, c] == [m, y, k] => Ok(ColorType::CMYK(c)), + _ => Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + self.photometric_interpretation, + self.bits_per_sample.clone(), + ), + )), + }, + PhotometricInterpretation::YCbCr => match self.bits_per_sample[..] { + [y, cb, cr] if [y, y] == [cb, cr] => Ok(ColorType::YCbCr(y)), + _ => Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + self.photometric_interpretation, + self.bits_per_sample.clone(), + ), + )), + }, + PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero + if self.bits_per_sample.len() == 1 => + { + Ok(ColorType::Gray(self.bits_per_sample[0])) + } + + // TODO: this is bad we should not fail at this point + _ => Err(TiffError::UnsupportedError( + TiffUnsupportedError::InterpretationWithBits( + self.photometric_interpretation, + self.bits_per_sample.clone(), + ), + )), + } + } + + fn create_reader<'r, R: 'r + Read>( + reader: R, + photometric_interpretation: PhotometricInterpretation, + compression_method: CompressionMethod, + compressed_length: u64, + jpeg_tables: Option>>, + ) -> TiffResult> { + Ok(match compression_method { + CompressionMethod::None => Box::new(reader), + CompressionMethod::LZW => { + Box::new(LZWReader::new(reader, usize::try_from(compressed_length)?)) + } + CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)), + CompressionMethod::Deflate | CompressionMethod::OldDeflate => { + Box::new(DeflateReader::new(reader)) + } + CompressionMethod::ModernJPEG => { + if jpeg_tables.is_some() && compressed_length < 2 { + return Err(TiffError::FormatError( + TiffFormatError::InvalidTagValueType(Tag::JPEGTables), + )); + } + + let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?; + let mut decoder = jpeg::Decoder::new(jpeg_reader); + + match photometric_interpretation { + PhotometricInterpretation::RGB => { + decoder.set_color_transform(jpeg::ColorTransform::RGB) + } + PhotometricInterpretation::WhiteIsZero => { + decoder.set_color_transform(jpeg::ColorTransform::None) + } + PhotometricInterpretation::BlackIsZero => { + decoder.set_color_transform(jpeg::ColorTransform::None) + } + PhotometricInterpretation::TransparencyMask => { + decoder.set_color_transform(jpeg::ColorTransform::None) + } + PhotometricInterpretation::CMYK => { + decoder.set_color_transform(jpeg::ColorTransform::CMYK) + } + PhotometricInterpretation::YCbCr => { + decoder.set_color_transform(jpeg::ColorTransform::YCbCr) + } + photometric_interpretation => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedInterpretation( + photometric_interpretation, + ), + )); + } + } + + let data = decoder.decode()?; + + Box::new(Cursor::new(data)) + } + method => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedCompressionMethod(method), + )) + } + }) + } + + pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> { + let file_offset = self + .chunk_offsets + .get(chunk as usize) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; + + let compressed_bytes = + self.chunk_bytes + .get(chunk as usize) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; + + Ok((*file_offset, *compressed_bytes)) + } + + pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> { + match self.chunk_type { + ChunkType::Strip => { + let strip_attrs = self.strip_decoder.as_ref().unwrap(); + Ok((self.width, strip_attrs.rows_per_strip)) + } + ChunkType::Tile => { + let tile_attrs = self.tile_attributes.as_ref().unwrap(); + Ok(( + u32::try_from(tile_attrs.tile_width)?, + u32::try_from(tile_attrs.tile_length)?, + )) + } + } + } + + pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> { + let dims = self.chunk_dimensions()?; + + match self.chunk_type { + ChunkType::Strip => { + let strip_height_without_padding = chunk_index + .checked_mul(dims.1) + .and_then(|x| self.height.checked_sub(x)) + .ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex( + chunk_index, + )))?; + + // Ignore potential vertical padding on the bottommost strip + let strip_height = dims.1.min(strip_height_without_padding); + + Ok((dims.0, strip_height)) + } + ChunkType::Tile => { + let tile_attrs = self.tile_attributes.as_ref().unwrap(); + let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize); + + let tile_width = tile_attrs.tile_width - padding_right; + let tile_length = tile_attrs.tile_length - padding_down; + + Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?)) + } + } + } + + pub(crate) fn expand_chunk( + &self, + reader: impl Read, + mut buffer: DecodingBuffer, + output_width: usize, + byte_order: ByteOrder, + chunk_index: u32, + ) -> TiffResult<()> { + // Validate that the provided buffer is of the expected type. + let color_type = self.colortype()?; + match (color_type, &buffer) { + (ColorType::RGB(n), _) + | (ColorType::RGBA(n), _) + | (ColorType::CMYK(n), _) + | (ColorType::YCbCr(n), _) + | (ColorType::Gray(n), _) + if usize::from(n) == buffer.byte_len() * 8 => {} + (ColorType::Gray(n), DecodingBuffer::U8(_)) if n < 8 => match self.predictor { + Predictor::None => {} + Predictor::Horizontal => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::HorizontalPredictor(color_type), + )) + } + Predictor::FloatingPoint => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::FloatingPointPredictor(color_type), + )); + } + }, + (type_, _) => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedColorType(type_), + )) + } + } + + // Validate that the predictor is supported for the sample type. + match (self.predictor, &buffer) { + (Predictor::Horizontal, DecodingBuffer::F32(_)) + | (Predictor::Horizontal, DecodingBuffer::F64(_)) => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::HorizontalPredictor(color_type), + )); + } + (Predictor::FloatingPoint, DecodingBuffer::F32(_)) + | (Predictor::FloatingPoint, DecodingBuffer::F64(_)) => {} + (Predictor::FloatingPoint, _) => { + return Err(TiffError::UnsupportedError( + TiffUnsupportedError::FloatingPointPredictor(color_type), + )); + } + _ => {} + } + + let compressed_bytes = + self.chunk_bytes + .get(chunk_index as usize) + .ok_or(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + ))?; + + let byte_len = buffer.byte_len(); + let compression_method = self.compression_method; + let photometric_interpretation = self.photometric_interpretation; + let predictor = self.predictor; + let samples = self.bits_per_sample.len(); + + let chunk_dims = self.chunk_dimensions()?; + let data_dims = self.chunk_data_dimensions(chunk_index)?; + + let padding_right = chunk_dims.0 - data_dims.0; + + let jpeg_tables = self.jpeg_tables.clone(); + let mut reader = Self::create_reader( + reader, + photometric_interpretation, + compression_method, + *compressed_bytes, + jpeg_tables, + )?; + + if output_width == data_dims.0 as usize && padding_right == 0 { + let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples; + let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len]; + reader.read_exact(tile)?; + + for row in 0..data_dims.1 as usize { + let row_start = row as usize * output_width as usize * samples; + let row_end = (row + 1) * output_width as usize * samples; + let row = buffer.subrange(row_start..row_end); + super::fix_endianness_and_predict(row, samples, byte_order, predictor); + } + if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + super::invert_colors(&mut buffer.subrange(0..total_samples), color_type); + } + } else if padding_right > 0 && self.predictor == Predictor::FloatingPoint { + // The floating point predictor shuffles the padding bytes into the encoded output, so + // this case is handled specially when needed. + let mut encoded = vec![0u8; chunk_dims.0 as usize * samples * byte_len]; + + for row in 0..data_dims.1 as usize { + let row_start = row * output_width as usize * samples; + let row_end = row_start + data_dims.0 as usize * samples; + + reader.read_exact(&mut encoded)?; + match buffer.subrange(row_start..row_end) { + DecodingBuffer::F32(buf) => fp_predict_f32(&mut encoded, buf, samples), + DecodingBuffer::F64(buf) => fp_predict_f64(&mut encoded, buf, samples), + _ => unreachable!(), + } + if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type); + } + } + } else { + for row in 0..data_dims.1 as usize { + let row_start = row * output_width as usize * samples; + let row_end = row_start + data_dims.0 as usize * samples; + + let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)]; + reader.read_exact(row)?; + + // Skip horizontal padding + if padding_right > 0 { + let len = u64::try_from(padding_right as usize * samples * byte_len)?; + io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; + } + + let mut row = buffer.subrange(row_start..row_end); + super::fix_endianness_and_predict(row.copy(), samples, byte_order, predictor); + if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { + super::invert_colors(&mut row, color_type); + } + } + } + + Ok(()) + } +} diff --git a/vendor/tiff/src/decoder/mod.rs b/vendor/tiff/src/decoder/mod.rs new file mode 100644 index 0000000..5fa1812 --- /dev/null +++ b/vendor/tiff/src/decoder/mod.rs @@ -0,0 +1,1176 @@ +use std::collections::{HashMap, HashSet}; +use std::convert::TryFrom; +use std::io::{self, Read, Seek}; +use std::ops::Range; + +use crate::{ + bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError, +}; + +use self::ifd::Directory; +use self::image::Image; +use crate::tags::{ + CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type, +}; + +use self::stream::{ByteOrder, EndianReader, SmartReader}; + +pub mod ifd; +mod image; +mod stream; +mod tag_reader; + +/// Result of a decoding process +#[derive(Debug)] +pub enum DecodingResult { + /// A vector of unsigned bytes + U8(Vec), + /// A vector of unsigned words + U16(Vec), + /// A vector of 32 bit unsigned ints + U32(Vec), + /// A vector of 64 bit unsigned ints + U64(Vec), + /// A vector of 32 bit IEEE floats + F32(Vec), + /// A vector of 64 bit IEEE floats + F64(Vec), + /// A vector of 8 bit signed ints + I8(Vec), + /// A vector of 16 bit signed ints + I16(Vec), + /// A vector of 32 bit signed ints + I32(Vec), + /// A vector of 64 bit signed ints + I64(Vec), +} + +impl DecodingResult { + fn new_u8(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::U8(vec![0; size])) + } + } + + fn new_u16(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / 2 { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::U16(vec![0; size])) + } + } + + fn new_u32(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / 4 { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::U32(vec![0; size])) + } + } + + fn new_u64(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / 8 { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::U64(vec![0; size])) + } + } + + fn new_f32(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / std::mem::size_of::() { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::F32(vec![0.0; size])) + } + } + + fn new_f64(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / std::mem::size_of::() { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::F64(vec![0.0; size])) + } + } + + fn new_i8(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / std::mem::size_of::() { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::I8(vec![0; size])) + } + } + + fn new_i16(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / 2 { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::I16(vec![0; size])) + } + } + + fn new_i32(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / 4 { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::I32(vec![0; size])) + } + } + + fn new_i64(size: usize, limits: &Limits) -> TiffResult { + if size > limits.decoding_buffer_size / 8 { + Err(TiffError::LimitsExceeded) + } else { + Ok(DecodingResult::I64(vec![0; size])) + } + } + + pub fn as_buffer(&mut self, start: usize) -> DecodingBuffer { + match *self { + DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]), + DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]), + DecodingResult::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[start..]), + DecodingResult::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[start..]), + DecodingResult::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[start..]), + DecodingResult::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[start..]), + DecodingResult::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[start..]), + DecodingResult::I16(ref mut buf) => DecodingBuffer::I16(&mut buf[start..]), + DecodingResult::I32(ref mut buf) => DecodingBuffer::I32(&mut buf[start..]), + DecodingResult::I64(ref mut buf) => DecodingBuffer::I64(&mut buf[start..]), + } + } +} + +// A buffer for image decoding +pub enum DecodingBuffer<'a> { + /// A slice of unsigned bytes + U8(&'a mut [u8]), + /// A slice of unsigned words + U16(&'a mut [u16]), + /// A slice of 32 bit unsigned ints + U32(&'a mut [u32]), + /// A slice of 64 bit unsigned ints + U64(&'a mut [u64]), + /// A slice of 32 bit IEEE floats + F32(&'a mut [f32]), + /// A slice of 64 bit IEEE floats + F64(&'a mut [f64]), + /// A slice of 8 bits signed ints + I8(&'a mut [i8]), + /// A slice of 16 bits signed ints + I16(&'a mut [i16]), + /// A slice of 32 bits signed ints + I32(&'a mut [i32]), + /// A slice of 64 bits signed ints + I64(&'a mut [i64]), +} + +impl<'a> DecodingBuffer<'a> { + fn byte_len(&self) -> usize { + match *self { + DecodingBuffer::U8(_) => 1, + DecodingBuffer::U16(_) => 2, + DecodingBuffer::U32(_) => 4, + DecodingBuffer::U64(_) => 8, + DecodingBuffer::F32(_) => 4, + DecodingBuffer::F64(_) => 8, + DecodingBuffer::I8(_) => 1, + DecodingBuffer::I16(_) => 2, + DecodingBuffer::I32(_) => 4, + DecodingBuffer::I64(_) => 8, + } + } + + fn copy<'b>(&'b mut self) -> DecodingBuffer<'b> + where + 'a: 'b, + { + match *self { + DecodingBuffer::U8(ref mut buf) => DecodingBuffer::U8(buf), + DecodingBuffer::U16(ref mut buf) => DecodingBuffer::U16(buf), + DecodingBuffer::U32(ref mut buf) => DecodingBuffer::U32(buf), + DecodingBuffer::U64(ref mut buf) => DecodingBuffer::U64(buf), + DecodingBuffer::F32(ref mut buf) => DecodingBuffer::F32(buf), + DecodingBuffer::F64(ref mut buf) => DecodingBuffer::F64(buf), + DecodingBuffer::I8(ref mut buf) => DecodingBuffer::I8(buf), + DecodingBuffer::I16(ref mut buf) => DecodingBuffer::I16(buf), + DecodingBuffer::I32(ref mut buf) => DecodingBuffer::I32(buf), + DecodingBuffer::I64(ref mut buf) => DecodingBuffer::I64(buf), + } + } + + fn subrange<'b>(&'b mut self, range: Range) -> DecodingBuffer<'b> + where + 'a: 'b, + { + match *self { + DecodingBuffer::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[range]), + DecodingBuffer::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[range]), + DecodingBuffer::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[range]), + DecodingBuffer::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[range]), + DecodingBuffer::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[range]), + DecodingBuffer::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[range]), + DecodingBuffer::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[range]), + DecodingBuffer::I16(ref mut buf) => DecodingBuffer::I16(&mut buf[range]), + DecodingBuffer::I32(ref mut buf) => DecodingBuffer::I32(&mut buf[range]), + DecodingBuffer::I64(ref mut buf) => DecodingBuffer::I64(&mut buf[range]), + } + } + + fn as_bytes_mut(&mut self) -> &mut [u8] { + match self { + DecodingBuffer::U8(buf) => &mut *buf, + DecodingBuffer::I8(buf) => bytecast::i8_as_ne_mut_bytes(buf), + DecodingBuffer::U16(buf) => bytecast::u16_as_ne_mut_bytes(buf), + DecodingBuffer::I16(buf) => bytecast::i16_as_ne_mut_bytes(buf), + DecodingBuffer::U32(buf) => bytecast::u32_as_ne_mut_bytes(buf), + DecodingBuffer::I32(buf) => bytecast::i32_as_ne_mut_bytes(buf), + DecodingBuffer::U64(buf) => bytecast::u64_as_ne_mut_bytes(buf), + DecodingBuffer::I64(buf) => bytecast::i64_as_ne_mut_bytes(buf), + DecodingBuffer::F32(buf) => bytecast::f32_as_ne_mut_bytes(buf), + DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +/// Chunk type of the internal representation +pub enum ChunkType { + Strip, + Tile, +} + +/// Decoding limits +#[derive(Clone, Debug)] +pub struct Limits { + /// The maximum size of any `DecodingResult` in bytes, the default is + /// 256MiB. If the entire image is decoded at once, then this will + /// be the maximum size of the image. If it is decoded one strip at a + /// time, this will be the maximum size of a strip. + pub decoding_buffer_size: usize, + /// The maximum size of any ifd value in bytes, the default is + /// 1MiB. + pub ifd_value_size: usize, + /// Maximum size for intermediate buffer which may be used to limit the amount of data read per + /// segment even if the entire image is decoded at once. + pub intermediate_buffer_size: usize, + /// The purpose of this is to prevent all the fields of the struct from + /// being public, as this would make adding new fields a major version + /// bump. + _non_exhaustive: (), +} + +impl Limits { + /// A configuration that does not impose any limits. + /// + /// This is a good start if the caller only wants to impose selective limits, contrary to the + /// default limits which allows selectively disabling limits. + /// + /// Note that this configuration is likely to crash on excessively large images since, + /// naturally, the machine running the program does not have infinite memory. + pub fn unlimited() -> Limits { + Limits { + decoding_buffer_size: usize::max_value(), + ifd_value_size: usize::max_value(), + intermediate_buffer_size: usize::max_value(), + _non_exhaustive: (), + } + } +} + +impl Default for Limits { + fn default() -> Limits { + Limits { + decoding_buffer_size: 256 * 1024 * 1024, + intermediate_buffer_size: 128 * 1024 * 1024, + ifd_value_size: 1024 * 1024, + _non_exhaustive: (), + } + } +} + +/// The representation of a TIFF decoder +/// +/// Currently does not support decoding of interlaced images +#[derive(Debug)] +pub struct Decoder +where + R: Read + Seek, +{ + reader: SmartReader, + bigtiff: bool, + limits: Limits, + next_ifd: Option, + ifd_offsets: Vec, + seen_ifds: HashSet, + image: Image, +} + +trait Wrapping { + fn wrapping_add(&self, other: Self) -> Self; +} + +impl Wrapping for u8 { + fn wrapping_add(&self, other: Self) -> Self { + u8::wrapping_add(*self, other) + } +} + +impl Wrapping for u16 { + fn wrapping_add(&self, other: Self) -> Self { + u16::wrapping_add(*self, other) + } +} + +impl Wrapping for u32 { + fn wrapping_add(&self, other: Self) -> Self { + u32::wrapping_add(*self, other) + } +} + +impl Wrapping for u64 { + fn wrapping_add(&self, other: Self) -> Self { + u64::wrapping_add(*self, other) + } +} + +impl Wrapping for i8 { + fn wrapping_add(&self, other: Self) -> Self { + i8::wrapping_add(*self, other) + } +} + +impl Wrapping for i16 { + fn wrapping_add(&self, other: Self) -> Self { + i16::wrapping_add(*self, other) + } +} + +impl Wrapping for i32 { + fn wrapping_add(&self, other: Self) -> Self { + i32::wrapping_add(*self, other) + } +} + +impl Wrapping for i64 { + fn wrapping_add(&self, other: Self) -> Self { + i64::wrapping_add(*self, other) + } +} + +fn rev_hpredict_nsamp(image: &mut [T], samples: usize) { + for col in samples..image.len() { + image[col] = image[col].wrapping_add(image[col - samples]); + } +} + +pub fn fp_predict_f32(input: &mut [u8], output: &mut [f32], samples: usize) { + rev_hpredict_nsamp(input, samples); + for i in 0..output.len() { + // TODO: use f32::from_be_bytes() when we can (version 1.40) + output[i] = f32::from_bits(u32::from_be_bytes([ + input[input.len() / 4 * 0 + i], + input[input.len() / 4 * 1 + i], + input[input.len() / 4 * 2 + i], + input[input.len() / 4 * 3 + i], + ])); + } +} + +pub fn fp_predict_f64(input: &mut [u8], output: &mut [f64], samples: usize) { + rev_hpredict_nsamp(input, samples); + for i in 0..output.len() { + // TODO: use f64::from_be_bytes() when we can (version 1.40) + output[i] = f64::from_bits(u64::from_be_bytes([ + input[input.len() / 8 * 0 + i], + input[input.len() / 8 * 1 + i], + input[input.len() / 8 * 2 + i], + input[input.len() / 8 * 3 + i], + input[input.len() / 8 * 4 + i], + input[input.len() / 8 * 5 + i], + input[input.len() / 8 * 6 + i], + input[input.len() / 8 * 7 + i], + ])); + } +} + +fn fix_endianness_and_predict( + mut image: DecodingBuffer, + samples: usize, + byte_order: ByteOrder, + predictor: Predictor, +) { + match predictor { + Predictor::None => { + fix_endianness(&mut image, byte_order); + } + Predictor::Horizontal => { + fix_endianness(&mut image, byte_order); + match image { + DecodingBuffer::U8(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::U16(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::U32(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::U64(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::I8(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::I16(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::I32(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::I64(buf) => rev_hpredict_nsamp(buf, samples), + DecodingBuffer::F32(_) | DecodingBuffer::F64(_) => { + unreachable!("Caller should have validated arguments. Please file a bug.") + } + } + } + Predictor::FloatingPoint => { + let mut buffer_copy = image.as_bytes_mut().to_vec(); + match image { + DecodingBuffer::F32(buf) => fp_predict_f32(&mut buffer_copy, buf, samples), + DecodingBuffer::F64(buf) => fp_predict_f64(&mut buffer_copy, buf, samples), + _ => unreachable!("Caller should have validated arguments. Please file a bug."), + } + } + } +} + +fn invert_colors_unsigned(buffer: &mut [T], max: T) +where + T: std::ops::Sub + std::ops::Sub + Copy, +{ + for datum in buffer.iter_mut() { + *datum = max - *datum + } +} + +fn invert_colors_fp(buffer: &mut [T], max: T) +where + T: std::ops::Sub + std::ops::Sub + Copy, +{ + for datum in buffer.iter_mut() { + // FIXME: assumes [0, 1) range for floats + *datum = max - *datum + } +} + +fn invert_colors(buf: &mut DecodingBuffer, color_type: ColorType) { + match (color_type, buf) { + (ColorType::Gray(64), DecodingBuffer::U64(ref mut buffer)) => { + invert_colors_unsigned(buffer, 0xffff_ffff_ffff_ffff); + } + (ColorType::Gray(32), DecodingBuffer::U32(ref mut buffer)) => { + invert_colors_unsigned(buffer, 0xffff_ffff); + } + (ColorType::Gray(16), DecodingBuffer::U16(ref mut buffer)) => { + invert_colors_unsigned(buffer, 0xffff); + } + (ColorType::Gray(n), DecodingBuffer::U8(ref mut buffer)) if n <= 8 => { + invert_colors_unsigned(buffer, 0xff); + } + (ColorType::Gray(32), DecodingBuffer::F32(ref mut buffer)) => { + invert_colors_fp(buffer, 1.0); + } + (ColorType::Gray(64), DecodingBuffer::F64(ref mut buffer)) => { + invert_colors_fp(buffer, 1.0); + } + _ => {} + } +} + +/// Fix endianness. If `byte_order` matches the host, then conversion is a no-op. +fn fix_endianness(buf: &mut DecodingBuffer, byte_order: ByteOrder) { + match byte_order { + ByteOrder::LittleEndian => match buf { + DecodingBuffer::U8(_) | DecodingBuffer::I8(_) => {} + DecodingBuffer::U16(b) => b.iter_mut().for_each(|v| *v = u16::from_le(*v)), + DecodingBuffer::I16(b) => b.iter_mut().for_each(|v| *v = i16::from_le(*v)), + DecodingBuffer::U32(b) => b.iter_mut().for_each(|v| *v = u32::from_le(*v)), + DecodingBuffer::I32(b) => b.iter_mut().for_each(|v| *v = i32::from_le(*v)), + DecodingBuffer::U64(b) => b.iter_mut().for_each(|v| *v = u64::from_le(*v)), + DecodingBuffer::I64(b) => b.iter_mut().for_each(|v| *v = i64::from_le(*v)), + DecodingBuffer::F32(b) => b + .iter_mut() + .for_each(|v| *v = f32::from_bits(u32::from_le(v.to_bits()))), + DecodingBuffer::F64(b) => b + .iter_mut() + .for_each(|v| *v = f64::from_bits(u64::from_le(v.to_bits()))), + }, + ByteOrder::BigEndian => match buf { + DecodingBuffer::U8(_) | DecodingBuffer::I8(_) => {} + DecodingBuffer::U16(b) => b.iter_mut().for_each(|v| *v = u16::from_be(*v)), + DecodingBuffer::I16(b) => b.iter_mut().for_each(|v| *v = i16::from_be(*v)), + DecodingBuffer::U32(b) => b.iter_mut().for_each(|v| *v = u32::from_be(*v)), + DecodingBuffer::I32(b) => b.iter_mut().for_each(|v| *v = i32::from_be(*v)), + DecodingBuffer::U64(b) => b.iter_mut().for_each(|v| *v = u64::from_be(*v)), + DecodingBuffer::I64(b) => b.iter_mut().for_each(|v| *v = i64::from_be(*v)), + DecodingBuffer::F32(b) => b + .iter_mut() + .for_each(|v| *v = f32::from_bits(u32::from_be(v.to_bits()))), + DecodingBuffer::F64(b) => b + .iter_mut() + .for_each(|v| *v = f64::from_bits(u64::from_be(v.to_bits()))), + }, + }; +} + +impl Decoder { + /// Create a new decoder that decodes from the stream ```r``` + pub fn new(mut r: R) -> TiffResult> { + let mut endianess = Vec::with_capacity(2); + (&mut r).take(2).read_to_end(&mut endianess)?; + let byte_order = match &*endianess { + b"II" => ByteOrder::LittleEndian, + b"MM" => ByteOrder::BigEndian, + _ => { + return Err(TiffError::FormatError( + TiffFormatError::TiffSignatureNotFound, + )) + } + }; + let mut reader = SmartReader::wrap(r, byte_order); + + let bigtiff = match reader.read_u16()? { + 42 => false, + 43 => { + // Read bytesize of offsets (in bigtiff it's alway 8 but provide a way to move to 16 some day) + if reader.read_u16()? != 8 { + return Err(TiffError::FormatError( + TiffFormatError::TiffSignatureNotFound, + )); + } + // This constant should always be 0 + if reader.read_u16()? != 0 { + return Err(TiffError::FormatError( + TiffFormatError::TiffSignatureNotFound, + )); + } + true + } + _ => { + return Err(TiffError::FormatError( + TiffFormatError::TiffSignatureInvalid, + )) + } + }; + let next_ifd = if bigtiff { + Some(reader.read_u64()?) + } else { + Some(u64::from(reader.read_u32()?)) + }; + + let mut seen_ifds = HashSet::new(); + seen_ifds.insert(*next_ifd.as_ref().unwrap()); + let ifd_offsets = vec![*next_ifd.as_ref().unwrap()]; + + let mut decoder = Decoder { + reader, + bigtiff, + limits: Default::default(), + next_ifd, + ifd_offsets, + seen_ifds, + image: Image { + ifd: None, + width: 0, + height: 0, + bits_per_sample: vec![1], + samples: 1, + sample_format: vec![SampleFormat::Uint], + photometric_interpretation: PhotometricInterpretation::BlackIsZero, + compression_method: CompressionMethod::None, + jpeg_tables: None, + predictor: Predictor::None, + chunk_type: ChunkType::Strip, + strip_decoder: None, + tile_attributes: None, + chunk_offsets: Vec::new(), + chunk_bytes: Vec::new(), + }, + }; + decoder.next_image()?; + Ok(decoder) + } + + pub fn with_limits(mut self, limits: Limits) -> Decoder { + self.limits = limits; + self + } + + pub fn dimensions(&mut self) -> TiffResult<(u32, u32)> { + Ok((self.image().width, self.image().height)) + } + + pub fn colortype(&mut self) -> TiffResult { + self.image().colortype() + } + + fn image(&self) -> &Image { + &self.image + } + + /// Loads the IFD at the specified index in the list, if one exists + pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> { + // Check whether we have seen this IFD before, if so then the index will be less than the length of the list of ifd offsets + if ifd_index >= self.ifd_offsets.len() { + // We possibly need to load in the next IFD + if self.next_ifd.is_none() { + return Err(TiffError::FormatError( + TiffFormatError::ImageFileDirectoryNotFound, + )); + } + + loop { + // Follow the list until we find the one we want, or we reach the end, whichever happens first + let (_ifd, next_ifd) = self.next_ifd()?; + + if next_ifd.is_none() { + break; + } + + if ifd_index < self.ifd_offsets.len() { + break; + } + } + } + + // If the index is within the list of ifds then we can load the selected image/IFD + if let Some(ifd_offset) = self.ifd_offsets.get(ifd_index) { + let (ifd, _next_ifd) = Self::read_ifd(&mut self.reader, self.bigtiff, *ifd_offset)?; + + self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?; + + Ok(()) + } else { + Err(TiffError::FormatError( + TiffFormatError::ImageFileDirectoryNotFound, + )) + } + } + + fn next_ifd(&mut self) -> TiffResult<(Directory, Option)> { + if self.next_ifd.is_none() { + return Err(TiffError::FormatError( + TiffFormatError::ImageFileDirectoryNotFound, + )); + } + + let (ifd, next_ifd) = Self::read_ifd( + &mut self.reader, + self.bigtiff, + self.next_ifd.take().unwrap(), + )?; + + if let Some(next) = next_ifd { + if !self.seen_ifds.insert(next) { + return Err(TiffError::FormatError(TiffFormatError::CycleInOffsets)); + } + self.next_ifd = Some(next); + self.ifd_offsets.push(next); + } + + Ok((ifd, next_ifd)) + } + + /// Reads in the next image. + /// If there is no further image in the TIFF file a format error is returned. + /// To determine whether there are more images call `TIFFDecoder::more_images` instead. + pub fn next_image(&mut self) -> TiffResult<()> { + let (ifd, _next_ifd) = self.next_ifd()?; + + self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?; + Ok(()) + } + + /// Returns `true` if there is at least one more image available. + pub fn more_images(&self) -> bool { + self.next_ifd.is_some() + } + + /// Returns the byte_order + pub fn byte_order(&self) -> ByteOrder { + self.reader.byte_order + } + + #[inline] + pub fn read_ifd_offset(&mut self) -> Result { + if self.bigtiff { + self.read_long8() + } else { + self.read_long().map(u64::from) + } + } + + /// Reads a TIFF byte value + #[inline] + pub fn read_byte(&mut self) -> Result { + let mut buf = [0; 1]; + self.reader.read_exact(&mut buf)?; + Ok(buf[0]) + } + + /// Reads a TIFF short value + #[inline] + pub fn read_short(&mut self) -> Result { + self.reader.read_u16() + } + + /// Reads a TIFF sshort value + #[inline] + pub fn read_sshort(&mut self) -> Result { + self.reader.read_i16() + } + + /// Reads a TIFF long value + #[inline] + pub fn read_long(&mut self) -> Result { + self.reader.read_u32() + } + + /// Reads a TIFF slong value + #[inline] + pub fn read_slong(&mut self) -> Result { + self.reader.read_i32() + } + + /// Reads a TIFF float value + #[inline] + pub fn read_float(&mut self) -> Result { + self.reader.read_f32() + } + + /// Reads a TIFF double value + #[inline] + pub fn read_double(&mut self) -> Result { + self.reader.read_f64() + } + + #[inline] + pub fn read_long8(&mut self) -> Result { + self.reader.read_u64() + } + + #[inline] + pub fn read_slong8(&mut self) -> Result { + self.reader.read_i64() + } + + /// Reads a string + #[inline] + pub fn read_string(&mut self, length: usize) -> TiffResult { + let mut out = vec![0; length]; + self.reader.read_exact(&mut out)?; + // Strings may be null-terminated, so we trim anything downstream of the null byte + if let Some(first) = out.iter().position(|&b| b == 0) { + out.truncate(first); + } + Ok(String::from_utf8(out)?) + } + + /// Reads a TIFF IFA offset/value field + #[inline] + pub fn read_offset(&mut self) -> TiffResult<[u8; 4]> { + if self.bigtiff { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } + let mut val = [0; 4]; + self.reader.read_exact(&mut val)?; + Ok(val) + } + + /// Reads a TIFF IFA offset/value field + #[inline] + pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> { + let mut val = [0; 8]; + self.reader.read_exact(&mut val)?; + Ok(val) + } + + /// Moves the cursor to the specified offset + #[inline] + pub fn goto_offset(&mut self, offset: u32) -> io::Result<()> { + self.goto_offset_u64(offset.into()) + } + + #[inline] + pub fn goto_offset_u64(&mut self, offset: u64) -> io::Result<()> { + self.reader.seek(io::SeekFrom::Start(offset)).map(|_| ()) + } + + /// Reads a IFD entry. + // An IFD entry has four fields: + // + // Tag 2 bytes + // Type 2 bytes + // Count 4 bytes + // Value 4 bytes either a pointer the value itself + fn read_entry( + reader: &mut SmartReader, + bigtiff: bool, + ) -> TiffResult> { + let tag = Tag::from_u16_exhaustive(reader.read_u16()?); + let type_ = match Type::from_u16(reader.read_u16()?) { + Some(t) => t, + None => { + // Unknown type. Skip this entry according to spec. + reader.read_u32()?; + reader.read_u32()?; + return Ok(None); + } + }; + let entry = if bigtiff { + let mut offset = [0; 8]; + + let count = reader.read_u64()?; + reader.read_exact(&mut offset)?; + ifd::Entry::new_u64(type_, count, offset) + } else { + let mut offset = [0; 4]; + + let count = reader.read_u32()?; + reader.read_exact(&mut offset)?; + ifd::Entry::new(type_, count, offset) + }; + Ok(Some((tag, entry))) + } + + /// Reads the IFD starting at the indicated location. + fn read_ifd( + reader: &mut SmartReader, + bigtiff: bool, + ifd_location: u64, + ) -> TiffResult<(Directory, Option)> { + reader.goto_offset(ifd_location)?; + + let mut dir: Directory = HashMap::new(); + + let num_tags = if bigtiff { + reader.read_u64()? + } else { + reader.read_u16()?.into() + }; + for _ in 0..num_tags { + let (tag, entry) = match Self::read_entry(reader, bigtiff)? { + Some(val) => val, + None => { + continue; + } // Unknown data type in tag, skip + }; + dir.insert(tag, entry); + } + + let next_ifd = if bigtiff { + reader.read_u64()? + } else { + reader.read_u32()?.into() + }; + + let next_ifd = match next_ifd { + 0 => None, + _ => Some(next_ifd), + }; + + Ok((dir, next_ifd)) + } + + /// Tries to retrieve a tag. + /// Return `Ok(None)` if the tag is not present. + pub fn find_tag(&mut self, tag: Tag) -> TiffResult> { + let entry = match self.image().ifd.as_ref().unwrap().get(&tag) { + None => return Ok(None), + Some(entry) => entry.clone(), + }; + + Ok(Some(entry.val( + &self.limits, + self.bigtiff, + &mut self.reader, + )?)) + } + + /// Tries to retrieve a tag and convert it to the desired unsigned type. + pub fn find_tag_unsigned>(&mut self, tag: Tag) -> TiffResult> { + self.find_tag(tag)? + .map(|v| v.into_u64()) + .transpose()? + .map(|value| { + T::try_from(value).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into()) + }) + .transpose() + } + + /// Tries to retrieve a vector of all a tag's values and convert them to + /// the desired unsigned type. + pub fn find_tag_unsigned_vec>( + &mut self, + tag: Tag, + ) -> TiffResult>> { + 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() + } + + /// Tries to retrieve a tag and convert it to the desired unsigned type. + /// Returns an error if the tag is not present. + pub fn get_tag_unsigned>(&mut self, tag: Tag) -> TiffResult { + self.find_tag_unsigned(tag)? + .ok_or_else(|| TiffFormatError::RequiredTagNotFound(tag).into()) + } + + /// Tries to retrieve a tag. + /// Returns an error if the tag is not present + pub fn get_tag(&mut self, tag: Tag) -> TiffResult { + match self.find_tag(tag)? { + Some(val) => Ok(val), + None => Err(TiffError::FormatError( + TiffFormatError::RequiredTagNotFound(tag), + )), + } + } + + /// Tries to retrieve a tag and convert it to the desired type. + pub fn get_tag_u32(&mut self, tag: Tag) -> TiffResult { + self.get_tag(tag)?.into_u32() + } + pub fn get_tag_u64(&mut self, tag: Tag) -> TiffResult { + self.get_tag(tag)?.into_u64() + } + + /// Tries to retrieve a tag and convert it to the desired type. + pub fn get_tag_f32(&mut self, tag: Tag) -> TiffResult { + self.get_tag(tag)?.into_f32() + } + + /// Tries to retrieve a tag and convert it to the desired type. + pub fn get_tag_f64(&mut self, tag: Tag) -> TiffResult { + self.get_tag(tag)?.into_f64() + } + + /// Tries to retrieve a tag and convert it to the desired type. + pub fn get_tag_u32_vec(&mut self, tag: Tag) -> TiffResult> { + self.get_tag(tag)?.into_u32_vec() + } + + pub fn get_tag_u16_vec(&mut self, tag: Tag) -> TiffResult> { + self.get_tag(tag)?.into_u16_vec() + } + pub fn get_tag_u64_vec(&mut self, tag: Tag) -> TiffResult> { + self.get_tag(tag)?.into_u64_vec() + } + + /// Tries to retrieve a tag and convert it to the desired type. + pub fn get_tag_f32_vec(&mut self, tag: Tag) -> TiffResult> { + self.get_tag(tag)?.into_f32_vec() + } + + /// Tries to retrieve a tag and convert it to the desired type. + pub fn get_tag_f64_vec(&mut self, tag: Tag) -> TiffResult> { + self.get_tag(tag)?.into_f64_vec() + } + + /// Tries to retrieve a tag and convert it to a 8bit vector. + pub fn get_tag_u8_vec(&mut self, tag: Tag) -> TiffResult> { + self.get_tag(tag)?.into_u8_vec() + } + + /// Tries to retrieve a tag and convert it to a ascii vector. + pub fn get_tag_ascii_string(&mut self, tag: Tag) -> TiffResult { + self.get_tag(tag)?.into_string() + } + + fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> { + if expected != self.image().chunk_type { + return Err(TiffError::UsageError(UsageError::InvalidChunkType( + expected, + self.image().chunk_type, + ))); + } + + Ok(()) + } + + /// The chunk type (Strips / Tiles) of the image + pub fn get_chunk_type(&self) -> ChunkType { + self.image().chunk_type + } + + /// Number of strips in image + pub fn strip_count(&mut self) -> TiffResult { + self.check_chunk_type(ChunkType::Strip)?; + let rows_per_strip = self.image().strip_decoder.as_ref().unwrap().rows_per_strip; + + if rows_per_strip == 0 { + return Ok(0); + } + + // rows_per_strip - 1 can never fail since we know it's at least 1 + let height = match self.image().height.checked_add(rows_per_strip - 1) { + Some(h) => h, + None => return Err(TiffError::IntSizeError), + }; + + Ok(height / rows_per_strip) + } + + /// Number of tiles in image + pub fn tile_count(&mut self) -> TiffResult { + self.check_chunk_type(ChunkType::Tile)?; + Ok(u32::try_from(self.image().chunk_offsets.len())?) + } + + pub fn read_chunk_to_buffer( + &mut self, + mut buffer: DecodingBuffer, + chunk_index: u32, + output_width: usize, + ) -> TiffResult<()> { + let offset = self.image.chunk_file_range(chunk_index)?.0; + self.goto_offset_u64(offset)?; + + let byte_order = self.reader.byte_order; + + self.image.expand_chunk( + &mut self.reader, + buffer.copy(), + output_width, + byte_order, + chunk_index, + )?; + + Ok(()) + } + + fn result_buffer(&self, width: usize, height: usize) -> TiffResult { + let buffer_size = match width + .checked_mul(height) + .and_then(|x| x.checked_mul(self.image().bits_per_sample.len())) + { + Some(s) => s, + None => return Err(TiffError::LimitsExceeded), + }; + + let max_sample_bits = self + .image() + .bits_per_sample + .iter() + .cloned() + .max() + .unwrap_or(8); + match self + .image() + .sample_format + .first() + .unwrap_or(&SampleFormat::Uint) + { + SampleFormat::Uint => match max_sample_bits { + n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits), + n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits), + n if n <= 32 => DecodingResult::new_u32(buffer_size, &self.limits), + n if n <= 64 => DecodingResult::new_u64(buffer_size, &self.limits), + n => Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedBitsPerChannel(n), + )), + }, + SampleFormat::IEEEFP => match max_sample_bits { + 32 => DecodingResult::new_f32(buffer_size, &self.limits), + 64 => DecodingResult::new_f64(buffer_size, &self.limits), + n => Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedBitsPerChannel(n), + )), + }, + SampleFormat::Int => match max_sample_bits { + n if n <= 8 => DecodingResult::new_i8(buffer_size, &self.limits), + n if n <= 16 => DecodingResult::new_i16(buffer_size, &self.limits), + n if n <= 32 => DecodingResult::new_i32(buffer_size, &self.limits), + n if n <= 64 => DecodingResult::new_i64(buffer_size, &self.limits), + n => Err(TiffError::UnsupportedError( + TiffUnsupportedError::UnsupportedBitsPerChannel(n), + )), + }, + format => { + Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format.clone()]).into()) + } + } + } + + /// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector. + pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult { + let data_dims = self.image().chunk_data_dimensions(chunk_index)?; + + let mut result = self.result_buffer(data_dims.0 as usize, data_dims.1 as usize)?; + + self.read_chunk_to_buffer(result.as_buffer(0), chunk_index, data_dims.0 as usize)?; + + Ok(result) + } + + /// Returns the default chunk size for the current image. Any given chunk in the image is at most as large as + /// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`. + pub fn chunk_dimensions(&self) -> (u32, u32) { + self.image().chunk_dimensions().unwrap() + } + + /// Returns the size of the data in the chunk with the specified index. This is the default size of the chunk, + /// minus any padding. + pub fn chunk_data_dimensions(&self, chunk_index: u32) -> (u32, u32) { + self.image() + .chunk_data_dimensions(chunk_index) + .expect("invalid chunk_index") + } + + /// Decodes the entire image and return it as a Vector + pub fn read_image(&mut self) -> TiffResult { + let width = self.image().width; + let height = self.image().height; + let mut result = self.result_buffer(width as usize, height as usize)?; + if width == 0 || height == 0 { + return Ok(result); + } + + let chunk_dimensions = self.image().chunk_dimensions()?; + let chunk_dimensions = ( + chunk_dimensions.0.min(width), + chunk_dimensions.1.min(height), + ); + if chunk_dimensions.0 == 0 || chunk_dimensions.1 == 0 { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } + + let samples = self.image().bits_per_sample.len(); + if samples == 0 { + return Err(TiffError::FormatError( + TiffFormatError::InconsistentSizesEncountered, + )); + } + + let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize; + let strip_samples = width as usize * chunk_dimensions.1 as usize * samples; + + for chunk in 0..self.image().chunk_offsets.len() { + self.goto_offset_u64(self.image().chunk_offsets[chunk])?; + + let x = chunk % chunks_across; + let y = chunk / chunks_across; + let buffer_offset = y * strip_samples + x * chunk_dimensions.0 as usize * samples; + let byte_order = self.reader.byte_order; + self.image.expand_chunk( + &mut self.reader, + result.as_buffer(buffer_offset).copy(), + width as usize, + byte_order, + chunk as u32, + )?; + } + + Ok(result) + } +} diff --git a/vendor/tiff/src/decoder/stream.rs b/vendor/tiff/src/decoder/stream.rs new file mode 100644 index 0000000..e0323c2 --- /dev/null +++ b/vendor/tiff/src/decoder/stream.rs @@ -0,0 +1,435 @@ +//! All IO functionality needed for TIFF decoding + +use std::convert::TryFrom; +use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Take}; +use std::sync::Arc; + +/// Byte order of the TIFF file. +#[derive(Clone, Copy, Debug)] +pub enum ByteOrder { + /// little endian byte order + LittleEndian, + /// big endian byte order + BigEndian, +} + +/// Reader that is aware of the byte order. +pub trait EndianReader: Read { + /// Byte order that should be adhered to + fn byte_order(&self) -> ByteOrder; + + /// Reads an u16 + #[inline(always)] + fn read_u16(&mut self) -> Result { + let mut n = [0u8; 2]; + self.read_exact(&mut n)?; + Ok(match self.byte_order() { + ByteOrder::LittleEndian => u16::from_le_bytes(n), + ByteOrder::BigEndian => u16::from_be_bytes(n), + }) + } + + /// Reads an i8 + #[inline(always)] + fn read_i8(&mut self) -> Result { + let mut n = [0u8; 1]; + self.read_exact(&mut n)?; + Ok(match self.byte_order() { + ByteOrder::LittleEndian => i8::from_le_bytes(n), + ByteOrder::BigEndian => i8::from_be_bytes(n), + }) + } + + /// Reads an i16 + #[inline(always)] + fn read_i16(&mut self) -> Result { + let mut n = [0u8; 2]; + self.read_exact(&mut n)?; + Ok(match self.byte_order() { + ByteOrder::LittleEndian => i16::from_le_bytes(n), + ByteOrder::BigEndian => i16::from_be_bytes(n), + }) + } + + /// Reads an u32 + #[inline(always)] + fn read_u32(&mut self) -> Result { + let mut n = [0u8; 4]; + self.read_exact(&mut n)?; + Ok(match self.byte_order() { + ByteOrder::LittleEndian => u32::from_le_bytes(n), + ByteOrder::BigEndian => u32::from_be_bytes(n), + }) + } + + /// Reads an i32 + #[inline(always)] + fn read_i32(&mut self) -> Result { + let mut n = [0u8; 4]; + self.read_exact(&mut n)?; + Ok(match self.byte_order() { + ByteOrder::LittleEndian => i32::from_le_bytes(n), + ByteOrder::BigEndian => i32::from_be_bytes(n), + }) + } + + /// Reads an u64 + #[inline(always)] + fn read_u64(&mut self) -> Result { + let mut n = [0u8; 8]; + self.read_exact(&mut n)?; + Ok(match self.byte_order() { + ByteOrder::LittleEndian => u64::from_le_bytes(n), + ByteOrder::BigEndian => u64::from_be_bytes(n), + }) + } + + /// Reads an i64 + #[inline(always)] + fn read_i64(&mut self) -> Result { + let mut n = [0u8; 8]; + self.read_exact(&mut n)?; + Ok(match self.byte_order() { + ByteOrder::LittleEndian => i64::from_le_bytes(n), + ByteOrder::BigEndian => i64::from_be_bytes(n), + }) + } + + /// Reads an f32 + #[inline(always)] + fn read_f32(&mut self) -> Result { + let mut n = [0u8; 4]; + self.read_exact(&mut n)?; + Ok(f32::from_bits(match self.byte_order() { + ByteOrder::LittleEndian => u32::from_le_bytes(n), + ByteOrder::BigEndian => u32::from_be_bytes(n), + })) + } + + /// Reads an f64 + #[inline(always)] + fn read_f64(&mut self) -> Result { + let mut n = [0u8; 8]; + self.read_exact(&mut n)?; + Ok(f64::from_bits(match self.byte_order() { + ByteOrder::LittleEndian => u64::from_le_bytes(n), + ByteOrder::BigEndian => u64::from_be_bytes(n), + })) + } +} + +/// +/// # READERS +/// + +/// +/// ## Deflate Reader +/// + +pub type DeflateReader = flate2::read::ZlibDecoder; + +/// +/// ## LZW Reader +/// + +/// Reader that decompresses LZW streams +pub struct LZWReader { + reader: BufReader>, + decoder: weezl::decode::Decoder, +} + +impl LZWReader { + /// Wraps a reader + pub fn new(reader: R, compressed_length: usize) -> LZWReader { + Self { + reader: BufReader::with_capacity( + (32 * 1024).min(compressed_length), + reader.take(u64::try_from(compressed_length).unwrap()), + ), + decoder: weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8), + } + } +} + +impl Read for LZWReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + loop { + let result = self.decoder.decode_bytes(self.reader.fill_buf()?, buf); + self.reader.consume(result.consumed_in); + + match result.status { + Ok(weezl::LzwStatus::Ok) => { + if result.consumed_out == 0 { + continue; + } else { + return Ok(result.consumed_out); + } + } + Ok(weezl::LzwStatus::NoProgress) => { + assert_eq!(result.consumed_in, 0); + assert_eq!(result.consumed_out, 0); + assert!(self.reader.buffer().is_empty()); + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "no lzw end code found", + )); + } + Ok(weezl::LzwStatus::Done) => { + return Ok(result.consumed_out); + } + Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)), + } + } + } +} + +/// +/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7)) +/// + +pub(crate) struct JpegReader { + jpeg_tables: Option>>, + + buffer: io::Cursor>, + + offset: usize, +} + +impl JpegReader { + /// Constructs new JpegReader wrapping a SmartReader. + /// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one + /// central location, the constructor accepts this data as `jpeg_tables` here containing either + /// or both. + /// These `jpeg_tables` are simply prepended to the remaining jpeg image data. + /// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker + /// which is also at the beginning of the remaining JPEG image data and would + /// confuse the JPEG renderer, one of these has to be taken off. In this case the first two + /// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`. + /// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker, + /// this has to be removed as well (last two bytes of `jpeg_tables`). + pub fn new( + mut reader: R, + length: u64, + jpeg_tables: Option>>, + ) -> io::Result { + // Read jpeg image data + let mut segment = vec![0; length as usize]; + + reader.read_exact(&mut segment[..])?; + + match jpeg_tables { + Some(jpeg_tables) => { + assert!( + jpeg_tables.len() >= 2, + "jpeg_tables, if given, must be at least 2 bytes long. Got {:?}", + jpeg_tables + ); + + assert!( + length >= 2, + "if jpeg_tables is given, length must be at least 2 bytes long, got {}", + length + ); + + let mut buffer = io::Cursor::new(segment); + // Skip the first two bytes (marker bytes) + buffer.seek(SeekFrom::Start(2))?; + + Ok(JpegReader { + buffer, + jpeg_tables: Some(jpeg_tables), + offset: 0, + }) + } + None => Ok(JpegReader { + buffer: io::Cursor::new(segment), + jpeg_tables: None, + offset: 0, + }), + } + } +} + +impl Read for JpegReader { + // #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut start = 0; + + if let Some(jpeg_tables) = &self.jpeg_tables { + if jpeg_tables.len() - 2 > self.offset { + // Read (rest of) jpeg_tables to buf (without the last two bytes) + let size_remaining = jpeg_tables.len() - self.offset - 2; + let to_copy = size_remaining.min(buf.len()); + + buf[start..start + to_copy] + .copy_from_slice(&jpeg_tables[self.offset..self.offset + to_copy]); + + self.offset += to_copy; + + if to_copy == buf.len() { + return Ok(to_copy); + } + + start += to_copy; + } + } + + let read = self.buffer.read(&mut buf[start..])?; + self.offset += read; + + Ok(read + start) + } +} + +/// +/// ## PackBits Reader +/// + +enum PackBitsReaderState { + Header, + Literal, + Repeat { value: u8 }, +} + +/// Reader that unpacks Apple's `PackBits` format +pub struct PackBitsReader { + reader: Take, + state: PackBitsReaderState, + count: usize, +} + +impl PackBitsReader { + /// Wraps a reader + pub fn new(reader: R, length: u64) -> Self { + Self { + reader: reader.take(length), + state: PackBitsReaderState::Header, + count: 0, + } + } +} + +impl Read for PackBitsReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + while let PackBitsReaderState::Header = self.state { + if self.reader.limit() == 0 { + return Ok(0); + } + let mut header: [u8; 1] = [0]; + self.reader.read_exact(&mut header)?; + let h = header[0] as i8; + if h >= -127 && h <= -1 { + let mut data: [u8; 1] = [0]; + self.reader.read_exact(&mut data)?; + self.state = PackBitsReaderState::Repeat { value: data[0] }; + self.count = (1 - h as isize) as usize; + } else if h >= 0 { + self.state = PackBitsReaderState::Literal; + self.count = h as usize + 1; + } else { + // h = -128 is a no-op. + } + } + + let length = buf.len().min(self.count); + let actual = match self.state { + PackBitsReaderState::Literal => self.reader.read(&mut buf[..length])?, + PackBitsReaderState::Repeat { value } => { + for b in &mut buf[..length] { + *b = value; + } + + length + } + PackBitsReaderState::Header => unreachable!(), + }; + + self.count -= actual; + if self.count == 0 { + self.state = PackBitsReaderState::Header; + } + return Ok(actual); + } +} + +/// +/// ## SmartReader Reader +/// + +/// Reader that is aware of the byte order. +#[derive(Debug)] +pub struct SmartReader +where + R: Read, +{ + reader: R, + pub byte_order: ByteOrder, +} + +impl SmartReader +where + R: Read, +{ + /// Wraps a reader + pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader { + SmartReader { reader, byte_order } + } + pub fn into_inner(self) -> R { + self.reader + } +} +impl SmartReader { + pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> { + self.seek(io::SeekFrom::Start(offset)).map(|_| ()) + } +} + +impl EndianReader for SmartReader +where + R: Read, +{ + #[inline(always)] + fn byte_order(&self) -> ByteOrder { + self.byte_order + } +} + +impl Read for SmartReader { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.reader.read(buf) + } +} + +impl Seek for SmartReader { + #[inline] + fn seek(&mut self, pos: io::SeekFrom) -> io::Result { + self.reader.seek(pos) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_packbits() { + let encoded = vec![ + 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, + 0xAA, + ]; + let encoded_len = encoded.len(); + + let buff = io::Cursor::new(encoded); + let mut decoder = PackBitsReader::new(buff, encoded_len as u64); + + let mut decoded = Vec::new(); + decoder.read_to_end(&mut decoded).unwrap(); + + let expected = vec![ + 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + ]; + assert_eq!(decoded, expected); + } +} diff --git a/vendor/tiff/src/decoder/tag_reader.rs b/vendor/tiff/src/decoder/tag_reader.rs new file mode 100644 index 0000000..837da40 --- /dev/null +++ b/vendor/tiff/src/decoder/tag_reader.rs @@ -0,0 +1,45 @@ +use std::convert::TryFrom; +use std::io::{Read, Seek}; + +use crate::tags::Tag; +use crate::{TiffError, TiffFormatError, TiffResult}; + +use super::ifd::{Directory, Value}; +use super::stream::SmartReader; +use super::Limits; + +pub(crate) struct TagReader<'a, R: Read + Seek> { + pub reader: &'a mut SmartReader, + pub ifd: &'a Directory, + pub limits: &'a Limits, + pub bigtiff: bool, +} +impl<'a, R: Read + Seek> TagReader<'a, R> { + pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult> { + Ok(match self.ifd.get(&tag) { + Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?), + None => None, + }) + } + pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult { + match self.find_tag(tag)? { + Some(val) => Ok(val), + None => Err(TiffError::FormatError( + TiffFormatError::RequiredTagNotFound(tag), + )), + } + } + pub fn find_tag_uint_vec>(&mut self, tag: Tag) -> TiffResult>> { + 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() + } +} diff --git a/vendor/tiff/src/encoder/colortype.rs b/vendor/tiff/src/encoder/colortype.rs new file mode 100644 index 0000000..1946daf --- /dev/null +++ b/vendor/tiff/src/encoder/colortype.rs @@ -0,0 +1,245 @@ +use crate::tags::{PhotometricInterpretation, SampleFormat}; + +/// Trait for different colortypes that can be encoded. +pub trait ColorType { + /// The type of each sample of this colortype + type Inner: super::TiffValue; + /// The value of the tiff tag `PhotometricInterpretation` + const TIFF_VALUE: PhotometricInterpretation; + /// The value of the tiff tag `BitsPerSample` + const BITS_PER_SAMPLE: &'static [u16]; + /// The value of the tiff tag `SampleFormat` + const SAMPLE_FORMAT: &'static [SampleFormat]; +} + +pub struct Gray8; +impl ColorType for Gray8 { + type Inner = u8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; +} + +pub struct GrayI8; +impl ColorType for GrayI8 { + type Inner = i8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; +} + +pub struct Gray16; +impl ColorType for Gray16 { + type Inner = u16; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[16]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; +} + +pub struct GrayI16; +impl ColorType for GrayI16 { + type Inner = i16; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[16]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; +} + +pub struct Gray32; +impl ColorType for Gray32 { + type Inner = u32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; +} + +pub struct GrayI32; +impl ColorType for GrayI32 { + type Inner = i32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; +} + +pub struct Gray32Float; +impl ColorType for Gray32Float { + type Inner = f32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP]; +} + +pub struct Gray64; +impl ColorType for Gray64 { + type Inner = u64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; +} + +pub struct GrayI64; +impl ColorType for GrayI64 { + type Inner = i64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; +} + +pub struct Gray64Float; +impl ColorType for Gray64Float { + type Inner = f64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; + const BITS_PER_SAMPLE: &'static [u16] = &[64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP]; +} + +pub struct RGB8; +impl ColorType for RGB8 { + type Inner = u8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; +} + +pub struct RGB16; +impl ColorType for RGB16 { + type Inner = u16; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; +} + +pub struct RGB32; +impl ColorType for RGB32 { + type Inner = u32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; +} + +pub struct RGB32Float; +impl ColorType for RGB32Float { + type Inner = f32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3]; +} + +pub struct RGB64; +impl ColorType for RGB64 { + type Inner = u64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; +} + +pub struct RGB64Float; +impl ColorType for RGB64Float { + type Inner = f64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3]; +} + +pub struct RGBA8; +impl ColorType for RGBA8 { + type Inner = u8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; +} + +pub struct RGBA16; +impl ColorType for RGBA16 { + type Inner = u16; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; +} + +pub struct RGBA32; +impl ColorType for RGBA32 { + type Inner = u32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; +} + +pub struct RGBA32Float; +impl ColorType for RGBA32Float { + type Inner = f32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; +} + +pub struct RGBA64; +impl ColorType for RGBA64 { + type Inner = u64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; +} + +pub struct RGBA64Float; +impl ColorType for RGBA64Float { + type Inner = f64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; + const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; +} + +pub struct CMYK8; +impl ColorType for CMYK8 { + type Inner = u8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; + const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; +} + +pub struct CMYK16; +impl ColorType for CMYK16 { + type Inner = u16; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; + const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; +} + +pub struct CMYK32; +impl ColorType for CMYK32 { + type Inner = u32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; + const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; +} + +pub struct CMYK32Float; +impl ColorType for CMYK32Float { + type Inner = f32; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; + const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; +} + +pub struct CMYK64; +impl ColorType for CMYK64 { + type Inner = u64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; + const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; +} + +pub struct CMYK64Float; +impl ColorType for CMYK64Float { + type Inner = f64; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; + const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; +} + +pub struct YCbCr8; +impl ColorType for YCbCr8 { + type Inner = u8; + const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr; + const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8]; + const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; +} diff --git a/vendor/tiff/src/encoder/compression/deflate.rs b/vendor/tiff/src/encoder/compression/deflate.rs new file mode 100644 index 0000000..5e7a261 --- /dev/null +++ b/vendor/tiff/src/encoder/compression/deflate.rs @@ -0,0 +1,83 @@ +use crate::{encoder::compression::*, tags::CompressionMethod}; +use flate2::{write::ZlibEncoder, Compression as FlateCompression}; +use std::io::Write; + +/// The Deflate algorithm used to compress image data in TIFF files. +#[derive(Debug, Clone, Copy)] +pub struct Deflate { + level: FlateCompression, +} + +/// The level of compression used by the Deflate algorithm. +/// It allows trading compression ratio for compression speed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum DeflateLevel { + /// The fastest possible compression mode. + Fast = 1, + /// The conserative choice between speed and ratio. + Balanced = 6, + /// The best compression available with Deflate. + Best = 9, +} + +impl Default for DeflateLevel { + fn default() -> Self { + DeflateLevel::Balanced + } +} + +impl Deflate { + /// Create a new deflate compressor with a specific level of compression. + pub fn with_level(level: DeflateLevel) -> Self { + Self { + level: FlateCompression::new(level as u32), + } + } +} + +impl Default for Deflate { + fn default() -> Self { + Self::with_level(DeflateLevel::default()) + } +} + +impl Compression for Deflate { + const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::Deflate; + + fn get_algorithm(&self) -> Compressor { + Compressor::Deflate(self.clone()) + } +} + +impl CompressionAlgorithm for Deflate { + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { + 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::::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()); + } +} diff --git a/vendor/tiff/src/encoder/compression/lzw.rs b/vendor/tiff/src/encoder/compression/lzw.rs new file mode 100644 index 0000000..0e0f2aa --- /dev/null +++ b/vendor/tiff/src/encoder/compression/lzw.rs @@ -0,0 +1,47 @@ +use crate::{encoder::compression::*, tags::CompressionMethod}; +use std::io::Write; +use weezl::encode::Encoder as LZWEncoder; + +/// The LZW algorithm used to compress image data in TIFF files. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Lzw; + +impl Compression for Lzw { + const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::LZW; + + fn get_algorithm(&self) -> Compressor { + Compressor::Lzw(*self) + } +} + +impl CompressionAlgorithm for Lzw { + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { + 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::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Lzw::default().write_to(&mut writer, TEST_DATA).unwrap(); + assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice()); + } +} diff --git a/vendor/tiff/src/encoder/compression/mod.rs b/vendor/tiff/src/encoder/compression/mod.rs new file mode 100644 index 0000000..04baef3 --- /dev/null +++ b/vendor/tiff/src/encoder/compression/mod.rs @@ -0,0 +1,60 @@ +use crate::tags::CompressionMethod; +use std::io::{self, Write}; + +mod deflate; +mod lzw; +mod packbits; +mod uncompressed; + +pub use self::deflate::{Deflate, DeflateLevel}; +pub use self::lzw::Lzw; +pub use self::packbits::Packbits; +pub use self::uncompressed::Uncompressed; + +/// An algorithm used for compression +pub trait CompressionAlgorithm { + /// The algorithm writes data directly into the writer. + /// It returns the total number of bytes written. + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result; +} + +/// An algorithm used for compression with associated enums and optional configurations. +pub trait Compression: CompressionAlgorithm { + /// The corresponding tag to the algorithm. + const COMPRESSION_METHOD: CompressionMethod; + + /// Method to optain a type that can store each variant of comression algorithm. + fn get_algorithm(&self) -> Compressor; +} + +/// An enum to store each compression algorithm. +pub enum Compressor { + Uncompressed(Uncompressed), + Lzw(Lzw), + Deflate(Deflate), + Packbits(Packbits), +} + +impl Default for Compressor { + /// The default compression strategy does not apply any compression. + fn default() -> Self { + Compressor::Uncompressed(Uncompressed::default()) + } +} + +impl CompressionAlgorithm for Compressor { + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { + match self { + Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes), + Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes), + Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes), + Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes), + } + } +} + +#[cfg(test)] +mod tests { + pub const TEST_DATA: &'static [u8] = + b"This is a string for checking various compression algorithms."; +} diff --git a/vendor/tiff/src/encoder/compression/packbits.rs b/vendor/tiff/src/encoder/compression/packbits.rs new file mode 100644 index 0000000..7ba3833 --- /dev/null +++ b/vendor/tiff/src/encoder/compression/packbits.rs @@ -0,0 +1,214 @@ +use crate::{encoder::compression::*, tags::CompressionMethod}; +use std::io::{BufWriter, Error, ErrorKind, Write}; + +/// Compressor that uses the Packbits[^note] algorithm to compress bytes. +/// +/// [^note]: PackBits is often ineffective on continuous tone images, +/// including many grayscale images. In such cases, it is better +/// to leave the image uncompressed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Packbits; + +impl Compression for Packbits { + const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::PackBits; + + fn get_algorithm(&self) -> Compressor { + Compressor::Packbits(*self) + } +} + +impl CompressionAlgorithm for Packbits { + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { + // 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(writer: &mut W, byte: u8) -> Result { + 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 = None; // The index of the first byte written into the writer + + let mut src_index: usize = 0; // Index of the current byte + let mut src_count = bytes.len(); //The number of bytes remaining to be compressed + + let mut in_run = false; // Indication whether counting of similar bytes is performed + let mut run_index = 0u8; // Distance into pending bytes that a run starts + + let mut bytes_pending = 0u8; // Bytes looked at but not yet output + let mut pending_index = 0usize; // Index of the first pending byte + + let mut curr_byte: u8; // Byte currently being considered + let mut last_byte: u8; // Previous byte + + // Need at least one byte to compress + if src_count == 0 { + return Err(Error::new(ErrorKind::WriteZero, "write zero")); + } + + // Prime compressor with first character. + last_byte = bytes[src_index]; + src_index += 1; + bytes_pending += 1; + + while src_count - 1 != 0 { + src_count -= 1; + curr_byte = bytes[src_index]; + src_index += 1; + bytes_pending += 1; + + if in_run { + if (curr_byte != last_byte) || (bytes_pending > MAX_BYTES) { + offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending - 1))?); + write_u8(&mut bufwriter, last_byte)?; + bytes_written += 2; + + bytes_pending = 1; + pending_index = src_index - 1; + run_index = 0; + in_run = false; + } + } else { + if bytes_pending > MAX_BYTES { + // We have as much differing data as we can output in one chunk. + // Output MAX_BYTES leaving one byte. + offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(MAX_BYTES))?); + bufwriter.write(&bytes[pending_index..pending_index + MAX_BYTES as usize])?; + bytes_written += 1 + MAX_BYTES as u64; + + pending_index += MAX_BYTES as usize; + bytes_pending -= MAX_BYTES; + run_index = bytes_pending - 1; // A run could start here + } else if curr_byte == last_byte { + if (bytes_pending - run_index >= MIN_REPT) || (run_index == 0) { + // This is a worthwhile run + if run_index != 0 { + // Flush differing data out of input buffer + offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(run_index))?); + bufwriter + .write(&bytes[pending_index..pending_index + run_index as usize])?; + bytes_written += 1 + run_index as u64; + } + bytes_pending -= run_index; // Length of run + in_run = true; + } + } else { + run_index = bytes_pending - 1; // A run could start here + } + } + last_byte = curr_byte; + } + + // Output the remainder + if in_run { + bytes_written += 2; + offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending))?); + write_u8(&mut bufwriter, last_byte)?; + } else { + bytes_written += 1 + bytes_pending as u64; + offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(bytes_pending))?); + bufwriter.write(&bytes[pending_index..pending_index + bytes_pending as usize])?; + } + + bufwriter.flush()?; + Ok(bytes_written) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::encoder::compression::tests::TEST_DATA; + use std::io::Cursor; + + #[test] + fn test_packbits_single_byte() { + // compress single byte + const UNCOMPRESSED_DATA: [u8; 1] = [0x3F]; + const EXPECTED_COMPRESSED_DATA: [u8; 2] = [0x00, 0x3F]; + + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Packbits::default() + .write_to(&mut writer, &UNCOMPRESSED_DATA) + .unwrap(); + assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + } + + #[test] + fn test_packbits_rept() { + // compress buffer with repetitive sequence + const UNCOMPRESSED_DATA: &'static [u8] = + b"This strrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrring hangs."; + const EXPECTED_COMPRESSED_DATA: &'static [u8] = b"\x06This st\xD1r\x09ing hangs."; + + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Packbits::default() + .write_to(&mut writer, UNCOMPRESSED_DATA) + .unwrap(); + assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + } + + #[test] + fn test_packbits_large_rept_nonrept() { + // compress buffer with large repetitive and non-repetitive sequence + let mut data = b"This st".to_vec(); + for _i in 0..158 { + data.push(b'r'); + } + data.extend_from_slice(b"ing hangs."); + for i in 0..158 { + data.push(i); + } + + const EXPECTED_COMPRESSED_DATA: [u8; 182] = [ + 0x06, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x74, 0x81, 0x72, 0xE3, 0x72, 0x7F, 0x69, + 0x6E, 0x67, 0x20, 0x68, 0x61, 0x6E, 0x67, 0x73, 0x2E, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, + 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, + 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, + 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, + 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, + 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x75, 0x27, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, + ]; + + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Packbits::default() + .write_to(&mut writer, data.as_slice()) + .unwrap(); + assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + } + + #[test] + fn test_packbits() { + // compress teststring + const EXPECTED_COMPRESSED_DATA: &'static [u8] = + b"\x3CThis is a string for checking various compression algorithms."; + + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Packbits::default() + .write_to(&mut writer, TEST_DATA) + .unwrap(); + assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA); + } +} diff --git a/vendor/tiff/src/encoder/compression/uncompressed.rs b/vendor/tiff/src/encoder/compression/uncompressed.rs new file mode 100644 index 0000000..900426f --- /dev/null +++ b/vendor/tiff/src/encoder/compression/uncompressed.rs @@ -0,0 +1,37 @@ +use crate::{encoder::compression::*, tags::CompressionMethod}; +use std::io::Write; + +/// The default algorithm which does not compress at all. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Uncompressed; + +impl Compression for Uncompressed { + const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::None; + + fn get_algorithm(&self) -> Compressor { + Compressor::Uncompressed(*self) + } +} + +impl CompressionAlgorithm for Uncompressed { + fn write_to(&mut self, writer: &mut W, bytes: &[u8]) -> Result { + writer.write(bytes).map(|byte_count| byte_count as u64) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::encoder::compression::tests::TEST_DATA; + use std::io::Cursor; + + #[test] + fn test_no_compression() { + let mut compressed_data = Vec::::new(); + let mut writer = Cursor::new(&mut compressed_data); + Uncompressed::default() + .write_to(&mut writer, TEST_DATA) + .unwrap(); + assert_eq!(TEST_DATA, compressed_data); + } +} diff --git a/vendor/tiff/src/encoder/mod.rs b/vendor/tiff/src/encoder/mod.rs new file mode 100644 index 0000000..6e39c93 --- /dev/null +++ b/vendor/tiff/src/encoder/mod.rs @@ -0,0 +1,681 @@ +pub use tiff_value::*; + +use std::{ + cmp, + collections::BTreeMap, + convert::{TryFrom, TryInto}, + io::{self, Seek, Write}, + marker::PhantomData, + mem, + num::TryFromIntError, +}; + +use crate::{ + error::TiffResult, + tags::{CompressionMethod, ResolutionUnit, Tag}, + TiffError, TiffFormatError, +}; + +pub mod colortype; +pub mod compression; +mod tiff_value; +mod writer; + +use self::colortype::*; +use self::compression::*; +use self::writer::*; + +/// Encoder for Tiff and BigTiff files. +/// +/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder` +/// to encode Tiff/BigTiff ifd directories with images. +/// +/// See `DirectoryEncoder` and `ImageEncoder`. +/// +/// # Examples +/// ``` +/// # extern crate tiff; +/// # fn main() { +/// # let mut file = std::io::Cursor::new(Vec::new()); +/// # let image_data = vec![0; 100*100*3]; +/// use tiff::encoder::*; +/// +/// // create a standard Tiff file +/// let mut tiff = TiffEncoder::new(&mut file).unwrap(); +/// tiff.write_image::(100, 100, &image_data).unwrap(); +/// +/// // create a BigTiff file +/// let mut bigtiff = TiffEncoder::new_big(&mut file).unwrap(); +/// bigtiff.write_image::(100, 100, &image_data).unwrap(); +/// +/// # } +/// ``` +pub struct TiffEncoder { + writer: TiffWriter, + kind: PhantomData, +} + +/// Constructor functions to create standard Tiff files. +impl TiffEncoder { + /// Creates a new encoder for standard Tiff files. + /// + /// To create BigTiff files, use [`new_big`][TiffEncoder::new_big] or + /// [`new_generic`][TiffEncoder::new_generic]. + pub fn new(writer: W) -> TiffResult> { + TiffEncoder::new_generic(writer) + } +} + +/// Constructor functions to create BigTiff files. +impl TiffEncoder { + /// Creates a new encoder for BigTiff files. + /// + /// To create standard Tiff files, use [`new`][TiffEncoder::new] or + /// [`new_generic`][TiffEncoder::new_generic]. + pub fn new_big(writer: W) -> TiffResult { + TiffEncoder::new_generic(writer) + } +} + +/// Generic functions that are available for both Tiff and BigTiff encoders. +impl TiffEncoder { + /// Creates a new Tiff or BigTiff encoder, inferred from the return type. + pub fn new_generic(writer: W) -> TiffResult { + let mut encoder = TiffEncoder { + writer: TiffWriter::new(writer), + kind: PhantomData, + }; + + K::write_header(&mut encoder.writer)?; + + Ok(encoder) + } + + /// Create a [`DirectoryEncoder`] to encode an ifd directory. + pub fn new_directory(&mut self) -> TiffResult> { + DirectoryEncoder::new(&mut self.writer) + } + + /// Create an [`ImageEncoder`] to encode an image one slice at a time. + pub fn new_image( + &mut self, + width: u32, + height: u32, + ) -> TiffResult> { + let encoder = DirectoryEncoder::new(&mut self.writer)?; + ImageEncoder::new(encoder, width, height) + } + + /// Create an [`ImageEncoder`] to encode an image one slice at a time. + pub fn new_image_with_compression( + &mut self, + width: u32, + height: u32, + compression: D, + ) -> TiffResult> { + let encoder = DirectoryEncoder::new(&mut self.writer)?; + ImageEncoder::with_compression(encoder, width, height, compression) + } + + /// Convenience function to write an entire image from memory. + pub fn write_image( + &mut self, + width: u32, + height: u32, + data: &[C::Inner], + ) -> TiffResult<()> + where + [C::Inner]: TiffValue, + { + let encoder = DirectoryEncoder::new(&mut self.writer)?; + let image: ImageEncoder = ImageEncoder::new(encoder, width, height)?; + image.write_data(data) + } + + /// Convenience function to write an entire image from memory with a given compression. + pub fn write_image_with_compression( + &mut self, + width: u32, + height: u32, + compression: D, + data: &[C::Inner], + ) -> TiffResult<()> + where + [C::Inner]: TiffValue, + { + let encoder = DirectoryEncoder::new(&mut self.writer)?; + let image: ImageEncoder = + ImageEncoder::with_compression(encoder, width, height, compression)?; + image.write_data(data) + } +} + +/// Low level interface to encode ifd directories. +/// +/// You should call `finish` on this when you are finished with it. +/// Encoding can silently fail while this is dropping. +pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> { + writer: &'a mut TiffWriter, + dropped: bool, + // We use BTreeMap to make sure tags are written in correct order + ifd_pointer_pos: u64, + ifd: BTreeMap>, +} + +impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> { + fn new(writer: &'a mut TiffWriter) -> TiffResult { + // the previous word is the IFD offset position + let ifd_pointer_pos = writer.offset() - mem::size_of::() as u64; + writer.pad_word_boundary()?; // TODO: Do we need to adjust this for BigTiff? + Ok(DirectoryEncoder { + writer, + dropped: false, + ifd_pointer_pos, + ifd: BTreeMap::new(), + }) + } + + /// Write a single ifd tag. + pub fn write_tag(&mut self, tag: Tag, value: T) -> TiffResult<()> { + let mut bytes = Vec::with_capacity(value.bytes()); + { + let mut writer = TiffWriter::new(&mut bytes); + value.write(&mut writer)?; + } + + self.ifd.insert( + tag.to_u16(), + DirectoryEntry { + data_type: ::FIELD_TYPE.to_u16(), + count: value.count().try_into()?, + data: bytes, + }, + ); + + Ok(()) + } + + fn write_directory(&mut self) -> TiffResult { + // Start by writing out all values + for &mut DirectoryEntry { + data: ref mut bytes, + .. + } in self.ifd.values_mut() + { + let data_bytes = mem::size_of::(); + + if bytes.len() > data_bytes { + let offset = self.writer.offset(); + self.writer.write_bytes(bytes)?; + *bytes = vec![0; data_bytes]; + let mut writer = TiffWriter::new(bytes as &mut [u8]); + K::write_offset(&mut writer, offset)?; + } else { + while bytes.len() < data_bytes { + bytes.push(0); + } + } + } + + let offset = self.writer.offset(); + + K::write_entry_count(&mut self.writer, self.ifd.len())?; + for ( + tag, + &DirectoryEntry { + data_type: ref field_type, + ref count, + data: ref offset, + }, + ) in self.ifd.iter() + { + self.writer.write_u16(*tag)?; + self.writer.write_u16(*field_type)?; + (*count).write(&mut self.writer)?; + self.writer.write_bytes(offset)?; + } + + Ok(offset) + } + + /// Write some data to the tiff file, the offset of the data is returned. + /// + /// This could be used to write tiff strips. + pub fn write_data(&mut self, value: T) -> TiffResult { + let offset = self.writer.offset(); + value.write(&mut self.writer)?; + Ok(offset) + } + + /// Provides the number of bytes written by the underlying TiffWriter during the last call. + fn last_written(&self) -> u64 { + self.writer.last_written() + } + + fn finish_internal(&mut self) -> TiffResult<()> { + let ifd_pointer = self.write_directory()?; + let curr_pos = self.writer.offset(); + + self.writer.goto_offset(self.ifd_pointer_pos)?; + K::write_offset(&mut self.writer, ifd_pointer)?; + self.writer.goto_offset(curr_pos)?; + K::write_offset(&mut self.writer, 0)?; + + self.dropped = true; + + Ok(()) + } + + /// Write out the ifd directory. + pub fn finish(mut self) -> TiffResult<()> { + self.finish_internal() + } +} + +impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> { + fn drop(&mut self) { + if !self.dropped { + let _ = self.finish_internal(); + } + } +} + +/// Type to encode images strip by strip. +/// +/// You should call `finish` on this when you are finished with it. +/// Encoding can silently fail while this is dropping. +/// +/// # Examples +/// ``` +/// # extern crate tiff; +/// # fn main() { +/// # let mut file = std::io::Cursor::new(Vec::new()); +/// # let image_data = vec![0; 100*100*3]; +/// use tiff::encoder::*; +/// use tiff::tags::Tag; +/// +/// let mut tiff = TiffEncoder::new(&mut file).unwrap(); +/// let mut image = tiff.new_image::(100, 100).unwrap(); +/// +/// // You can encode tags here +/// image.encoder().write_tag(Tag::Artist, "Image-tiff").unwrap(); +/// +/// // Strip size can be configured before writing data +/// image.rows_per_strip(2).unwrap(); +/// +/// let mut idx = 0; +/// while image.next_strip_sample_count() > 0 { +/// let sample_count = image.next_strip_sample_count() as usize; +/// image.write_strip(&image_data[idx..idx+sample_count]).unwrap(); +/// idx += sample_count; +/// } +/// image.finish().unwrap(); +/// # } +/// ``` +/// You can also call write_data function wich will encode by strip and finish +pub struct ImageEncoder< + 'a, + W: 'a + Write + Seek, + C: ColorType, + K: TiffKind, + D: Compression = Uncompressed, +> { + encoder: DirectoryEncoder<'a, W, K>, + strip_idx: u64, + strip_count: u64, + row_samples: u64, + width: u32, + height: u32, + rows_per_strip: u64, + strip_offsets: Vec, + strip_byte_count: Vec, + dropped: bool, + compression: D, + _phantom: ::std::marker::PhantomData, +} + +impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> + ImageEncoder<'a, W, T, K, D> +{ + fn new(encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult + where + D: Default, + { + Self::with_compression(encoder, width, height, D::default()) + } + + fn with_compression( + mut encoder: DirectoryEncoder<'a, W, K>, + width: u32, + height: u32, + compression: D, + ) -> TiffResult { + if width == 0 || height == 0 { + return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions( + width, height, + ))); + } + + let row_samples = u64::from(width) * u64::try_from(::BITS_PER_SAMPLE.len())?; + let row_bytes = row_samples * u64::from(::BYTE_LEN); + + // Limit the strip size to prevent potential memory and security issues. + // Also keep the multiple strip handling 'oiled' + let rows_per_strip = { + match D::COMPRESSION_METHOD { + CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries + _ => (1_000_000 + row_bytes - 1) / row_bytes, + } + }; + + let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip; + + encoder.write_tag(Tag::ImageWidth, width)?; + encoder.write_tag(Tag::ImageLength, height)?; + encoder.write_tag(Tag::Compression, D::COMPRESSION_METHOD.to_u16())?; + + encoder.write_tag(Tag::BitsPerSample, ::BITS_PER_SAMPLE)?; + let sample_format: Vec<_> = ::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect(); + encoder.write_tag(Tag::SampleFormat, &sample_format[..])?; + encoder.write_tag(Tag::PhotometricInterpretation, ::TIFF_VALUE.to_u16())?; + + encoder.write_tag(Tag::RowsPerStrip, u32::try_from(rows_per_strip)?)?; + + encoder.write_tag( + Tag::SamplesPerPixel, + u16::try_from(::BITS_PER_SAMPLE.len())?, + )?; + encoder.write_tag(Tag::XResolution, Rational { n: 1, d: 1 })?; + encoder.write_tag(Tag::YResolution, Rational { n: 1, d: 1 })?; + encoder.write_tag(Tag::ResolutionUnit, ResolutionUnit::None.to_u16())?; + + Ok(ImageEncoder { + encoder, + strip_count, + strip_idx: 0, + row_samples, + rows_per_strip, + width, + height, + strip_offsets: Vec::new(), + strip_byte_count: Vec::new(), + dropped: false, + compression: compression, + _phantom: ::std::marker::PhantomData, + }) + } + + /// Number of samples the next strip should have. + pub fn next_strip_sample_count(&self) -> u64 { + if self.strip_idx >= self.strip_count { + return 0; + } + + let raw_start_row = self.strip_idx * self.rows_per_strip; + let start_row = cmp::min(u64::from(self.height), raw_start_row); + let end_row = cmp::min(u64::from(self.height), raw_start_row + self.rows_per_strip); + + (end_row - start_row) * self.row_samples + } + + /// Write a single strip. + pub fn write_strip(&mut self, value: &[T::Inner]) -> TiffResult<()> + where + [T::Inner]: TiffValue, + { + let samples = self.next_strip_sample_count(); + if u64::try_from(value.len())? != samples { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Slice is wrong size for strip", + ) + .into()); + } + + // Write the (possible compressed) data to the encoder. + let offset = self.encoder.write_data(value)?; + let byte_count = self.encoder.last_written() as usize; + + self.strip_offsets.push(K::convert_offset(offset)?); + self.strip_byte_count.push(byte_count.try_into()?); + + self.strip_idx += 1; + Ok(()) + } + + /// Write strips from data + pub fn write_data(mut self, data: &[T::Inner]) -> TiffResult<()> + where + [T::Inner]: TiffValue, + { + let num_pix = usize::try_from(self.width)? + .checked_mul(usize::try_from(self.height)?) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "Image width * height exceeds usize", + ) + })?; + if data.len() < num_pix { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Input data slice is undersized for provided dimensions", + ) + .into()); + } + + self.encoder + .writer + .set_compression(self.compression.get_algorithm()); + + let mut idx = 0; + while self.next_strip_sample_count() > 0 { + let sample_count = usize::try_from(self.next_strip_sample_count())?; + self.write_strip(&data[idx..idx + sample_count])?; + idx += sample_count; + } + + self.encoder.writer.reset_compression(); + self.finish()?; + Ok(()) + } + + /// Set image resolution + pub fn resolution(&mut self, unit: ResolutionUnit, value: Rational) { + self.encoder + .write_tag(Tag::ResolutionUnit, unit.to_u16()) + .unwrap(); + self.encoder + .write_tag(Tag::XResolution, value.clone()) + .unwrap(); + self.encoder.write_tag(Tag::YResolution, value).unwrap(); + } + + /// Set image resolution unit + pub fn resolution_unit(&mut self, unit: ResolutionUnit) { + self.encoder + .write_tag(Tag::ResolutionUnit, unit.to_u16()) + .unwrap(); + } + + /// Set image x-resolution + pub fn x_resolution(&mut self, value: Rational) { + self.encoder.write_tag(Tag::XResolution, value).unwrap(); + } + + /// Set image y-resolution + pub fn y_resolution(&mut self, value: Rational) { + self.encoder.write_tag(Tag::YResolution, value).unwrap(); + } + + /// Set image number of lines per strip + /// + /// This function needs to be called before any calls to `write_data` or + /// `write_strip` and will return an error otherwise. + pub fn rows_per_strip(&mut self, value: u32) -> TiffResult<()> { + if self.strip_idx != 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Cannot change strip size after data was written", + ) + .into()); + } + // Write tag as 32 bits + self.encoder.write_tag(Tag::RowsPerStrip, value)?; + + let value: u64 = value as u64; + self.strip_count = (self.height as u64 + value - 1) / value; + self.rows_per_strip = value; + + Ok(()) + } + + fn finish_internal(&mut self) -> TiffResult<()> { + self.encoder + .write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?; + self.encoder.write_tag( + Tag::StripByteCounts, + K::convert_slice(&self.strip_byte_count), + )?; + self.dropped = true; + + self.encoder.finish_internal() + } + + /// Get a reference of the underlying `DirectoryEncoder` + pub fn encoder(&mut self) -> &mut DirectoryEncoder<'a, W, K> { + &mut self.encoder + } + + /// Write out image and ifd directory. + pub fn finish(mut self) -> TiffResult<()> { + self.finish_internal() + } +} + +impl<'a, W: Write + Seek, C: ColorType, K: TiffKind, D: Compression> Drop + for ImageEncoder<'a, W, C, K, D> +{ + fn drop(&mut self) { + if !self.dropped { + let _ = self.finish_internal(); + } + } +} + +struct DirectoryEntry { + data_type: u16, + count: S, + data: Vec, +} + +/// Trait to abstract over Tiff/BigTiff differences. +/// +/// Implemented for [`TiffKindStandard`] and [`TiffKindBig`]. +pub trait TiffKind { + /// The type of offset fields, `u32` for normal Tiff, `u64` for BigTiff. + type OffsetType: TryFrom + Into + TiffValue; + + /// Needed for the `convert_slice` method. + type OffsetArrayType: ?Sized + TiffValue; + + /// Write the (Big)Tiff header. + fn write_header(writer: &mut TiffWriter) -> TiffResult<()>; + + /// Convert a file offset to `Self::OffsetType`. + /// + /// This returns an error for normal Tiff if the offset is larger than `u32::MAX`. + fn convert_offset(offset: u64) -> TiffResult; + + /// Write an offset value to the given writer. + /// + /// Like `convert_offset`, this errors if `offset > u32::MAX` for normal Tiff. + fn write_offset(writer: &mut TiffWriter, offset: u64) -> TiffResult<()>; + + /// Write the IFD entry count field with the given `count` value. + /// + /// The entry count field is an `u16` for normal Tiff and `u64` for BigTiff. Errors + /// if the given `usize` is larger than the representable values. + fn write_entry_count(writer: &mut TiffWriter, count: usize) -> TiffResult<()>; + + /// Internal helper method for satisfying Rust's type checker. + /// + /// The `TiffValue` trait is implemented for both primitive values (e.g. `u8`, `u32`) and + /// slices of primitive values (e.g. `[u8]`, `[u32]`). However, this is not represented in + /// the type system, so there is no guarantee that that for all `T: TiffValue` there is also + /// an implementation of `TiffValue` for `[T]`. This method works around that problem by + /// providing a conversion from `[T]` to some value that implements `TiffValue`, thereby + /// making all slices of `OffsetType` usable with `write_tag` and similar methods. + /// + /// Implementations of this trait should always set `OffsetArrayType` to `[OffsetType]`. + fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType; +} + +/// Create a standard Tiff file. +pub struct TiffKindStandard; + +impl TiffKind for TiffKindStandard { + type OffsetType = u32; + type OffsetArrayType = [u32]; + + fn write_header(writer: &mut TiffWriter) -> TiffResult<()> { + write_tiff_header(writer)?; + // blank the IFD offset location + writer.write_u32(0)?; + + Ok(()) + } + + fn convert_offset(offset: u64) -> TiffResult { + Ok(Self::OffsetType::try_from(offset)?) + } + + fn write_offset(writer: &mut TiffWriter, offset: u64) -> TiffResult<()> { + writer.write_u32(u32::try_from(offset)?)?; + Ok(()) + } + + fn write_entry_count(writer: &mut TiffWriter, count: usize) -> TiffResult<()> { + writer.write_u16(u16::try_from(count)?)?; + + Ok(()) + } + + fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType { + slice + } +} + +/// Create a BigTiff file. +pub struct TiffKindBig; + +impl TiffKind for TiffKindBig { + type OffsetType = u64; + type OffsetArrayType = [u64]; + + fn write_header(writer: &mut TiffWriter) -> TiffResult<()> { + write_bigtiff_header(writer)?; + // blank the IFD offset location + writer.write_u64(0)?; + + Ok(()) + } + + fn convert_offset(offset: u64) -> TiffResult { + Ok(offset) + } + + fn write_offset(writer: &mut TiffWriter, offset: u64) -> TiffResult<()> { + writer.write_u64(offset)?; + Ok(()) + } + + fn write_entry_count(writer: &mut TiffWriter, count: usize) -> TiffResult<()> { + writer.write_u64(u64::try_from(count)?)?; + Ok(()) + } + + fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType { + slice + } +} diff --git a/vendor/tiff/src/encoder/tiff_value.rs b/vendor/tiff/src/encoder/tiff_value.rs new file mode 100644 index 0000000..43653f4 --- /dev/null +++ b/vendor/tiff/src/encoder/tiff_value.rs @@ -0,0 +1,523 @@ +use std::{borrow::Cow, io::Write, slice::from_ref}; + +use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult}; + +use super::writer::TiffWriter; + +/// Trait for types that can be encoded in a tiff file +pub trait TiffValue { + const BYTE_LEN: u8; + const FIELD_TYPE: Type; + fn count(&self) -> usize; + fn bytes(&self) -> usize { + self.count() * usize::from(Self::BYTE_LEN) + } + + /// Access this value as an contiguous sequence of bytes. + /// If their is no trivial representation, allocate it on the heap. + fn data(&self) -> Cow<[u8]>; + + /// Write this value to a TiffWriter. + /// While the default implementation will work in all cases, it may require unnecessary allocations. + /// The written bytes of any custom implementation MUST be the same as yielded by `self.data()`. + fn write(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> TiffResult<()> { + if self.is_ascii() && !self.bytes().any(|b| b == 0) { + writer.write_bytes(self.as_bytes())?; + writer.write_u8(0)?; + Ok(()) + } else { + Err(TiffError::FormatError(TiffFormatError::InvalidTag)) + } + } + + fn data(&self) -> Cow<[u8]> { + Cow::Owned({ + if self.is_ascii() && !self.bytes().any(|b| b == 0) { + let bytes: &[u8] = self.as_bytes(); + [bytes, &[0]].concat() + } else { + vec![] + } + }) + } +} + +impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T { + const BYTE_LEN: u8 = T::BYTE_LEN; + const FIELD_TYPE: Type = T::FIELD_TYPE; + + fn count(&self) -> usize { + (*self).count() + } + + fn write(&self, writer: &mut TiffWriter) -> 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(&self, writer: &mut TiffWriter) -> TiffResult<()> { + for x in self { + x.write(writer)?; + } + Ok(()) + } + + fn data(&self) -> Cow<[u8]> { + let mut buf: Vec = 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, +} diff --git a/vendor/tiff/src/encoder/writer.rs b/vendor/tiff/src/encoder/writer.rs new file mode 100644 index 0000000..c5139e9 --- /dev/null +++ b/vendor/tiff/src/encoder/writer.rs @@ -0,0 +1,188 @@ +use crate::encoder::compression::*; +use crate::error::TiffResult; +use std::io::{self, Seek, SeekFrom, Write}; + +pub fn write_tiff_header(writer: &mut TiffWriter) -> TiffResult<()> { + #[cfg(target_endian = "little")] + 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. +/// +/// Writes the byte order, version number, offset byte size, and zero constant fields. Does +// _not_ write the offset to the first IFD, this should be done by the caller. +pub fn write_bigtiff_header(writer: &mut TiffWriter) -> TiffResult<()> { + #[cfg(target_endian = "little")] + let boi: u8 = 0x49; + #[cfg(not(target_endian = "little"))] + let boi: u8 = 0x4d; + + // byte order indication + writer.writer.write_all(&[boi, boi])?; + // version number + writer.writer.write_all(&43u16.to_ne_bytes())?; + // bytesize of offsets (pointer size) + writer.writer.write_all(&8u16.to_ne_bytes())?; + // always 0 + writer.writer.write_all(&0u16.to_ne_bytes())?; + + // we wrote 8 bytes, so set the internal offset accordingly + writer.offset += 8; + + Ok(()) +} + +pub struct TiffWriter { + writer: W, + offset: u64, + byte_count: u64, + compressor: Compressor, +} + +impl TiffWriter { + pub fn new(writer: W) -> Self { + Self { + writer, + offset: 0, + byte_count: 0, + compressor: Compressor::default(), + } + } + + pub fn set_compression(&mut self, compressor: Compressor) { + self.compressor = compressor; + } + + pub fn reset_compression(&mut self) { + self.compressor = Compressor::default(); + } + + pub fn offset(&self) -> u64 { + self.offset + } + + pub fn last_written(&self) -> u64 { + self.byte_count + } + + pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> { + self.byte_count = self.compressor.write_to(&mut self.writer, bytes)?; + self.offset += self.byte_count; + Ok(()) + } + + pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> { + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; + Ok(()) + } + + pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> { + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; + Ok(()) + } + + pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> { + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; + + Ok(()) + } + + pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> { + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; + + Ok(()) + } + + pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> { + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; + + Ok(()) + } + + pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> { + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; + + Ok(()) + } + + pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> { + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; + + Ok(()) + } + + pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> { + self.byte_count = self + .compressor + .write_to(&mut self.writer, &n.to_ne_bytes())?; + self.offset += self.byte_count; + + Ok(()) + } + + pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> { + self.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(()) + } + + pub fn pad_word_boundary(&mut self) -> Result<(), io::Error> { + if self.offset % 4 != 0 { + let padding = [0, 0, 0]; + let padd_len = 4 - (self.offset % 4); + self.writer.write_all(&padding[..padd_len as usize])?; + self.offset += padd_len; + } + + Ok(()) + } +} + +impl TiffWriter { + pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> { + self.offset = offset; + self.writer.seek(SeekFrom::Start(offset as u64))?; + Ok(()) + } +} diff --git a/vendor/tiff/src/error.rs b/vendor/tiff/src/error.rs new file mode 100644 index 0000000..a401c6f --- /dev/null +++ b/vendor/tiff/src/error.rs @@ -0,0 +1,369 @@ +use std::error::Error; +use std::fmt; +use std::fmt::Display; +use std::io; +use std::str; +use std::string; +use std::sync::Arc; + +use jpeg::UnsupportedFeature; + +use crate::decoder::{ifd::Value, ChunkType}; +use crate::tags::{ + CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag, +}; +use crate::ColorType; + +use crate::weezl::LzwError; + +/// Tiff error kinds. +#[derive(Debug)] +pub enum TiffError { + /// The Image is not formatted properly. + FormatError(TiffFormatError), + + /// The Decoder does not support features required by the image. + UnsupportedError(TiffUnsupportedError), + + /// An I/O Error occurred while decoding the image. + IoError(io::Error), + + /// The Limits of the Decoder is exceeded. + LimitsExceeded, + + /// An integer conversion to or from a platform size failed, either due to + /// limits of the platform size or limits of the format. + IntSizeError, + + /// The image does not support the requested operation + UsageError(UsageError), +} + +/// The image is not formatted properly. +/// +/// This indicates that the encoder producing the image might behave incorrectly or that the input +/// file has been corrupted. +/// +/// The list of variants may grow to incorporate errors of future features. Matching against this +/// exhaustively is not covered by interface stability guarantees. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub enum TiffFormatError { + TiffSignatureNotFound, + TiffSignatureInvalid, + ImageFileDirectoryNotFound, + InconsistentSizesEncountered, + UnexpectedCompressedData { + actual_bytes: usize, + required_bytes: usize, + }, + InconsistentStripSamples { + actual_samples: usize, + required_samples: usize, + }, + InvalidDimensions(u32, u32), + InvalidTag, + InvalidTagValueType(Tag), + RequiredTagNotFound(Tag), + UnknownPredictor(u16), + ByteExpected(Value), + UnsignedIntegerExpected(Value), + SignedIntegerExpected(Value), + Format(String), + RequiredTagEmpty(Tag), + StripTileTagConflict, + CycleInOffsets, + JpegDecoder(JpegDecoderError), +} + +impl fmt::Display for TiffFormatError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + use self::TiffFormatError::*; + match *self { + TiffSignatureNotFound => write!(fmt, "TIFF signature not found."), + TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."), + ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."), + InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."), + UnexpectedCompressedData { + actual_bytes, + required_bytes, + } => { + write!( + fmt, + "Decompression returned different amount of bytes than expected: got {}, expected {}.", + actual_bytes, required_bytes + ) + } + InconsistentStripSamples { + actual_samples, + required_samples, + } => { + write!( + fmt, + "Inconsistent elements in strip: got {}, expected {}.", + actual_samples, required_samples + ) + } + InvalidDimensions(width, height) => write!(fmt, "Invalid dimensions: {}x{}.", width, height), + InvalidTag => write!(fmt, "Image contains invalid tag."), + InvalidTagValueType(ref tag) => { + write!(fmt, "Tag `{:?}` did not have the expected value type.", tag) + } + RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag), + UnknownPredictor(ref predictor) => { + write!(fmt, "Unknown predictor “{}” encountered", predictor) + } + ByteExpected(ref val) => write!(fmt, "Expected byte, {:?} found.", val), + UnsignedIntegerExpected(ref val) => { + write!(fmt, "Expected unsigned integer, {:?} found.", val) + } + SignedIntegerExpected(ref val) => { + write!(fmt, "Expected signed integer, {:?} found.", val) + } + Format(ref val) => write!(fmt, "Invalid format: {:?}.", val), + RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val), + StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."), + CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"), + JpegDecoder(ref error) => write!(fmt, "{}", error), + } + } +} + +/// The Decoder does not support features required by the image. +/// +/// This only captures known failures for which the standard either does not require support or an +/// implementation has been planned but not yet completed. Some variants may become unused over +/// time and will then get deprecated before being removed. +/// +/// The list of variants may grow. Matching against this exhaustively is not covered by interface +/// stability guarantees. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum TiffUnsupportedError { + FloatingPointPredictor(ColorType), + HorizontalPredictor(ColorType), + InterpretationWithBits(PhotometricInterpretation, Vec), + UnknownInterpretation, + UnknownCompressionMethod, + UnsupportedCompressionMethod(CompressionMethod), + UnsupportedSampleDepth(u8), + UnsupportedSampleFormat(Vec), + UnsupportedColorType(ColorType), + UnsupportedBitsPerChannel(u8), + UnsupportedPlanarConfig(Option), + UnsupportedDataType, + UnsupportedInterpretation(PhotometricInterpretation), + UnsupportedJpegFeature(UnsupportedFeature), +} + +impl fmt::Display for TiffUnsupportedError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + use self::TiffUnsupportedError::*; + match *self { + FloatingPointPredictor(color_type) => write!( + fmt, + "Floating point predictor for {:?} is unsupported.", + color_type + ), + HorizontalPredictor(color_type) => write!( + fmt, + "Horizontal predictor for {:?} is unsupported.", + color_type + ), + InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => write!( + fmt, + "{:?} with {:?} bits per sample is unsupported", + photometric_interpretation, bits_per_sample + ), + UnknownInterpretation => write!( + fmt, + "The image is using an unknown photometric interpretation." + ), + UnknownCompressionMethod => write!(fmt, "Unknown compression method."), + UnsupportedCompressionMethod(method) => { + write!(fmt, "Compression method {:?} is unsupported", method) + } + UnsupportedSampleDepth(samples) => { + write!(fmt, "{} samples per pixel is unsupported.", samples) + } + UnsupportedSampleFormat(ref formats) => { + write!(fmt, "Sample format {:?} is unsupported.", formats) + } + UnsupportedColorType(color_type) => { + write!(fmt, "Color type {:?} is unsupported", color_type) + } + UnsupportedBitsPerChannel(bits) => { + write!(fmt, "{} bits per channel not supported", bits) + } + UnsupportedPlanarConfig(config) => { + write!(fmt, "Unsupported planar configuration “{:?}”.", config) + } + UnsupportedDataType => write!(fmt, "Unsupported data type."), + UnsupportedInterpretation(interpretation) => { + write!( + fmt, + "Unsupported photometric interpretation \"{:?}\".", + interpretation + ) + } + UnsupportedJpegFeature(ref unsupported_feature) => { + write!(fmt, "Unsupported JPEG feature {:?}", unsupported_feature) + } + } + } +} + +/// User attempted to use the Decoder in a way that is incompatible with a specific image. +/// +/// For example: attempting to read a tile from a stripped image. +#[derive(Debug)] +pub enum UsageError { + InvalidChunkType(ChunkType, ChunkType), + InvalidChunkIndex(u32), +} + +impl fmt::Display for UsageError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + use self::UsageError::*; + match *self { + InvalidChunkType(expected, actual) => { + write!( + fmt, + "Requested operation is only valid for images with chunk encoding of type: {:?}, got {:?}.", + expected, actual + ) + } + InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({}) requested.", index), + } + } +} + +impl fmt::Display for TiffError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + TiffError::FormatError(ref e) => write!(fmt, "Format error: {}", e), + TiffError::UnsupportedError(ref f) => write!( + fmt, + "The Decoder does not support the \ + image format `{}`", + f + ), + TiffError::IoError(ref e) => e.fmt(fmt), + TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"), + TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"), + TiffError::UsageError(ref e) => write!(fmt, "Usage error: {}", e), + } + } +} + +impl Error for TiffError { + fn description(&self) -> &str { + match *self { + TiffError::FormatError(..) => "Format error", + TiffError::UnsupportedError(..) => "Unsupported error", + TiffError::IoError(..) => "IO error", + TiffError::LimitsExceeded => "Decoder limits exceeded", + TiffError::IntSizeError => "Platform or format size limits exceeded", + TiffError::UsageError(..) => "Invalid usage", + } + } + + fn cause(&self) -> Option<&dyn Error> { + match *self { + TiffError::IoError(ref e) => Some(e), + _ => None, + } + } +} + +impl From for TiffError { + fn from(err: io::Error) -> TiffError { + TiffError::IoError(err) + } +} + +impl From for TiffError { + fn from(_err: str::Utf8Error) -> TiffError { + TiffError::FormatError(TiffFormatError::InvalidTag) + } +} + +impl From for TiffError { + fn from(_err: string::FromUtf8Error) -> TiffError { + TiffError::FormatError(TiffFormatError::InvalidTag) + } +} + +impl From for TiffError { + fn from(err: TiffFormatError) -> TiffError { + TiffError::FormatError(err) + } +} + +impl From for TiffError { + fn from(err: TiffUnsupportedError) -> TiffError { + TiffError::UnsupportedError(err) + } +} + +impl From for TiffError { + fn from(err: UsageError) -> TiffError { + TiffError::UsageError(err) + } +} + +impl From for TiffError { + fn from(_err: std::num::TryFromIntError) -> TiffError { + TiffError::IntSizeError + } +} + +impl From for TiffError { + fn from(err: LzwError) -> TiffError { + match err { + LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from( + "LZW compressed data corrupted", + ))), + } + } +} + +#[derive(Debug, Clone)] +pub struct JpegDecoderError { + inner: Arc, +} + +impl JpegDecoderError { + fn new(error: jpeg::Error) -> Self { + Self { + inner: Arc::new(error), + } + } +} + +impl PartialEq for JpegDecoderError { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.inner, &other.inner) + } +} + +impl Display for JpegDecoderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl From for TiffError { + fn from(error: JpegDecoderError) -> Self { + TiffError::FormatError(TiffFormatError::JpegDecoder(error)) + } +} + +impl From for TiffError { + fn from(error: jpeg::Error) -> Self { + JpegDecoderError::new(error).into() + } +} + +/// Result of an image decoding/encoding process +pub type TiffResult = Result; diff --git a/vendor/tiff/src/lib.rs b/vendor/tiff/src/lib.rs new file mode 100644 index 0000000..8f23f35 --- /dev/null +++ b/vendor/tiff/src/lib.rs @@ -0,0 +1,43 @@ +//! Decoding and Encoding of TIFF Images +//! +//! TIFF (Tagged Image File Format) is a versatile image format that supports +//! lossless and lossy compression. +//! +//! # Related Links +//! * - The TIFF specification + +extern crate jpeg; +extern crate weezl; + +mod bytecast; +pub mod decoder; +pub mod encoder; +mod error; +pub mod tags; + +pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError}; + +/// An enumeration over supported color types and their bit depths +#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] +pub enum ColorType { + /// Pixel is grayscale + Gray(u8), + + /// Pixel contains R, G and B channels + RGB(u8), + + /// Pixel is an index into a color palette + Palette(u8), + + /// Pixel is grayscale with an alpha channel + GrayA(u8), + + /// Pixel is RGB with an alpha channel + RGBA(u8), + + /// Pixel is CMYK + CMYK(u8), + + /// Pixel is YCbCr + YCbCr(u8), +} diff --git a/vendor/tiff/src/tags.rs b/vendor/tiff/src/tags.rs new file mode 100644 index 0000000..cbf7472 --- /dev/null +++ b/vendor/tiff/src/tags.rs @@ -0,0 +1,234 @@ +macro_rules! tags { + { + // Permit arbitrary meta items, which include documentation. + $( #[$enum_attr:meta] )* + $vis:vis enum $name:ident($ty:tt) $(unknown($unknown_doc:literal))* { + // Each of the `Name = Val,` permitting documentation. + $($(#[$ident_attr:meta])* $tag:ident = $val:expr,)* + } + } => { + $( #[$enum_attr] )* + #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] + #[non_exhaustive] + pub enum $name { + $($(#[$ident_attr])* $tag,)* + $( + #[doc = $unknown_doc] + Unknown($ty), + )* + } + + impl $name { + #[inline(always)] + fn __from_inner_type(n: $ty) -> Result { + match n { + $( $val => Ok($name::$tag), )* + n => Err(n), + } + } + + #[inline(always)] + fn __to_inner_type(&self) -> $ty { + match *self { + $( $name::$tag => $val, )* + $( $name::Unknown(n) => { $unknown_doc; n }, )* + } + } + } + + tags!($name, $ty, $($unknown_doc)*); + }; + // For u16 tags, provide direct inherent primitive conversion methods. + ($name:tt, u16, $($unknown_doc:literal)*) => { + impl $name { + #[inline(always)] + pub fn from_u16(val: u16) -> Option { + Self::__from_inner_type(val).ok() + } + + $( + #[inline(always)] + pub fn from_u16_exhaustive(val: u16) -> Self { + $unknown_doc; + Self::__from_inner_type(val).unwrap_or_else(|_| $name::Unknown(val)) + } + )* + + #[inline(always)] + pub fn to_u16(&self) -> u16 { + Self::__to_inner_type(self) + } + } + }; + // For other tag types, do nothing for now. With concat_idents one could + // provide inherent conversion methods for all types. + ($name:tt, $ty:tt, $($unknown_doc:literal)*) => {}; +} + +// Note: These tags appear in the order they are mentioned in the TIFF reference +tags! { +/// TIFF tags +pub enum Tag(u16) unknown("A private or extension tag") { + // Baseline tags: + Artist = 315, + // grayscale images PhotometricInterpretation 1 or 3 + BitsPerSample = 258, + CellLength = 265, // TODO add support + CellWidth = 264, // TODO add support + // palette-color images (PhotometricInterpretation 3) + ColorMap = 320, // TODO add support + Compression = 259, // TODO add support for 2 and 32773 + Copyright = 33_432, + DateTime = 306, + ExtraSamples = 338, // TODO add support + FillOrder = 266, // TODO add support + FreeByteCounts = 289, // TODO add support + FreeOffsets = 288, // TODO add support + GrayResponseCurve = 291, // TODO add support + GrayResponseUnit = 290, // TODO add support + HostComputer = 316, + ImageDescription = 270, + ImageLength = 257, + ImageWidth = 256, + Make = 271, + MaxSampleValue = 281, // TODO add support + MinSampleValue = 280, // TODO add support + Model = 272, + NewSubfileType = 254, // TODO add support + Orientation = 274, // TODO add support + PhotometricInterpretation = 262, + PlanarConfiguration = 284, + ResolutionUnit = 296, // TODO add support + RowsPerStrip = 278, + SamplesPerPixel = 277, + Software = 305, + StripByteCounts = 279, + StripOffsets = 273, + SubfileType = 255, // TODO add support + Threshholding = 263, // TODO add support + XResolution = 282, + YResolution = 283, + // Advanced tags + Predictor = 317, + TileWidth = 322, + TileLength = 323, + TileOffsets = 324, + TileByteCounts = 325, + // Data Sample Format + SampleFormat = 339, + SMinSampleValue = 340, // TODO add support + SMaxSampleValue = 341, // TODO add support + // JPEG + JPEGTables = 347, + // GeoTIFF + ModelPixelScaleTag = 33550, // (SoftDesk) + ModelTransformationTag = 34264, // (JPL Carto Group) + ModelTiepointTag = 33922, // (Intergraph) + GeoKeyDirectoryTag = 34735, // (SPOT) + GeoDoubleParamsTag = 34736, // (SPOT) + GeoAsciiParamsTag = 34737, // (SPOT) + GdalNodata = 42113, // Contains areas with missing data +} +} + +tags! { +/// The type of an IFD entry (a 2 byte field). +pub enum Type(u16) { + /// 8-bit unsigned integer + BYTE = 1, + /// 8-bit byte that contains a 7-bit ASCII code; the last byte must be zero + ASCII = 2, + /// 16-bit unsigned integer + SHORT = 3, + /// 32-bit unsigned integer + LONG = 4, + /// Fraction stored as two 32-bit unsigned integers + RATIONAL = 5, + /// 8-bit signed integer + SBYTE = 6, + /// 8-bit byte that may contain anything, depending on the field + UNDEFINED = 7, + /// 16-bit signed integer + SSHORT = 8, + /// 32-bit signed integer + SLONG = 9, + /// Fraction stored as two 32-bit signed integers + SRATIONAL = 10, + /// 32-bit IEEE floating point + FLOAT = 11, + /// 64-bit IEEE floating point + DOUBLE = 12, + /// 32-bit unsigned integer (offset) + IFD = 13, + /// BigTIFF 64-bit unsigned integer + LONG8 = 16, + /// BigTIFF 64-bit signed integer + SLONG8 = 17, + /// BigTIFF 64-bit unsigned integer (offset) + IFD8 = 18, +} +} + +tags! { +/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html) +/// for reference. +pub enum CompressionMethod(u16) { + None = 1, + Huffman = 2, + Fax3 = 3, + Fax4 = 4, + LZW = 5, + JPEG = 6, + // "Extended JPEG" or "new JPEG" style + ModernJPEG = 7, + Deflate = 8, + OldDeflate = 0x80B2, + PackBits = 0x8005, +} +} + +tags! { +pub enum PhotometricInterpretation(u16) { + WhiteIsZero = 0, + BlackIsZero = 1, + RGB = 2, + RGBPalette = 3, + TransparencyMask = 4, + CMYK = 5, + YCbCr = 6, + CIELab = 8, +} +} + +tags! { +pub enum PlanarConfiguration(u16) { + Chunky = 1, + Planar = 2, +} +} + +tags! { +pub enum Predictor(u16) { + None = 1, + Horizontal = 2, + FloatingPoint = 3, +} +} + +tags! { +/// Type to represent resolution units +pub enum ResolutionUnit(u16) { + None = 1, + Inch = 2, + Centimeter = 3, +} +} + +tags! { +pub enum SampleFormat(u16) unknown("An unknown extension sample format") { + Uint = 1, + Int = 2, + IEEEFP = 3, + Void = 4, +} +} diff --git a/vendor/tiff/tests/benches/README.md b/vendor/tiff/tests/benches/README.md new file mode 100644 index 0000000..616292c --- /dev/null +++ b/vendor/tiff/tests/benches/README.md @@ -0,0 +1,4 @@ +Copyrights: + +kodim*.png: Eastman Kodak Company, released for unrestricted use +Transparency.png: Public Domain, according to Wikimedia diff --git a/vendor/tiff/tests/benches/Transparency-lzw.tif b/vendor/tiff/tests/benches/Transparency-lzw.tif new file mode 100644 index 0000000..a7a26a9 Binary files /dev/null and b/vendor/tiff/tests/benches/Transparency-lzw.tif differ diff --git a/vendor/tiff/tests/benches/kodim02-lzw.tif b/vendor/tiff/tests/benches/kodim02-lzw.tif new file mode 100644 index 0000000..a1aabfb Binary files /dev/null and b/vendor/tiff/tests/benches/kodim02-lzw.tif differ diff --git a/vendor/tiff/tests/benches/kodim07-lzw.tif b/vendor/tiff/tests/benches/kodim07-lzw.tif new file mode 100644 index 0000000..f8d543d Binary files /dev/null and b/vendor/tiff/tests/benches/kodim07-lzw.tif differ diff --git a/vendor/tiff/tests/decode_bigtiff_images.rs b/vendor/tiff/tests/decode_bigtiff_images.rs new file mode 100644 index 0000000..9113f42 --- /dev/null +++ b/vendor/tiff/tests/decode_bigtiff_images.rs @@ -0,0 +1,46 @@ +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 + ) + } +} diff --git a/vendor/tiff/tests/decode_images.rs b/vendor/tiff/tests/decode_images.rs new file mode 100644 index 0000000..9d5b809 --- /dev/null +++ b/vendor/tiff/tests/decode_images.rs @@ -0,0 +1,479 @@ +extern crate tiff; + +use tiff::decoder::{ifd, Decoder, DecodingResult}; +use tiff::ColorType; + +use std::fs::File; +use std::path::PathBuf; + +const TEST_IMAGE_DIR: &str = "./tests/images/"; + +macro_rules! test_image_sum { + ($name:ident, $buffer:ident, $sum_ty:ty) => { + fn $name(file: &str, expected_type: ColorType, expected_sum: $sum_ty) { + 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 img_res = decoder.read_image().unwrap(); + + match img_res { + DecodingResult::$buffer(res) => { + let sum: $sum_ty = res.into_iter().map(<$sum_ty>::from).sum(); + assert_eq!(sum, expected_sum); + } + _ => panic!("Wrong bit depth"), + } + } + }; +} + +test_image_sum!(test_image_sum_u8, U8, u64); +test_image_sum!(test_image_sum_i8, I8, i64); +test_image_sum!(test_image_sum_u16, U16, u64); +test_image_sum!(test_image_sum_i16, I16, i64); +test_image_sum!(test_image_sum_u32, U32, u64); +test_image_sum!(test_image_sum_u64, U64, u64); +test_image_sum!(test_image_sum_f32, F32, f32); +test_image_sum!(test_image_sum_f64, F64, f64); + +/// 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 +/// unsupported. +fn test_image_color_type_unsupported(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); + assert!(match decoder.read_image() { + Err(tiff::TiffError::UnsupportedError( + tiff::TiffUnsupportedError::UnsupportedColorType(_), + )) => true, + _ => false, + }); +} + +#[test] +fn test_cmyk_u8() { + test_image_sum_u8("cmyk-3c-8b.tiff", ColorType::CMYK(8), 8522658); +} + +#[test] +fn test_cmyk_u16() { + 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] +fn test_gray_u8() { + test_image_sum_u8("minisblack-1c-8b.tiff", ColorType::Gray(8), 2840893); +} + +#[test] +fn test_gray_u12() { + test_image_color_type_unsupported("12bit.cropped.tiff", ColorType::Gray(12)); +} + +#[test] +fn test_gray_u16() { + test_image_sum_u16("minisblack-1c-16b.tiff", ColorType::Gray(16), 733126239); +} + +#[test] +fn test_gray_u32() { + test_image_sum_u32("gradient-1c-32b.tiff", ColorType::Gray(32), 549892913787); +} + +#[test] +fn test_gray_u64() { + 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] +fn test_rgb_u8() { + test_image_sum_u8("rgb-3c-8b.tiff", ColorType::RGB(8), 7842108); +} + +#[test] +fn test_rgb_u12() { + test_image_color_type_unsupported("12bit.cropped.rgb.tiff", ColorType::RGB(12)); +} + +#[test] +fn test_rgb_u16() { + test_image_sum_u16("rgb-3c-16b.tiff", ColorType::RGB(16), 2024349944); +} + +#[test] +fn test_rgb_u32() { + test_image_sum_u32("gradient-3c-32b.tiff", ColorType::RGB(32), 2030834111716); +} + +#[test] +fn test_rgb_u64() { + 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] +fn test_string_tags() { + // these files have null-terminated strings for their Software tag. One has extra bytes after + // the null byte, so we check both to ensure that we're truncating properly + let filenames = ["minisblack-1c-16b.tiff", "rgb-3c-16b.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"); + let software = decoder.get_tag(tiff::tags::Tag::Software).unwrap(); + match software { + ifd::Value::Ascii(s) => assert_eq!( + &s, + "GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/" + ), + _ => assert!(false), + }; + } +} + +#[test] +fn test_decode_data() { + 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 file = File::open("./tests/decodedata-rgb-3c-8b.tiff").unwrap(); + let mut decoder = Decoder::new(file).unwrap(); + assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); + assert_eq!(decoder.dimensions().unwrap(), (100, 100)); + if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() { + assert_eq!(image_data, img_res); + } else { + panic!("Wrong data type"); + } +} + +#[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 +//#[test] +//fn test_gray_alpha_u8() +//{ +//let img_file = File::open("./tests/images/minisblack-2c-8b-alpha.tiff").expect("Cannot find test image!"); +//let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); +//assert_eq!(decoder.colortype().unwrap(), ColorType::GrayA(8)); +//let img_res = decoder.read_image(); +//assert!(img_res.is_ok()); +//} + +#[test] +fn test_tiled_rgb_u8() { + test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948); +} + +#[test] +fn test_tiled_rect_rgb_u8() { + test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032); +} + +/* #[test] +fn test_tiled_jpeg_rgb_u8() { + test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606); +} */ + +#[test] +fn test_tiled_oversize_gray_i8() { + test_image_sum_i8("tiled-oversize-gray-i8.tif", ColorType::Gray(8), 1214996); +} + +#[test] +fn test_tiled_cmyk_i8() { + test_image_sum_i8("tiled-cmyk-i8.tif", ColorType::CMYK(8), 1759101); +} + +#[test] +fn test_tiled_incremental() { + let file = "tiled-rgb-u8.tif"; + let expected_type = ColorType::RGB(8); + let sums = [ + 188760, 195639, 108148, 81986, 665088, 366140, 705317, 423366, 172033, 324455, 244102, + 81853, 181258, 247971, 129486, 55600, 565625, 422102, 730888, 379271, 232142, 292549, + 244045, 86866, 188141, 115036, 150785, 84389, 353170, 459325, 719619, 329594, 278663, + 220474, 243048, 113563, 189152, 109684, 179391, 122188, 279651, 622093, 724682, 302459, + 268428, 204499, 224255, 124674, 170668, 121868, 192768, 183367, 378029, 585651, 657712, + 296790, 241444, 197083, 198429, 134869, 182318, 86034, 203655, 182338, 297255, 601284, + 633813, 242531, 228578, 206441, 193552, 125412, 181527, 165439, 202531, 159538, 268388, + 565790, 611382, 272967, 236497, 215154, 158881, 90806, 106114, 182342, 191824, 186138, + 215174, 393193, 701228, 198866, 227944, 193830, 166330, 49008, 55719, 122820, 197316, + 161969, 203152, 170986, 624427, 188605, 186187, 111064, 115192, 39538, 48626, 163929, + 144682, 135796, 194141, 154198, 584125, 180255, 153524, 121433, 132641, 35743, 47798, + 152343, 162874, 167664, 160175, 133038, 659882, 138339, 166470, 124173, 118929, 51317, + 45267, 155776, 161331, 161006, 130052, 137618, 337291, 106481, 161999, 127343, 87724, + 59540, 63907, 155677, 140668, 141523, 108061, 168657, 186482, 98599, 147614, 139963, 90444, + 56602, 92547, 125644, 134212, 126569, 144153, 179800, 174516, 133969, 129399, 117681, + 83305, 55075, 110737, 115108, 128572, 128911, 130922, 179986, 143288, 145884, 155856, + 96683, 94057, 56238, 79649, 71651, 70182, 75010, 77009, 98855, 78979, 74341, 83482, 53403, + 59842, 30305, + ]; + + let path = PathBuf::from(TEST_IMAGE_DIR).join(file); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!(decoder.colortype().unwrap(), expected_type); + + let tiles = decoder.tile_count().unwrap(); + assert_eq!(tiles as usize, sums.len()); + + for tile in 0..tiles { + match decoder.read_chunk(tile).unwrap() { + DecodingResult::U8(res) => { + let sum: u64 = res.into_iter().map(::from).sum(); + assert_eq!(sum, sums[tile as usize]); + } + _ => panic!("Wrong bit depth"), + } + } +} + +#[test] +fn test_div_zero() { + use tiff::{TiffError, TiffFormatError}; + + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, + 0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 39, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 158, 0, 0, 251, 67, 1, 3, 0, + 1, 0, 0, 0, 40, 0, 0, 0, 66, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 178, 178, 178, 178, + 178, 178, 178, + ]; + + let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + + match err { + TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {} + unexpected => panic!("Unexpected error {}", unexpected), + } +} + +#[test] +fn test_too_many_value_bytes() { + let image = [ + 73, 73, 43, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 8, 0, 0, 0, + 23, 0, 12, 0, 0, 65, 4, 0, 1, 6, 0, 0, 1, 16, 0, 1, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 59, 73, 84, 186, 202, 83, 240, 66, 1, 53, 22, 56, 47, + 0, 0, 0, 0, 0, 0, 1, 222, 4, 0, 58, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 4, 0, 0, 100, 0, + 0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18, + ]; + + let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + + match error { + tiff::TiffError::LimitsExceeded => {} + unexpected => panic!("Unexpected error {}", unexpected), + } +} + +#[test] +fn fuzzer_testcase5() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0, + 0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 246, 16, 0, 0, 22, 1, 4, 0, 1, + 0, 0, 0, 40, 0, 251, 255, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 178, 178, 178, 178, + 178, 178, 178, + ]; + + let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap(); + + let _ = decoder.read_image().unwrap_err(); +} + +#[test] +fn fuzzer_testcase1() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1, + 0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 17, 1, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, + 22, 1, 4, 0, 1, 0, 0, 0, 44, 0, 0, 0, 23, 1, 4, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 1, 0, 178, + 178, + ]; + + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +} + +#[test] +fn test_stripped_image_overflow() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0, + 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, 22, + 1, 4, 0, 1, 0, 0, 0, 44, 0, 248, 255, 23, 1, 4, 0, 1, 0, 0, 0, 178, 178, 178, 0, 1, 178, + 178, 178, + ]; + + let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap(); + + let err = decoder.read_image().unwrap_err(); + + match err { + tiff::TiffError::LimitsExceeded => {} + unexpected => panic!("Unexpected error {}", unexpected), + } +} + +#[test] +fn oom() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, + 0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, + 17, 1, 4, 0, 1, 0, 0, 0, 3, 77, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1, + 0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178, + ]; + + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +} + +#[test] +fn fuzzer_testcase4() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, + 0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1, + 0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13, + ]; + + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +} + +#[test] +fn fuzzer_testcase2() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0, + 0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0, + 73, 42, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 158, 0, 0, 251, 3, 1, + 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 4, 61, 1, 18, 0, 1, 0, 0, 0, 202, 0, 0, 0, 17, + 1, 100, 0, 129, 0, 0, 0, 0, 0, 0, 0, 232, 254, 252, 255, 254, 255, 255, 255, 1, 29, 0, 0, + 22, 1, 3, 0, 1, 0, 0, 0, 16, 0, 0, 0, 23, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 123, 73, 254, 0, + 73, + ]; + + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +} + +#[test] +fn invalid_jpeg_tag_2() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 242, 0, 1, 4, 0, 1, 0, + 0, 0, 0, 129, 16, 0, 1, 1, 4, 0, 1, 0, 0, 0, 214, 0, 0, 248, 253, 1, 3, 0, 1, 0, 0, 0, 64, + 0, 0, 0, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 64, 14, 1, 0, + 2, 0, 0, 148, 0, 206, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 22, 1, 4, 0, 17, 0, 0, 201, 1, 0, 0, 0, 23, 1, 2, 0, 20, 0, 0, 0, 194, 0, 0, 0, + 91, 1, 7, 0, 5, 0, 0, 0, 64, 0, 0, 0, 237, 254, 65, 255, 255, 255, 255, 255, 1, 0, 0, 0, + 22, 1, 4, 0, 1, 0, 0, 0, 42, 0, 0, 0, 23, 1, 255, 255, 255, 255, 255, 36, 36, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0, + ]; + + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +} + +#[test] +fn fuzzer_testcase3() { + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22, + 128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 3, 0, 1, 0, 0, 0, 17, 1, 0, 231, 22, 1, 1, 0, 1, 0, 0, 0, 130, 0, 0, 0, 23, 1, 4, + 0, 14, 0, 0, 0, 0, 0, 0, 0, 133, 133, 133, 77, 77, 77, 0, 0, 22, 128, 0, 255, 255, 255, + 255, 255, + ]; + + let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +} + +#[test] +fn timeout() { + use tiff::{TiffError, TiffFormatError}; + + let image = [ + 73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 68, 0, 0, 0, 2, 0, 32, 254, 252, 0, 109, + 0, 129, 0, 0, 0, 32, 0, 58, 0, 1, 4, 0, 1, 0, 6, 0, 0, 0, 8, 0, 0, 1, 73, 73, 42, 0, 8, 0, + 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 21, 0, 0, 0, 61, 1, 255, 128, 9, 0, 0, 8, 0, 1, 113, 2, + 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 112, 0, 0, 36, 0, 0, + 0, 112, 56, 200, 0, 5, 0, 0, 64, 0, 0, 1, 0, 4, 0, 0, 0, 2, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, + 0, 0, 4, 17, 1, 1, 0, 93, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 3, 6, 0, 231, 22, 1, + 1, 0, 1, 0, 0, 0, 2, 64, 118, 36, 23, 1, 1, 0, 43, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 8, + 0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32, + ]; + + let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + + match error { + TiffError::FormatError(TiffFormatError::CycleInOffsets) => {} + e => panic!("Unexpected error {:?}", e), + } +} + +#[test] +fn test_no_rows_per_strip() { + test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840); +} + +#[test] +fn test_predictor_3_rgb_f32() { + test_image_sum_f32("predictor-3-rgb-f32.tif", ColorType::RGB(32), 54004.33); +} + +#[test] +fn test_predictor_3_gray_f32() { + test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275); +} diff --git a/vendor/tiff/tests/decodedata-rgb-3c-8b.tiff b/vendor/tiff/tests/decodedata-rgb-3c-8b.tiff new file mode 100644 index 0000000..ad80292 Binary files /dev/null and b/vendor/tiff/tests/decodedata-rgb-3c-8b.tiff differ diff --git a/vendor/tiff/tests/encode_images.rs b/vendor/tiff/tests/encode_images.rs new file mode 100644 index 0000000..57c569f --- /dev/null +++ b/vendor/tiff/tests/encode_images.rs @@ -0,0 +1,529 @@ +extern crate tiff; + +use tiff::decoder::{ifd, Decoder, DecodingResult}; +use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder}; +use tiff::tags::Tag; +use tiff::ColorType; + +use std::fs::File; +use std::io::{Cursor, Seek, SeekFrom}; +use std::path::PathBuf; + +#[test] +fn encode_decode() { + 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(&mut file).unwrap(); + + let mut image = tiff.new_image::(100, 100).unwrap(); + image + .encoder() + .write_tag(Tag::Artist, "Image-tiff") + .unwrap(); + image.write_data(&image_data).unwrap(); + } + { + file.seek(SeekFrom::Start(0)).unwrap(); + let mut decoder = Decoder::new(&mut file).unwrap(); + assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); + assert_eq!(decoder.dimensions().unwrap(), (100, 100)); + assert_eq!( + decoder.get_tag(Tag::Artist).unwrap(), + ifd::Value::Ascii("Image-tiff".into()) + ); + if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() { + assert_eq!(image_data, img_res); + } else { + panic!("Wrong data type"); + } + } +} + +#[test] +fn 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::(100, 100).unwrap(); + image + .encoder() + .write_tag(Tag::Artist, "Image-tiff") + .unwrap(); + image.write_data(&image_data).unwrap(); + } + { + file.seek(SeekFrom::Start(0)).unwrap(); + let mut decoder = Decoder::new(&mut file).unwrap(); + assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); + assert_eq!(decoder.dimensions().unwrap(), (100, 100)); + assert_eq!( + decoder.get_tag(Tag::Artist).unwrap(), + ifd::Value::Ascii("Image-tiff".into()) + ); + if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() { + assert_eq!(image_data, img_res); + } else { + panic!("Wrong data type"); + } + } +} + +#[test] +fn test_encode_ifd() { + let mut data = Cursor::new(Vec::new()); + + { + let mut tiff = TiffEncoder::new(&mut data).unwrap(); + let mut image_encoder = tiff.new_image::(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 that attempting to encode when the input buffer is undersized returns +/// an error rather than panicking. +/// See: https://github.com/PistonDevelopers/image-tiff/issues/35 +fn test_encode_undersized_buffer() { + let input_data = vec![1, 2, 3]; + let output = Vec::new(); + let mut output_stream = Cursor::new(output); + if let Ok(mut tiff) = TiffEncoder::new(&mut output_stream) { + let res = tiff.write_image::(50, 50, &input_data); + assert!(res.is_err()); + } +} + +const TEST_IMAGE_DIR: &str = "./tests/images/"; + +macro_rules! test_roundtrip { + ($name:ident, $buffer:ident, $buffer_ty:ty) => { + fn $name>( + 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(); + + let (width, height) = decoder.dimensions().unwrap(); + tiff.write_image::(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().unwrap() { + assert_eq!(image_data, img_res); + } else { + panic!("Wrong data type"); + } + } + } + }; +} + +test_roundtrip!(test_u8_roundtrip, U8, u8); +test_roundtrip!(test_u16_roundtrip, U16, u16); +test_roundtrip!(test_u32_roundtrip, U32, u32); +test_roundtrip!(test_u64_roundtrip, U64, u64); +test_roundtrip!(test_f32_roundtrip, F32, f32); +test_roundtrip!(test_f64_roundtrip, F64, f64); + +#[test] +fn test_gray_u8_roundtrip() { + test_u8_roundtrip::("minisblack-1c-8b.tiff", ColorType::Gray(8)); +} + +#[test] +fn test_rgb_u8_roundtrip() { + test_u8_roundtrip::("rgb-3c-8b.tiff", ColorType::RGB(8)); +} + +#[test] +fn test_cmyk_u8_roundtrip() { + test_u8_roundtrip::("cmyk-3c-8b.tiff", ColorType::CMYK(8)); +} + +#[test] +fn test_gray_u16_roundtrip() { + test_u16_roundtrip::("minisblack-1c-16b.tiff", ColorType::Gray(16)); +} + +#[test] +fn test_rgb_u16_roundtrip() { + test_u16_roundtrip::("rgb-3c-16b.tiff", ColorType::RGB(16)); +} + +#[test] +fn test_cmyk_u16_roundtrip() { + test_u16_roundtrip::("cmyk-3c-16b.tiff", ColorType::CMYK(16)); +} + +#[test] +fn test_gray_u32_roundtrip() { + test_u32_roundtrip::("gradient-1c-32b.tiff", ColorType::Gray(32)); +} + +#[test] +fn test_rgb_u32_roundtrip() { + test_u32_roundtrip::("gradient-3c-32b.tiff", ColorType::RGB(32)); +} + +#[test] +fn test_gray_u64_roundtrip() { + test_u64_roundtrip::("gradient-1c-64b.tiff", ColorType::Gray(64)); +} + +#[test] +fn test_rgb_u64_roundtrip() { + test_u64_roundtrip::("gradient-3c-64b.tiff", ColorType::RGB(64)); +} + +#[test] +fn test_gray_f32_roundtrip() { + test_f32_roundtrip::("gradient-1c-32b-float.tiff", ColorType::Gray(32)); +} + +#[test] +fn test_rgb_f32_roundtrip() { + test_f32_roundtrip::("gradient-3c-32b-float.tiff", ColorType::RGB(32)); +} + +#[test] +fn test_cmyk_f32_roundtrip() { + test_f32_roundtrip::("cmyk-3c-32b-float.tiff", ColorType::CMYK(32)); +} + +#[test] +fn test_gray_f64_roundtrip() { + test_f64_roundtrip::("gradient-1c-64b-float.tiff", ColorType::Gray(64)); +} + +#[test] +fn test_ycbcr_u8_roundtrip() { + test_u8_roundtrip::("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; + fn assert_tag_i32(&mut self, tag: u16) -> i32; + fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec; + fn assert_tag_u64(&mut self, tag: u16) -> u64; + fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec; + fn assert_tag_i64(&mut self, tag: u16) -> i64; + fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec; +} + +impl AssertDecode for Decoder { + 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 { + 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 { + 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 { + 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 { + self.get_tag(Tag::Unknown(tag)) + .unwrap() + .into_i64_vec() + .unwrap() + } +} + +#[test] +fn test_multiple_byte() { + let mut data = Cursor::new(Vec::new()); + + { + let mut tiff = TiffEncoder::new(&mut data).unwrap(); + let mut image_encoder = tiff.new_image::(1, 1).unwrap(); + image_encoder.write_strip(&[1]).unwrap(); + let encoder = image_encoder.encoder(); + + encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]).unwrap(); + encoder + .write_tag(Tag::Unknown(65001), &[1_u8, 2][..]) + .unwrap(); + encoder + .write_tag(Tag::Unknown(65002), &[1_u8, 2, 3][..]) + .unwrap(); + encoder + .write_tag(Tag::Unknown(65003), &[1_u8, 2, 3, 4][..]) + .unwrap(); + encoder + .write_tag(Tag::Unknown(65004), &[1_u8, 2, 3, 4, 5][..]) + .unwrap(); + } + + data.set_position(0); + { + let mut decoder = Decoder::new(&mut data).unwrap(); + + assert_eq!(decoder.assert_tag_u32_vec(65000), [1]); + assert_eq!(decoder.assert_tag_u32_vec(65001), [1, 2]); + assert_eq!(decoder.assert_tag_u32_vec(65002), [1, 2, 3]); + assert_eq!(decoder.assert_tag_u32_vec(65003), [1, 2, 3, 4]); + assert_eq!(decoder.assert_tag_u32_vec(65004), [1, 2, 3, 4, 5]); + } +} + +#[test] +/// Test writing signed tags from TIFF 6.0 +fn test_signed() { + 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 image_encoder = tiff.new_image::(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), -1_i8).unwrap(); + encoder + .write_tag(Tag::Unknown(65001), &[-1_i8][..]) + .unwrap(); + encoder + .write_tag(Tag::Unknown(65002), &[-1_i8, 2][..]) + .unwrap(); + encoder + .write_tag(Tag::Unknown(65003), &[-1_i8, 2, -3][..]) + .unwrap(); + encoder + .write_tag(Tag::Unknown(65004), &[-1_i8, 2, -3, 4][..]) + .unwrap(); + encoder + .write_tag(Tag::Unknown(65005), &[-1_i8, 2, -3, 4, -5][..]) + .unwrap(); + + encoder.write_tag(Tag::Unknown(65010), -1_i16).unwrap(); + encoder.write_tag(Tag::Unknown(65011), -1_i16).unwrap(); + encoder + .write_tag(Tag::Unknown(65012), &[-1_i16, 2][..]) + .unwrap(); + encoder + .write_tag(Tag::Unknown(65013), &[-1_i16, 2, -3][..]) + .unwrap(); + + encoder.write_tag(Tag::Unknown(65020), -1_i32).unwrap(); + encoder + .write_tag(Tag::Unknown(65021), &[-1_i32][..]) + .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(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 + data.set_position(0); + { + let mut decoder = Decoder::new(&mut data).unwrap(); + + assert_eq!(decoder.assert_tag_i32(65000), -1); + assert_eq!(decoder.assert_tag_i32_vec(65001), [-1]); + assert_eq!(decoder.assert_tag_i32_vec(65002), [-1, 2]); + assert_eq!(decoder.assert_tag_i32_vec(65003), [-1, 2, -3]); + assert_eq!(decoder.assert_tag_i32_vec(65004), [-1, 2, -3, 4]); + assert_eq!(decoder.assert_tag_i32_vec(65005), [-1, 2, -3, 4, -5],); + + assert_eq!(decoder.assert_tag_i32(65010), -1); + assert_eq!(decoder.assert_tag_i32_vec(65011), [-1]); + assert_eq!(decoder.assert_tag_i32_vec(65012), [-1, 2]); + assert_eq!(decoder.assert_tag_i32_vec(65013), [-1, 2, -3]); + + assert_eq!(decoder.assert_tag_i32(65020), -1); + assert_eq!(decoder.assert_tag_i32_vec(65021), [-1]); + assert_eq!(decoder.assert_tag_i32_vec(65022), [-1, 2]); + + assert_eq!(decoder.assert_tag_i64(65030), -1); + assert_eq!(decoder.assert_tag_i64_vec(65031), [-1]); + assert_eq!(decoder.assert_tag_i64_vec(65032), [-1, 2]); + + assert_eq!(decoder.assert_tag_i32_vec(65040), [-1, 100]); + assert_eq!(decoder.assert_tag_i32_vec(65041), [-1_i32, 100, 2, 100]); + } +} + +#[test] +/// check multipage image handling +fn test_multipage_image() { + let mut img_file = Cursor::new(Vec::new()); + + { + // first create a multipage image with 2 images + let mut img_encoder = TiffEncoder::new(&mut img_file).unwrap(); + + // write first grayscale image (2x2 16-bit) + let img1: Vec = [1, 2, 3, 4].to_vec(); + img_encoder + .write_image::(2, 2, &img1[..]) + .unwrap(); + // write second grayscale image (3x3 8-bit) + let img2: Vec = [9, 8, 7, 6, 5, 4, 3, 2, 1].to_vec(); + img_encoder + .write_image::(3, 3, &img2[..]) + .unwrap(); + } + + // seek to the beginning of the file, so that it can be decoded + img_file.seek(SeekFrom::Start(0)).unwrap(); + + { + let mut img_decoder = Decoder::new(&mut img_file).unwrap(); + + // check the dimensions of the image in the first page + assert_eq!(img_decoder.dimensions().unwrap(), (2, 2)); + img_decoder.next_image().unwrap(); + // check the dimensions of the image in the second page + 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::(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 = 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 = 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), + } + } + } +} diff --git a/vendor/tiff/tests/encode_images_with_compression.rs b/vendor/tiff/tests/encode_images_with_compression.rs new file mode 100644 index 0000000..1cf178f --- /dev/null +++ b/vendor/tiff/tests/encode_images_with_compression.rs @@ -0,0 +1,157 @@ +extern crate tiff; + +use std::io::{Cursor, Seek, Write}; +use tiff::{ + decoder::{Decoder, DecodingResult}, + encoder::{ + colortype::{self, ColorType}, + compression::*, + TiffEncoder, TiffValue, + }, +}; + +trait TestImage: From::Inner>> { + const WIDTH: u32; + const HEIGHT: u32; + type Color: ColorType; + + fn reference_data(&self) -> &[::Inner]; + fn generate_pixel(x: u32, y: u32) -> [::Inner; NUM_CHANNELS]; + + fn compress( + &self, + encoder: &mut TiffEncoder, + compression: C, + ) where + [::Inner]: TiffValue, + { + let image = encoder + .new_image_with_compression::(Self::WIDTH, Self::HEIGHT, compression) + .unwrap(); + image.write_data(self.reference_data()).unwrap(); + } + + fn generate() -> Self { + assert_eq!( + Self::Color::BITS_PER_SAMPLE.len(), + NUM_CHANNELS, + "Incompatible color type" + ); + + let mut data = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * NUM_CHANNELS); + for x in 0..Self::WIDTH { + for y in 0..Self::HEIGHT { + data.extend(IntoIterator::into_iter(Self::generate_pixel(x, y))); + } + } + Self::from(data) + } +} + +struct TestImageColor(Vec); + +impl From> for TestImageColor { + fn from(value: Vec) -> 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) -> [::Inner; 3] { + let val = (x + y) % ::Inner::MAX as u32; + [val as ::Inner; 3] + } +} + +struct TestImageGrayscale(Vec); + +impl From> for TestImageGrayscale { + fn from(value: Vec) -> 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) -> [::Inner; 1] { + let val = (x + y) % ::Inner::MAX as u32; + [val as ::Inner] + } +} + +fn encode_decode_with_compression(compression: C) { + let mut data = Cursor::new(Vec::new()); + + let image_rgb = TestImageColor::generate(); + let image_grayscale = TestImageGrayscale::generate(); + + // Encode tiff with compression + { + // Create a multipage image with 2 images + let mut encoder = TiffEncoder::new(&mut data).unwrap(); + image_rgb.compress(&mut encoder, compression.clone()); + image_grayscale.compress(&mut encoder, compression); + } + + // Decode tiff + data.set_position(0); + { + let mut decoder = Decoder::new(data).unwrap(); + + // Check the RGB image + assert_eq!( + match decoder.read_image() { + Ok(DecodingResult::U16(image_data)) => image_data, + unexpected => panic!("Descoding RGB failed: {:?}", unexpected), + }, + image_rgb.reference_data() + ); + + // Check the grayscale image + decoder.next_image().unwrap(); + assert_eq!( + match decoder.read_image() { + Ok(DecodingResult::U8(image_data)) => image_data, + unexpected => panic!("Decoding grayscale failed: {:?}", unexpected), + }, + image_grayscale.reference_data() + ); + } +} + +#[test] +fn encode_decode_without_compression() { + encode_decode_with_compression(Uncompressed::default()); +} + +#[test] +fn encode_decode_with_lzw() { + encode_decode_with_compression(Lzw::default()); +} + +#[test] +fn encode_decode_with_deflate() { + encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast)); + encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced)); + encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best)); +} + +#[test] +fn encode_decode_with_packbits() { + encode_decode_with_compression(Packbits::default()); +} diff --git a/vendor/tiff/tests/fuzz_tests.rs b/vendor/tiff/tests/fuzz_tests.rs new file mode 100644 index 0000000..446d6bd --- /dev/null +++ b/vendor/tiff/tests/fuzz_tests.rs @@ -0,0 +1,51 @@ +extern crate tiff; + +use tiff::decoder::Decoder; +use tiff::TiffResult; + +use std::fs::File; + +fn test_directory bool>(path: &str, f: F) { + for entry in std::fs::read_dir(path).unwrap() { + let file = File::open(entry.unwrap().path()).unwrap(); + assert!(f(file)); + } +} + +fn decode_tiff(file: File) -> TiffResult<()> { + let mut decoder = Decoder::new(file)?; + decoder.read_image()?; + Ok(()) +} + +#[test] +fn oor_panic() { + test_directory("./tests/fuzz_images/oor_panic", |file| { + let _ = decode_tiff(file); + true + }); +} + +#[test] +fn oom_crash() { + test_directory("./tests/fuzz_images/oom_crash", |file| { + decode_tiff(file).is_err() + }); +} + +#[test] +fn inf_loop() { + test_directory("./tests/fuzz_images/inf_loop", |file| { + let _ = decode_tiff(file); + true + }); +} + +// https://github.com/image-rs/image-tiff/issues/33 +#[test] +fn divide_by_zero() { + test_directory("./tests/fuzz_images/divide_by_zero", |file| { + let _ = decode_tiff(file); + true + }); +} -- cgit v1.2.3