Compare commits

..
23 Commits
Author SHA1 Message Date
Jonathan Behrens 46086e2bcf Release 0.10.0 (#271) 2025-06-20 20:15:42 -07:00
Jonathan Behrens d7be2a7b67 Make the Compressor enum non-exhaustive (#272) 2025-06-15 18:31:03 -07:00
Emma 29b71ff597 add CMYKA (#208) 2025-06-15 16:38:22 -07:00
Jonathan Behrens 83e6d64751 Create feature flags for more compression algorithms (#270) 2025-06-15 15:50:14 -07:00
Jonathan Behrens dc2c67469b Switch to zune-jpeg (#268)
]
2025-06-14 21:13:16 -07:00
Jonathan Behrens b5d8ee5f86 Switch to quick_error and cleaner error messages (#269) 2025-06-09 19:10:12 -07:00
Jonathan Behrens f44da4a00d Switch from SmartReader to EndianReader (#233) 2025-06-08 18:45:10 -07:00
Wei Ji ee0dbf373b Read Byte as u8, SignedByte as i8 (#236) 2025-06-08 18:31:51 -07:00
Wei Ji aeab243a96 Read Short as u16, SignedShort as i16 (#235) 2025-06-08 18:03:08 -07:00
Wei Ji 36f7b3b4aa Bump MSRV from 1.61.0 to 1.70.0
Found using `cargo msrv find`, the `1.70.0` version is determined by `half=2.4.1` having an MSRV of 1.70.0.
2025-03-16 21:31:12 +00:00
Micah Chambers 3bfb43e83e Add FP16 support (#257) 2025-02-04 19:12:01 -08:00
Jonathan Behrens d42e6be507 Fix formatting (#263) 2025-01-30 23:29:29 -08:00
Stefan Schake a19cf37587 Support ZSTD compression method for reading (#256) 2024-12-20 12:50:34 -08:00
Gunnar Schulze ff52aea06e Add support for iterating over all tags (#255) 2024-11-25 19:34:11 -08:00
Jonathan Behrens 96d3a02efe Fix 1bpp image decoding and add a test image (#252) 2024-10-31 20:21:02 -07:00
Jean-Baptiste Skutnik e28ad564d9 Add horizontal predictor support (#240) 2024-09-23 19:45:45 +01:00
Even Rouault de6d9cf131 README.md: advertize JPEG decode support (#244) 2024-09-07 10:49:22 -07:00
Jonathan Behrens 6dc7a266d3 Make decoding internally operate on byte buffers (#230) 2024-07-22 20:13:39 -07:00
Wei Ji 307cd8be97 Add SignedByte and SignedShort enum variants (#234) 2024-07-21 20:37:25 -07:00
Wei Jiandtromper 0c54a18e21 Add ColorType::Multiband enum variant to support samples>1 per pixel (#224)
Co-authored-by: tromper <scott@hopkinsunderground.com>
2024-03-24 15:04:52 -07:00
Jonathan Behrens 90ae5b8e54 Fix warnings (#225) 2024-03-23 13:51:28 -07:00
Jonathan Behrens 9b06e3726d Sample format should not be a Vec (#226) 2024-03-23 13:51:19 -07:00
Jonathan Behrens 895c70a1ad Test minimal versions in CI (#223) 2024-02-11 12:40:11 -08:00
41 changed files with 1542 additions and 1514 deletions

No files matched your search

+22 -18
View File
@@ -9,9 +9,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
rust: ["1.61.0", stable, beta, nightly] rust: ["1.74.0", stable, beta, nightly]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@nightly
if: ${{ matrix.rust == '1.74.0' }}
- name: Generate Cargo.lock with minimal-version dependencies
if: ${{ matrix.rust == '1.74.0' }}
run: cargo -Zminimal-versions generate-lockfile
- uses: dtolnay/rust-toolchain@v1 - uses: dtolnay/rust-toolchain@v1
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
@@ -19,15 +26,25 @@ jobs:
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
cache-on-failure: true cache-on-failure: true
- name: Install MSRV Cargo.lock
if: ${{ matrix.rust == '1.61.0' }}
run: mv Cargo.lock.msrv Cargo.lock
- name: build - name: build
run: cargo build -v run: cargo build -v
- name: test - name: test
if: ${{ matrix.rust != '1.61.0' }} if: ${{ matrix.rust != '1.74.0' }}
run: cargo test -v && cargo doc -v run: cargo test -v && cargo doc -v
features:
runs-on: ubuntu-latest
strategy:
matrix:
feature: ["", deflate, jpeg, lzw, zstd, default, "default,zstd"]
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cargo check
run: cargo check --features "${{ matrix.feature }}" --no-default-features
rustfmt: rustfmt:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -46,16 +63,3 @@ jobs:
with: with:
components: clippy components: clippy
- run: cargo clippy --all-features # TODO: enable "-- -D warnings" once all outstanding clippy lints are fixed - run: cargo clippy --all-features # TODO: enable "-- -D warnings" once all outstanding clippy lints are fixed
verify_msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install or use cached `cargo-msrv`
uses: baptiste0928/cargo-install@v1
with:
crate: cargo-msrv
- name: Install MSRV Cargo.lock
run: mv Cargo.lock.msrv Cargo.lock
- name: Verify Minimum Rust Version
run: cargo-msrv verify
+22
View File
@@ -1,3 +1,25 @@
# Version 0.10.0
New features:
- Added `ColorType::Multiband` and `ColorType::CMYKA`.
- Added `Value::Byte` and `Value::SignedByte` to represent `u8` and `i8` tags.
- Encoding can now use horizontal predictor.
- The `Decoder::tag_iter` method allows iterating over all tags in the image.
- Half precision floating point values are now supported during decoding.
- ZStandard compression has been added as an additional compression method
during decoding, but requires the `zstd` feature flag.
Changes:
- Switched `u16` and `i16` values to be intepreted as `Value::Short` and
`Value::SignedShort` respectively.
- Changed JPEG compression to use the `zune-jpeg` crate instead of
`jpeg-decoder`.
- The `deflate`, `jpeg`, and `lzw` compression methods are now controlled by
(enabled by default) feature flags.
Fixes:
- Fixed a bug in 1 bit-per-pixel decoding.
# Version 0.9.1 # Version 0.9.1
New features: New features:
-638
View File
@@ -1,638 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
dependencies = [
"memchr",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bumpalo"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"bitflags",
"textwrap",
"unicode-width",
]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "criterion"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
dependencies = [
"atty",
"cast",
"clap",
"criterion-plot",
"csv",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
[[package]]
name = "csv"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "flate2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "jpeg-decoder"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
[[package]]
name = "js-sys"
version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "num-traits"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi 0.3.2",
"libc",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "plotters"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
[[package]]
name = "plotters-svg"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
dependencies = [
"plotters-backend",
]
[[package]]
name = "proc-macro2"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "regex"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
[[package]]
name = "serde_cbor"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.183"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "tiff"
version = "0.9.0"
dependencies = [
"criterion",
"flate2",
"jpeg-decoder",
"weezl",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "walkdir"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
[[package]]
name = "web-sys"
version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "weezl"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+18 -5
View File
@@ -1,11 +1,11 @@
[package] [package]
name = "tiff" name = "tiff"
version = "0.9.1" version = "0.10.0"
edition = "2021" edition = "2021"
resolver = "2" resolver = "2"
# note: when changed, also update test runner in `.github/workflows/rust.yml` # note: when changed, also update test runner in `.github/workflows/rust.yml`
rust-version = "1.61.0" rust-version = "1.74.0"
license = "MIT" license = "MIT"
description = "TIFF decoding and encoding library in pure Rust" description = "TIFF decoding and encoding library in pure Rust"
@@ -17,13 +17,26 @@ categories = ["multimedia::images", "multimedia::encoding"]
exclude = ["tests/images/*", "tests/fuzz_images/*"] exclude = ["tests/images/*", "tests/fuzz_images/*"]
[dependencies] [dependencies]
weezl = "0.1.0" half = { version = "2.4.1" }
jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false } quick-error = "2.0.1"
flate2 = "1.0.20"
flate2 = { version = "1.0.20", optional = true }
weezl = { version = "0.1.0", optional = true }
zstd = { version = "0.13", optional = true }
zune-jpeg = { version = "0.4.17", optional = true }
[dev-dependencies] [dev-dependencies]
criterion = "0.3.1" criterion = "0.3.1"
[features]
default = ["deflate", "jpeg", "lzw"]
# Compression algorithms
deflate = ["dep:flate2"]
jpeg = ["dep:zune-jpeg"]
lzw = ["dep:weezl"]
zstd = ["dep:zstd"]
[[bench]] [[bench]]
name = "lzw" name = "lzw"
harness = false harness = false
+3
View File
@@ -24,6 +24,7 @@ This table lists photometric interpretations and sample formats which are suppor
| `RGBPalette` | | | | `RGBPalette` | | |
| `Mask` | | | | `Mask` | | |
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) | | `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
| `CMYKA` | CMYKA(8) | |
| `YCbCr` | | | | `YCbCr` | | |
| `CIELab` | | | | `CIELab` | | |
@@ -35,6 +36,8 @@ This table lists photometric interpretations and sample formats which are suppor
| LZW | ✓ | ✓ | | LZW | ✓ | ✓ |
| Deflate | ✓ | ✓ | | Deflate | ✓ | ✓ |
| PackBits | ✓ | ✓ | | PackBits | ✓ | ✓ |
| JPEG | ✓ | not yet |
| ZSTD | ✓ | not yet |
## Not yet supported ## Not yet supported
+3
View File
@@ -12,6 +12,8 @@
//! TODO: Would like to use std-lib here. //! TODO: Would like to use std-lib here.
use std::{mem, slice}; use std::{mem, slice};
use half::f16;
macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => { macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => {
pub(crate) fn $const(slice: &[$int]) -> &[u8] { pub(crate) fn $const(slice: &[$int]) -> &[u8] {
assert!(mem::align_of::<$int>() <= mem::size_of::<$int>()); assert!(mem::align_of::<$int>() <= mem::size_of::<$int>());
@@ -31,4 +33,5 @@ integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes);
integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes); integral_slice_as_bytes!(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!(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!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes);
integral_slice_as_bytes!(f16, f16_as_ne_bytes, f16_as_ne_mut_bytes);
integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes); integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes);
+71 -70
View File
@@ -1,18 +1,17 @@
//! Function for reading TIFF tags //! Function for reading TIFF tags
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::io::{self, Read, Seek}; use std::io::{self, Read, Seek};
use std::mem; use std::mem;
use std::str; use std::str;
use super::stream::{ByteOrder, EndianReader, SmartReader}; use super::stream::{ByteOrder, EndianReader};
use crate::tags::{Tag, Type}; use crate::tags::{Tag, Type};
use crate::{TiffError, TiffFormatError, TiffResult}; use crate::{TiffError, TiffFormatError, TiffResult};
use self::Value::{ use self::Value::{
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig, Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
Short, Signed, SignedBig, Unsigned, UnsignedBig, Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig,
}; };
#[allow(unused_qualifications)] #[allow(unused_qualifications)]
@@ -21,6 +20,8 @@ use self::Value::{
pub enum Value { pub enum Value {
Byte(u8), Byte(u8),
Short(u16), Short(u16),
SignedByte(i8),
SignedShort(i16),
Signed(i32), Signed(i32),
SignedBig(i64), SignedBig(i64),
Unsigned(u32), Unsigned(u32),
@@ -41,91 +42,98 @@ impl Value {
pub fn into_u8(self) -> TiffResult<u8> { pub fn into_u8(self) -> TiffResult<u8> {
match self { match self {
Byte(val) => Ok(val), Byte(val) => Ok(val),
val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))), _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_i8(self) -> TiffResult<i8> {
match self {
SignedByte(val) => Ok(val),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
} }
} }
pub fn into_u16(self) -> TiffResult<u16> { pub fn into_u16(self) -> TiffResult<u16> {
match self { match self {
Byte(val) => Ok(val.into()),
Short(val) => Ok(val), Short(val) => Ok(val),
Unsigned(val) => Ok(u16::try_from(val)?), Unsigned(val) => Ok(u16::try_from(val)?),
UnsignedBig(val) => Ok(u16::try_from(val)?), UnsignedBig(val) => Ok(u16::try_from(val)?),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val), }
)), }
pub fn into_i16(self) -> TiffResult<i16> {
match self {
SignedByte(val) => Ok(val.into()),
SignedShort(val) => Ok(val),
Signed(val) => Ok(i16::try_from(val)?),
SignedBig(val) => Ok(i16::try_from(val)?),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
} }
} }
pub fn into_u32(self) -> TiffResult<u32> { pub fn into_u32(self) -> TiffResult<u32> {
match self { match self {
Byte(val) => Ok(val.into()),
Short(val) => Ok(val.into()), Short(val) => Ok(val.into()),
Unsigned(val) => Ok(val), Unsigned(val) => Ok(val),
UnsignedBig(val) => Ok(u32::try_from(val)?), UnsignedBig(val) => Ok(u32::try_from(val)?),
Ifd(val) => Ok(val), Ifd(val) => Ok(val),
IfdBig(val) => Ok(u32::try_from(val)?), IfdBig(val) => Ok(u32::try_from(val)?),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
pub fn into_i32(self) -> TiffResult<i32> { pub fn into_i32(self) -> TiffResult<i32> {
match self { match self {
SignedByte(val) => Ok(val.into()),
SignedShort(val) => Ok(val.into()),
Signed(val) => Ok(val), Signed(val) => Ok(val),
SignedBig(val) => Ok(i32::try_from(val)?), SignedBig(val) => Ok(i32::try_from(val)?),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
pub fn into_u64(self) -> TiffResult<u64> { pub fn into_u64(self) -> TiffResult<u64> {
match self { match self {
Byte(val) => Ok(val.into()),
Short(val) => Ok(val.into()), Short(val) => Ok(val.into()),
Unsigned(val) => Ok(val.into()), Unsigned(val) => Ok(val.into()),
UnsignedBig(val) => Ok(val), UnsignedBig(val) => Ok(val),
Ifd(val) => Ok(val.into()), Ifd(val) => Ok(val.into()),
IfdBig(val) => Ok(val), IfdBig(val) => Ok(val),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
pub fn into_i64(self) -> TiffResult<i64> { pub fn into_i64(self) -> TiffResult<i64> {
match self { match self {
SignedByte(val) => Ok(val.into()),
SignedShort(val) => Ok(val.into()),
Signed(val) => Ok(val.into()), Signed(val) => Ok(val.into()),
SignedBig(val) => Ok(val), SignedBig(val) => Ok(val),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
pub fn into_f32(self) -> TiffResult<f32> { pub fn into_f32(self) -> TiffResult<f32> {
match self { match self {
Float(val) => Ok(val), Float(val) => Ok(val),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
pub fn into_f64(self) -> TiffResult<f64> { pub fn into_f64(self) -> TiffResult<f64> {
match self { match self {
Double(val) => Ok(val), Double(val) => Ok(val),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
pub fn into_string(self) -> TiffResult<String> { pub fn into_string(self) -> TiffResult<String> {
match self { match self {
Ascii(val) => Ok(val), Ascii(val) => Ok(val),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
@@ -138,6 +146,8 @@ impl Value {
} }
Ok(new_vec) Ok(new_vec)
} }
Byte(val) => Ok(vec![val.into()]),
Short(val) => Ok(vec![val.into()]),
Unsigned(val) => Ok(vec![val]), Unsigned(val) => Ok(vec![val]),
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]), UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
Rational(numerator, denominator) => Ok(vec![numerator, denominator]), Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
@@ -147,9 +157,7 @@ impl Value {
Ifd(val) => Ok(vec![val]), Ifd(val) => Ok(vec![val]),
IfdBig(val) => Ok(vec![u32::try_from(val)?]), IfdBig(val) => Ok(vec![u32::try_from(val)?]),
Ascii(val) => Ok(val.chars().map(u32::from).collect()), Ascii(val) => Ok(val.chars().map(u32::from).collect()),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -164,9 +172,7 @@ impl Value {
} }
Byte(val) => Ok(vec![val]), Byte(val) => Ok(vec![val]),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -179,10 +185,9 @@ impl Value {
} }
Ok(new_vec) Ok(new_vec)
} }
Byte(val) => Ok(vec![val.into()]),
Short(val) => Ok(vec![val]), Short(val) => Ok(vec![val]),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -205,15 +210,15 @@ impl Value {
} }
Ok(new_vec) Ok(new_vec)
} }
SignedByte(val) => Ok(vec![val.into()]),
SignedShort(val) => Ok(vec![val.into()]),
Signed(val) => Ok(vec![val]), Signed(val) => Ok(vec![val]),
SignedBig(val) => Ok(vec![i32::try_from(val)?]), SignedBig(val) => Ok(vec![i32::try_from(val)?]),
SRational(numerator, denominator) => Ok(vec![numerator, denominator]), SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
SRationalBig(numerator, denominator) => { SRationalBig(numerator, denominator) => {
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?]) Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
} }
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
@@ -227,9 +232,7 @@ impl Value {
Ok(new_vec) Ok(new_vec)
} }
Float(val) => Ok(vec![val]), Float(val) => Ok(vec![val]),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -243,9 +246,7 @@ impl Value {
Ok(new_vec) Ok(new_vec)
} }
Double(val) => Ok(vec![val]), Double(val) => Ok(vec![val]),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -258,6 +259,8 @@ impl Value {
} }
Ok(new_vec) Ok(new_vec)
} }
Byte(val) => Ok(vec![val.into()]),
Short(val) => Ok(vec![val.into()]),
Unsigned(val) => Ok(vec![val.into()]), Unsigned(val) => Ok(vec![val.into()]),
UnsignedBig(val) => Ok(vec![val]), UnsignedBig(val) => Ok(vec![val]),
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]), Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
@@ -265,9 +268,7 @@ impl Value {
Ifd(val) => Ok(vec![val.into()]), Ifd(val) => Ok(vec![val.into()]),
IfdBig(val) => Ok(vec![val]), IfdBig(val) => Ok(vec![val]),
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()), Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -290,13 +291,13 @@ impl Value {
} }
Ok(new_vec) Ok(new_vec)
} }
SignedByte(val) => Ok(vec![val.into()]),
SignedShort(val) => Ok(vec![val.into()]),
Signed(val) => Ok(vec![val.into()]), Signed(val) => Ok(vec![val.into()]),
SignedBig(val) => Ok(vec![val]), SignedBig(val) => Ok(vec![val]),
SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]), SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]), SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
} }
@@ -333,22 +334,22 @@ impl Entry {
} }
/// Returns a mem_reader for the offset/value field /// Returns a mem_reader for the offset/value field
fn r(&self, byte_order: ByteOrder) -> SmartReader<io::Cursor<Vec<u8>>> { fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
SmartReader::wrap(io::Cursor::new(self.offset.to_vec()), byte_order) EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
} }
pub fn val<R: Read + Seek>( pub(crate) fn val<R: Read + Seek>(
&self, &self,
limits: &super::Limits, limits: &super::Limits,
bigtiff: bool, bigtiff: bool,
reader: &mut SmartReader<R>, reader: &mut EndianReader<R>,
) -> TiffResult<Value> { ) -> TiffResult<Value> {
// Case 1: there are no values so we can return immediately. // Case 1: there are no values so we can return immediately.
if self.count == 0 { if self.count == 0 {
return Ok(List(Vec::new())); return Ok(List(Vec::new()));
} }
let bo = reader.byte_order(); let bo = reader.byte_order;
let tag_size = match self.type_ { let tag_size = match self.type_ {
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
@@ -401,11 +402,11 @@ impl Entry {
// 2b: the value is at most 4 bytes or doesn't fit in the offset field. // 2b: the value is at most 4 bytes or doesn't fit in the offset field.
return Ok(match self.type_ { return Ok(match self.type_ {
Type::BYTE => Unsigned(u32::from(self.offset[0])), Type::BYTE => Byte(self.offset[0]),
Type::SBYTE => Signed(i32::from(self.offset[0] as i8)), Type::SBYTE => SignedByte(i8::from(self.offset[0] as i8)),
Type::UNDEFINED => Byte(self.offset[0]), Type::UNDEFINED => Byte(self.offset[0]),
Type::SHORT => Unsigned(u32::from(self.r(bo).read_u16()?)), Type::SHORT => Short(self.r(bo).read_u16()?),
Type::SSHORT => Signed(i32::from(self.r(bo).read_i16()?)), Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
Type::LONG => Unsigned(self.r(bo).read_u32()?), Type::LONG => Unsigned(self.r(bo).read_u32()?),
Type::SLONG => Signed(self.r(bo).read_i32()?), Type::SLONG => Signed(self.r(bo).read_i32()?),
Type::FLOAT => Float(self.r(bo).read_f32()?), Type::FLOAT => Float(self.r(bo).read_f32()?),
@@ -451,7 +452,7 @@ impl Entry {
Type::SBYTE => return offset_to_sbytes(self.count as usize, self), Type::SBYTE => return offset_to_sbytes(self.count as usize, self),
Type::ASCII => { Type::ASCII => {
let mut buf = vec![0; self.count as usize]; let mut buf = vec![0; self.count as usize];
self.r(bo).read_exact(&mut buf)?; buf.copy_from_slice(&self.offset[..self.count as usize]);
if buf.is_ascii() && buf.ends_with(&[0]) { if buf.is_ascii() && buf.ends_with(&[0]) {
let v = str::from_utf8(&buf)?; let v = str::from_utf8(&buf)?;
let v = v.trim_matches(char::from(0)); let v = v.trim_matches(char::from(0));
@@ -480,7 +481,7 @@ impl Entry {
let mut r = self.r(bo); let mut r = self.r(bo);
let mut v = Vec::new(); let mut v = Vec::new();
for _ in 0..self.count { for _ in 0..self.count {
v.push(Signed(i32::from(r.read_i16()?))); v.push(SignedShort(r.read_i16()?));
} }
return Ok(List(v)); return Ok(List(v));
} }
@@ -533,17 +534,17 @@ impl Entry {
// at a different endianess of file/computer. // at a different endianess of file/computer.
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
let mut buf = [0; 1]; let mut buf = [0; 1];
reader.read_exact(&mut buf)?; reader.inner().read_exact(&mut buf)?;
Ok(UnsignedBig(u64::from(buf[0]))) Ok(UnsignedBig(u64::from(buf[0])))
}), }),
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SignedBig(i64::from(reader.read_i8()?))) Ok(SignedBig(i64::from(reader.read_i8()?)))
}), }),
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(UnsignedBig(u64::from(reader.read_u16()?))) Ok(Short(reader.read_u16()?))
}), }),
Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SignedBig(i64::from(reader.read_i16()?))) Ok(SignedShort(reader.read_i16()?))
}), }),
Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Unsigned(reader.read_u32()?)) Ok(Unsigned(reader.read_u32()?))
@@ -582,7 +583,7 @@ impl Entry {
Type::UNDEFINED => { Type::UNDEFINED => {
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
let mut buf = [0; 1]; let mut buf = [0; 1];
reader.read_exact(&mut buf)?; reader.inner().read_exact(&mut buf)?;
Ok(Byte(buf[0])) Ok(Byte(buf[0]))
}) })
} }
@@ -599,7 +600,7 @@ impl Entry {
} }
let mut out = vec![0; n]; let mut out = vec![0; n];
reader.read_exact(&mut out)?; reader.inner().read_exact(&mut out)?;
// Strings may be null-terminated, so we trim anything downstream of the null byte // Strings may be null-terminated, so we trim anything downstream of the null byte
if let Some(first) = out.iter().position(|&b| b == 0) { if let Some(first) = out.iter().position(|&b| b == 0) {
out.truncate(first); out.truncate(first);
@@ -616,12 +617,12 @@ impl Entry {
bo: ByteOrder, bo: ByteOrder,
bigtiff: bool, bigtiff: bool,
limits: &super::Limits, limits: &super::Limits,
reader: &mut SmartReader<R>, reader: &mut EndianReader<R>,
decode_fn: F, decode_fn: F,
) -> TiffResult<Value> ) -> TiffResult<Value>
where where
R: Read + Seek, R: Read + Seek,
F: Fn(&mut SmartReader<R>) -> TiffResult<Value>, F: Fn(&mut EndianReader<R>) -> TiffResult<Value>,
{ {
let value_count = usize::try_from(value_count)?; let value_count = usize::try_from(value_count)?;
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() { if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
+131 -89
View File
@@ -1,13 +1,12 @@
use super::ifd::{Directory, Value}; use super::ifd::{Directory, Value};
use super::stream::{ByteOrder, DeflateReader, LZWReader, PackBitsReader}; use super::stream::{ByteOrder, PackBitsReader};
use super::tag_reader::TagReader; use super::tag_reader::TagReader;
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits}; use super::{predict_f16, predict_f32, predict_f64, Limits};
use super::{stream::SmartReader, ChunkType}; use super::{stream::EndianReader, ChunkType};
use crate::tags::{ use crate::tags::{
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag, CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
}; };
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError}; use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
use std::convert::TryFrom;
use std::io::{self, Cursor, Read, Seek}; use std::io::{self, Cursor, Read, Seek};
use std::sync::Arc; use std::sync::Arc;
@@ -65,9 +64,8 @@ pub(crate) struct Image {
pub width: u32, pub width: u32,
pub height: u32, pub height: u32,
pub bits_per_sample: u8, pub bits_per_sample: u8,
#[allow(unused)]
pub samples: u16, pub samples: u16,
pub sample_format: Vec<SampleFormat>, pub sample_format: SampleFormat,
pub photometric_interpretation: PhotometricInterpretation, pub photometric_interpretation: PhotometricInterpretation,
pub compression_method: CompressionMethod, pub compression_method: CompressionMethod,
pub predictor: Predictor, pub predictor: Predictor,
@@ -82,7 +80,7 @@ pub(crate) struct Image {
impl Image { impl Image {
pub fn from_reader<R: Read + Seek>( pub fn from_reader<R: Read + Seek>(
reader: &mut SmartReader<R>, reader: &mut EndianReader<R>,
ifd: Directory, ifd: Directory,
limits: &Limits, limits: &Limits,
bigtiff: bool, bigtiff: bool,
@@ -155,9 +153,9 @@ impl Image {
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into()); return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
} }
sample_format sample_format[0]
} }
None => vec![SampleFormat::Uint], None => SampleFormat::Uint,
}; };
let bits_per_sample: Vec<u8> = tag_reader let bits_per_sample: Vec<u8> = tag_reader
@@ -172,8 +170,9 @@ impl Image {
)); ));
} }
// This library (and libtiff) do not support mixed sample formats. // This library (and libtiff) do not support mixed sample formats and zero bits per sample
if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) { // doesn't make sense.
if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) || bits_per_sample[0] == 0 {
return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into()); return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into());
} }
@@ -331,6 +330,7 @@ impl Image {
}, },
PhotometricInterpretation::CMYK => match self.samples { PhotometricInterpretation::CMYK => match self.samples {
4 => Ok(ColorType::CMYK(self.bits_per_sample)), 4 => Ok(ColorType::CMYK(self.bits_per_sample)),
5 => Ok(ColorType::CMYKA(self.bits_per_sample)),
_ => Err(TiffError::UnsupportedError( _ => Err(TiffError::UnsupportedError(
TiffUnsupportedError::InterpretationWithBits( TiffUnsupportedError::InterpretationWithBits(
self.photometric_interpretation, self.photometric_interpretation,
@@ -347,14 +347,19 @@ impl Image {
), ),
)), )),
}, },
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
if self.samples == 1 => match self.samples {
{ 1 => Ok(ColorType::Gray(self.bits_per_sample)),
Ok(ColorType::Gray(self.bits_per_sample)) _ => Ok(ColorType::Multiband {
bit_depth: self.bits_per_sample,
num_samples: self.samples,
}),
}
} }
// TODO: this is bad we should not fail at this point // TODO: this is bad we should not fail at this point
_ => Err(TiffError::UnsupportedError( PhotometricInterpretation::RGBPalette
| PhotometricInterpretation::TransparencyMask
| PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError(
TiffUnsupportedError::InterpretationWithBits( TiffUnsupportedError::InterpretationWithBits(
self.photometric_interpretation, self.photometric_interpretation,
vec![self.bits_per_sample; self.samples as usize], vec![self.bits_per_sample; self.samples as usize],
@@ -365,21 +370,29 @@ impl Image {
fn create_reader<'r, R: 'r + Read>( fn create_reader<'r, R: 'r + Read>(
reader: R, reader: R,
photometric_interpretation: PhotometricInterpretation, #[allow(unused_variables)] photometric_interpretation: PhotometricInterpretation,
compression_method: CompressionMethod, compression_method: CompressionMethod,
compressed_length: u64, compressed_length: u64,
jpeg_tables: Option<&[u8]>, #[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
) -> TiffResult<Box<dyn Read + 'r>> { ) -> TiffResult<Box<dyn Read + 'r>> {
Ok(match compression_method { Ok(match compression_method {
CompressionMethod::None => Box::new(reader), CompressionMethod::None => Box::new(reader),
CompressionMethod::LZW => { #[cfg(feature = "lzw")]
Box::new(LZWReader::new(reader, usize::try_from(compressed_length)?)) CompressionMethod::LZW => Box::new(super::stream::LZWReader::new(
} reader,
usize::try_from(compressed_length)?,
)),
#[cfg(feature = "zstd")]
CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?),
CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)), CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)),
#[cfg(feature = "deflate")]
CompressionMethod::Deflate | CompressionMethod::OldDeflate => { CompressionMethod::Deflate | CompressionMethod::OldDeflate => {
Box::new(DeflateReader::new(reader)) Box::new(super::stream::DeflateReader::new(reader))
} }
#[cfg(feature = "jpeg")]
CompressionMethod::ModernJPEG => { CompressionMethod::ModernJPEG => {
use zune_jpeg::zune_core;
if jpeg_tables.is_some() && compressed_length < 2 { if jpeg_tables.is_some() && compressed_length < 2 {
return Err(TiffError::FormatError( return Err(TiffError::FormatError(
TiffFormatError::InvalidTagValueType(Tag::JPEGTables), TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
@@ -396,7 +409,7 @@ impl Image {
// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`. // 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, // 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`). // this has to be removed as well (last two bytes of `jpeg_tables`).
let jpeg_reader = match jpeg_tables { let mut jpeg_reader = match jpeg_tables {
Some(jpeg_tables) => { Some(jpeg_tables) => {
let mut reader = reader.take(compressed_length); let mut reader = reader.take(compressed_length);
reader.read_exact(&mut [0; 2])?; reader.read_exact(&mut [0; 2])?;
@@ -409,28 +422,28 @@ impl Image {
None => Box::new(reader.take(compressed_length)), None => Box::new(reader.take(compressed_length)),
}; };
let mut decoder = jpeg::Decoder::new(jpeg_reader); let mut jpeg_data = Vec::new();
jpeg_reader.read_to_end(&mut jpeg_data)?;
let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
let mut options: zune_core::options::DecoderOptions = Default::default();
match photometric_interpretation { match photometric_interpretation {
PhotometricInterpretation::RGB => { PhotometricInterpretation::RGB => {
decoder.set_color_transform(jpeg::ColorTransform::RGB) options =
} options.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::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 => { PhotometricInterpretation::CMYK => {
decoder.set_color_transform(jpeg::ColorTransform::CMYK) options = options
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::CMYK);
} }
PhotometricInterpretation::YCbCr => { PhotometricInterpretation::YCbCr => {
decoder.set_color_transform(jpeg::ColorTransform::YCbCr) options = options
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::YCbCr);
} }
photometric_interpretation => { PhotometricInterpretation::WhiteIsZero
| PhotometricInterpretation::BlackIsZero
| PhotometricInterpretation::TransparencyMask => {}
PhotometricInterpretation::RGBPalette | PhotometricInterpretation::CIELab => {
return Err(TiffError::UnsupportedError( return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedInterpretation( TiffUnsupportedError::UnsupportedInterpretation(
photometric_interpretation, photometric_interpretation,
@@ -438,6 +451,7 @@ impl Image {
)); ));
} }
} }
decoder.set_options(options);
let data = decoder.decode()?; let data = decoder.decode()?;
@@ -542,27 +556,35 @@ impl Image {
pub(crate) fn expand_chunk( pub(crate) fn expand_chunk(
&self, &self,
reader: impl Read, reader: impl Read,
mut buffer: DecodingBuffer, buf: &mut [u8],
output_width: usize, output_row_stride: usize,
byte_order: ByteOrder, byte_order: ByteOrder,
chunk_index: u32, chunk_index: u32,
limits: &Limits, limits: &Limits,
) -> TiffResult<()> { ) -> TiffResult<()> {
// Validate that the provided buffer is of the expected type. // Validate that the color type is supported.
let color_type = self.colortype()?; let color_type = self.colortype()?;
match (color_type, &buffer) { match color_type {
(ColorType::RGB(n), _) ColorType::RGB(n)
| (ColorType::RGBA(n), _) | ColorType::RGBA(n)
| (ColorType::CMYK(n), _) | ColorType::CMYK(n)
| (ColorType::YCbCr(n), _) | ColorType::CMYKA(n)
| (ColorType::Gray(n), _) | ColorType::YCbCr(n)
if usize::from(n) == buffer.byte_len() * 8 => {} | ColorType::Gray(n)
(ColorType::Gray(n), DecodingBuffer::U8(_)) if n < 8 => match self.predictor { | ColorType::Multiband {
bit_depth: n,
num_samples: _,
} if n == 8 || n == 16 || n == 32 || n == 64 => {}
ColorType::Gray(n)
| ColorType::Multiband {
bit_depth: n,
num_samples: _,
} if n < 8 => match self.predictor {
Predictor::None => {} Predictor::None => {}
Predictor::Horizontal => { Predictor::Horizontal => {
return Err(TiffError::UnsupportedError( return Err(TiffError::UnsupportedError(
TiffUnsupportedError::HorizontalPredictor(color_type), TiffUnsupportedError::HorizontalPredictor(color_type),
)) ));
} }
Predictor::FloatingPoint => { Predictor::FloatingPoint => {
return Err(TiffError::UnsupportedError( return Err(TiffError::UnsupportedError(
@@ -570,23 +592,25 @@ impl Image {
)); ));
} }
}, },
(type_, _) => { type_ => {
return Err(TiffError::UnsupportedError( return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedColorType(type_), TiffUnsupportedError::UnsupportedColorType(type_),
)) ));
} }
} }
// Validate that the predictor is supported for the sample type. // Validate that the predictor is supported for the sample type.
match (self.predictor, &buffer) { match (self.predictor, self.sample_format) {
(Predictor::Horizontal, DecodingBuffer::F32(_)) (
| (Predictor::Horizontal, DecodingBuffer::F64(_)) => { Predictor::Horizontal,
SampleFormat::Int | SampleFormat::Uint | SampleFormat::IEEEFP,
) => {}
(Predictor::Horizontal, _) => {
return Err(TiffError::UnsupportedError( return Err(TiffError::UnsupportedError(
TiffUnsupportedError::HorizontalPredictor(color_type), TiffUnsupportedError::HorizontalPredictor(color_type),
)); ));
} }
(Predictor::FloatingPoint, DecodingBuffer::F32(_)) (Predictor::FloatingPoint, SampleFormat::IEEEFP) => {}
| (Predictor::FloatingPoint, DecodingBuffer::F64(_)) => {}
(Predictor::FloatingPoint, _) => { (Predictor::FloatingPoint, _) => {
return Err(TiffError::UnsupportedError( return Err(TiffError::UnsupportedError(
TiffUnsupportedError::FloatingPointPredictor(color_type), TiffUnsupportedError::FloatingPointPredictor(color_type),
@@ -605,7 +629,6 @@ impl Image {
return Err(TiffError::LimitsExceeded); return Err(TiffError::LimitsExceeded);
} }
let byte_len = buffer.byte_len();
let compression_method = self.compression_method; let compression_method = self.compression_method;
let photometric_interpretation = self.photometric_interpretation; let photometric_interpretation = self.photometric_interpretation;
let predictor = self.predictor; let predictor = self.predictor;
@@ -614,7 +637,19 @@ impl Image {
let chunk_dims = self.chunk_dimensions()?; let chunk_dims = self.chunk_dimensions()?;
let data_dims = self.chunk_data_dimensions(chunk_index)?; let data_dims = self.chunk_data_dimensions(chunk_index)?;
let padding_right = chunk_dims.0 - data_dims.0; let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let data_row_bytes: usize = ((data_row_bits + 7) / 8).try_into()?;
// TODO: Should these return errors instead?
assert!(output_row_stride >= data_row_bytes);
assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
let mut reader = Self::create_reader( let mut reader = Self::create_reader(
reader, reader,
@@ -624,57 +659,64 @@ impl Image {
self.jpeg_tables.as_deref().map(|a| &**a), self.jpeg_tables.as_deref().map(|a| &**a),
)?; )?;
if output_width == data_dims.0 as usize && padding_right == 0 { if output_row_stride == chunk_row_bytes as usize {
let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples; let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len];
reader.read_exact(tile)?; reader.read_exact(tile)?;
for row in 0..data_dims.1 as usize { for row in tile.chunks_mut(chunk_row_bytes as usize) {
let row_start = row * output_width * samples; super::fix_endianness_and_predict(
let row_end = (row + 1) * output_width * samples; row,
let row = buffer.subrange(row_start..row_end); color_type.bit_depth(),
super::fix_endianness_and_predict(row, samples, byte_order, predictor); samples,
byte_order,
predictor,
);
} }
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(&mut buffer.subrange(0..total_samples), color_type); super::invert_colors(tile, color_type, self.sample_format);
} }
} else if padding_right > 0 && self.predictor == Predictor::FloatingPoint { } else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint {
// The floating point predictor shuffles the padding bytes into the encoded output, so // The floating point predictor shuffles the padding bytes into the encoded output, so
// this case is handled specially when needed. // this case is handled specially when needed.
let mut encoded = vec![0u8; chunk_dims.0 as usize * samples * byte_len]; let mut encoded = vec![0u8; chunk_row_bytes];
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
for row in 0..data_dims.1 as usize {
let row_start = row * output_width * samples;
let row_end = row_start + data_dims.0 as usize * samples;
reader.read_exact(&mut encoded)?; reader.read_exact(&mut encoded)?;
match buffer.subrange(row_start..row_end) {
DecodingBuffer::F32(buf) => fp_predict_f32(&mut encoded, buf, samples), let row = &mut row[..data_row_bytes];
DecodingBuffer::F64(buf) => fp_predict_f64(&mut encoded, buf, samples), match color_type.bit_depth() {
16 => predict_f16(&mut encoded, row, samples),
32 => predict_f32(&mut encoded, row, samples),
64 => predict_f64(&mut encoded, row, samples),
_ => unreachable!(), _ => unreachable!(),
} }
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type); super::invert_colors(row, color_type, self.sample_format);
} }
} }
} else { } else {
for row in 0..data_dims.1 as usize { for (i, row) in buf
let row_start = row * output_width * samples; .chunks_mut(output_row_stride)
let row_end = row_start + data_dims.0 as usize * samples; .take(data_dims.1 as usize)
.enumerate()
let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)]; {
let row = &mut row[..data_row_bytes];
reader.read_exact(row)?; reader.read_exact(row)?;
// Skip horizontal padding // Skip horizontal padding
if padding_right > 0 { if chunk_row_bytes > data_row_bytes {
let len = u64::try_from(padding_right as usize * samples * byte_len)?; let len = u64::try_from(chunk_row_bytes - data_row_bytes)?;
io::copy(&mut reader.by_ref().take(len), &mut io::sink())?; 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(
super::fix_endianness_and_predict(row.copy(), samples, byte_order, predictor); row,
color_type.bit_depth(),
samples,
byte_order,
predictor,
);
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero { if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(&mut row, color_type); super::invert_colors(row, color_type, self.sample_format);
} }
} }
} }
+206 -237
View File
@@ -1,20 +1,18 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::io::{self, Read, Seek}; 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::{ use crate::tags::{
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat,
Tag, Type, Tag, Type,
}; };
use crate::{
bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
};
use half::f16;
use self::stream::{ByteOrder, EndianReader, SmartReader}; use self::ifd::Directory;
use self::image::Image;
use self::stream::{ByteOrder, EndianReader};
pub mod ifd; pub mod ifd;
mod image; mod image;
@@ -32,6 +30,8 @@ pub enum DecodingResult {
U32(Vec<u32>), U32(Vec<u32>),
/// A vector of 64 bit unsigned ints /// A vector of 64 bit unsigned ints
U64(Vec<u64>), U64(Vec<u64>),
/// A vector of 16 bit IEEE floats (held in u16)
F16(Vec<f16>),
/// A vector of 32 bit IEEE floats /// A vector of 32 bit IEEE floats
F32(Vec<f32>), F32(Vec<f32>),
/// A vector of 64 bit IEEE floats /// A vector of 64 bit IEEE floats
@@ -95,6 +95,14 @@ impl DecodingResult {
} }
} }
fn new_f16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / std::mem::size_of::<u16>() {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::F16(vec![f16::ZERO; size]))
}
}
fn new_i8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> { fn new_i8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / std::mem::size_of::<i8>() { if size > limits.decoding_buffer_size / std::mem::size_of::<i8>() {
Err(TiffError::LimitsExceeded) Err(TiffError::LimitsExceeded)
@@ -133,6 +141,7 @@ impl DecodingResult {
DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]), DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]),
DecodingResult::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[start..]), DecodingResult::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[start..]),
DecodingResult::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[start..]), DecodingResult::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[start..]),
DecodingResult::F16(ref mut buf) => DecodingBuffer::F16(&mut buf[start..]),
DecodingResult::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[start..]), DecodingResult::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[start..]),
DecodingResult::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[start..]), DecodingResult::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[start..]),
DecodingResult::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[start..]), DecodingResult::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[start..]),
@@ -153,6 +162,8 @@ pub enum DecodingBuffer<'a> {
U32(&'a mut [u32]), U32(&'a mut [u32]),
/// A slice of 64 bit unsigned ints /// A slice of 64 bit unsigned ints
U64(&'a mut [u64]), U64(&'a mut [u64]),
/// A slice of 16 bit IEEE floats
F16(&'a mut [f16]),
/// A slice of 32 bit IEEE floats /// A slice of 32 bit IEEE floats
F32(&'a mut [f32]), F32(&'a mut [f32]),
/// A slice of 64 bit IEEE floats /// A slice of 64 bit IEEE floats
@@ -168,57 +179,6 @@ pub enum DecodingBuffer<'a> {
} }
impl<'a> DecodingBuffer<'a> { 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<usize>) -> 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] { fn as_bytes_mut(&mut self) -> &mut [u8] {
match self { match self {
DecodingBuffer::U8(ref mut buf) => buf, DecodingBuffer::U8(ref mut buf) => buf,
@@ -229,6 +189,7 @@ impl<'a> DecodingBuffer<'a> {
DecodingBuffer::I32(buf) => bytecast::i32_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::U64(buf) => bytecast::u64_as_ne_mut_bytes(buf),
DecodingBuffer::I64(buf) => bytecast::i64_as_ne_mut_bytes(buf), DecodingBuffer::I64(buf) => bytecast::i64_as_ne_mut_bytes(buf),
DecodingBuffer::F16(buf) => bytecast::f16_as_ne_mut_bytes(buf),
DecodingBuffer::F32(buf) => bytecast::f32_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), DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf),
} }
@@ -299,7 +260,7 @@ pub struct Decoder<R>
where where
R: Read + Seek, R: Read + Seek,
{ {
reader: SmartReader<R>, reader: EndianReader<R>,
bigtiff: bool, bigtiff: bool,
limits: Limits, limits: Limits,
next_ifd: Option<u64>, next_ifd: Option<u64>,
@@ -308,82 +269,75 @@ where
image: Image, image: Image,
} }
trait Wrapping { fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
fn wrapping_add(&self, other: Self) -> Self; match bit_depth {
0..=8 => {
for i in samples..buf.len() {
buf[i] = buf[i].wrapping_add(buf[i - samples]);
}
}
9..=16 => {
for i in (samples * 2..buf.len()).step_by(2) {
let v = u16::from_ne_bytes(buf[i..][..2].try_into().unwrap());
let p = u16::from_ne_bytes(buf[i - 2 * samples..][..2].try_into().unwrap());
buf[i..][..2].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
17..=32 => {
for i in (samples * 4..buf.len()).step_by(4) {
let v = u32::from_ne_bytes(buf[i..][..4].try_into().unwrap());
let p = u32::from_ne_bytes(buf[i - 4 * samples..][..4].try_into().unwrap());
buf[i..][..4].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
33..=64 => {
for i in (samples * 8..buf.len()).step_by(8) {
let v = u64::from_ne_bytes(buf[i..][..8].try_into().unwrap());
let p = u64::from_ne_bytes(buf[i - 8 * samples..][..8].try_into().unwrap());
buf[i..][..8].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
_ => {
unreachable!("Caller should have validated arguments. Please file a bug.")
} }
impl Wrapping for u8 {
fn wrapping_add(&self, other: Self) -> Self {
u8::wrapping_add(*self, other)
} }
} }
impl Wrapping for u16 { fn predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) {
fn wrapping_add(&self, other: Self) -> Self { for i in samples..input.len() {
u16::wrapping_add(*self, other) input[i] = input[i].wrapping_add(input[i - samples]);
}
} }
impl Wrapping for u32 { for (i, chunk) in output.chunks_mut(4).enumerate() {
fn wrapping_add(&self, other: Self) -> Self { chunk.copy_from_slice(&u32::to_ne_bytes(u32::from_be_bytes([
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<T: Copy + Wrapping>(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[i], input[i],
input[input.len() / 4 + i], input[input.len() / 4 + i],
input[input.len() / 4 * 2 + i], input[input.len() / 4 * 2 + i],
input[input.len() / 4 * 3 + i], input[input.len() / 4 * 3 + i],
])); ])));
} }
} }
pub fn fp_predict_f64(input: &mut [u8], output: &mut [f64], samples: usize) { fn predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) {
rev_hpredict_nsamp(input, samples); for i in samples..input.len() {
for i in 0..output.len() { input[i] = input[i].wrapping_add(input[i - samples]);
// TODO: use f64::from_be_bytes() when we can (version 1.40) }
output[i] = f64::from_bits(u64::from_be_bytes([
for (i, chunk) in output.chunks_mut(2).enumerate() {
chunk.copy_from_slice(&u16::to_ne_bytes(u16::from_be_bytes([
input[i],
input[input.len() / 2 + i],
])));
}
}
fn predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) {
for i in samples..input.len() {
input[i] = input[i].wrapping_add(input[i - samples]);
}
for (i, chunk) in output.chunks_mut(8).enumerate() {
chunk.copy_from_slice(&u64::to_ne_bytes(u64::from_be_bytes([
input[i], input[i],
input[input.len() / 8 + i], input[input.len() / 8 + i],
input[input.len() / 8 * 2 + i], input[input.len() / 8 * 2 + i],
@@ -392,122 +346,104 @@ pub fn fp_predict_f64(input: &mut [u8], output: &mut [f64], samples: usize) {
input[input.len() / 8 * 5 + i], input[input.len() / 8 * 5 + i],
input[input.len() / 8 * 6 + i], input[input.len() / 8 * 6 + i],
input[input.len() / 8 * 7 + i], input[input.len() / 8 * 7 + i],
])); ])));
} }
} }
fn fix_endianness_and_predict( fn fix_endianness_and_predict(
mut image: DecodingBuffer, buf: &mut [u8],
bit_depth: u8,
samples: usize, samples: usize,
byte_order: ByteOrder, byte_order: ByteOrder,
predictor: Predictor, predictor: Predictor,
) { ) {
match predictor { match predictor {
Predictor::None => { Predictor::None => {
fix_endianness(&mut image, byte_order); fix_endianness(buf, byte_order, bit_depth);
} }
Predictor::Horizontal => { Predictor::Horizontal => {
fix_endianness(&mut image, byte_order); fix_endianness(buf, byte_order, bit_depth);
match image { rev_hpredict_nsamp(buf, bit_depth, samples);
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 => { Predictor::FloatingPoint => {
let mut buffer_copy = image.as_bytes_mut().to_vec(); let mut buffer_copy = buf.to_vec();
match image { match bit_depth {
DecodingBuffer::F32(buf) => fp_predict_f32(&mut buffer_copy, buf, samples), 16 => predict_f16(&mut buffer_copy, buf, samples),
DecodingBuffer::F64(buf) => fp_predict_f64(&mut buffer_copy, buf, samples), 32 => predict_f32(&mut buffer_copy, buf, samples),
64 => predict_f64(&mut buffer_copy, buf, samples),
_ => unreachable!("Caller should have validated arguments. Please file a bug."), _ => unreachable!("Caller should have validated arguments. Please file a bug."),
} }
} }
} }
} }
fn invert_colors_unsigned<T>(buffer: &mut [T], max: T) fn invert_colors(buf: &mut [u8], color_type: ColorType, sample_format: SampleFormat) {
where match (color_type, sample_format) {
T: std::ops::Sub<T> + std::ops::Sub<Output = T> + Copy, (ColorType::Gray(8), SampleFormat::Uint) => {
{ for x in buf {
for datum in buffer.iter_mut() { *x = 0xff - *x;
*datum = max - *datum
} }
} }
(ColorType::Gray(16), SampleFormat::Uint) => {
fn invert_colors_fp<T>(buffer: &mut [T], max: T) for x in buf.chunks_mut(2) {
where let v = u16::from_ne_bytes(x.try_into().unwrap());
T: std::ops::Sub<T> + std::ops::Sub<Output = T> + Copy, x.copy_from_slice(&(0xffff - v).to_ne_bytes());
{
for datum in buffer.iter_mut() {
// FIXME: assumes [0, 1) range for floats
*datum = max - *datum
} }
} }
(ColorType::Gray(32), SampleFormat::Uint) => {
fn invert_colors(buf: &mut DecodingBuffer, color_type: ColorType) { for x in buf.chunks_mut(4) {
match (color_type, buf) { let v = u32::from_ne_bytes(x.try_into().unwrap());
(ColorType::Gray(64), DecodingBuffer::U64(ref mut buffer)) => { x.copy_from_slice(&(0xffff_ffff - v).to_ne_bytes());
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)) => { (ColorType::Gray(64), SampleFormat::Uint) => {
invert_colors_unsigned(buffer, 0xffff); for x in buf.chunks_mut(8) {
let v = u64::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(0xffff_ffff_ffff_ffff - v).to_ne_bytes());
} }
(ColorType::Gray(n), DecodingBuffer::U8(ref mut buffer)) if n <= 8 => {
invert_colors_unsigned(buffer, 0xff);
} }
(ColorType::Gray(32), DecodingBuffer::F32(ref mut buffer)) => { (ColorType::Gray(32), SampleFormat::IEEEFP) => {
invert_colors_fp(buffer, 1.0); for x in buf.chunks_mut(4) {
let v = f32::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
}
}
(ColorType::Gray(64), SampleFormat::IEEEFP) => {
for x in buf.chunks_mut(8) {
let v = f64::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
} }
(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. /// Fix endianness. If `byte_order` matches the host, then conversion is a no-op.
fn fix_endianness(buf: &mut DecodingBuffer, byte_order: ByteOrder) { fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
match byte_order { match byte_order {
ByteOrder::LittleEndian => match buf { ByteOrder::LittleEndian => match bit_depth {
DecodingBuffer::U8(_) | DecodingBuffer::I8(_) => {} 0..=8 => {}
DecodingBuffer::U16(b) => b.iter_mut().for_each(|v| *v = u16::from_le(*v)), 9..=16 => buf.chunks_exact_mut(2).for_each(|v| {
DecodingBuffer::I16(b) => b.iter_mut().for_each(|v| *v = i16::from_le(*v)), v.copy_from_slice(&u16::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
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)), 17..=32 => buf.chunks_exact_mut(4).for_each(|v| {
DecodingBuffer::U64(b) => b.iter_mut().for_each(|v| *v = u64::from_le(*v)), v.copy_from_slice(&u32::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
DecodingBuffer::I64(b) => b.iter_mut().for_each(|v| *v = i64::from_le(*v)), }),
DecodingBuffer::F32(b) => b _ => buf.chunks_exact_mut(8).for_each(|v| {
.iter_mut() v.copy_from_slice(&u64::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
.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 { ByteOrder::BigEndian => match bit_depth {
DecodingBuffer::U8(_) | DecodingBuffer::I8(_) => {} 0..=8 => {}
DecodingBuffer::U16(b) => b.iter_mut().for_each(|v| *v = u16::from_be(*v)), 9..=16 => buf.chunks_exact_mut(2).for_each(|v| {
DecodingBuffer::I16(b) => b.iter_mut().for_each(|v| *v = i16::from_be(*v)), v.copy_from_slice(&u16::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
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)), 17..=32 => buf.chunks_exact_mut(4).for_each(|v| {
DecodingBuffer::U64(b) => b.iter_mut().for_each(|v| *v = u64::from_be(*v)), v.copy_from_slice(&u32::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
DecodingBuffer::I64(b) => b.iter_mut().for_each(|v| *v = i64::from_be(*v)), }),
DecodingBuffer::F32(b) => b _ => buf.chunks_exact_mut(8).for_each(|v| {
.iter_mut() v.copy_from_slice(&u64::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
.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()))),
}, },
}; };
} }
@@ -526,7 +462,7 @@ impl<R: Read + Seek> Decoder<R> {
)) ))
} }
}; };
let mut reader = SmartReader::wrap(r, byte_order); let mut reader = EndianReader::new(r, byte_order);
let bigtiff = match reader.read_u16()? { let bigtiff = match reader.read_u16()? {
42 => false, 42 => false,
@@ -574,7 +510,7 @@ impl<R: Read + Seek> Decoder<R> {
height: 0, height: 0,
bits_per_sample: 1, bits_per_sample: 1,
samples: 1, samples: 1,
sample_format: vec![SampleFormat::Uint], sample_format: SampleFormat::Uint,
photometric_interpretation: PhotometricInterpretation::BlackIsZero, photometric_interpretation: PhotometricInterpretation::BlackIsZero,
compression_method: CompressionMethod::None, compression_method: CompressionMethod::None,
jpeg_tables: None, jpeg_tables: None,
@@ -704,7 +640,7 @@ impl<R: Read + Seek> Decoder<R> {
#[inline] #[inline]
pub fn read_byte(&mut self) -> Result<u8, io::Error> { pub fn read_byte(&mut self) -> Result<u8, io::Error> {
let mut buf = [0; 1]; let mut buf = [0; 1];
self.reader.read_exact(&mut buf)?; self.reader.inner().read_exact(&mut buf)?;
Ok(buf[0]) Ok(buf[0])
} }
@@ -758,7 +694,7 @@ impl<R: Read + Seek> Decoder<R> {
#[inline] #[inline]
pub fn read_string(&mut self, length: usize) -> TiffResult<String> { pub fn read_string(&mut self, length: usize) -> TiffResult<String> {
let mut out = vec![0; length]; let mut out = vec![0; length];
self.reader.read_exact(&mut out)?; self.reader.inner().read_exact(&mut out)?;
// Strings may be null-terminated, so we trim anything downstream of the null byte // Strings may be null-terminated, so we trim anything downstream of the null byte
if let Some(first) = out.iter().position(|&b| b == 0) { if let Some(first) = out.iter().position(|&b| b == 0) {
out.truncate(first); out.truncate(first);
@@ -775,7 +711,7 @@ impl<R: Read + Seek> Decoder<R> {
)); ));
} }
let mut val = [0; 4]; let mut val = [0; 4];
self.reader.read_exact(&mut val)?; self.reader.inner().read_exact(&mut val)?;
Ok(val) Ok(val)
} }
@@ -783,7 +719,7 @@ impl<R: Read + Seek> Decoder<R> {
#[inline] #[inline]
pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> { pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> {
let mut val = [0; 8]; let mut val = [0; 8];
self.reader.read_exact(&mut val)?; self.reader.inner().read_exact(&mut val)?;
Ok(val) Ok(val)
} }
@@ -795,7 +731,7 @@ impl<R: Read + Seek> Decoder<R> {
#[inline] #[inline]
pub fn goto_offset_u64(&mut self, offset: u64) -> io::Result<()> { pub fn goto_offset_u64(&mut self, offset: u64) -> io::Result<()> {
self.reader.seek(io::SeekFrom::Start(offset)).map(|_| ()) self.reader.goto_offset(offset)
} }
/// Reads a IFD entry. /// Reads a IFD entry.
@@ -806,7 +742,7 @@ impl<R: Read + Seek> Decoder<R> {
// Count 4 bytes // Count 4 bytes
// Value 4 bytes either a pointer the value itself // Value 4 bytes either a pointer the value itself
fn read_entry( fn read_entry(
reader: &mut SmartReader<R>, reader: &mut EndianReader<R>,
bigtiff: bool, bigtiff: bool,
) -> TiffResult<Option<(Tag, ifd::Entry)>> { ) -> TiffResult<Option<(Tag, ifd::Entry)>> {
let tag = Tag::from_u16_exhaustive(reader.read_u16()?); let tag = Tag::from_u16_exhaustive(reader.read_u16()?);
@@ -823,13 +759,13 @@ impl<R: Read + Seek> Decoder<R> {
let mut offset = [0; 8]; let mut offset = [0; 8];
let count = reader.read_u64()?; let count = reader.read_u64()?;
reader.read_exact(&mut offset)?; reader.inner().read_exact(&mut offset)?;
ifd::Entry::new_u64(type_, count, offset) ifd::Entry::new_u64(type_, count, offset)
} else { } else {
let mut offset = [0; 4]; let mut offset = [0; 4];
let count = reader.read_u32()?; let count = reader.read_u32()?;
reader.read_exact(&mut offset)?; reader.inner().read_exact(&mut offset)?;
ifd::Entry::new(type_, count, offset) ifd::Entry::new(type_, count, offset)
}; };
Ok(Some((tag, entry))) Ok(Some((tag, entry)))
@@ -837,7 +773,7 @@ impl<R: Read + Seek> Decoder<R> {
/// Reads the IFD starting at the indicated location. /// Reads the IFD starting at the indicated location.
fn read_ifd( fn read_ifd(
reader: &mut SmartReader<R>, reader: &mut EndianReader<R>,
bigtiff: bool, bigtiff: bool,
ifd_location: u64, ifd_location: u64,
) -> TiffResult<(Directory, Option<u64>)> { ) -> TiffResult<(Directory, Option<u64>)> {
@@ -987,6 +923,15 @@ impl<R: Read + Seek> Decoder<R> {
self.get_tag(tag)?.into_string() self.get_tag(tag)?.into_string()
} }
/// Returns an iterator over all tags in the current image, along with their values.
pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
self.image.ifd.as_ref().unwrap().iter().map(|(tag, entry)| {
entry
.val(&self.limits, self.bigtiff, &mut self.reader)
.map(|value| (*tag, value))
})
}
fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> { fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> {
if expected != self.image().chunk_type { if expected != self.image().chunk_type {
return Err(TiffError::UsageError(UsageError::InvalidChunkType( return Err(TiffError::UsageError(UsageError::InvalidChunkType(
@@ -1039,14 +984,19 @@ impl<R: Read + Seek> Decoder<R> {
output_width: usize, output_width: usize,
) -> TiffResult<()> { ) -> TiffResult<()> {
let offset = self.image.chunk_file_range(chunk_index)?.0; let offset = self.image.chunk_file_range(chunk_index)?.0;
self.goto_offset_u64(offset)?; self.reader.goto_offset(offset)?;
let byte_order = self.reader.byte_order; let byte_order = self.reader.byte_order;
let output_row_stride = (output_width as u64)
.saturating_mul(self.image.samples_per_pixel() as u64)
.saturating_mul(self.image.bits_per_sample as u64)
/ 8;
self.image.expand_chunk( self.image.expand_chunk(
&mut self.reader, self.reader.inner(),
buffer.copy(), buffer.as_bytes_mut(),
output_width, output_row_stride.try_into()?,
byte_order, byte_order,
chunk_index, chunk_index,
&self.limits, &self.limits,
@@ -1056,21 +1006,23 @@ impl<R: Read + Seek> Decoder<R> {
} }
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> { fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
let buffer_size = match width let bits_per_sample = self.image().bits_per_sample;
.checked_mul(height)
.and_then(|x| x.checked_mul(self.image().samples_per_pixel())) let row_samples = if bits_per_sample >= 8 {
{ width
Some(s) => s, } else {
None => return Err(TiffError::LimitsExceeded), ((((width as u64) * bits_per_sample as u64) + 7) / 8)
.try_into()
.map_err(|_| TiffError::LimitsExceeded)?
}; };
let buffer_size = row_samples
.checked_mul(height)
.and_then(|x| x.checked_mul(self.image().samples_per_pixel()))
.ok_or(TiffError::LimitsExceeded)?;
let max_sample_bits = self.image().bits_per_sample; let max_sample_bits = self.image().bits_per_sample;
match self match self.image().sample_format {
.image()
.sample_format
.first()
.unwrap_or(&SampleFormat::Uint)
{
SampleFormat::Uint => match max_sample_bits { SampleFormat::Uint => match max_sample_bits {
n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits), 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 <= 16 => DecodingResult::new_u16(buffer_size, &self.limits),
@@ -1081,6 +1033,7 @@ impl<R: Read + Seek> Decoder<R> {
)), )),
}, },
SampleFormat::IEEEFP => match max_sample_bits { SampleFormat::IEEEFP => match max_sample_bits {
16 => DecodingResult::new_f16(buffer_size, &self.limits),
32 => DecodingResult::new_f32(buffer_size, &self.limits), 32 => DecodingResult::new_f32(buffer_size, &self.limits),
64 => DecodingResult::new_f64(buffer_size, &self.limits), 64 => DecodingResult::new_f64(buffer_size, &self.limits),
n => Err(TiffError::UnsupportedError( n => Err(TiffError::UnsupportedError(
@@ -1096,7 +1049,7 @@ impl<R: Read + Seek> Decoder<R> {
TiffUnsupportedError::UnsupportedBitsPerChannel(n), TiffUnsupportedError::UnsupportedBitsPerChannel(n),
)), )),
}, },
format => Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![*format]).into()), format => Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format]).into()),
} }
} }
@@ -1152,8 +1105,23 @@ impl<R: Read + Seek> Decoder<R> {
)); ));
} }
let output_row_bits = (width as u64 * self.image.bits_per_sample as u64)
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let output_row_stride: usize = ((output_row_bits + 7) / 8).try_into()?;
let chunk_row_bits = (chunk_dimensions.0 as u64 * self.image.bits_per_sample as u64)
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize; let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize;
let strip_samples = width as usize * chunk_dimensions.1 as usize * samples;
if chunks_across > 1 && chunk_row_bits % 8 != 0 {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::MisalignedTileBoundaries,
));
}
let image_chunks = self.image().chunk_offsets.len() / self.image().strips_per_pixel(); let image_chunks = self.image().chunk_offsets.len() / self.image().strips_per_pixel();
// For multi-band images, only the first band is read. // For multi-band images, only the first band is read.
@@ -1161,16 +1129,17 @@ impl<R: Read + Seek> Decoder<R> {
// * pass requested band as parameter // * pass requested band as parameter
// * collect bands to a RGB encoding result in case of RGB bands // * collect bands to a RGB encoding result in case of RGB bands
for chunk in 0..image_chunks { for chunk in 0..image_chunks {
self.goto_offset_u64(self.image().chunk_offsets[chunk])?; self.reader.goto_offset(self.image().chunk_offsets[chunk])?;
let x = chunk % chunks_across; let x = chunk % chunks_across;
let y = chunk / chunks_across; let y = chunk / chunks_across;
let buffer_offset = y * strip_samples + x * chunk_dimensions.0 as usize * samples; let buffer_offset =
y * output_row_stride * chunk_dimensions.1 as usize + x * chunk_row_bytes;
let byte_order = self.reader.byte_order; let byte_order = self.reader.byte_order;
self.image.expand_chunk( self.image.expand_chunk(
&mut self.reader, self.reader.inner(),
result.as_buffer(buffer_offset).copy(), &mut result.as_buffer(0).as_bytes_mut()[buffer_offset..],
width as usize, output_row_stride,
byte_order, byte_order,
chunk as u32, chunk as u32,
&self.limits, &self.limits,
+50 -87
View File
@@ -1,6 +1,5 @@
//! All IO functionality needed for TIFF decoding //! All IO functionality needed for TIFF decoding
use std::convert::TryFrom;
use std::io::{self, BufRead, BufReader, Read, Seek, Take}; use std::io::{self, BufRead, BufReader, Read, Seek, Take};
/// Byte order of the TIFF file. /// Byte order of the TIFF file.
@@ -13,16 +12,32 @@ pub enum ByteOrder {
} }
/// Reader that is aware of the byte order. /// Reader that is aware of the byte order.
pub trait EndianReader: Read { #[derive(Debug)]
/// Byte order that should be adhered to pub struct EndianReader<R> {
fn byte_order(&self) -> ByteOrder; reader: R,
pub(crate) byte_order: ByteOrder,
}
impl<R: Read + Seek> EndianReader<R> {
pub fn new(reader: R, byte_order: ByteOrder) -> Self {
Self { reader, byte_order }
}
pub fn inner(&mut self) -> &mut R {
&mut self.reader
}
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
self.reader.seek(io::SeekFrom::Start(offset))?;
Ok(())
}
/// Reads an u16 /// Reads an u16
#[inline(always)] #[inline(always)]
fn read_u16(&mut self) -> Result<u16, io::Error> { pub fn read_u16(&mut self) -> Result<u16, io::Error> {
let mut n = [0u8; 2]; let mut n = [0u8; 2];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => u16::from_le_bytes(n), ByteOrder::LittleEndian => u16::from_le_bytes(n),
ByteOrder::BigEndian => u16::from_be_bytes(n), ByteOrder::BigEndian => u16::from_be_bytes(n),
}) })
@@ -30,10 +45,10 @@ pub trait EndianReader: Read {
/// Reads an i8 /// Reads an i8
#[inline(always)] #[inline(always)]
fn read_i8(&mut self) -> Result<i8, io::Error> { pub fn read_i8(&mut self) -> Result<i8, io::Error> {
let mut n = [0u8; 1]; let mut n = [0u8; 1];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => i8::from_le_bytes(n), ByteOrder::LittleEndian => i8::from_le_bytes(n),
ByteOrder::BigEndian => i8::from_be_bytes(n), ByteOrder::BigEndian => i8::from_be_bytes(n),
}) })
@@ -41,10 +56,10 @@ pub trait EndianReader: Read {
/// Reads an i16 /// Reads an i16
#[inline(always)] #[inline(always)]
fn read_i16(&mut self) -> Result<i16, io::Error> { pub fn read_i16(&mut self) -> Result<i16, io::Error> {
let mut n = [0u8; 2]; let mut n = [0u8; 2];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => i16::from_le_bytes(n), ByteOrder::LittleEndian => i16::from_le_bytes(n),
ByteOrder::BigEndian => i16::from_be_bytes(n), ByteOrder::BigEndian => i16::from_be_bytes(n),
}) })
@@ -52,10 +67,10 @@ pub trait EndianReader: Read {
/// Reads an u32 /// Reads an u32
#[inline(always)] #[inline(always)]
fn read_u32(&mut self) -> Result<u32, io::Error> { pub fn read_u32(&mut self) -> Result<u32, io::Error> {
let mut n = [0u8; 4]; let mut n = [0u8; 4];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => u32::from_le_bytes(n), ByteOrder::LittleEndian => u32::from_le_bytes(n),
ByteOrder::BigEndian => u32::from_be_bytes(n), ByteOrder::BigEndian => u32::from_be_bytes(n),
}) })
@@ -63,10 +78,10 @@ pub trait EndianReader: Read {
/// Reads an i32 /// Reads an i32
#[inline(always)] #[inline(always)]
fn read_i32(&mut self) -> Result<i32, io::Error> { pub fn read_i32(&mut self) -> Result<i32, io::Error> {
let mut n = [0u8; 4]; let mut n = [0u8; 4];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => i32::from_le_bytes(n), ByteOrder::LittleEndian => i32::from_le_bytes(n),
ByteOrder::BigEndian => i32::from_be_bytes(n), ByteOrder::BigEndian => i32::from_be_bytes(n),
}) })
@@ -74,10 +89,10 @@ pub trait EndianReader: Read {
/// Reads an u64 /// Reads an u64
#[inline(always)] #[inline(always)]
fn read_u64(&mut self) -> Result<u64, io::Error> { pub fn read_u64(&mut self) -> Result<u64, io::Error> {
let mut n = [0u8; 8]; let mut n = [0u8; 8];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => u64::from_le_bytes(n), ByteOrder::LittleEndian => u64::from_le_bytes(n),
ByteOrder::BigEndian => u64::from_be_bytes(n), ByteOrder::BigEndian => u64::from_be_bytes(n),
}) })
@@ -85,10 +100,10 @@ pub trait EndianReader: Read {
/// Reads an i64 /// Reads an i64
#[inline(always)] #[inline(always)]
fn read_i64(&mut self) -> Result<i64, io::Error> { pub fn read_i64(&mut self) -> Result<i64, io::Error> {
let mut n = [0u8; 8]; let mut n = [0u8; 8];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => i64::from_le_bytes(n), ByteOrder::LittleEndian => i64::from_le_bytes(n),
ByteOrder::BigEndian => i64::from_be_bytes(n), ByteOrder::BigEndian => i64::from_be_bytes(n),
}) })
@@ -96,10 +111,10 @@ pub trait EndianReader: Read {
/// Reads an f32 /// Reads an f32
#[inline(always)] #[inline(always)]
fn read_f32(&mut self) -> Result<f32, io::Error> { pub fn read_f32(&mut self) -> Result<f32, io::Error> {
let mut n = [0u8; 4]; let mut n = [0u8; 4];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(f32::from_bits(match self.byte_order() { Ok(f32::from_bits(match self.byte_order {
ByteOrder::LittleEndian => u32::from_le_bytes(n), ByteOrder::LittleEndian => u32::from_le_bytes(n),
ByteOrder::BigEndian => u32::from_be_bytes(n), ByteOrder::BigEndian => u32::from_be_bytes(n),
})) }))
@@ -107,10 +122,10 @@ pub trait EndianReader: Read {
/// Reads an f64 /// Reads an f64
#[inline(always)] #[inline(always)]
fn read_f64(&mut self) -> Result<f64, io::Error> { pub fn read_f64(&mut self) -> Result<f64, io::Error> {
let mut n = [0u8; 8]; let mut n = [0u8; 8];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(f64::from_bits(match self.byte_order() { Ok(f64::from_bits(match self.byte_order {
ByteOrder::LittleEndian => u64::from_le_bytes(n), ByteOrder::LittleEndian => u64::from_le_bytes(n),
ByteOrder::BigEndian => u64::from_be_bytes(n), ByteOrder::BigEndian => u64::from_be_bytes(n),
})) }))
@@ -125,6 +140,7 @@ pub trait EndianReader: Read {
/// ## Deflate Reader /// ## Deflate Reader
/// ///
#[cfg(feature = "deflate")]
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>; pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
/// ///
@@ -132,11 +148,13 @@ pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
/// ///
/// Reader that decompresses LZW streams /// Reader that decompresses LZW streams
#[cfg(feature = "lzw")]
pub struct LZWReader<R: Read> { pub struct LZWReader<R: Read> {
reader: BufReader<Take<R>>, reader: BufReader<Take<R>>,
decoder: weezl::decode::Decoder, decoder: weezl::decode::Decoder,
} }
#[cfg(feature = "lzw")]
impl<R: Read> LZWReader<R> { impl<R: Read> LZWReader<R> {
/// Wraps a reader /// Wraps a reader
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> { pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
@@ -150,6 +168,7 @@ impl<R: Read> LZWReader<R> {
} }
} }
#[cfg(feature = "lzw")]
impl<R: Read> Read for LZWReader<R> { impl<R: Read> Read for LZWReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
loop { loop {
@@ -253,62 +272,6 @@ impl<R: Read> Read for PackBitsReader<R> {
} }
} }
///
/// ## SmartReader Reader
///
/// Reader that is aware of the byte order.
#[derive(Debug)]
pub struct SmartReader<R>
where
R: Read,
{
reader: R,
pub byte_order: ByteOrder,
}
impl<R> SmartReader<R>
where
R: Read,
{
/// Wraps a reader
pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> {
SmartReader { reader, byte_order }
}
pub fn into_inner(self) -> R {
self.reader
}
}
impl<R: Read + Seek> SmartReader<R> {
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
self.seek(io::SeekFrom::Start(offset)).map(|_| ())
}
}
impl<R> EndianReader for SmartReader<R>
where
R: Read,
{
#[inline(always)]
fn byte_order(&self) -> ByteOrder {
self.byte_order
}
}
impl<R: Read> Read for SmartReader<R> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.reader.read(buf)
}
}
impl<R: Read + Seek> Seek for SmartReader<R> {
#[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
self.reader.seek(pos)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
+2 -3
View File
@@ -1,15 +1,14 @@
use std::convert::TryFrom;
use std::io::{Read, Seek}; use std::io::{Read, Seek};
use crate::tags::Tag; use crate::tags::Tag;
use crate::{TiffError, TiffFormatError, TiffResult}; use crate::{TiffError, TiffFormatError, TiffResult};
use super::ifd::{Directory, Value}; use super::ifd::{Directory, Value};
use super::stream::SmartReader; use super::stream::EndianReader;
use super::Limits; use super::Limits;
pub(crate) struct TagReader<'a, R: Read + Seek> { pub(crate) struct TagReader<'a, R: Read + Seek> {
pub reader: &'a mut SmartReader<R>, pub reader: &'a mut EndianReader<R>,
pub ifd: &'a Directory, pub ifd: &'a Directory,
pub limits: &'a Limits, pub limits: &'a Limits,
pub bigtiff: bool, pub bigtiff: bool,
+108
View File
@@ -1,5 +1,31 @@
use crate::tags::{PhotometricInterpretation, SampleFormat}; use crate::tags::{PhotometricInterpretation, SampleFormat};
macro_rules! integer_horizontal_predict {
() => {
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
let sample_size = Self::SAMPLE_FORMAT.len();
if row.len() < sample_size {
debug_assert!(false);
return;
}
let (start, rest) = row.split_at(sample_size);
result.extend_from_slice(start);
if result.capacity() - result.len() < rest.len() {
return;
}
result.extend(
row.into_iter()
.zip(rest)
.map(|(prev, current)| current.wrapping_sub(*prev)),
);
}
};
}
/// Trait for different colortypes that can be encoded. /// Trait for different colortypes that can be encoded.
pub trait ColorType { pub trait ColorType {
/// The type of each sample of this colortype /// The type of each sample of this colortype
@@ -10,6 +36,8 @@ pub trait ColorType {
const BITS_PER_SAMPLE: &'static [u16]; const BITS_PER_SAMPLE: &'static [u16];
/// The value of the tiff tag `SampleFormat` /// The value of the tiff tag `SampleFormat`
const SAMPLE_FORMAT: &'static [SampleFormat]; const SAMPLE_FORMAT: &'static [SampleFormat];
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
} }
pub struct Gray8; pub struct Gray8;
@@ -18,6 +46,8 @@ impl ColorType for Gray8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[8]; const BITS_PER_SAMPLE: &'static [u16] = &[8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
} }
pub struct GrayI8; pub struct GrayI8;
@@ -26,6 +56,8 @@ impl ColorType for GrayI8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[8]; const BITS_PER_SAMPLE: &'static [u16] = &[8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray16; pub struct Gray16;
@@ -34,6 +66,8 @@ impl ColorType for Gray16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[16]; const BITS_PER_SAMPLE: &'static [u16] = &[16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
} }
pub struct GrayI16; pub struct GrayI16;
@@ -42,6 +76,8 @@ impl ColorType for GrayI16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[16]; const BITS_PER_SAMPLE: &'static [u16] = &[16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray32; pub struct Gray32;
@@ -50,6 +86,8 @@ impl ColorType for Gray32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32]; const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
} }
pub struct GrayI32; pub struct GrayI32;
@@ -58,6 +96,8 @@ impl ColorType for GrayI32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32]; const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray32Float; pub struct Gray32Float;
@@ -66,6 +106,10 @@ impl ColorType for Gray32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32]; const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct Gray64; pub struct Gray64;
@@ -74,6 +118,8 @@ impl ColorType for Gray64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64]; const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
} }
pub struct GrayI64; pub struct GrayI64;
@@ -82,6 +128,8 @@ impl ColorType for GrayI64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64]; const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray64Float; pub struct Gray64Float;
@@ -90,6 +138,10 @@ impl ColorType for Gray64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64]; const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGB8; pub struct RGB8;
@@ -98,6 +150,8 @@ impl ColorType for RGB8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB16; pub struct RGB16;
@@ -106,6 +160,8 @@ impl ColorType for RGB16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16]; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB32; pub struct RGB32;
@@ -114,6 +170,8 @@ impl ColorType for RGB32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB32Float; pub struct RGB32Float;
@@ -122,6 +180,9 @@ impl ColorType for RGB32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGB64; pub struct RGB64;
@@ -130,6 +191,8 @@ impl ColorType for RGB64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB64Float; pub struct RGB64Float;
@@ -138,6 +201,9 @@ impl ColorType for RGB64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGBA8; pub struct RGBA8;
@@ -146,6 +212,8 @@ impl ColorType for RGBA8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA16; pub struct RGBA16;
@@ -154,6 +222,8 @@ impl ColorType for RGBA16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA32; pub struct RGBA32;
@@ -162,6 +232,8 @@ impl ColorType for RGBA32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA32Float; pub struct RGBA32Float;
@@ -170,6 +242,9 @@ impl ColorType for RGBA32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGBA64; pub struct RGBA64;
@@ -178,6 +253,8 @@ impl ColorType for RGBA64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA64Float; pub struct RGBA64Float;
@@ -186,6 +263,9 @@ impl ColorType for RGBA64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct CMYK8; pub struct CMYK8;
@@ -194,6 +274,8 @@ impl ColorType for CMYK8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK16; pub struct CMYK16;
@@ -202,6 +284,8 @@ impl ColorType for CMYK16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK32; pub struct CMYK32;
@@ -210,6 +294,8 @@ impl ColorType for CMYK32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK32Float; pub struct CMYK32Float;
@@ -218,6 +304,10 @@ impl ColorType for CMYK32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct CMYK64; pub struct CMYK64;
@@ -226,6 +316,8 @@ impl ColorType for CMYK64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK64Float; pub struct CMYK64Float;
@@ -234,6 +326,10 @@ impl ColorType for CMYK64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct YCbCr8; pub struct YCbCr8;
@@ -242,4 +338,16 @@ impl ColorType for YCbCr8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
}
pub struct CMYKA8;
impl ColorType for CMYKA8 {
type Inner = u8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 5];
integer_horizontal_predict!();
} }
+1 -2
View File
@@ -1,6 +1,5 @@
use crate::{encoder::compression::*, tags::CompressionMethod}; use crate::encoder::compression::*;
use flate2::{write::ZlibEncoder, Compression as FlateCompression}; use flate2::{write::ZlibEncoder, Compression as FlateCompression};
use std::io::Write;
/// The Deflate algorithm used to compress image data in TIFF files. /// The Deflate algorithm used to compress image data in TIFF files.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
+1 -2
View File
@@ -1,5 +1,4 @@
use crate::{encoder::compression::*, tags::CompressionMethod}; use crate::encoder::compression::*;
use std::io::Write;
use weezl::encode::Encoder as LZWEncoder; use weezl::encode::Encoder as LZWEncoder;
/// The LZW algorithm used to compress image data in TIFF files. /// The LZW algorithm used to compress image data in TIFF files.
+11
View File
@@ -1,13 +1,19 @@
use crate::tags::CompressionMethod; use crate::tags::CompressionMethod;
use std::io::{self, Write}; use std::io::{self, Write};
#[cfg(feature = "deflate")]
mod deflate; mod deflate;
#[cfg(feature = "lzw")]
mod lzw; mod lzw;
mod packbits; mod packbits;
mod uncompressed; mod uncompressed;
#[cfg(feature = "deflate")]
pub use self::deflate::{Deflate, DeflateLevel}; pub use self::deflate::{Deflate, DeflateLevel};
#[cfg(feature = "lzw")]
pub use self::lzw::Lzw; pub use self::lzw::Lzw;
pub use self::packbits::Packbits; pub use self::packbits::Packbits;
pub use self::uncompressed::Uncompressed; pub use self::uncompressed::Uncompressed;
@@ -28,9 +34,12 @@ pub trait Compression: CompressionAlgorithm {
} }
/// An enum to store each compression algorithm. /// An enum to store each compression algorithm.
#[non_exhaustive]
pub enum Compressor { pub enum Compressor {
Uncompressed(Uncompressed), Uncompressed(Uncompressed),
#[cfg(feature = "lzw")]
Lzw(Lzw), Lzw(Lzw),
#[cfg(feature = "deflate")]
Deflate(Deflate), Deflate(Deflate),
Packbits(Packbits), Packbits(Packbits),
} }
@@ -46,7 +55,9 @@ impl CompressionAlgorithm for Compressor {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> { fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
match self { match self {
Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes), Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes),
#[cfg(feature = "lzw")]
Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes), Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes),
#[cfg(feature = "deflate")]
Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes), Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes),
Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes), Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
} }
+2 -2
View File
@@ -1,5 +1,5 @@
use crate::{encoder::compression::*, tags::CompressionMethod}; use crate::encoder::compression::*;
use std::io::{BufWriter, Error, ErrorKind, Write}; use std::io::{BufWriter, Error, ErrorKind};
/// Compressor that uses the Packbits[^note] algorithm to compress bytes. /// Compressor that uses the Packbits[^note] algorithm to compress bytes.
/// ///
+1 -2
View File
@@ -1,5 +1,4 @@
use crate::{encoder::compression::*, tags::CompressionMethod}; use crate::encoder::compression::*;
use std::io::Write;
/// The default algorithm which does not compress at all. /// The default algorithm which does not compress at all.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+116 -58
View File
@@ -3,7 +3,6 @@ pub use tiff_value::*;
use std::{ use std::{
cmp, cmp,
collections::BTreeMap, collections::BTreeMap,
convert::{TryFrom, TryInto},
io::{self, Seek, Write}, io::{self, Seek, Write},
marker::PhantomData, marker::PhantomData,
mem, mem,
@@ -11,8 +10,8 @@ use std::{
}; };
use crate::{ use crate::{
error::TiffResult, error::{TiffResult, UsageError},
tags::{CompressionMethod, ResolutionUnit, Tag}, tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag},
TiffError, TiffFormatError, TiffError, TiffFormatError,
}; };
@@ -22,9 +21,62 @@ mod tiff_value;
mod writer; mod writer;
use self::colortype::*; use self::colortype::*;
use self::compression::Compression as Comp;
use self::compression::*; use self::compression::*;
use self::writer::*; use self::writer::*;
/// Type of prediction to prepare the image with.
///
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
/// passes ran over the image data to prepare it for compression. This is mostly used for LZW
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
/// ratio over the unpredicted compression !
///
/// [Predictor::FloatingPoint] is currently not supported.
pub type Predictor = crate::tags::Predictor;
#[cfg(feature = "deflate")]
pub type DeflateLevel = compression::DeflateLevel;
#[derive(Clone, Copy, PartialEq)]
pub enum Compression {
Uncompressed,
#[cfg(feature = "lzw")]
Lzw,
#[cfg(feature = "deflate")]
Deflate(DeflateLevel),
Packbits,
}
impl Default for Compression {
fn default() -> Self {
Self::Uncompressed
}
}
impl Compression {
fn tag(&self) -> CompressionMethod {
match self {
Compression::Uncompressed => CompressionMethod::None,
#[cfg(feature = "lzw")]
Compression::Lzw => CompressionMethod::LZW,
#[cfg(feature = "deflate")]
Compression::Deflate(_) => CompressionMethod::Deflate,
Compression::Packbits => CompressionMethod::PackBits,
}
}
fn get_algorithm(&self) -> Compressor {
match self {
Compression::Uncompressed => compression::Uncompressed {}.get_algorithm(),
#[cfg(feature = "lzw")]
Compression::Lzw => compression::Lzw {}.get_algorithm(),
#[cfg(feature = "deflate")]
Compression::Deflate(level) => compression::Deflate::with_level(*level).get_algorithm(),
Compression::Packbits => compression::Packbits {}.get_algorithm(),
}
}
}
/// Encoder for Tiff and BigTiff files. /// Encoder for Tiff and BigTiff files.
/// ///
/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder` /// With this type you can get a `DirectoryEncoder` or a `ImageEncoder`
@@ -53,6 +105,8 @@ use self::writer::*;
pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> { pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> {
writer: TiffWriter<W>, writer: TiffWriter<W>,
kind: PhantomData<K>, kind: PhantomData<K>,
predictor: Predictor,
compression: Compression,
} }
/// Constructor functions to create standard Tiff files. /// Constructor functions to create standard Tiff files.
@@ -84,6 +138,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
let mut encoder = TiffEncoder { let mut encoder = TiffEncoder {
writer: TiffWriter::new(writer), writer: TiffWriter::new(writer),
kind: PhantomData, kind: PhantomData,
predictor: Predictor::None,
compression: Compression::Uncompressed,
}; };
K::write_header(&mut encoder.writer)?; K::write_header(&mut encoder.writer)?;
@@ -91,6 +147,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
Ok(encoder) Ok(encoder)
} }
/// Set the predictor to use
///
/// A predictor is used to simplify the file before writing it. This is very
/// useful when writing a file compressed using LZW as it can improve efficiency
pub fn with_predictor(mut self, predictor: Predictor) -> Self {
self.predictor = predictor;
self
}
/// Set the compression method to use
pub fn with_compression(mut self, compression: Compression) -> Self {
self.compression = compression;
self
}
/// Create a [`DirectoryEncoder`] to encode an ifd directory. /// Create a [`DirectoryEncoder`] to encode an ifd directory.
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> { pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> {
DirectoryEncoder::new(&mut self.writer) DirectoryEncoder::new(&mut self.writer)
@@ -101,20 +174,9 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
&mut self, &mut self,
width: u32, width: u32,
height: u32, height: u32,
) -> TiffResult<ImageEncoder<W, C, K, Uncompressed>> { ) -> TiffResult<ImageEncoder<W, C, K>> {
let encoder = DirectoryEncoder::new(&mut self.writer)?; let encoder = DirectoryEncoder::new(&mut self.writer)?;
ImageEncoder::new(encoder, width, height) ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
}
/// Create an [`ImageEncoder`] to encode an image one slice at a time.
pub fn new_image_with_compression<C: ColorType, D: Compression>(
&mut self,
width: u32,
height: u32,
compression: D,
) -> TiffResult<ImageEncoder<W, C, K, D>> {
let encoder = DirectoryEncoder::new(&mut self.writer)?;
ImageEncoder::with_compression(encoder, width, height, compression)
} }
/// Convenience function to write an entire image from memory. /// Convenience function to write an entire image from memory.
@@ -128,24 +190,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
[C::Inner]: TiffValue, [C::Inner]: TiffValue,
{ {
let encoder = DirectoryEncoder::new(&mut self.writer)?; let encoder = DirectoryEncoder::new(&mut self.writer)?;
let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?; let image: ImageEncoder<W, C, K> =
image.write_data(data) ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?;
}
/// Convenience function to write an entire image from memory with a given compression.
pub fn write_image_with_compression<C: ColorType, D: Compression>(
&mut self,
width: u32,
height: u32,
compression: D,
data: &[C::Inner],
) -> TiffResult<()>
where
[C::Inner]: TiffValue,
{
let encoder = DirectoryEncoder::new(&mut self.writer)?;
let image: ImageEncoder<W, C, K, D> =
ImageEncoder::with_compression(encoder, width, height, compression)?;
image.write_data(data) image.write_data(data)
} }
} }
@@ -313,13 +359,7 @@ impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> {
/// # } /// # }
/// ``` /// ```
/// You can also call write_data function wich will encode by strip and finish /// You can also call write_data function wich will encode by strip and finish
pub struct ImageEncoder< pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
'a,
W: 'a + Write + Seek,
C: ColorType,
K: TiffKind,
D: Compression = Uncompressed,
> {
encoder: DirectoryEncoder<'a, W, K>, encoder: DirectoryEncoder<'a, W, K>,
strip_idx: u64, strip_idx: u64,
strip_count: u64, strip_count: u64,
@@ -330,25 +370,30 @@ pub struct ImageEncoder<
strip_offsets: Vec<K::OffsetType>, strip_offsets: Vec<K::OffsetType>,
strip_byte_count: Vec<K::OffsetType>, strip_byte_count: Vec<K::OffsetType>,
dropped: bool, dropped: bool,
compression: D, compression: Compression,
predictor: Predictor,
_phantom: ::std::marker::PhantomData<C>, _phantom: ::std::marker::PhantomData<C>,
} }
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression> impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
ImageEncoder<'a, W, T, K, D> fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
{ match (predictor, compression, T::SAMPLE_FORMAT[0]) {
fn new(encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self> (Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
where Err(TiffError::UsageError(UsageError::PredictorIncompatible))
D: Default, }
{ (Predictor::FloatingPoint, _, _) => {
Self::with_compression(encoder, width, height, D::default()) Err(TiffError::UsageError(UsageError::PredictorUnavailable))
}
_ => Ok(()),
}
} }
fn with_compression( fn new(
mut encoder: DirectoryEncoder<'a, W, K>, mut encoder: DirectoryEncoder<'a, W, K>,
width: u32, width: u32,
height: u32, height: u32,
compression: D, compression: Compression,
predictor: Predictor,
) -> TiffResult<Self> { ) -> TiffResult<Self> {
if width == 0 || height == 0 { if width == 0 || height == 0 {
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions( return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
@@ -356,13 +401,15 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
))); )));
} }
Self::sanity_check(compression, predictor)?;
let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?; let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN); let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
// Limit the strip size to prevent potential memory and security issues. // Limit the strip size to prevent potential memory and security issues.
// Also keep the multiple strip handling 'oiled' // Also keep the multiple strip handling 'oiled'
let rows_per_strip = { let rows_per_strip = {
match D::COMPRESSION_METHOD { match compression.tag() {
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
_ => (1_000_000 + row_bytes - 1) / row_bytes, _ => (1_000_000 + row_bytes - 1) / row_bytes,
} }
@@ -372,7 +419,8 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
encoder.write_tag(Tag::ImageWidth, width)?; encoder.write_tag(Tag::ImageWidth, width)?;
encoder.write_tag(Tag::ImageLength, height)?; encoder.write_tag(Tag::ImageLength, height)?;
encoder.write_tag(Tag::Compression, D::COMPRESSION_METHOD.to_u16())?; encoder.write_tag(Tag::Compression, compression.tag().to_u16())?;
encoder.write_tag(Tag::Predictor, predictor.to_u16())?;
encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?; encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect(); let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
@@ -401,6 +449,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
strip_byte_count: Vec::new(), strip_byte_count: Vec::new(),
dropped: false, dropped: false,
compression, compression,
predictor,
_phantom: ::std::marker::PhantomData, _phantom: ::std::marker::PhantomData,
}) })
} }
@@ -433,7 +482,18 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
} }
// Write the (possible compressed) data to the encoder. // Write the (possible compressed) data to the encoder.
let offset = self.encoder.write_data(value)?; let offset = match self.predictor {
Predictor::None => self.encoder.write_data(value)?,
Predictor::Horizontal => {
let mut row_result = Vec::with_capacity(value.len());
for row in value.chunks_exact(self.row_samples as usize) {
T::horizontal_predict(row, &mut row_result);
}
self.encoder.write_data(row_result.as_slice())?
}
_ => unimplemented!(),
};
let byte_count = self.encoder.last_written() as usize; let byte_count = self.encoder.last_written() as usize;
self.strip_offsets.push(K::convert_offset(offset)?); self.strip_offsets.push(K::convert_offset(offset)?);
@@ -553,9 +613,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
} }
} }
impl<'a, W: Write + Seek, C: ColorType, K: TiffKind, D: Compression> Drop impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W, C, K> {
for ImageEncoder<'a, W, C, K, D>
{
fn drop(&mut self) { fn drop(&mut self) {
if !self.dropped { if !self.dropped {
let _ = self.finish_internal(); let _ = self.finish_internal();
+125 -281
View File
@@ -1,294 +1,189 @@
use std::error::Error;
use std::fmt;
use std::fmt::Display;
use std::io; use std::io;
use std::str; use std::str;
use std::string; use std::string;
use std::sync::Arc;
use jpeg::UnsupportedFeature; use quick_error::quick_error;
use crate::decoder::{ifd::Value, ChunkType}; use crate::decoder::ChunkType;
use crate::tags::{ use crate::tags::{
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag, CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
}; };
use crate::ColorType; use crate::ColorType;
use crate::weezl::LzwError; quick_error! {
/// Tiff error kinds. /// Tiff error kinds.
#[derive(Debug)] #[derive(Debug)]
pub enum TiffError { pub enum TiffError {
/// The Image is not formatted properly. /// The Image is not formatted properly.
FormatError(TiffFormatError), FormatError(err: TiffFormatError) {
display("format error: {err}")
from()
source(err)
}
/// The Decoder does not support features required by the image. /// The Decoder does not support features required by the image.
UnsupportedError(TiffUnsupportedError), UnsupportedError(err: TiffUnsupportedError) {
display("unsupported error: {err}")
from()
source(err)
}
/// An I/O Error occurred while decoding the image. /// An I/O Error occurred while decoding the image.
IoError(io::Error), IoError(err: io::Error) {
display("{err}")
from()
source(err)
}
/// The Limits of the Decoder is exceeded. /// The Limits of the Decoder is exceeded.
LimitsExceeded, LimitsExceeded {
display("decoder limits exceeded")
/// An integer conversion to or from a platform size failed, either due to }
/// limits of the platform size or limits of the format. /// An integer conversion to or from a platform size failed.
IntSizeError, IntSizeError {
display("platform or format size limits exceeded")
}
/// The image does not support the requested operation /// The image does not support the requested operation
UsageError(UsageError), UsageError(err: UsageError) {
display("usage error: {err}")
from()
source(err)
}
}
} }
quick_error! {
/// The image is not formatted properly. /// The image is not formatted properly.
/// ///
/// This indicates that the encoder producing the image might behave incorrectly or that the input /// This indicates that the encoder producing the image might behave incorrectly or that the
/// file has been corrupted. /// 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)] #[derive(Debug, Clone, PartialEq)]
#[non_exhaustive] #[non_exhaustive]
pub enum TiffFormatError { pub enum TiffFormatError {
TiffSignatureNotFound, TiffSignatureNotFound {
TiffSignatureInvalid, display("TIFF signature not found")
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),
UnknownPlanarConfiguration(u16),
ByteExpected(Value),
UnsignedIntegerExpected(Value),
SignedIntegerExpected(Value),
Format(String),
RequiredTagEmpty(Tag),
StripTileTagConflict,
CycleInOffsets,
JpegDecoder(JpegDecoderError),
SamplesPerPixelIsZero,
} }
TiffSignatureInvalid {
impl fmt::Display for TiffFormatError { display("TIFF signature invalid")
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 { ImageFileDirectoryNotFound {
actual_samples, display("image file directory not found")
required_samples,
} => {
write!(
fmt,
"Inconsistent elements in strip: got {}, expected {}.",
actual_samples, required_samples
)
} }
InvalidDimensions(width, height) => write!(fmt, "Invalid dimensions: {}x{}.", width, height), InconsistentSizesEncountered {
InvalidTag => write!(fmt, "Image contains invalid tag."), display("inconsistent sizes encountered")
InvalidTagValueType(ref tag) => {
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
} }
RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag), InvalidDimensions(width: u32, height: u32) {
UnknownPredictor(ref predictor) => { display("invalid dimensions: {width}x{height}")
write!(fmt, "Unknown predictor “{}” encountered", predictor)
} }
UnknownPlanarConfiguration(ref planar_config) => { InvalidTag {
write!(fmt, "Unknown planar configuration “{}” encountered", planar_config) display("image contains invalid tag")
} }
ByteExpected(ref val) => write!(fmt, "Expected byte, {:?} found.", val), InvalidTagValueType(tag: Tag) {
UnsignedIntegerExpected(ref val) => { display("tag `{tag:?}` did not have the expected value type")
write!(fmt, "Expected unsigned integer, {:?} found.", val)
} }
SignedIntegerExpected(ref val) => { RequiredTagNotFound(tag: Tag) {
write!(fmt, "Expected signed integer, {:?} found.", val) display("required tag `{tag:?}` not found")
} }
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val), UnknownPredictor(pred: u16) {
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val), display("unknown predictor “{pred}” encountered")
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"), UnknownPlanarConfiguration(cfg: u16) {
JpegDecoder(ref error) => write!(fmt, "{}", error), display("unknown planar configuration “{cfg}”")
SamplesPerPixelIsZero => write!(fmt, "Samples per pixel is zero"), }
InvalidTypeForTag {
display("tag has invalid type")
}
StripTileTagConflict {
display("file should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found")
}
CycleInOffsets {
display("file contained a cycle in the list of IFDs")
}
SamplesPerPixelIsZero {
display("samples per pixel is zero")
}
CompressedDataCorrupt(message: String) {
display("compressed data is corrupt: {message}")
} }
} }
} }
quick_error! {
/// The Decoder does not support features required by the image. /// The Decoder does not support features required by the image.
/// ///
/// This only captures known failures for which the standard either does not require support or an /// This only captures known failures for which the standard either does not require support or an
/// implementation has been planned but not yet completed. Some variants may become unused over /// implementation has been planned but not yet completed. Some variants may become unused over
/// time and will then get deprecated before being removed. /// 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)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive] #[non_exhaustive]
pub enum TiffUnsupportedError { pub enum TiffUnsupportedError {
FloatingPointPredictor(ColorType), FloatingPointPredictor(color_type: ColorType) {
HorizontalPredictor(ColorType), display("floating point predictor for {color_type:?} is unsupported")
InconsistentBitsPerSample(Vec<u8>),
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
UnknownInterpretation,
UnknownCompressionMethod,
UnsupportedCompressionMethod(CompressionMethod),
UnsupportedSampleDepth(u8),
UnsupportedSampleFormat(Vec<SampleFormat>),
UnsupportedColorType(ColorType),
UnsupportedBitsPerChannel(u8),
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
UnsupportedDataType,
UnsupportedInterpretation(PhotometricInterpretation),
UnsupportedJpegFeature(UnsupportedFeature),
} }
HorizontalPredictor(color_type: ColorType) {
impl fmt::Display for TiffUnsupportedError { display("horizontal predictor for {color_type:?} is unsupported")
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
),
InconsistentBitsPerSample(ref bits_per_sample) => {
write!(fmt, "Inconsistent bits per sample: {:?}.", bits_per_sample)
} }
InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => write!( InconsistentBitsPerSample(bits_per_sample: Vec<u8>) {
fmt, display("inconsistent bits per sample: {bits_per_sample:?}")
"{:?} 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) => { InterpretationWithBits(interpretation: PhotometricInterpretation, bits_per_sample: Vec<u8>) {
write!(fmt, "{} samples per pixel is unsupported.", samples) display("Photometric interpretation {interpretation:?} with bits per sample {bits_per_sample:?} is unsupported")
} }
UnsupportedSampleFormat(ref formats) => { UnknownInterpretation {
write!(fmt, "Sample format {:?} is unsupported.", formats) display("unknown photometric interpretation")
} }
UnsupportedColorType(color_type) => { UnknownCompressionMethod {
write!(fmt, "Color type {:?} is unsupported", color_type) display("unknown compression method")
} }
UnsupportedBitsPerChannel(bits) => { UnsupportedCompressionMethod(method: CompressionMethod) {
write!(fmt, "{} bits per channel not supported", bits) display("compression method {method:?} is unsupported")
} }
UnsupportedPlanarConfig(config) => { UnsupportedSampleDepth(depth: u8) {
write!(fmt, "Unsupported planar configuration “{:?}”.", config) display("{depth} samples per pixel is unsupported")
} }
UnsupportedDataType => write!(fmt, "Unsupported data type."), UnsupportedSampleFormat(sample_format: Vec<SampleFormat>) {
UnsupportedInterpretation(interpretation) => { display("sample format {sample_format:?} is unsupported")
write!(
fmt,
"Unsupported photometric interpretation \"{:?}\".",
interpretation
)
} }
UnsupportedJpegFeature(ref unsupported_feature) => { UnsupportedColorType(color_type: ColorType) {
write!(fmt, "Unsupported JPEG feature {:?}", unsupported_feature) display("color type {color_type:?} is unsupported")
} }
UnsupportedBitsPerChannel(bits_per_channel: u8) {
display("{bits_per_channel} bits per channel not supported")
}
UnsupportedPlanarConfig(planar: Option<PlanarConfiguration>) {
display("unsupported planar configuration “{planar:?}”")
}
UnsupportedDataType {
display("unsupported data type.")
}
UnsupportedInterpretation(interpretation: PhotometricInterpretation) {
display("unsupported photometric interpretation \"{interpretation:?}\"")
}
MisalignedTileBoundaries {
display("tile rows are not aligned to byte boundaries")
} }
} }
} }
quick_error! {
/// User attempted to use the Decoder in a way that is incompatible with a specific image. /// 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. /// For example: attempting to read a tile from a stripped image.
#[derive(Debug)] #[derive(Debug)]
#[non_exhaustive]
pub enum UsageError { pub enum UsageError {
InvalidChunkType(ChunkType, ChunkType), InvalidChunkType(expected: ChunkType, actual: ChunkType) {
InvalidChunkIndex(u32), display("requested operation is only valid for images with chunk encoding of type {expected:?} but got {actual:?}")
} }
InvalidChunkIndex(index: u32) {
impl fmt::Display for UsageError { display("invalid chunk index ({index}) requested")
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), PredictorCompressionMismatch {
display("requested predictor is not compatible with the requested compression")
} }
PredictorIncompatible {
display("the requested predictor is not compatible with the image's format")
} }
PredictorUnavailable {
display("the requested predictor is not available")
} }
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<io::Error> for TiffError {
fn from(err: io::Error) -> TiffError {
TiffError::IoError(err)
} }
} }
@@ -304,74 +199,23 @@ impl From<string::FromUtf8Error> for TiffError {
} }
} }
impl From<TiffFormatError> for TiffError {
fn from(err: TiffFormatError) -> TiffError {
TiffError::FormatError(err)
}
}
impl From<TiffUnsupportedError> for TiffError {
fn from(err: TiffUnsupportedError) -> TiffError {
TiffError::UnsupportedError(err)
}
}
impl From<UsageError> for TiffError {
fn from(err: UsageError) -> TiffError {
TiffError::UsageError(err)
}
}
impl From<std::num::TryFromIntError> for TiffError { impl From<std::num::TryFromIntError> for TiffError {
fn from(_err: std::num::TryFromIntError) -> TiffError { fn from(_err: std::num::TryFromIntError) -> TiffError {
TiffError::IntSizeError TiffError::IntSizeError
} }
} }
impl From<LzwError> for TiffError { #[cfg(feature = "lzw")]
fn from(err: LzwError) -> TiffError { impl From<weezl::LzwError> for TiffError {
match err { fn from(err: weezl::LzwError) -> TiffError {
LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from( TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
"LZW compressed data corrupted",
))),
}
}
}
#[derive(Debug, Clone)]
pub struct JpegDecoderError {
inner: Arc<jpeg::Error>,
}
impl JpegDecoderError {
fn new(error: jpeg::Error) -> Self {
Self {
inner: Arc::new(error),
}
}
}
impl PartialEq for JpegDecoderError {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
} }
} }
impl Display for JpegDecoderError { #[cfg(feature = "jpeg")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { impl From<zune_jpeg::errors::DecodeErrors> for TiffError {
self.inner.fmt(f) fn from(err: zune_jpeg::errors::DecodeErrors) -> Self {
} TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
}
impl From<JpegDecoderError> for TiffError {
fn from(error: JpegDecoderError) -> Self {
TiffError::FormatError(TiffFormatError::JpegDecoder(error))
}
}
impl From<jpeg::Error> for TiffError {
fn from(error: jpeg::Error) -> Self {
JpegDecoderError::new(error).into()
} }
} }
+22 -3
View File
@@ -6,9 +6,6 @@
//! # Related Links //! # Related Links
//! * <https://web.archive.org/web/20210108073850/https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification //! * <https://web.archive.org/web/20210108073850/https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
extern crate jpeg;
extern crate weezl;
mod bytecast; mod bytecast;
pub mod decoder; pub mod decoder;
pub mod encoder; pub mod encoder;
@@ -19,6 +16,7 @@ pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedErr
/// An enumeration over supported color types and their bit depths /// An enumeration over supported color types and their bit depths
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
#[non_exhaustive]
pub enum ColorType { pub enum ColorType {
/// Pixel is grayscale /// Pixel is grayscale
Gray(u8), Gray(u8),
@@ -38,6 +36,27 @@ pub enum ColorType {
/// Pixel is CMYK /// Pixel is CMYK
CMYK(u8), CMYK(u8),
/// Pixel is CMYK with an alpha channel
CMYKA(u8),
/// Pixel is YCbCr /// Pixel is YCbCr
YCbCr(u8), YCbCr(u8),
/// Pixel has multiple bands/channels
Multiband { bit_depth: u8, num_samples: u16 },
}
impl ColorType {
fn bit_depth(&self) -> u8 {
match *self {
ColorType::Gray(b)
| ColorType::RGB(b)
| ColorType::Palette(b)
| ColorType::GrayA(b)
| ColorType::RGBA(b)
| ColorType::CMYK(b)
| ColorType::CMYKA(b)
| ColorType::YCbCr(b)
| ColorType::Multiband { bit_depth: b, .. } => b,
}
}
} }
+9
View File
@@ -184,6 +184,9 @@ pub enum CompressionMethod(u16) unknown("A custom compression method") {
Deflate = 8, Deflate = 8,
OldDeflate = 0x80B2, OldDeflate = 0x80B2,
PackBits = 0x8005, PackBits = 0x8005,
// Self-assigned by libtiff
ZSTD = 0xC350,
} }
} }
@@ -209,8 +212,14 @@ pub enum PlanarConfiguration(u16) {
tags! { tags! {
pub enum Predictor(u16) { pub enum Predictor(u16) {
/// No changes were made to the data
None = 1, None = 1,
/// The images' rows were processed to contain the difference of each pixel from the previous one.
///
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
Horizontal = 2, Horizontal = 2,
/// Not currently supported
FloatingPoint = 3, FloatingPoint = 3,
} }
} }
+195
View File
@@ -0,0 +1,195 @@
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::ColorType;
use std::fs::File;
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images/";
/// Test a basic all white image
#[test]
fn test_white_ieee_fp16() {
let filenames = ["white-fp16.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for p in img {
assert!(p == half::f16::from_f32_const(1.0));
}
} else {
panic!("Wrong data type");
}
}
}
/// Test a single black pixel, to make sure scaling is ok
#[test]
fn test_one_black_pixel_ieee_fp16() {
let filenames = ["single-black-fp16.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for (i, p) in img.iter().enumerate() {
if i == 0 {
assert!(p < &half::f16::from_f32_const(0.001));
} else {
assert!(p == &half::f16::from_f32_const(1.0));
}
}
} else {
panic!("Wrong data type");
}
}
}
/// Test white with horizontal differencing predictor
#[test]
fn test_pattern_horizontal_differencing_ieee_fp16() {
let filenames = ["white-fp16-pred2.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
// 0, 2, 5, 8, 12, 16, 255 are black
let black = [0, 2, 5, 8, 12, 16, 255];
for (i, p) in img.iter().enumerate() {
if black.contains(&i) {
assert!(p < &half::f16::from_f32_const(0.001));
} else {
assert!(p == &half::f16::from_f32_const(1.0));
}
}
} else {
panic!("Wrong data type");
}
}
}
/// Test white with floating point predictor
#[test]
fn test_pattern_predictor_ieee_fp16() {
let filenames = ["white-fp16-pred3.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
// 0, 2, 5, 8, 12, 16, 255 are black
let black = [0, 2, 5, 8, 12, 16, 255];
for (i, p) in img.iter().enumerate() {
if black.contains(&i) {
assert!(p < &half::f16::from_f32_const(0.001));
} else {
assert!(p == &half::f16::from_f32_const(1.0));
}
}
} else {
panic!("Wrong data type");
}
}
}
/// Test several random images
/// we'rell compare against a pnm file, that scales from 0 (for 0.0) to 65767 (for 1.0)
#[test]
fn test_predictor_ieee_fp16() {
// first parse pnm, skip the first 4 \n
let pnm_path = PathBuf::from(TEST_IMAGE_DIR).join("random-fp16.pgm");
let pnm_bytes = std::fs::read(pnm_path).expect("Failed to read expected PNM file");
// PGM looks like this:
// ---
// P5
// #Created with GIMP
// 16 16
// 65535
// ... <big-endian bytes>
// ---
// get index of 4th \n
let byte_start = pnm_bytes
.iter()
.enumerate()
.filter(|(_, &v)| v == b'\n')
.map(|(i, _)| i)
.nth(3)
.expect("Must be 4 \\n's");
let pnm_values: Vec<f32> = pnm_bytes[(byte_start + 1)..]
.chunks(2)
.map(|slice| {
let bts = [slice[0], slice[1]];
(u16::from_be_bytes(bts) as f32) / (u16::MAX as f32)
})
.collect();
assert!(pnm_values.len() == 256);
let filenames = [
"random-fp16-pred2.tiff",
"random-fp16-pred3.tiff",
"random-fp16.tiff",
];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(16, 16)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) {
assert!((exp - found.to_f32()).abs() < 0.0001);
}
} else {
panic!("Wrong data type");
}
}
}
+63
View File
@@ -0,0 +1,63 @@
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::tags::Tag;
use tiff::ColorType;
use std::fs::File;
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images";
#[test]
fn test_geo_tiff() {
let filenames = ["geo-5b.tif"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
decoder = decoder.with_limits(tiff::decoder::Limits::unlimited());
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(10, 10)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Multiband {
bit_depth: 16,
num_samples: 5
}
);
assert_eq!(
decoder
.get_tag_u64(Tag::StripOffsets)
.expect("Cannot get StripOffsets"),
418
);
assert_eq!(
decoder
.get_tag_u64(Tag::RowsPerStrip)
.expect("Cannot get RowsPerStrip"),
10
);
assert_eq!(
decoder
.get_tag_u64(Tag::StripByteCounts)
.expect("Cannot get StripByteCounts"),
1000
);
assert_eq!(
decoder
.get_tag(Tag::ModelPixelScaleTag)
.expect("Cannot get pixel scale")
.into_f64_vec()
.expect("Cannot get pixel scale"),
vec![60.0, 60.0, 0.0]
);
let DecodingResult::I16(data) = decoder.read_image().unwrap() else {
panic!("Cannot read band data")
};
assert_eq!(data.len(), 500);
}
}
+12
View File
@@ -216,6 +216,11 @@ fn issue_69() {
//assert!(img_res.is_ok()); //assert!(img_res.is_ok());
//} //}
#[test]
fn test_tiled_gray_i1() {
test_image_sum_u8("tiled-gray-i1.tif", ColorType::Gray(1), 30531);
}
#[test] #[test]
fn test_tiled_rgb_u8() { fn test_tiled_rgb_u8() {
test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948); test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948);
@@ -510,3 +515,10 @@ fn test_predictor_3_rgb_f32() {
fn test_predictor_3_gray_f32() { fn test_predictor_3_gray_f32() {
test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275); test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275);
} }
#[test]
#[cfg(feature = "zstd")]
fn test_zstd_compression() {
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
}
+99
View File
@@ -36,6 +36,47 @@ fn encode_decode() {
let mut decoder = Decoder::new(&mut file).unwrap(); let mut decoder = Decoder::new(&mut file).unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
assert_eq!(decoder.dimensions().unwrap(), (100, 100)); assert_eq!(decoder.dimensions().unwrap(), (100, 100));
let mut all_tags = decoder
.tag_iter()
.filter_map(Result::ok)
.collect::<Vec<_>>();
all_tags.sort_by_key(|(t, _)| t.to_u16());
assert_eq!(
all_tags,
vec![
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
(Tag::ImageLength, ifd::Value::Unsigned(100)),
(
Tag::BitsPerSample,
ifd::Value::List(vec![
ifd::Value::Short(8),
ifd::Value::Short(8),
ifd::Value::Short(8)
])
),
(Tag::Compression, ifd::Value::Short(1)),
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
(Tag::StripOffsets, ifd::Value::Unsigned(8)),
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
(Tag::XResolution, ifd::Value::Rational(1, 1)),
(Tag::YResolution, ifd::Value::Rational(1, 1)),
(Tag::ResolutionUnit, ifd::Value::Short(1)),
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
(Tag::Predictor, ifd::Value::Short(1)),
(
Tag::SampleFormat,
ifd::Value::List(vec![
ifd::Value::Short(1),
ifd::Value::Short(1),
ifd::Value::Short(1)
])
),
]
);
assert_eq!( assert_eq!(
decoder.get_tag(Tag::Artist).unwrap(), decoder.get_tag(Tag::Artist).unwrap(),
ifd::Value::Ascii("Image-tiff".into()) ifd::Value::Ascii("Image-tiff".into())
@@ -75,6 +116,47 @@ fn encode_decode_big() {
let mut decoder = Decoder::new(&mut file).unwrap(); let mut decoder = Decoder::new(&mut file).unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
assert_eq!(decoder.dimensions().unwrap(), (100, 100)); assert_eq!(decoder.dimensions().unwrap(), (100, 100));
let mut all_tags = decoder
.tag_iter()
.filter_map(Result::ok)
.collect::<Vec<_>>();
all_tags.sort_by_key(|(t, _)| t.to_u16());
assert_eq!(
all_tags,
vec![
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
(Tag::ImageLength, ifd::Value::Unsigned(100)),
(
Tag::BitsPerSample,
ifd::Value::List(vec![
ifd::Value::Short(8),
ifd::Value::Short(8),
ifd::Value::Short(8)
])
),
(Tag::Compression, ifd::Value::Short(1)),
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
(Tag::StripOffsets, ifd::Value::UnsignedBig(16)),
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
(Tag::StripByteCounts, ifd::Value::UnsignedBig(30000)),
(Tag::XResolution, ifd::Value::Rational(1, 1)),
(Tag::YResolution, ifd::Value::Rational(1, 1)),
(Tag::ResolutionUnit, ifd::Value::Short(1)),
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
(Tag::Predictor, ifd::Value::Short(1)),
(
Tag::SampleFormat,
ifd::Value::List(vec![
ifd::Value::Short(1),
ifd::Value::Short(1),
ifd::Value::Short(1)
])
),
]
);
assert_eq!( assert_eq!(
decoder.get_tag(Tag::Artist).unwrap(), decoder.get_tag(Tag::Artist).unwrap(),
ifd::Value::Ascii("Image-tiff".into()) ifd::Value::Ascii("Image-tiff".into())
@@ -188,7 +270,9 @@ macro_rules! test_roundtrip {
} }
test_roundtrip!(test_u8_roundtrip, U8, u8); test_roundtrip!(test_u8_roundtrip, U8, u8);
test_roundtrip!(test_i8_roundtrip, I8, i8);
test_roundtrip!(test_u16_roundtrip, U16, u16); test_roundtrip!(test_u16_roundtrip, U16, u16);
test_roundtrip!(test_i16_roundtrip, I16, i16);
test_roundtrip!(test_u32_roundtrip, U32, u32); test_roundtrip!(test_u32_roundtrip, U32, u32);
test_roundtrip!(test_u64_roundtrip, U64, u64); test_roundtrip!(test_u64_roundtrip, U64, u64);
test_roundtrip!(test_f32_roundtrip, F32, f32); test_roundtrip!(test_f32_roundtrip, F32, f32);
@@ -199,6 +283,11 @@ fn test_gray_u8_roundtrip() {
test_u8_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8)); test_u8_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
} }
#[test]
fn test_gray_i8_roundtrip() {
test_i8_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test] #[test]
fn test_rgb_u8_roundtrip() { fn test_rgb_u8_roundtrip() {
test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8)); test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
@@ -209,11 +298,21 @@ fn test_cmyk_u8_roundtrip() {
test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8)); test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
} }
#[test]
fn test_cmyka_u8_roundtrip() {
test_u8_roundtrip::<colortype::CMYKA8>("cmyk-4c-8b.tiff", ColorType::CMYKA(8));
}
#[test] #[test]
fn test_gray_u16_roundtrip() { fn test_gray_u16_roundtrip() {
test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16)); test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
} }
#[test]
fn test_gray_i16_roundtrip() {
test_i16_roundtrip::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
}
#[test] #[test]
fn test_rgb_u16_roundtrip() { fn test_rgb_u16_roundtrip() {
test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16)); test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
+18 -17
View File
@@ -6,7 +6,7 @@ use tiff::{
encoder::{ encoder::{
colortype::{self, ColorType}, colortype::{self, ColorType},
compression::*, compression::*,
TiffEncoder, TiffValue, Compression, TiffEncoder, TiffValue,
}, },
}; };
@@ -18,15 +18,12 @@ trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>:
fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner]; fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS]; fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
fn compress<C: Compression, W: Write + Seek>( fn compress<W: Write + Seek>(&self, encoder: &mut TiffEncoder<W>)
&self, where
encoder: &mut TiffEncoder<W>,
compression: C,
) where
[<Self::Color as ColorType>::Inner]: TiffValue, [<Self::Color as ColorType>::Inner]: TiffValue,
{ {
let image = encoder let image = encoder
.new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression) .new_image::<Self::Color>(Self::WIDTH, Self::HEIGHT)
.unwrap(); .unwrap();
image.write_data(self.reference_data()).unwrap(); image.write_data(self.reference_data()).unwrap();
} }
@@ -94,7 +91,7 @@ impl TestImage<1> for TestImageGrayscale {
} }
} }
fn encode_decode_with_compression<C: Compression + Clone>(compression: C) { fn encode_decode_with_compression(compression: Compression) {
let mut data = Cursor::new(Vec::new()); let mut data = Cursor::new(Vec::new());
let image_rgb = TestImageColor::generate(); let image_rgb = TestImageColor::generate();
@@ -103,9 +100,11 @@ fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
// Encode tiff with compression // Encode tiff with compression
{ {
// Create a multipage image with 2 images // Create a multipage image with 2 images
let mut encoder = TiffEncoder::new(&mut data).unwrap(); let mut encoder = TiffEncoder::new(&mut data)
image_rgb.compress(&mut encoder, compression.clone()); .unwrap()
image_grayscale.compress(&mut encoder, compression); .with_compression(compression);
image_rgb.compress(&mut encoder);
image_grayscale.compress(&mut encoder);
} }
// Decode tiff // Decode tiff
@@ -136,22 +135,24 @@ fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
#[test] #[test]
fn encode_decode_without_compression() { fn encode_decode_without_compression() {
encode_decode_with_compression(Uncompressed::default()); encode_decode_with_compression(Compression::Uncompressed);
} }
#[test] #[test]
#[cfg(feature = "lzw")]
fn encode_decode_with_lzw() { fn encode_decode_with_lzw() {
encode_decode_with_compression(Lzw::default()); encode_decode_with_compression(Compression::Lzw);
} }
#[test] #[test]
#[cfg(feature = "deflate")]
fn encode_decode_with_deflate() { fn encode_decode_with_deflate() {
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast)); encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced)); encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best)); encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
} }
#[test] #[test]
fn encode_decode_with_packbits() { fn encode_decode_with_packbits() {
encode_decode_with_compression(Packbits::default()); encode_decode_with_compression(Compression::Packbits);
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+231
View File
@@ -0,0 +1,231 @@
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
use tiff::ColorType;
use std::fs::File;
use std::io::{Cursor, Seek, SeekFrom};
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images/";
macro_rules! test_predict {
($name:ident, $buffer:ident, $buffer_ty:ty) => {
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
file: &str,
expected_type: ColorType,
) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(file).expect("Cannot create decoder!");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
DecodingResult::$buffer(res) => res,
_ => panic!("Wrong data type"),
};
let mut predicted = Vec::with_capacity(image_data.len());
C::horizontal_predict(&image_data, &mut predicted);
let sample_size = C::SAMPLE_FORMAT.len();
(0..sample_size).for_each(|i| {
assert_eq!(predicted[i], image_data[i]);
});
(sample_size..image_data.len()).for_each(|i| {
predicted[i] = predicted[i].wrapping_add(predicted[i - sample_size]);
assert_eq!(predicted[i], image_data[i]);
});
}
};
}
test_predict!(test_u8_predict, U8, u8);
test_predict!(test_i8_predict, I8, i8);
test_predict!(test_u16_predict, U16, u16);
test_predict!(test_i16_predict, I16, i16);
test_predict!(test_u32_predict, U32, u32);
test_predict!(test_u64_predict, U64, u64);
#[test]
fn test_gray_u8_predict() {
test_u8_predict::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_gray_i8_predict() {
test_i8_predict::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_rgb_u8_predict() {
test_u8_predict::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
}
#[test]
fn test_cmyk_u8_predict() {
test_u8_predict::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
}
#[test]
fn test_gray_u16_predict() {
test_u16_predict::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_gray_i16_predict() {
test_i16_predict::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_rgb_u16_predict() {
test_u16_predict::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
}
#[test]
fn test_cmyk_u16_predict() {
test_u16_predict::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
}
#[test]
fn test_gray_u32_predict() {
test_u32_predict::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
}
#[test]
fn test_rgb_u32_predict() {
test_u32_predict::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
}
#[test]
fn test_gray_u64_predict() {
test_u64_predict::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
}
#[test]
fn test_rgb_u64_predict() {
test_u64_predict::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
}
#[test]
fn test_ycbcr_u8_predict() {
test_u8_predict::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}
macro_rules! test_predict_roundtrip {
($name:ident, $buffer:ident, $buffer_ty:ty) => {
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
file: &str,
expected_type: ColorType,
) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
DecodingResult::$buffer(res) => res,
_ => panic!("Wrong data type"),
};
let mut file = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new(&mut file)
.unwrap()
.with_predictor(Predictor::Horizontal);
let (width, height) = decoder.dimensions().unwrap();
tiff.write_image::<C>(width, height, &image_data).unwrap();
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::new(&mut file).unwrap();
if let DecodingResult::$buffer(img_res) =
decoder.read_image().expect("Decoding image failed")
{
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
}
}
}
};
}
test_predict_roundtrip!(test_u8_predict_roundtrip, U8, u8);
test_predict_roundtrip!(test_i8_predict_roundtrip, I8, i8);
test_predict_roundtrip!(test_u16_predict_roundtrip, U16, u16);
test_predict_roundtrip!(test_i16_predict_roundtrip, I16, i16);
test_predict_roundtrip!(test_u32_predict_roundtrip, U32, u32);
test_predict_roundtrip!(test_u64_predict_roundtrip, U64, u64);
#[test]
fn test_gray_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_gray_i8_predict_roundtrip() {
test_i8_predict_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_rgb_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
}
#[test]
fn test_cmyk_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
}
#[test]
fn test_gray_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_gray_i16_predict_roundtrip() {
test_i16_predict_roundtrip::<colortype::GrayI16>(
"minisblack-1c-i16b.tiff",
ColorType::Gray(16),
);
}
#[test]
fn test_rgb_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
}
#[test]
fn test_cmyk_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
}
#[test]
fn test_gray_u32_predict_roundtrip() {
test_u32_predict_roundtrip::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
}
#[test]
fn test_rgb_u32_predict_roundtrip() {
test_u32_predict_roundtrip::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
}
#[test]
fn test_gray_u64_predict_roundtrip() {
test_u64_predict_roundtrip::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
}
#[test]
fn test_rgb_u64_predict_roundtrip() {
test_u64_predict_roundtrip::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
}
#[test]
fn test_ycbcr_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}