Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46086e2bcf | ||
|
|
d7be2a7b67 | ||
|
|
29b71ff597 | ||
|
|
83e6d64751 | ||
|
|
dc2c67469b | ||
|
|
b5d8ee5f86 | ||
|
|
f44da4a00d | ||
|
|
ee0dbf373b | ||
|
|
aeab243a96 | ||
|
|
36f7b3b4aa | ||
|
|
3bfb43e83e | ||
|
|
d42e6be507 | ||
|
|
a19cf37587 | ||
|
|
ff52aea06e | ||
|
|
96d3a02efe | ||
|
|
e28ad564d9 | ||
|
|
de6d9cf131 | ||
|
|
6dc7a266d3 | ||
|
|
307cd8be97 | ||
|
|
0c54a18e21 | ||
|
|
90ae5b8e54 | ||
|
|
9b06e3726d | ||
|
|
895c70a1ad |
No files matched your search
+22
-18
@@ -9,9 +9,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust: ["1.61.0", stable, beta, nightly]
|
||||
rust: ["1.74.0", stable, beta, nightly]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
if: ${{ matrix.rust == '1.74.0' }}
|
||||
- name: Generate Cargo.lock with minimal-version dependencies
|
||||
if: ${{ matrix.rust == '1.74.0' }}
|
||||
run: cargo -Zminimal-versions generate-lockfile
|
||||
|
||||
- uses: dtolnay/rust-toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
@@ -19,15 +26,25 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Install MSRV Cargo.lock
|
||||
if: ${{ matrix.rust == '1.61.0' }}
|
||||
run: mv Cargo.lock.msrv Cargo.lock
|
||||
- name: build
|
||||
run: cargo build -v
|
||||
- name: test
|
||||
if: ${{ matrix.rust != '1.61.0' }}
|
||||
if: ${{ matrix.rust != '1.74.0' }}
|
||||
run: cargo test -v && cargo doc -v
|
||||
|
||||
features:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
feature: ["", deflate, jpeg, lzw, zstd, default, "default,zstd"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- name: Cargo check
|
||||
run: cargo check --features "${{ matrix.feature }}" --no-default-features
|
||||
|
||||
rustfmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -46,16 +63,3 @@ jobs:
|
||||
with:
|
||||
components: clippy
|
||||
- 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
@@ -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
|
||||
|
||||
New features:
|
||||
|
||||
-638
@@ -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
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "tiff"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
||||
rust-version = "1.61.0"
|
||||
rust-version = "1.74.0"
|
||||
|
||||
license = "MIT"
|
||||
description = "TIFF decoding and encoding library in pure Rust"
|
||||
@@ -17,13 +17,26 @@ categories = ["multimedia::images", "multimedia::encoding"]
|
||||
exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
||||
|
||||
[dependencies]
|
||||
weezl = "0.1.0"
|
||||
jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false }
|
||||
flate2 = "1.0.20"
|
||||
half = { version = "2.4.1" }
|
||||
quick-error = "2.0.1"
|
||||
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
weezl = { version = "0.1.0", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
zune-jpeg = { version = "0.4.17", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.1"
|
||||
|
||||
[features]
|
||||
default = ["deflate", "jpeg", "lzw"]
|
||||
|
||||
# Compression algorithms
|
||||
deflate = ["dep:flate2"]
|
||||
jpeg = ["dep:zune-jpeg"]
|
||||
lzw = ["dep:weezl"]
|
||||
zstd = ["dep:zstd"]
|
||||
|
||||
[[bench]]
|
||||
name = "lzw"
|
||||
harness = false
|
||||
@@ -24,6 +24,7 @@ This table lists photometric interpretations and sample formats which are suppor
|
||||
| `RGBPalette` | | |
|
||||
| `Mask` | | |
|
||||
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
|
||||
| `CMYKA` | CMYKA(8) | |
|
||||
| `YCbCr` | | |
|
||||
| `CIELab` | | |
|
||||
|
||||
@@ -35,6 +36,8 @@ This table lists photometric interpretations and sample formats which are suppor
|
||||
| LZW | ✓ | ✓ |
|
||||
| Deflate | ✓ | ✓ |
|
||||
| PackBits | ✓ | ✓ |
|
||||
| JPEG | ✓ | not yet |
|
||||
| ZSTD | ✓ | not yet |
|
||||
|
||||
|
||||
## Not yet supported
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
//! TODO: Would like to use std-lib here.
|
||||
use std::{mem, slice};
|
||||
|
||||
use half::f16;
|
||||
|
||||
macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => {
|
||||
pub(crate) fn $const(slice: &[$int]) -> &[u8] {
|
||||
assert!(mem::align_of::<$int>() <= mem::size_of::<$int>());
|
||||
@@ -31,4 +33,5 @@ integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(i64, i64_as_ne_bytes, i64_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(f16, f16_as_ne_bytes, f16_as_ne_mut_bytes);
|
||||
integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes);
|
||||
+71
-70
@@ -1,18 +1,17 @@
|
||||
//! Function for reading TIFF tags
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::io::{self, Read, Seek};
|
||||
use std::mem;
|
||||
use std::str;
|
||||
|
||||
use super::stream::{ByteOrder, EndianReader, SmartReader};
|
||||
use super::stream::{ByteOrder, EndianReader};
|
||||
use crate::tags::{Tag, Type};
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use self::Value::{
|
||||
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
|
||||
Short, Signed, SignedBig, Unsigned, UnsignedBig,
|
||||
Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig,
|
||||
};
|
||||
|
||||
#[allow(unused_qualifications)]
|
||||
@@ -21,6 +20,8 @@ use self::Value::{
|
||||
pub enum Value {
|
||||
Byte(u8),
|
||||
Short(u16),
|
||||
SignedByte(i8),
|
||||
SignedShort(i16),
|
||||
Signed(i32),
|
||||
SignedBig(i64),
|
||||
Unsigned(u32),
|
||||
@@ -41,91 +42,98 @@ impl Value {
|
||||
pub fn into_u8(self) -> TiffResult<u8> {
|
||||
match self {
|
||||
Byte(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
pub fn into_i8(self) -> TiffResult<i8> {
|
||||
match self {
|
||||
SignedByte(val) => Ok(val),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_u16(self) -> TiffResult<u16> {
|
||||
match self {
|
||||
Byte(val) => Ok(val.into()),
|
||||
Short(val) => Ok(val),
|
||||
Unsigned(val) => Ok(u16::try_from(val)?),
|
||||
UnsignedBig(val) => Ok(u16::try_from(val)?),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_i16(self) -> TiffResult<i16> {
|
||||
match self {
|
||||
SignedByte(val) => Ok(val.into()),
|
||||
SignedShort(val) => Ok(val),
|
||||
Signed(val) => Ok(i16::try_from(val)?),
|
||||
SignedBig(val) => Ok(i16::try_from(val)?),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_u32(self) -> TiffResult<u32> {
|
||||
match self {
|
||||
Byte(val) => Ok(val.into()),
|
||||
Short(val) => Ok(val.into()),
|
||||
Unsigned(val) => Ok(val),
|
||||
UnsignedBig(val) => Ok(u32::try_from(val)?),
|
||||
Ifd(val) => Ok(val),
|
||||
IfdBig(val) => Ok(u32::try_from(val)?),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_i32(self) -> TiffResult<i32> {
|
||||
match self {
|
||||
SignedByte(val) => Ok(val.into()),
|
||||
SignedShort(val) => Ok(val.into()),
|
||||
Signed(val) => Ok(val),
|
||||
SignedBig(val) => Ok(i32::try_from(val)?),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_u64(self) -> TiffResult<u64> {
|
||||
match self {
|
||||
Byte(val) => Ok(val.into()),
|
||||
Short(val) => Ok(val.into()),
|
||||
Unsigned(val) => Ok(val.into()),
|
||||
UnsignedBig(val) => Ok(val),
|
||||
Ifd(val) => Ok(val.into()),
|
||||
IfdBig(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_i64(self) -> TiffResult<i64> {
|
||||
match self {
|
||||
SignedByte(val) => Ok(val.into()),
|
||||
SignedShort(val) => Ok(val.into()),
|
||||
Signed(val) => Ok(val.into()),
|
||||
SignedBig(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_f32(self) -> TiffResult<f32> {
|
||||
match self {
|
||||
Float(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_f64(self) -> TiffResult<f64> {
|
||||
match self {
|
||||
Double(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> TiffResult<String> {
|
||||
match self {
|
||||
Ascii(val) => Ok(val),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +146,8 @@ impl Value {
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
Byte(val) => Ok(vec![val.into()]),
|
||||
Short(val) => Ok(vec![val.into()]),
|
||||
Unsigned(val) => Ok(vec![val]),
|
||||
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
|
||||
Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
@@ -147,9 +157,7 @@ impl Value {
|
||||
Ifd(val) => Ok(vec![val]),
|
||||
IfdBig(val) => Ok(vec![u32::try_from(val)?]),
|
||||
Ascii(val) => Ok(val.chars().map(u32::from).collect()),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,9 +172,7 @@ impl Value {
|
||||
}
|
||||
Byte(val) => Ok(vec![val]),
|
||||
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,10 +185,9 @@ impl Value {
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
Byte(val) => Ok(vec![val.into()]),
|
||||
Short(val) => Ok(vec![val]),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,15 +210,15 @@ impl Value {
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
SignedByte(val) => Ok(vec![val.into()]),
|
||||
SignedShort(val) => Ok(vec![val.into()]),
|
||||
Signed(val) => Ok(vec![val]),
|
||||
SignedBig(val) => Ok(vec![i32::try_from(val)?]),
|
||||
SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
SRationalBig(numerator, denominator) => {
|
||||
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
|
||||
}
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +232,7 @@ impl Value {
|
||||
Ok(new_vec)
|
||||
}
|
||||
Float(val) => Ok(vec![val]),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,9 +246,7 @@ impl Value {
|
||||
Ok(new_vec)
|
||||
}
|
||||
Double(val) => Ok(vec![val]),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,6 +259,8 @@ impl Value {
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
Byte(val) => Ok(vec![val.into()]),
|
||||
Short(val) => Ok(vec![val.into()]),
|
||||
Unsigned(val) => Ok(vec![val.into()]),
|
||||
UnsignedBig(val) => Ok(vec![val]),
|
||||
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
||||
@@ -265,9 +268,7 @@ impl Value {
|
||||
Ifd(val) => Ok(vec![val.into()]),
|
||||
IfdBig(val) => Ok(vec![val]),
|
||||
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::UnsignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,13 +291,13 @@ impl Value {
|
||||
}
|
||||
Ok(new_vec)
|
||||
}
|
||||
SignedByte(val) => Ok(vec![val.into()]),
|
||||
SignedShort(val) => Ok(vec![val.into()]),
|
||||
Signed(val) => Ok(vec![val.into()]),
|
||||
SignedBig(val) => Ok(vec![val]),
|
||||
SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
||||
SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
val => Err(TiffError::FormatError(
|
||||
TiffFormatError::SignedIntegerExpected(val),
|
||||
)),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,22 +334,22 @@ impl Entry {
|
||||
}
|
||||
|
||||
/// Returns a mem_reader for the offset/value field
|
||||
fn r(&self, byte_order: ByteOrder) -> SmartReader<io::Cursor<Vec<u8>>> {
|
||||
SmartReader::wrap(io::Cursor::new(self.offset.to_vec()), byte_order)
|
||||
fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
|
||||
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
||||
}
|
||||
|
||||
pub fn val<R: Read + Seek>(
|
||||
pub(crate) fn val<R: Read + Seek>(
|
||||
&self,
|
||||
limits: &super::Limits,
|
||||
bigtiff: bool,
|
||||
reader: &mut SmartReader<R>,
|
||||
reader: &mut EndianReader<R>,
|
||||
) -> TiffResult<Value> {
|
||||
// Case 1: there are no values so we can return immediately.
|
||||
if self.count == 0 {
|
||||
return Ok(List(Vec::new()));
|
||||
}
|
||||
|
||||
let bo = reader.byte_order();
|
||||
let bo = reader.byte_order;
|
||||
|
||||
let tag_size = match self.type_ {
|
||||
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.
|
||||
return Ok(match self.type_ {
|
||||
Type::BYTE => Unsigned(u32::from(self.offset[0])),
|
||||
Type::SBYTE => Signed(i32::from(self.offset[0] as i8)),
|
||||
Type::BYTE => Byte(self.offset[0]),
|
||||
Type::SBYTE => SignedByte(i8::from(self.offset[0] as i8)),
|
||||
Type::UNDEFINED => Byte(self.offset[0]),
|
||||
Type::SHORT => Unsigned(u32::from(self.r(bo).read_u16()?)),
|
||||
Type::SSHORT => Signed(i32::from(self.r(bo).read_i16()?)),
|
||||
Type::SHORT => Short(self.r(bo).read_u16()?),
|
||||
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
||||
Type::LONG => Unsigned(self.r(bo).read_u32()?),
|
||||
Type::SLONG => Signed(self.r(bo).read_i32()?),
|
||||
Type::FLOAT => Float(self.r(bo).read_f32()?),
|
||||
@@ -451,7 +452,7 @@ impl Entry {
|
||||
Type::SBYTE => return offset_to_sbytes(self.count as usize, self),
|
||||
Type::ASCII => {
|
||||
let mut buf = vec![0; self.count as usize];
|
||||
self.r(bo).read_exact(&mut buf)?;
|
||||
buf.copy_from_slice(&self.offset[..self.count as usize]);
|
||||
if buf.is_ascii() && buf.ends_with(&[0]) {
|
||||
let v = str::from_utf8(&buf)?;
|
||||
let v = v.trim_matches(char::from(0));
|
||||
@@ -480,7 +481,7 @@ impl Entry {
|
||||
let mut r = self.r(bo);
|
||||
let mut v = Vec::new();
|
||||
for _ in 0..self.count {
|
||||
v.push(Signed(i32::from(r.read_i16()?)));
|
||||
v.push(SignedShort(r.read_i16()?));
|
||||
}
|
||||
return Ok(List(v));
|
||||
}
|
||||
@@ -533,17 +534,17 @@ impl Entry {
|
||||
// at a different endianess of file/computer.
|
||||
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
let mut buf = [0; 1];
|
||||
reader.read_exact(&mut buf)?;
|
||||
reader.inner().read_exact(&mut buf)?;
|
||||
Ok(UnsignedBig(u64::from(buf[0])))
|
||||
}),
|
||||
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(SignedBig(i64::from(reader.read_i8()?)))
|
||||
}),
|
||||
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| {
|
||||
Ok(SignedBig(i64::from(reader.read_i16()?)))
|
||||
Ok(SignedShort(reader.read_i16()?))
|
||||
}),
|
||||
Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
Ok(Unsigned(reader.read_u32()?))
|
||||
@@ -582,7 +583,7 @@ impl Entry {
|
||||
Type::UNDEFINED => {
|
||||
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||
let mut buf = [0; 1];
|
||||
reader.read_exact(&mut buf)?;
|
||||
reader.inner().read_exact(&mut buf)?;
|
||||
Ok(Byte(buf[0]))
|
||||
})
|
||||
}
|
||||
@@ -599,7 +600,7 @@ impl Entry {
|
||||
}
|
||||
|
||||
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
|
||||
if let Some(first) = out.iter().position(|&b| b == 0) {
|
||||
out.truncate(first);
|
||||
@@ -616,12 +617,12 @@ impl Entry {
|
||||
bo: ByteOrder,
|
||||
bigtiff: bool,
|
||||
limits: &super::Limits,
|
||||
reader: &mut SmartReader<R>,
|
||||
reader: &mut EndianReader<R>,
|
||||
decode_fn: F,
|
||||
) -> TiffResult<Value>
|
||||
where
|
||||
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)?;
|
||||
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
|
||||
|
||||
+131
-89
@@ -1,13 +1,12 @@
|
||||
use super::ifd::{Directory, Value};
|
||||
use super::stream::{ByteOrder, DeflateReader, LZWReader, PackBitsReader};
|
||||
use super::stream::{ByteOrder, PackBitsReader};
|
||||
use super::tag_reader::TagReader;
|
||||
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
|
||||
use super::{stream::SmartReader, ChunkType};
|
||||
use super::{predict_f16, predict_f32, predict_f64, Limits};
|
||||
use super::{stream::EndianReader, ChunkType};
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
|
||||
};
|
||||
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{self, Cursor, Read, Seek};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -65,9 +64,8 @@ pub(crate) struct Image {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub bits_per_sample: u8,
|
||||
#[allow(unused)]
|
||||
pub samples: u16,
|
||||
pub sample_format: Vec<SampleFormat>,
|
||||
pub sample_format: SampleFormat,
|
||||
pub photometric_interpretation: PhotometricInterpretation,
|
||||
pub compression_method: CompressionMethod,
|
||||
pub predictor: Predictor,
|
||||
@@ -82,7 +80,7 @@ pub(crate) struct Image {
|
||||
|
||||
impl Image {
|
||||
pub fn from_reader<R: Read + Seek>(
|
||||
reader: &mut SmartReader<R>,
|
||||
reader: &mut EndianReader<R>,
|
||||
ifd: Directory,
|
||||
limits: &Limits,
|
||||
bigtiff: bool,
|
||||
@@ -155,9 +153,9 @@ impl Image {
|
||||
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
|
||||
@@ -172,8 +170,9 @@ impl Image {
|
||||
));
|
||||
}
|
||||
|
||||
// This library (and libtiff) do not support mixed sample formats.
|
||||
if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) {
|
||||
// This library (and libtiff) do not support mixed sample formats and zero bits per sample
|
||||
// doesn't make sense.
|
||||
if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) || bits_per_sample[0] == 0 {
|
||||
return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into());
|
||||
}
|
||||
|
||||
@@ -331,6 +330,7 @@ impl Image {
|
||||
},
|
||||
PhotometricInterpretation::CMYK => match self.samples {
|
||||
4 => Ok(ColorType::CMYK(self.bits_per_sample)),
|
||||
5 => Ok(ColorType::CMYKA(self.bits_per_sample)),
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
@@ -347,14 +347,19 @@ impl Image {
|
||||
),
|
||||
)),
|
||||
},
|
||||
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
|
||||
if self.samples == 1 =>
|
||||
{
|
||||
Ok(ColorType::Gray(self.bits_per_sample))
|
||||
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
|
||||
match self.samples {
|
||||
1 => Ok(ColorType::Gray(self.bits_per_sample)),
|
||||
_ => Ok(ColorType::Multiband {
|
||||
bit_depth: self.bits_per_sample,
|
||||
num_samples: self.samples,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is bad we should not fail at this point
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
PhotometricInterpretation::RGBPalette
|
||||
| PhotometricInterpretation::TransparencyMask
|
||||
| PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
vec![self.bits_per_sample; self.samples as usize],
|
||||
@@ -365,21 +370,29 @@ impl Image {
|
||||
|
||||
fn create_reader<'r, R: 'r + Read>(
|
||||
reader: R,
|
||||
photometric_interpretation: PhotometricInterpretation,
|
||||
#[allow(unused_variables)] photometric_interpretation: PhotometricInterpretation,
|
||||
compression_method: CompressionMethod,
|
||||
compressed_length: u64,
|
||||
jpeg_tables: Option<&[u8]>,
|
||||
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
|
||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||
Ok(match compression_method {
|
||||
CompressionMethod::None => Box::new(reader),
|
||||
CompressionMethod::LZW => {
|
||||
Box::new(LZWReader::new(reader, usize::try_from(compressed_length)?))
|
||||
}
|
||||
#[cfg(feature = "lzw")]
|
||||
CompressionMethod::LZW => Box::new(super::stream::LZWReader::new(
|
||||
reader,
|
||||
usize::try_from(compressed_length)?,
|
||||
)),
|
||||
#[cfg(feature = "zstd")]
|
||||
CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?),
|
||||
CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)),
|
||||
#[cfg(feature = "deflate")]
|
||||
CompressionMethod::Deflate | CompressionMethod::OldDeflate => {
|
||||
Box::new(DeflateReader::new(reader))
|
||||
Box::new(super::stream::DeflateReader::new(reader))
|
||||
}
|
||||
#[cfg(feature = "jpeg")]
|
||||
CompressionMethod::ModernJPEG => {
|
||||
use zune_jpeg::zune_core;
|
||||
|
||||
if jpeg_tables.is_some() && compressed_length < 2 {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
||||
@@ -396,7 +409,7 @@ impl Image {
|
||||
// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
|
||||
// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
|
||||
// this has to be removed as well (last two bytes of `jpeg_tables`).
|
||||
let jpeg_reader = match jpeg_tables {
|
||||
let mut jpeg_reader = match jpeg_tables {
|
||||
Some(jpeg_tables) => {
|
||||
let mut reader = reader.take(compressed_length);
|
||||
reader.read_exact(&mut [0; 2])?;
|
||||
@@ -409,28 +422,28 @@ impl Image {
|
||||
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 {
|
||||
PhotometricInterpretation::RGB => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::RGB)
|
||||
}
|
||||
PhotometricInterpretation::WhiteIsZero => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||
}
|
||||
PhotometricInterpretation::BlackIsZero => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||
}
|
||||
PhotometricInterpretation::TransparencyMask => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||
options =
|
||||
options.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::RGB);
|
||||
}
|
||||
PhotometricInterpretation::CMYK => {
|
||||
decoder.set_color_transform(jpeg::ColorTransform::CMYK)
|
||||
options = options
|
||||
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::CMYK);
|
||||
}
|
||||
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(
|
||||
TiffUnsupportedError::UnsupportedInterpretation(
|
||||
photometric_interpretation,
|
||||
@@ -438,6 +451,7 @@ impl Image {
|
||||
));
|
||||
}
|
||||
}
|
||||
decoder.set_options(options);
|
||||
|
||||
let data = decoder.decode()?;
|
||||
|
||||
@@ -542,27 +556,35 @@ impl Image {
|
||||
pub(crate) fn expand_chunk(
|
||||
&self,
|
||||
reader: impl Read,
|
||||
mut buffer: DecodingBuffer,
|
||||
output_width: usize,
|
||||
buf: &mut [u8],
|
||||
output_row_stride: usize,
|
||||
byte_order: ByteOrder,
|
||||
chunk_index: u32,
|
||||
limits: &Limits,
|
||||
) -> TiffResult<()> {
|
||||
// Validate that the provided buffer is of the expected type.
|
||||
// Validate that the color type is supported.
|
||||
let color_type = self.colortype()?;
|
||||
match (color_type, &buffer) {
|
||||
(ColorType::RGB(n), _)
|
||||
| (ColorType::RGBA(n), _)
|
||||
| (ColorType::CMYK(n), _)
|
||||
| (ColorType::YCbCr(n), _)
|
||||
| (ColorType::Gray(n), _)
|
||||
if usize::from(n) == buffer.byte_len() * 8 => {}
|
||||
(ColorType::Gray(n), DecodingBuffer::U8(_)) if n < 8 => match self.predictor {
|
||||
match color_type {
|
||||
ColorType::RGB(n)
|
||||
| ColorType::RGBA(n)
|
||||
| ColorType::CMYK(n)
|
||||
| ColorType::CMYKA(n)
|
||||
| ColorType::YCbCr(n)
|
||||
| ColorType::Gray(n)
|
||||
| ColorType::Multiband {
|
||||
bit_depth: n,
|
||||
num_samples: _,
|
||||
} if n == 8 || n == 16 || n == 32 || n == 64 => {}
|
||||
ColorType::Gray(n)
|
||||
| ColorType::Multiband {
|
||||
bit_depth: n,
|
||||
num_samples: _,
|
||||
} if n < 8 => match self.predictor {
|
||||
Predictor::None => {}
|
||||
Predictor::Horizontal => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::HorizontalPredictor(color_type),
|
||||
))
|
||||
));
|
||||
}
|
||||
Predictor::FloatingPoint => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
@@ -570,23 +592,25 @@ impl Image {
|
||||
));
|
||||
}
|
||||
},
|
||||
(type_, _) => {
|
||||
type_ => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedColorType(type_),
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that the predictor is supported for the sample type.
|
||||
match (self.predictor, &buffer) {
|
||||
(Predictor::Horizontal, DecodingBuffer::F32(_))
|
||||
| (Predictor::Horizontal, DecodingBuffer::F64(_)) => {
|
||||
match (self.predictor, self.sample_format) {
|
||||
(
|
||||
Predictor::Horizontal,
|
||||
SampleFormat::Int | SampleFormat::Uint | SampleFormat::IEEEFP,
|
||||
) => {}
|
||||
(Predictor::Horizontal, _) => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::HorizontalPredictor(color_type),
|
||||
));
|
||||
}
|
||||
(Predictor::FloatingPoint, DecodingBuffer::F32(_))
|
||||
| (Predictor::FloatingPoint, DecodingBuffer::F64(_)) => {}
|
||||
(Predictor::FloatingPoint, SampleFormat::IEEEFP) => {}
|
||||
(Predictor::FloatingPoint, _) => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::FloatingPointPredictor(color_type),
|
||||
@@ -605,7 +629,6 @@ impl Image {
|
||||
return Err(TiffError::LimitsExceeded);
|
||||
}
|
||||
|
||||
let byte_len = buffer.byte_len();
|
||||
let compression_method = self.compression_method;
|
||||
let photometric_interpretation = self.photometric_interpretation;
|
||||
let predictor = self.predictor;
|
||||
@@ -614,7 +637,19 @@ impl Image {
|
||||
let chunk_dims = self.chunk_dimensions()?;
|
||||
let data_dims = self.chunk_data_dimensions(chunk_index)?;
|
||||
|
||||
let padding_right = chunk_dims.0 - data_dims.0;
|
||||
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
|
||||
|
||||
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let data_row_bytes: usize = ((data_row_bits + 7) / 8).try_into()?;
|
||||
|
||||
// TODO: Should these return errors instead?
|
||||
assert!(output_row_stride >= data_row_bytes);
|
||||
assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
|
||||
|
||||
let mut reader = Self::create_reader(
|
||||
reader,
|
||||
@@ -624,57 +659,64 @@ impl Image {
|
||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||
)?;
|
||||
|
||||
if output_width == data_dims.0 as usize && padding_right == 0 {
|
||||
let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples;
|
||||
let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len];
|
||||
if output_row_stride == chunk_row_bytes as usize {
|
||||
let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
|
||||
reader.read_exact(tile)?;
|
||||
|
||||
for row in 0..data_dims.1 as usize {
|
||||
let row_start = row * output_width * samples;
|
||||
let row_end = (row + 1) * output_width * samples;
|
||||
let row = buffer.subrange(row_start..row_end);
|
||||
super::fix_endianness_and_predict(row, samples, byte_order, predictor);
|
||||
for row in tile.chunks_mut(chunk_row_bytes as usize) {
|
||||
super::fix_endianness_and_predict(
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
samples,
|
||||
byte_order,
|
||||
predictor,
|
||||
);
|
||||
}
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(&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
|
||||
// this case is handled specially when needed.
|
||||
let mut encoded = vec![0u8; chunk_dims.0 as usize * samples * byte_len];
|
||||
|
||||
for row in 0..data_dims.1 as usize {
|
||||
let row_start = row * output_width * samples;
|
||||
let row_end = row_start + data_dims.0 as usize * samples;
|
||||
|
||||
let mut encoded = vec![0u8; chunk_row_bytes];
|
||||
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
|
||||
reader.read_exact(&mut encoded)?;
|
||||
match buffer.subrange(row_start..row_end) {
|
||||
DecodingBuffer::F32(buf) => fp_predict_f32(&mut encoded, buf, samples),
|
||||
DecodingBuffer::F64(buf) => fp_predict_f64(&mut encoded, buf, samples),
|
||||
|
||||
let row = &mut row[..data_row_bytes];
|
||||
match color_type.bit_depth() {
|
||||
16 => predict_f16(&mut encoded, row, samples),
|
||||
32 => predict_f32(&mut encoded, row, samples),
|
||||
64 => predict_f64(&mut encoded, row, samples),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type);
|
||||
super::invert_colors(row, color_type, self.sample_format);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
|
||||
let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)];
|
||||
for (i, row) in buf
|
||||
.chunks_mut(output_row_stride)
|
||||
.take(data_dims.1 as usize)
|
||||
.enumerate()
|
||||
{
|
||||
let row = &mut row[..data_row_bytes];
|
||||
reader.read_exact(row)?;
|
||||
|
||||
// Skip horizontal padding
|
||||
if padding_right > 0 {
|
||||
let len = u64::try_from(padding_right as usize * samples * byte_len)?;
|
||||
if chunk_row_bytes > data_row_bytes {
|
||||
let len = u64::try_from(chunk_row_bytes - data_row_bytes)?;
|
||||
io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
|
||||
}
|
||||
|
||||
let mut row = buffer.subrange(row_start..row_end);
|
||||
super::fix_endianness_and_predict(row.copy(), samples, byte_order, predictor);
|
||||
super::fix_endianness_and_predict(
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
samples,
|
||||
byte_order,
|
||||
predictor,
|
||||
);
|
||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||
super::invert_colors(&mut row, color_type);
|
||||
super::invert_colors(row, color_type, self.sample_format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+206
-237
@@ -1,20 +1,18 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{self, Read, Seek};
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::{
|
||||
bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||
};
|
||||
|
||||
use self::ifd::Directory;
|
||||
use self::image::Image;
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat,
|
||||
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;
|
||||
mod image;
|
||||
@@ -32,6 +30,8 @@ pub enum DecodingResult {
|
||||
U32(Vec<u32>),
|
||||
/// A vector of 64 bit unsigned ints
|
||||
U64(Vec<u64>),
|
||||
/// A vector of 16 bit IEEE floats (held in u16)
|
||||
F16(Vec<f16>),
|
||||
/// A vector of 32 bit IEEE floats
|
||||
F32(Vec<f32>),
|
||||
/// 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> {
|
||||
if size > limits.decoding_buffer_size / std::mem::size_of::<i8>() {
|
||||
Err(TiffError::LimitsExceeded)
|
||||
@@ -133,6 +141,7 @@ impl DecodingResult {
|
||||
DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]),
|
||||
DecodingResult::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[start..]),
|
||||
DecodingResult::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[start..]),
|
||||
DecodingResult::F16(ref mut buf) => DecodingBuffer::F16(&mut buf[start..]),
|
||||
DecodingResult::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[start..]),
|
||||
DecodingResult::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[start..]),
|
||||
DecodingResult::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[start..]),
|
||||
@@ -153,6 +162,8 @@ pub enum DecodingBuffer<'a> {
|
||||
U32(&'a mut [u32]),
|
||||
/// A slice of 64 bit unsigned ints
|
||||
U64(&'a mut [u64]),
|
||||
/// A slice of 16 bit IEEE floats
|
||||
F16(&'a mut [f16]),
|
||||
/// A slice of 32 bit IEEE floats
|
||||
F32(&'a mut [f32]),
|
||||
/// A slice of 64 bit IEEE floats
|
||||
@@ -168,57 +179,6 @@ pub enum 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] {
|
||||
match self {
|
||||
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::U64(buf) => bytecast::u64_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::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf),
|
||||
}
|
||||
@@ -299,7 +260,7 @@ pub struct Decoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
reader: SmartReader<R>,
|
||||
reader: EndianReader<R>,
|
||||
bigtiff: bool,
|
||||
limits: Limits,
|
||||
next_ifd: Option<u64>,
|
||||
@@ -308,82 +269,75 @@ where
|
||||
image: Image,
|
||||
}
|
||||
|
||||
trait Wrapping {
|
||||
fn wrapping_add(&self, other: Self) -> Self;
|
||||
fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
|
||||
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 wrapping_add(&self, other: Self) -> Self {
|
||||
u16::wrapping_add(*self, other)
|
||||
}
|
||||
fn predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) {
|
||||
for i in samples..input.len() {
|
||||
input[i] = input[i].wrapping_add(input[i - samples]);
|
||||
}
|
||||
|
||||
impl Wrapping for u32 {
|
||||
fn wrapping_add(&self, other: Self) -> Self {
|
||||
u32::wrapping_add(*self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Wrapping for u64 {
|
||||
fn wrapping_add(&self, other: Self) -> Self {
|
||||
u64::wrapping_add(*self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Wrapping for i8 {
|
||||
fn wrapping_add(&self, other: Self) -> Self {
|
||||
i8::wrapping_add(*self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Wrapping for i16 {
|
||||
fn wrapping_add(&self, other: Self) -> Self {
|
||||
i16::wrapping_add(*self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Wrapping for i32 {
|
||||
fn wrapping_add(&self, other: Self) -> Self {
|
||||
i32::wrapping_add(*self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Wrapping for i64 {
|
||||
fn wrapping_add(&self, other: Self) -> Self {
|
||||
i64::wrapping_add(*self, other)
|
||||
}
|
||||
}
|
||||
|
||||
fn rev_hpredict_nsamp<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([
|
||||
for (i, chunk) in output.chunks_mut(4).enumerate() {
|
||||
chunk.copy_from_slice(&u32::to_ne_bytes(u32::from_be_bytes([
|
||||
input[i],
|
||||
input[input.len() / 4 + i],
|
||||
input[input.len() / 4 * 2 + i],
|
||||
input[input.len() / 4 * 3 + i],
|
||||
]));
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fp_predict_f64(input: &mut [u8], output: &mut [f64], samples: usize) {
|
||||
rev_hpredict_nsamp(input, samples);
|
||||
for i in 0..output.len() {
|
||||
// TODO: use f64::from_be_bytes() when we can (version 1.40)
|
||||
output[i] = f64::from_bits(u64::from_be_bytes([
|
||||
fn predict_f16(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(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[input.len() / 8 + 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 * 6 + i],
|
||||
input[input.len() / 8 * 7 + i],
|
||||
]));
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_endianness_and_predict(
|
||||
mut image: DecodingBuffer,
|
||||
buf: &mut [u8],
|
||||
bit_depth: u8,
|
||||
samples: usize,
|
||||
byte_order: ByteOrder,
|
||||
predictor: Predictor,
|
||||
) {
|
||||
match predictor {
|
||||
Predictor::None => {
|
||||
fix_endianness(&mut image, byte_order);
|
||||
fix_endianness(buf, byte_order, bit_depth);
|
||||
}
|
||||
Predictor::Horizontal => {
|
||||
fix_endianness(&mut image, byte_order);
|
||||
match image {
|
||||
DecodingBuffer::U8(buf) => rev_hpredict_nsamp(buf, samples),
|
||||
DecodingBuffer::U16(buf) => rev_hpredict_nsamp(buf, samples),
|
||||
DecodingBuffer::U32(buf) => rev_hpredict_nsamp(buf, samples),
|
||||
DecodingBuffer::U64(buf) => rev_hpredict_nsamp(buf, samples),
|
||||
DecodingBuffer::I8(buf) => rev_hpredict_nsamp(buf, samples),
|
||||
DecodingBuffer::I16(buf) => rev_hpredict_nsamp(buf, samples),
|
||||
DecodingBuffer::I32(buf) => rev_hpredict_nsamp(buf, samples),
|
||||
DecodingBuffer::I64(buf) => rev_hpredict_nsamp(buf, samples),
|
||||
DecodingBuffer::F32(_) | DecodingBuffer::F64(_) => {
|
||||
unreachable!("Caller should have validated arguments. Please file a bug.")
|
||||
}
|
||||
}
|
||||
fix_endianness(buf, byte_order, bit_depth);
|
||||
rev_hpredict_nsamp(buf, bit_depth, samples);
|
||||
}
|
||||
Predictor::FloatingPoint => {
|
||||
let mut buffer_copy = image.as_bytes_mut().to_vec();
|
||||
match image {
|
||||
DecodingBuffer::F32(buf) => fp_predict_f32(&mut buffer_copy, buf, samples),
|
||||
DecodingBuffer::F64(buf) => fp_predict_f64(&mut buffer_copy, buf, samples),
|
||||
let mut buffer_copy = buf.to_vec();
|
||||
match bit_depth {
|
||||
16 => predict_f16(&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."),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn invert_colors_unsigned<T>(buffer: &mut [T], max: T)
|
||||
where
|
||||
T: std::ops::Sub<T> + std::ops::Sub<Output = T> + Copy,
|
||||
{
|
||||
for datum in buffer.iter_mut() {
|
||||
*datum = max - *datum
|
||||
fn invert_colors(buf: &mut [u8], color_type: ColorType, sample_format: SampleFormat) {
|
||||
match (color_type, sample_format) {
|
||||
(ColorType::Gray(8), SampleFormat::Uint) => {
|
||||
for x in buf {
|
||||
*x = 0xff - *x;
|
||||
}
|
||||
}
|
||||
|
||||
fn invert_colors_fp<T>(buffer: &mut [T], max: T)
|
||||
where
|
||||
T: std::ops::Sub<T> + std::ops::Sub<Output = T> + Copy,
|
||||
{
|
||||
for datum in buffer.iter_mut() {
|
||||
// FIXME: assumes [0, 1) range for floats
|
||||
*datum = max - *datum
|
||||
(ColorType::Gray(16), SampleFormat::Uint) => {
|
||||
for x in buf.chunks_mut(2) {
|
||||
let v = u16::from_ne_bytes(x.try_into().unwrap());
|
||||
x.copy_from_slice(&(0xffff - v).to_ne_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
fn invert_colors(buf: &mut DecodingBuffer, color_type: ColorType) {
|
||||
match (color_type, buf) {
|
||||
(ColorType::Gray(64), DecodingBuffer::U64(ref mut buffer)) => {
|
||||
invert_colors_unsigned(buffer, 0xffff_ffff_ffff_ffff);
|
||||
(ColorType::Gray(32), SampleFormat::Uint) => {
|
||||
for x in buf.chunks_mut(4) {
|
||||
let v = u32::from_ne_bytes(x.try_into().unwrap());
|
||||
x.copy_from_slice(&(0xffff_ffff - v).to_ne_bytes());
|
||||
}
|
||||
(ColorType::Gray(32), DecodingBuffer::U32(ref mut buffer)) => {
|
||||
invert_colors_unsigned(buffer, 0xffff_ffff);
|
||||
}
|
||||
(ColorType::Gray(16), DecodingBuffer::U16(ref mut buffer)) => {
|
||||
invert_colors_unsigned(buffer, 0xffff);
|
||||
(ColorType::Gray(64), SampleFormat::Uint) => {
|
||||
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)) => {
|
||||
invert_colors_fp(buffer, 1.0);
|
||||
(ColorType::Gray(32), SampleFormat::IEEEFP) => {
|
||||
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.
|
||||
fn fix_endianness(buf: &mut DecodingBuffer, byte_order: ByteOrder) {
|
||||
fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
|
||||
match byte_order {
|
||||
ByteOrder::LittleEndian => match buf {
|
||||
DecodingBuffer::U8(_) | DecodingBuffer::I8(_) => {}
|
||||
DecodingBuffer::U16(b) => b.iter_mut().for_each(|v| *v = u16::from_le(*v)),
|
||||
DecodingBuffer::I16(b) => b.iter_mut().for_each(|v| *v = i16::from_le(*v)),
|
||||
DecodingBuffer::U32(b) => b.iter_mut().for_each(|v| *v = u32::from_le(*v)),
|
||||
DecodingBuffer::I32(b) => b.iter_mut().for_each(|v| *v = i32::from_le(*v)),
|
||||
DecodingBuffer::U64(b) => b.iter_mut().for_each(|v| *v = u64::from_le(*v)),
|
||||
DecodingBuffer::I64(b) => b.iter_mut().for_each(|v| *v = i64::from_le(*v)),
|
||||
DecodingBuffer::F32(b) => b
|
||||
.iter_mut()
|
||||
.for_each(|v| *v = f32::from_bits(u32::from_le(v.to_bits()))),
|
||||
DecodingBuffer::F64(b) => b
|
||||
.iter_mut()
|
||||
.for_each(|v| *v = f64::from_bits(u64::from_le(v.to_bits()))),
|
||||
ByteOrder::LittleEndian => match bit_depth {
|
||||
0..=8 => {}
|
||||
9..=16 => buf.chunks_exact_mut(2).for_each(|v| {
|
||||
v.copy_from_slice(&u16::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
17..=32 => buf.chunks_exact_mut(4).for_each(|v| {
|
||||
v.copy_from_slice(&u32::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
_ => buf.chunks_exact_mut(8).for_each(|v| {
|
||||
v.copy_from_slice(&u64::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
},
|
||||
ByteOrder::BigEndian => match buf {
|
||||
DecodingBuffer::U8(_) | DecodingBuffer::I8(_) => {}
|
||||
DecodingBuffer::U16(b) => b.iter_mut().for_each(|v| *v = u16::from_be(*v)),
|
||||
DecodingBuffer::I16(b) => b.iter_mut().for_each(|v| *v = i16::from_be(*v)),
|
||||
DecodingBuffer::U32(b) => b.iter_mut().for_each(|v| *v = u32::from_be(*v)),
|
||||
DecodingBuffer::I32(b) => b.iter_mut().for_each(|v| *v = i32::from_be(*v)),
|
||||
DecodingBuffer::U64(b) => b.iter_mut().for_each(|v| *v = u64::from_be(*v)),
|
||||
DecodingBuffer::I64(b) => b.iter_mut().for_each(|v| *v = i64::from_be(*v)),
|
||||
DecodingBuffer::F32(b) => b
|
||||
.iter_mut()
|
||||
.for_each(|v| *v = f32::from_bits(u32::from_be(v.to_bits()))),
|
||||
DecodingBuffer::F64(b) => b
|
||||
.iter_mut()
|
||||
.for_each(|v| *v = f64::from_bits(u64::from_be(v.to_bits()))),
|
||||
ByteOrder::BigEndian => match bit_depth {
|
||||
0..=8 => {}
|
||||
9..=16 => buf.chunks_exact_mut(2).for_each(|v| {
|
||||
v.copy_from_slice(&u16::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
17..=32 => buf.chunks_exact_mut(4).for_each(|v| {
|
||||
v.copy_from_slice(&u32::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
_ => buf.chunks_exact_mut(8).for_each(|v| {
|
||||
v.copy_from_slice(&u64::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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()? {
|
||||
42 => false,
|
||||
@@ -574,7 +510,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
height: 0,
|
||||
bits_per_sample: 1,
|
||||
samples: 1,
|
||||
sample_format: vec![SampleFormat::Uint],
|
||||
sample_format: SampleFormat::Uint,
|
||||
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
|
||||
compression_method: CompressionMethod::None,
|
||||
jpeg_tables: None,
|
||||
@@ -704,7 +640,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
#[inline]
|
||||
pub fn read_byte(&mut self) -> Result<u8, io::Error> {
|
||||
let mut buf = [0; 1];
|
||||
self.reader.read_exact(&mut buf)?;
|
||||
self.reader.inner().read_exact(&mut buf)?;
|
||||
Ok(buf[0])
|
||||
}
|
||||
|
||||
@@ -758,7 +694,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
#[inline]
|
||||
pub fn read_string(&mut self, length: usize) -> TiffResult<String> {
|
||||
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
|
||||
if let Some(first) = out.iter().position(|&b| b == 0) {
|
||||
out.truncate(first);
|
||||
@@ -775,7 +711,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
));
|
||||
}
|
||||
let mut val = [0; 4];
|
||||
self.reader.read_exact(&mut val)?;
|
||||
self.reader.inner().read_exact(&mut val)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -783,7 +719,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
#[inline]
|
||||
pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> {
|
||||
let mut val = [0; 8];
|
||||
self.reader.read_exact(&mut val)?;
|
||||
self.reader.inner().read_exact(&mut val)?;
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -795,7 +731,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
|
||||
#[inline]
|
||||
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.
|
||||
@@ -806,7 +742,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
// Count 4 bytes
|
||||
// Value 4 bytes either a pointer the value itself
|
||||
fn read_entry(
|
||||
reader: &mut SmartReader<R>,
|
||||
reader: &mut EndianReader<R>,
|
||||
bigtiff: bool,
|
||||
) -> TiffResult<Option<(Tag, ifd::Entry)>> {
|
||||
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 count = reader.read_u64()?;
|
||||
reader.read_exact(&mut offset)?;
|
||||
reader.inner().read_exact(&mut offset)?;
|
||||
ifd::Entry::new_u64(type_, count, offset)
|
||||
} else {
|
||||
let mut offset = [0; 4];
|
||||
|
||||
let count = reader.read_u32()?;
|
||||
reader.read_exact(&mut offset)?;
|
||||
reader.inner().read_exact(&mut offset)?;
|
||||
ifd::Entry::new(type_, count, offset)
|
||||
};
|
||||
Ok(Some((tag, entry)))
|
||||
@@ -837,7 +773,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
|
||||
/// Reads the IFD starting at the indicated location.
|
||||
fn read_ifd(
|
||||
reader: &mut SmartReader<R>,
|
||||
reader: &mut EndianReader<R>,
|
||||
bigtiff: bool,
|
||||
ifd_location: u64,
|
||||
) -> TiffResult<(Directory, Option<u64>)> {
|
||||
@@ -987,6 +923,15 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
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<()> {
|
||||
if expected != self.image().chunk_type {
|
||||
return Err(TiffError::UsageError(UsageError::InvalidChunkType(
|
||||
@@ -1039,14 +984,19 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
output_width: usize,
|
||||
) -> TiffResult<()> {
|
||||
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 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(
|
||||
&mut self.reader,
|
||||
buffer.copy(),
|
||||
output_width,
|
||||
self.reader.inner(),
|
||||
buffer.as_bytes_mut(),
|
||||
output_row_stride.try_into()?,
|
||||
byte_order,
|
||||
chunk_index,
|
||||
&self.limits,
|
||||
@@ -1056,21 +1006,23 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
}
|
||||
|
||||
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
|
||||
let buffer_size = match width
|
||||
.checked_mul(height)
|
||||
.and_then(|x| x.checked_mul(self.image().samples_per_pixel()))
|
||||
{
|
||||
Some(s) => s,
|
||||
None => return Err(TiffError::LimitsExceeded),
|
||||
let bits_per_sample = self.image().bits_per_sample;
|
||||
|
||||
let row_samples = if bits_per_sample >= 8 {
|
||||
width
|
||||
} else {
|
||||
((((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;
|
||||
match self
|
||||
.image()
|
||||
.sample_format
|
||||
.first()
|
||||
.unwrap_or(&SampleFormat::Uint)
|
||||
{
|
||||
match self.image().sample_format {
|
||||
SampleFormat::Uint => match max_sample_bits {
|
||||
n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits),
|
||||
n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits),
|
||||
@@ -1081,6 +1033,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
)),
|
||||
},
|
||||
SampleFormat::IEEEFP => match max_sample_bits {
|
||||
16 => DecodingResult::new_f16(buffer_size, &self.limits),
|
||||
32 => DecodingResult::new_f32(buffer_size, &self.limits),
|
||||
64 => DecodingResult::new_f64(buffer_size, &self.limits),
|
||||
n => Err(TiffError::UnsupportedError(
|
||||
@@ -1096,7 +1049,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
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 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();
|
||||
// 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
|
||||
// * collect bands to a RGB encoding result in case of RGB bands
|
||||
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 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;
|
||||
self.image.expand_chunk(
|
||||
&mut self.reader,
|
||||
result.as_buffer(buffer_offset).copy(),
|
||||
width as usize,
|
||||
self.reader.inner(),
|
||||
&mut result.as_buffer(0).as_bytes_mut()[buffer_offset..],
|
||||
output_row_stride,
|
||||
byte_order,
|
||||
chunk as u32,
|
||||
&self.limits,
|
||||
|
||||
+50
-87
@@ -1,6 +1,5 @@
|
||||
//! All IO functionality needed for TIFF decoding
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
||||
|
||||
/// Byte order of the TIFF file.
|
||||
@@ -13,16 +12,32 @@ pub enum ByteOrder {
|
||||
}
|
||||
|
||||
/// Reader that is aware of the byte order.
|
||||
pub trait EndianReader: Read {
|
||||
/// Byte order that should be adhered to
|
||||
fn byte_order(&self) -> ByteOrder;
|
||||
#[derive(Debug)]
|
||||
pub struct EndianReader<R> {
|
||||
reader: R,
|
||||
pub(crate) byte_order: ByteOrder,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> EndianReader<R> {
|
||||
pub fn new(reader: R, byte_order: ByteOrder) -> Self {
|
||||
Self { reader, byte_order }
|
||||
}
|
||||
|
||||
pub fn inner(&mut self) -> &mut R {
|
||||
&mut self.reader
|
||||
}
|
||||
|
||||
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
|
||||
self.reader.seek(io::SeekFrom::Start(offset))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads an u16
|
||||
#[inline(always)]
|
||||
fn read_u16(&mut self) -> Result<u16, io::Error> {
|
||||
pub fn read_u16(&mut self) -> Result<u16, io::Error> {
|
||||
let mut n = [0u8; 2];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => u16::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => u16::from_be_bytes(n),
|
||||
})
|
||||
@@ -30,10 +45,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an i8
|
||||
#[inline(always)]
|
||||
fn read_i8(&mut self) -> Result<i8, io::Error> {
|
||||
pub fn read_i8(&mut self) -> Result<i8, io::Error> {
|
||||
let mut n = [0u8; 1];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => i8::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i8::from_be_bytes(n),
|
||||
})
|
||||
@@ -41,10 +56,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an i16
|
||||
#[inline(always)]
|
||||
fn read_i16(&mut self) -> Result<i16, io::Error> {
|
||||
pub fn read_i16(&mut self) -> Result<i16, io::Error> {
|
||||
let mut n = [0u8; 2];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => i16::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i16::from_be_bytes(n),
|
||||
})
|
||||
@@ -52,10 +67,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an u32
|
||||
#[inline(always)]
|
||||
fn read_u32(&mut self) -> Result<u32, io::Error> {
|
||||
pub fn read_u32(&mut self) -> Result<u32, io::Error> {
|
||||
let mut n = [0u8; 4];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => u32::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => u32::from_be_bytes(n),
|
||||
})
|
||||
@@ -63,10 +78,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an i32
|
||||
#[inline(always)]
|
||||
fn read_i32(&mut self) -> Result<i32, io::Error> {
|
||||
pub fn read_i32(&mut self) -> Result<i32, io::Error> {
|
||||
let mut n = [0u8; 4];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => i32::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i32::from_be_bytes(n),
|
||||
})
|
||||
@@ -74,10 +89,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an u64
|
||||
#[inline(always)]
|
||||
fn read_u64(&mut self) -> Result<u64, io::Error> {
|
||||
pub fn read_u64(&mut self) -> Result<u64, io::Error> {
|
||||
let mut n = [0u8; 8];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => u64::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => u64::from_be_bytes(n),
|
||||
})
|
||||
@@ -85,10 +100,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an i64
|
||||
#[inline(always)]
|
||||
fn read_i64(&mut self) -> Result<i64, io::Error> {
|
||||
pub fn read_i64(&mut self) -> Result<i64, io::Error> {
|
||||
let mut n = [0u8; 8];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(match self.byte_order {
|
||||
ByteOrder::LittleEndian => i64::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => i64::from_be_bytes(n),
|
||||
})
|
||||
@@ -96,10 +111,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an f32
|
||||
#[inline(always)]
|
||||
fn read_f32(&mut self) -> Result<f32, io::Error> {
|
||||
pub fn read_f32(&mut self) -> Result<f32, io::Error> {
|
||||
let mut n = [0u8; 4];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(f32::from_bits(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(f32::from_bits(match self.byte_order {
|
||||
ByteOrder::LittleEndian => u32::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => u32::from_be_bytes(n),
|
||||
}))
|
||||
@@ -107,10 +122,10 @@ pub trait EndianReader: Read {
|
||||
|
||||
/// Reads an f64
|
||||
#[inline(always)]
|
||||
fn read_f64(&mut self) -> Result<f64, io::Error> {
|
||||
pub fn read_f64(&mut self) -> Result<f64, io::Error> {
|
||||
let mut n = [0u8; 8];
|
||||
self.read_exact(&mut n)?;
|
||||
Ok(f64::from_bits(match self.byte_order() {
|
||||
self.reader.read_exact(&mut n)?;
|
||||
Ok(f64::from_bits(match self.byte_order {
|
||||
ByteOrder::LittleEndian => u64::from_le_bytes(n),
|
||||
ByteOrder::BigEndian => u64::from_be_bytes(n),
|
||||
}))
|
||||
@@ -125,6 +140,7 @@ pub trait EndianReader: Read {
|
||||
/// ## Deflate Reader
|
||||
///
|
||||
|
||||
#[cfg(feature = "deflate")]
|
||||
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
|
||||
#[cfg(feature = "lzw")]
|
||||
pub struct LZWReader<R: Read> {
|
||||
reader: BufReader<Take<R>>,
|
||||
decoder: weezl::decode::Decoder,
|
||||
}
|
||||
|
||||
#[cfg(feature = "lzw")]
|
||||
impl<R: Read> LZWReader<R> {
|
||||
/// Wraps a reader
|
||||
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
|
||||
@@ -150,6 +168,7 @@ impl<R: Read> LZWReader<R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "lzw")]
|
||||
impl<R: Read> Read for LZWReader<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
use crate::tags::Tag;
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use super::ifd::{Directory, Value};
|
||||
use super::stream::SmartReader;
|
||||
use super::stream::EndianReader;
|
||||
use super::Limits;
|
||||
|
||||
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 limits: &'a Limits,
|
||||
pub bigtiff: bool,
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
use crate::tags::{PhotometricInterpretation, SampleFormat};
|
||||
|
||||
macro_rules! integer_horizontal_predict {
|
||||
() => {
|
||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
|
||||
let sample_size = Self::SAMPLE_FORMAT.len();
|
||||
|
||||
if row.len() < sample_size {
|
||||
debug_assert!(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let (start, rest) = row.split_at(sample_size);
|
||||
|
||||
result.extend_from_slice(start);
|
||||
if result.capacity() - result.len() < rest.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
result.extend(
|
||||
row.into_iter()
|
||||
.zip(rest)
|
||||
.map(|(prev, current)| current.wrapping_sub(*prev)),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Trait for different colortypes that can be encoded.
|
||||
pub trait ColorType {
|
||||
/// The type of each sample of this colortype
|
||||
@@ -10,6 +36,8 @@ pub trait ColorType {
|
||||
const BITS_PER_SAMPLE: &'static [u16];
|
||||
/// The value of the tiff tag `SampleFormat`
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat];
|
||||
|
||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
|
||||
}
|
||||
|
||||
pub struct Gray8;
|
||||
@@ -18,6 +46,8 @@ impl ColorType for Gray8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct GrayI8;
|
||||
@@ -26,6 +56,8 @@ impl ColorType for GrayI8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray16;
|
||||
@@ -34,6 +66,8 @@ impl ColorType for Gray16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct GrayI16;
|
||||
@@ -42,6 +76,8 @@ impl ColorType for GrayI16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray32;
|
||||
@@ -50,6 +86,8 @@ impl ColorType for Gray32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct GrayI32;
|
||||
@@ -58,6 +96,8 @@ impl ColorType for GrayI32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray32Float;
|
||||
@@ -66,6 +106,10 @@ impl ColorType for Gray32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Gray64;
|
||||
@@ -74,6 +118,8 @@ impl ColorType for Gray64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct GrayI64;
|
||||
@@ -82,6 +128,8 @@ impl ColorType for GrayI64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct Gray64Float;
|
||||
@@ -90,6 +138,10 @@ impl ColorType for Gray64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGB8;
|
||||
@@ -98,6 +150,8 @@ impl ColorType for RGB8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB16;
|
||||
@@ -106,6 +160,8 @@ impl ColorType for RGB16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB32;
|
||||
@@ -114,6 +170,8 @@ impl ColorType for RGB32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB32Float;
|
||||
@@ -122,6 +180,9 @@ impl ColorType for RGB32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGB64;
|
||||
@@ -130,6 +191,8 @@ impl ColorType for RGB64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGB64Float;
|
||||
@@ -138,6 +201,9 @@ impl ColorType for RGB64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGBA8;
|
||||
@@ -146,6 +212,8 @@ impl ColorType for RGBA8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA16;
|
||||
@@ -154,6 +222,8 @@ impl ColorType for RGBA16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA32;
|
||||
@@ -162,6 +232,8 @@ impl ColorType for RGBA32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA32Float;
|
||||
@@ -170,6 +242,9 @@ impl ColorType for RGBA32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RGBA64;
|
||||
@@ -178,6 +253,8 @@ impl ColorType for RGBA64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct RGBA64Float;
|
||||
@@ -186,6 +263,9 @@ impl ColorType for RGBA64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CMYK8;
|
||||
@@ -194,6 +274,8 @@ impl ColorType for CMYK8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK16;
|
||||
@@ -202,6 +284,8 @@ impl ColorType for CMYK16 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK32;
|
||||
@@ -210,6 +294,8 @@ impl ColorType for CMYK32 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK32Float;
|
||||
@@ -218,6 +304,10 @@ impl ColorType for CMYK32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CMYK64;
|
||||
@@ -226,6 +316,8 @@ impl ColorType for CMYK64 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYK64Float;
|
||||
@@ -234,6 +326,10 @@ impl ColorType for CMYK64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct YCbCr8;
|
||||
@@ -242,4 +338,16 @@ impl ColorType for YCbCr8 {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
|
||||
pub struct CMYKA8;
|
||||
impl ColorType for CMYKA8 {
|
||||
type Inner = u8;
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8, 8];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 5];
|
||||
|
||||
integer_horizontal_predict!();
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||
use crate::encoder::compression::*;
|
||||
use flate2::{write::ZlibEncoder, Compression as FlateCompression};
|
||||
use std::io::Write;
|
||||
|
||||
/// The Deflate algorithm used to compress image data in TIFF files.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||
use std::io::Write;
|
||||
use crate::encoder::compression::*;
|
||||
use weezl::encode::Encoder as LZWEncoder;
|
||||
|
||||
/// The LZW algorithm used to compress image data in TIFF files.
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
use crate::tags::CompressionMethod;
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[cfg(feature = "deflate")]
|
||||
mod deflate;
|
||||
#[cfg(feature = "lzw")]
|
||||
mod lzw;
|
||||
mod packbits;
|
||||
mod uncompressed;
|
||||
|
||||
#[cfg(feature = "deflate")]
|
||||
pub use self::deflate::{Deflate, DeflateLevel};
|
||||
|
||||
#[cfg(feature = "lzw")]
|
||||
pub use self::lzw::Lzw;
|
||||
|
||||
pub use self::packbits::Packbits;
|
||||
pub use self::uncompressed::Uncompressed;
|
||||
|
||||
@@ -28,9 +34,12 @@ pub trait Compression: CompressionAlgorithm {
|
||||
}
|
||||
|
||||
/// An enum to store each compression algorithm.
|
||||
#[non_exhaustive]
|
||||
pub enum Compressor {
|
||||
Uncompressed(Uncompressed),
|
||||
#[cfg(feature = "lzw")]
|
||||
Lzw(Lzw),
|
||||
#[cfg(feature = "deflate")]
|
||||
Deflate(Deflate),
|
||||
Packbits(Packbits),
|
||||
}
|
||||
@@ -46,7 +55,9 @@ impl CompressionAlgorithm for Compressor {
|
||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||
match self {
|
||||
Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes),
|
||||
#[cfg(feature = "lzw")]
|
||||
Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes),
|
||||
#[cfg(feature = "deflate")]
|
||||
Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes),
|
||||
Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||
use std::io::{BufWriter, Error, ErrorKind, Write};
|
||||
use crate::encoder::compression::*;
|
||||
use std::io::{BufWriter, Error, ErrorKind};
|
||||
|
||||
/// Compressor that uses the Packbits[^note] algorithm to compress bytes.
|
||||
///
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::{encoder::compression::*, tags::CompressionMethod};
|
||||
use std::io::Write;
|
||||
use crate::encoder::compression::*;
|
||||
|
||||
/// The default algorithm which does not compress at all.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
|
||||
+116
-58
@@ -3,7 +3,6 @@ pub use tiff_value::*;
|
||||
use std::{
|
||||
cmp,
|
||||
collections::BTreeMap,
|
||||
convert::{TryFrom, TryInto},
|
||||
io::{self, Seek, Write},
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
@@ -11,8 +10,8 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::TiffResult,
|
||||
tags::{CompressionMethod, ResolutionUnit, Tag},
|
||||
error::{TiffResult, UsageError},
|
||||
tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag},
|
||||
TiffError, TiffFormatError,
|
||||
};
|
||||
|
||||
@@ -22,9 +21,62 @@ mod tiff_value;
|
||||
mod writer;
|
||||
|
||||
use self::colortype::*;
|
||||
use self::compression::Compression as Comp;
|
||||
use self::compression::*;
|
||||
use self::writer::*;
|
||||
|
||||
/// Type of prediction to prepare the image with.
|
||||
///
|
||||
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
|
||||
/// passes ran over the image data to prepare it for compression. This is mostly used for LZW
|
||||
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
|
||||
/// ratio over the unpredicted compression !
|
||||
///
|
||||
/// [Predictor::FloatingPoint] is currently not supported.
|
||||
pub type Predictor = crate::tags::Predictor;
|
||||
#[cfg(feature = "deflate")]
|
||||
pub type DeflateLevel = compression::DeflateLevel;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Compression {
|
||||
Uncompressed,
|
||||
#[cfg(feature = "lzw")]
|
||||
Lzw,
|
||||
#[cfg(feature = "deflate")]
|
||||
Deflate(DeflateLevel),
|
||||
Packbits,
|
||||
}
|
||||
|
||||
impl Default for Compression {
|
||||
fn default() -> Self {
|
||||
Self::Uncompressed
|
||||
}
|
||||
}
|
||||
|
||||
impl Compression {
|
||||
fn tag(&self) -> CompressionMethod {
|
||||
match self {
|
||||
Compression::Uncompressed => CompressionMethod::None,
|
||||
#[cfg(feature = "lzw")]
|
||||
Compression::Lzw => CompressionMethod::LZW,
|
||||
#[cfg(feature = "deflate")]
|
||||
Compression::Deflate(_) => CompressionMethod::Deflate,
|
||||
Compression::Packbits => CompressionMethod::PackBits,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_algorithm(&self) -> Compressor {
|
||||
match self {
|
||||
Compression::Uncompressed => compression::Uncompressed {}.get_algorithm(),
|
||||
#[cfg(feature = "lzw")]
|
||||
Compression::Lzw => compression::Lzw {}.get_algorithm(),
|
||||
#[cfg(feature = "deflate")]
|
||||
Compression::Deflate(level) => compression::Deflate::with_level(*level).get_algorithm(),
|
||||
Compression::Packbits => compression::Packbits {}.get_algorithm(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Encoder for Tiff and BigTiff files.
|
||||
///
|
||||
/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder`
|
||||
@@ -53,6 +105,8 @@ use self::writer::*;
|
||||
pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> {
|
||||
writer: TiffWriter<W>,
|
||||
kind: PhantomData<K>,
|
||||
predictor: Predictor,
|
||||
compression: Compression,
|
||||
}
|
||||
|
||||
/// Constructor functions to create standard Tiff files.
|
||||
@@ -84,6 +138,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
let mut encoder = TiffEncoder {
|
||||
writer: TiffWriter::new(writer),
|
||||
kind: PhantomData,
|
||||
predictor: Predictor::None,
|
||||
compression: Compression::Uncompressed,
|
||||
};
|
||||
|
||||
K::write_header(&mut encoder.writer)?;
|
||||
@@ -91,6 +147,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
Ok(encoder)
|
||||
}
|
||||
|
||||
/// Set the predictor to use
|
||||
///
|
||||
/// A predictor is used to simplify the file before writing it. This is very
|
||||
/// useful when writing a file compressed using LZW as it can improve efficiency
|
||||
pub fn with_predictor(mut self, predictor: Predictor) -> Self {
|
||||
self.predictor = predictor;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the compression method to use
|
||||
pub fn with_compression(mut self, compression: Compression) -> Self {
|
||||
self.compression = compression;
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> {
|
||||
DirectoryEncoder::new(&mut self.writer)
|
||||
@@ -101,20 +174,9 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> TiffResult<ImageEncoder<W, C, K, Uncompressed>> {
|
||||
) -> TiffResult<ImageEncoder<W, C, K>> {
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
ImageEncoder::new(encoder, width, height)
|
||||
}
|
||||
|
||||
/// Create an [`ImageEncoder`] to encode an image one slice at a time.
|
||||
pub fn new_image_with_compression<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)
|
||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
||||
}
|
||||
|
||||
/// 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,
|
||||
{
|
||||
let encoder = DirectoryEncoder::new(&mut self.writer)?;
|
||||
let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?;
|
||||
image.write_data(data)
|
||||
}
|
||||
|
||||
/// Convenience function to write an entire image from memory with a given compression.
|
||||
pub fn write_image_with_compression<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)?;
|
||||
let image: ImageEncoder<W, C, K> =
|
||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?;
|
||||
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
|
||||
pub struct ImageEncoder<
|
||||
'a,
|
||||
W: 'a + Write + Seek,
|
||||
C: ColorType,
|
||||
K: TiffKind,
|
||||
D: Compression = Uncompressed,
|
||||
> {
|
||||
pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
|
||||
encoder: DirectoryEncoder<'a, W, K>,
|
||||
strip_idx: u64,
|
||||
strip_count: u64,
|
||||
@@ -330,25 +370,30 @@ pub struct ImageEncoder<
|
||||
strip_offsets: Vec<K::OffsetType>,
|
||||
strip_byte_count: Vec<K::OffsetType>,
|
||||
dropped: bool,
|
||||
compression: D,
|
||||
compression: Compression,
|
||||
predictor: Predictor,
|
||||
_phantom: ::std::marker::PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
|
||||
ImageEncoder<'a, W, T, K, D>
|
||||
{
|
||||
fn new(encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self>
|
||||
where
|
||||
D: Default,
|
||||
{
|
||||
Self::with_compression(encoder, width, height, D::default())
|
||||
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
|
||||
fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
|
||||
match (predictor, compression, T::SAMPLE_FORMAT[0]) {
|
||||
(Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
|
||||
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
||||
}
|
||||
(Predictor::FloatingPoint, _, _) => {
|
||||
Err(TiffError::UsageError(UsageError::PredictorUnavailable))
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_compression(
|
||||
fn new(
|
||||
mut encoder: DirectoryEncoder<'a, W, K>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
compression: D,
|
||||
compression: Compression,
|
||||
predictor: Predictor,
|
||||
) -> TiffResult<Self> {
|
||||
if width == 0 || height == 0 {
|
||||
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_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
|
||||
|
||||
// Limit the strip size to prevent potential memory and security issues.
|
||||
// Also keep the multiple strip handling 'oiled'
|
||||
let rows_per_strip = {
|
||||
match D::COMPRESSION_METHOD {
|
||||
match compression.tag() {
|
||||
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
|
||||
_ => (1_000_000 + row_bytes - 1) / row_bytes,
|
||||
}
|
||||
@@ -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::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)?;
|
||||
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(),
|
||||
dropped: false,
|
||||
compression,
|
||||
predictor,
|
||||
_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.
|
||||
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;
|
||||
|
||||
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
|
||||
for ImageEncoder<'a, W, C, K, D>
|
||||
{
|
||||
impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W, C, K> {
|
||||
fn drop(&mut self) {
|
||||
if !self.dropped {
|
||||
let _ = self.finish_internal();
|
||||
|
||||
+125
-281
@@ -1,294 +1,189 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
use std::io;
|
||||
use std::str;
|
||||
use std::string;
|
||||
use std::sync::Arc;
|
||||
|
||||
use jpeg::UnsupportedFeature;
|
||||
use quick_error::quick_error;
|
||||
|
||||
use crate::decoder::{ifd::Value, ChunkType};
|
||||
use crate::decoder::ChunkType;
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
|
||||
};
|
||||
use crate::ColorType;
|
||||
|
||||
use crate::weezl::LzwError;
|
||||
|
||||
quick_error! {
|
||||
/// Tiff error kinds.
|
||||
#[derive(Debug)]
|
||||
pub enum TiffError {
|
||||
/// 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.
|
||||
UnsupportedError(TiffUnsupportedError),
|
||||
|
||||
UnsupportedError(err: TiffUnsupportedError) {
|
||||
display("unsupported error: {err}")
|
||||
from()
|
||||
source(err)
|
||||
}
|
||||
/// 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.
|
||||
LimitsExceeded,
|
||||
|
||||
/// An integer conversion to or from a platform size failed, either due to
|
||||
/// limits of the platform size or limits of the format.
|
||||
IntSizeError,
|
||||
|
||||
LimitsExceeded {
|
||||
display("decoder limits exceeded")
|
||||
}
|
||||
/// An integer conversion to or from a platform size failed.
|
||||
IntSizeError {
|
||||
display("platform or format size limits exceeded")
|
||||
}
|
||||
/// The image does not support the requested operation
|
||||
UsageError(UsageError),
|
||||
UsageError(err: UsageError) {
|
||||
display("usage error: {err}")
|
||||
from()
|
||||
source(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quick_error! {
|
||||
/// The image is not formatted properly.
|
||||
///
|
||||
/// This indicates that the encoder producing the image might behave incorrectly or that the input
|
||||
/// file has been corrupted.
|
||||
///
|
||||
/// The list of variants may grow to incorporate errors of future features. Matching against this
|
||||
/// exhaustively is not covered by interface stability guarantees.
|
||||
/// This indicates that the encoder producing the image might behave incorrectly or that the
|
||||
/// input file has been corrupted.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum TiffFormatError {
|
||||
TiffSignatureNotFound,
|
||||
TiffSignatureInvalid,
|
||||
ImageFileDirectoryNotFound,
|
||||
InconsistentSizesEncountered,
|
||||
UnexpectedCompressedData {
|
||||
actual_bytes: usize,
|
||||
required_bytes: usize,
|
||||
},
|
||||
InconsistentStripSamples {
|
||||
actual_samples: usize,
|
||||
required_samples: usize,
|
||||
},
|
||||
InvalidDimensions(u32, u32),
|
||||
InvalidTag,
|
||||
InvalidTagValueType(Tag),
|
||||
RequiredTagNotFound(Tag),
|
||||
UnknownPredictor(u16),
|
||||
UnknownPlanarConfiguration(u16),
|
||||
ByteExpected(Value),
|
||||
UnsignedIntegerExpected(Value),
|
||||
SignedIntegerExpected(Value),
|
||||
Format(String),
|
||||
RequiredTagEmpty(Tag),
|
||||
StripTileTagConflict,
|
||||
CycleInOffsets,
|
||||
JpegDecoder(JpegDecoderError),
|
||||
SamplesPerPixelIsZero,
|
||||
TiffSignatureNotFound {
|
||||
display("TIFF signature not found")
|
||||
}
|
||||
|
||||
impl fmt::Display for TiffFormatError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use self::TiffFormatError::*;
|
||||
match *self {
|
||||
TiffSignatureNotFound => write!(fmt, "TIFF signature not found."),
|
||||
TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."),
|
||||
ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
|
||||
InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."),
|
||||
UnexpectedCompressedData {
|
||||
actual_bytes,
|
||||
required_bytes,
|
||||
} => {
|
||||
write!(
|
||||
fmt,
|
||||
"Decompression returned different amount of bytes than expected: got {}, expected {}.",
|
||||
actual_bytes, required_bytes
|
||||
)
|
||||
TiffSignatureInvalid {
|
||||
display("TIFF signature invalid")
|
||||
}
|
||||
InconsistentStripSamples {
|
||||
actual_samples,
|
||||
required_samples,
|
||||
} => {
|
||||
write!(
|
||||
fmt,
|
||||
"Inconsistent elements in strip: got {}, expected {}.",
|
||||
actual_samples, required_samples
|
||||
)
|
||||
ImageFileDirectoryNotFound {
|
||||
display("image file directory not found")
|
||||
}
|
||||
InvalidDimensions(width, height) => write!(fmt, "Invalid dimensions: {}x{}.", width, height),
|
||||
InvalidTag => write!(fmt, "Image contains invalid tag."),
|
||||
InvalidTagValueType(ref tag) => {
|
||||
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
|
||||
InconsistentSizesEncountered {
|
||||
display("inconsistent sizes encountered")
|
||||
}
|
||||
RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag),
|
||||
UnknownPredictor(ref predictor) => {
|
||||
write!(fmt, "Unknown predictor “{}” encountered", predictor)
|
||||
InvalidDimensions(width: u32, height: u32) {
|
||||
display("invalid dimensions: {width}x{height}")
|
||||
}
|
||||
UnknownPlanarConfiguration(ref planar_config) => {
|
||||
write!(fmt, "Unknown planar configuration “{}” encountered", planar_config)
|
||||
InvalidTag {
|
||||
display("image contains invalid tag")
|
||||
}
|
||||
ByteExpected(ref val) => write!(fmt, "Expected byte, {:?} found.", val),
|
||||
UnsignedIntegerExpected(ref val) => {
|
||||
write!(fmt, "Expected unsigned integer, {:?} found.", val)
|
||||
InvalidTagValueType(tag: Tag) {
|
||||
display("tag `{tag:?}` did not have the expected value type")
|
||||
}
|
||||
SignedIntegerExpected(ref val) => {
|
||||
write!(fmt, "Expected signed integer, {:?} found.", val)
|
||||
RequiredTagNotFound(tag: Tag) {
|
||||
display("required tag `{tag:?}` not found")
|
||||
}
|
||||
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
|
||||
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
|
||||
StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
|
||||
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
|
||||
JpegDecoder(ref error) => write!(fmt, "{}", error),
|
||||
SamplesPerPixelIsZero => write!(fmt, "Samples per pixel is zero"),
|
||||
UnknownPredictor(pred: u16) {
|
||||
display("unknown predictor “{pred}” encountered")
|
||||
}
|
||||
UnknownPlanarConfiguration(cfg: u16) {
|
||||
display("unknown planar configuration “{cfg}”")
|
||||
}
|
||||
InvalidTypeForTag {
|
||||
display("tag has invalid type")
|
||||
}
|
||||
StripTileTagConflict {
|
||||
display("file should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found")
|
||||
}
|
||||
CycleInOffsets {
|
||||
display("file contained a cycle in the list of IFDs")
|
||||
}
|
||||
SamplesPerPixelIsZero {
|
||||
display("samples per pixel is zero")
|
||||
}
|
||||
CompressedDataCorrupt(message: String) {
|
||||
display("compressed data is corrupt: {message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quick_error! {
|
||||
/// The Decoder does not support features required by the image.
|
||||
///
|
||||
/// This only captures known failures for which the standard either does not require support or an
|
||||
/// implementation has been planned but not yet completed. Some variants may become unused over
|
||||
/// time and will then get deprecated before being removed.
|
||||
///
|
||||
/// The list of variants may grow. Matching against this exhaustively is not covered by interface
|
||||
/// stability guarantees.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum TiffUnsupportedError {
|
||||
FloatingPointPredictor(ColorType),
|
||||
HorizontalPredictor(ColorType),
|
||||
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),
|
||||
FloatingPointPredictor(color_type: ColorType) {
|
||||
display("floating point predictor for {color_type:?} is unsupported")
|
||||
}
|
||||
|
||||
impl fmt::Display for TiffUnsupportedError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use self::TiffUnsupportedError::*;
|
||||
match *self {
|
||||
FloatingPointPredictor(color_type) => write!(
|
||||
fmt,
|
||||
"Floating point predictor for {:?} is unsupported.",
|
||||
color_type
|
||||
),
|
||||
HorizontalPredictor(color_type) => write!(
|
||||
fmt,
|
||||
"Horizontal predictor for {:?} is unsupported.",
|
||||
color_type
|
||||
),
|
||||
InconsistentBitsPerSample(ref bits_per_sample) => {
|
||||
write!(fmt, "Inconsistent bits per sample: {:?}.", bits_per_sample)
|
||||
HorizontalPredictor(color_type: ColorType) {
|
||||
display("horizontal predictor for {color_type:?} is unsupported")
|
||||
}
|
||||
InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => write!(
|
||||
fmt,
|
||||
"{:?} with {:?} bits per sample is unsupported",
|
||||
photometric_interpretation, bits_per_sample
|
||||
),
|
||||
UnknownInterpretation => write!(
|
||||
fmt,
|
||||
"The image is using an unknown photometric interpretation."
|
||||
),
|
||||
UnknownCompressionMethod => write!(fmt, "Unknown compression method."),
|
||||
UnsupportedCompressionMethod(method) => {
|
||||
write!(fmt, "Compression method {:?} is unsupported", method)
|
||||
InconsistentBitsPerSample(bits_per_sample: Vec<u8>) {
|
||||
display("inconsistent bits per sample: {bits_per_sample:?}")
|
||||
}
|
||||
UnsupportedSampleDepth(samples) => {
|
||||
write!(fmt, "{} samples per pixel is unsupported.", samples)
|
||||
InterpretationWithBits(interpretation: PhotometricInterpretation, bits_per_sample: Vec<u8>) {
|
||||
display("Photometric interpretation {interpretation:?} with bits per sample {bits_per_sample:?} is unsupported")
|
||||
}
|
||||
UnsupportedSampleFormat(ref formats) => {
|
||||
write!(fmt, "Sample format {:?} is unsupported.", formats)
|
||||
UnknownInterpretation {
|
||||
display("unknown photometric interpretation")
|
||||
}
|
||||
UnsupportedColorType(color_type) => {
|
||||
write!(fmt, "Color type {:?} is unsupported", color_type)
|
||||
UnknownCompressionMethod {
|
||||
display("unknown compression method")
|
||||
}
|
||||
UnsupportedBitsPerChannel(bits) => {
|
||||
write!(fmt, "{} bits per channel not supported", bits)
|
||||
UnsupportedCompressionMethod(method: CompressionMethod) {
|
||||
display("compression method {method:?} is unsupported")
|
||||
}
|
||||
UnsupportedPlanarConfig(config) => {
|
||||
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
||||
UnsupportedSampleDepth(depth: u8) {
|
||||
display("{depth} samples per pixel is unsupported")
|
||||
}
|
||||
UnsupportedDataType => write!(fmt, "Unsupported data type."),
|
||||
UnsupportedInterpretation(interpretation) => {
|
||||
write!(
|
||||
fmt,
|
||||
"Unsupported photometric interpretation \"{:?}\".",
|
||||
interpretation
|
||||
)
|
||||
UnsupportedSampleFormat(sample_format: Vec<SampleFormat>) {
|
||||
display("sample format {sample_format:?} is unsupported")
|
||||
}
|
||||
UnsupportedJpegFeature(ref unsupported_feature) => {
|
||||
write!(fmt, "Unsupported JPEG feature {:?}", unsupported_feature)
|
||||
UnsupportedColorType(color_type: ColorType) {
|
||||
display("color type {color_type:?} is unsupported")
|
||||
}
|
||||
UnsupportedBitsPerChannel(bits_per_channel: u8) {
|
||||
display("{bits_per_channel} bits per channel not supported")
|
||||
}
|
||||
UnsupportedPlanarConfig(planar: Option<PlanarConfiguration>) {
|
||||
display("unsupported planar configuration “{planar:?}”")
|
||||
}
|
||||
UnsupportedDataType {
|
||||
display("unsupported data type.")
|
||||
}
|
||||
UnsupportedInterpretation(interpretation: PhotometricInterpretation) {
|
||||
display("unsupported photometric interpretation \"{interpretation:?}\"")
|
||||
}
|
||||
MisalignedTileBoundaries {
|
||||
display("tile rows are not aligned to byte boundaries")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quick_error! {
|
||||
/// User attempted to use the Decoder in a way that is incompatible with a specific image.
|
||||
///
|
||||
/// For example: attempting to read a tile from a stripped image.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum UsageError {
|
||||
InvalidChunkType(ChunkType, ChunkType),
|
||||
InvalidChunkIndex(u32),
|
||||
InvalidChunkType(expected: ChunkType, actual: ChunkType) {
|
||||
display("requested operation is only valid for images with chunk encoding of type {expected:?} but got {actual:?}")
|
||||
}
|
||||
|
||||
impl fmt::Display for UsageError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use self::UsageError::*;
|
||||
match *self {
|
||||
InvalidChunkType(expected, actual) => {
|
||||
write!(
|
||||
fmt,
|
||||
"Requested operation is only valid for images with chunk encoding of type: {:?}, got {:?}.",
|
||||
expected, actual
|
||||
)
|
||||
InvalidChunkIndex(index: u32) {
|
||||
display("invalid chunk index ({index}) requested")
|
||||
}
|
||||
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 {
|
||||
fn from(_err: std::num::TryFromIntError) -> TiffError {
|
||||
TiffError::IntSizeError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LzwError> for TiffError {
|
||||
fn from(err: LzwError) -> TiffError {
|
||||
match err {
|
||||
LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from(
|
||||
"LZW compressed data corrupted",
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JpegDecoderError {
|
||||
inner: Arc<jpeg::Error>,
|
||||
}
|
||||
|
||||
impl JpegDecoderError {
|
||||
fn new(error: jpeg::Error) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for JpegDecoderError {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.inner, &other.inner)
|
||||
#[cfg(feature = "lzw")]
|
||||
impl From<weezl::LzwError> for TiffError {
|
||||
fn from(err: weezl::LzwError) -> TiffError {
|
||||
TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for JpegDecoderError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JpegDecoderError> for TiffError {
|
||||
fn from(error: JpegDecoderError) -> Self {
|
||||
TiffError::FormatError(TiffFormatError::JpegDecoder(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jpeg::Error> for TiffError {
|
||||
fn from(error: jpeg::Error) -> Self {
|
||||
JpegDecoderError::new(error).into()
|
||||
#[cfg(feature = "jpeg")]
|
||||
impl From<zune_jpeg::errors::DecodeErrors> for TiffError {
|
||||
fn from(err: zune_jpeg::errors::DecodeErrors) -> Self {
|
||||
TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-3
@@ -6,9 +6,6 @@
|
||||
//! # Related Links
|
||||
//! * <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;
|
||||
pub mod decoder;
|
||||
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
|
||||
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum ColorType {
|
||||
/// Pixel is grayscale
|
||||
Gray(u8),
|
||||
@@ -38,6 +36,27 @@ pub enum ColorType {
|
||||
/// Pixel is CMYK
|
||||
CMYK(u8),
|
||||
|
||||
/// Pixel is CMYK with an alpha channel
|
||||
CMYKA(u8),
|
||||
|
||||
/// Pixel is YCbCr
|
||||
YCbCr(u8),
|
||||
|
||||
/// Pixel has multiple bands/channels
|
||||
Multiband { bit_depth: u8, num_samples: u16 },
|
||||
}
|
||||
impl ColorType {
|
||||
fn bit_depth(&self) -> u8 {
|
||||
match *self {
|
||||
ColorType::Gray(b)
|
||||
| ColorType::RGB(b)
|
||||
| ColorType::Palette(b)
|
||||
| ColorType::GrayA(b)
|
||||
| ColorType::RGBA(b)
|
||||
| ColorType::CMYK(b)
|
||||
| ColorType::CMYKA(b)
|
||||
| ColorType::YCbCr(b)
|
||||
| ColorType::Multiband { bit_depth: b, .. } => b,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,6 +184,9 @@ pub enum CompressionMethod(u16) unknown("A custom compression method") {
|
||||
Deflate = 8,
|
||||
OldDeflate = 0x80B2,
|
||||
PackBits = 0x8005,
|
||||
|
||||
// Self-assigned by libtiff
|
||||
ZSTD = 0xC350,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,8 +212,14 @@ pub enum PlanarConfiguration(u16) {
|
||||
|
||||
tags! {
|
||||
pub enum Predictor(u16) {
|
||||
/// No changes were made to the data
|
||||
None = 1,
|
||||
/// The images' rows were processed to contain the difference of each pixel from the previous one.
|
||||
///
|
||||
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
|
||||
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
|
||||
Horizontal = 2,
|
||||
/// Not currently supported
|
||||
FloatingPoint = 3,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
extern crate tiff;
|
||||
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::ColorType;
|
||||
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
||||
|
||||
/// Test a basic all white image
|
||||
#[test]
|
||||
fn test_white_ieee_fp16() {
|
||||
let filenames = ["white-fp16.tiff"];
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
for p in img {
|
||||
assert!(p == half::f16::from_f32_const(1.0));
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test a single black pixel, to make sure scaling is ok
|
||||
#[test]
|
||||
fn test_one_black_pixel_ieee_fp16() {
|
||||
let filenames = ["single-black-fp16.tiff"];
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
for (i, p) in img.iter().enumerate() {
|
||||
if i == 0 {
|
||||
assert!(p < &half::f16::from_f32_const(0.001));
|
||||
} else {
|
||||
assert!(p == &half::f16::from_f32_const(1.0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test white with horizontal differencing predictor
|
||||
#[test]
|
||||
fn test_pattern_horizontal_differencing_ieee_fp16() {
|
||||
let filenames = ["white-fp16-pred2.tiff"];
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
// 0, 2, 5, 8, 12, 16, 255 are black
|
||||
let black = [0, 2, 5, 8, 12, 16, 255];
|
||||
for (i, p) in img.iter().enumerate() {
|
||||
if black.contains(&i) {
|
||||
assert!(p < &half::f16::from_f32_const(0.001));
|
||||
} else {
|
||||
assert!(p == &half::f16::from_f32_const(1.0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test white with floating point predictor
|
||||
#[test]
|
||||
fn test_pattern_predictor_ieee_fp16() {
|
||||
let filenames = ["white-fp16-pred3.tiff"];
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
// 0, 2, 5, 8, 12, 16, 255 are black
|
||||
let black = [0, 2, 5, 8, 12, 16, 255];
|
||||
for (i, p) in img.iter().enumerate() {
|
||||
if black.contains(&i) {
|
||||
assert!(p < &half::f16::from_f32_const(0.001));
|
||||
} else {
|
||||
assert!(p == &half::f16::from_f32_const(1.0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test several random images
|
||||
/// we'rell compare against a pnm file, that scales from 0 (for 0.0) to 65767 (for 1.0)
|
||||
#[test]
|
||||
fn test_predictor_ieee_fp16() {
|
||||
// first parse pnm, skip the first 4 \n
|
||||
let pnm_path = PathBuf::from(TEST_IMAGE_DIR).join("random-fp16.pgm");
|
||||
let pnm_bytes = std::fs::read(pnm_path).expect("Failed to read expected PNM file");
|
||||
|
||||
// PGM looks like this:
|
||||
// ---
|
||||
// P5
|
||||
// #Created with GIMP
|
||||
// 16 16
|
||||
// 65535
|
||||
// ... <big-endian bytes>
|
||||
// ---
|
||||
// get index of 4th \n
|
||||
let byte_start = pnm_bytes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, &v)| v == b'\n')
|
||||
.map(|(i, _)| i)
|
||||
.nth(3)
|
||||
.expect("Must be 4 \\n's");
|
||||
|
||||
let pnm_values: Vec<f32> = pnm_bytes[(byte_start + 1)..]
|
||||
.chunks(2)
|
||||
.map(|slice| {
|
||||
let bts = [slice[0], slice[1]];
|
||||
(u16::from_be_bytes(bts) as f32) / (u16::MAX as f32)
|
||||
})
|
||||
.collect();
|
||||
assert!(pnm_values.len() == 256);
|
||||
|
||||
let filenames = [
|
||||
"random-fp16-pred2.tiff",
|
||||
"random-fp16-pred3.tiff",
|
||||
"random-fp16.tiff",
|
||||
];
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(16, 16)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) {
|
||||
assert!((exp - found.to_f32()).abs() < 0.0001);
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
extern crate tiff;
|
||||
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::tags::Tag;
|
||||
use tiff::ColorType;
|
||||
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TEST_IMAGE_DIR: &str = "./tests/images";
|
||||
|
||||
#[test]
|
||||
fn test_geo_tiff() {
|
||||
let filenames = ["geo-5b.tif"];
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
decoder = decoder.with_limits(tiff::decoder::Limits::unlimited());
|
||||
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(10, 10)
|
||||
);
|
||||
assert_eq!(
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Multiband {
|
||||
bit_depth: 16,
|
||||
num_samples: 5
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
decoder
|
||||
.get_tag_u64(Tag::StripOffsets)
|
||||
.expect("Cannot get StripOffsets"),
|
||||
418
|
||||
);
|
||||
assert_eq!(
|
||||
decoder
|
||||
.get_tag_u64(Tag::RowsPerStrip)
|
||||
.expect("Cannot get RowsPerStrip"),
|
||||
10
|
||||
);
|
||||
assert_eq!(
|
||||
decoder
|
||||
.get_tag_u64(Tag::StripByteCounts)
|
||||
.expect("Cannot get StripByteCounts"),
|
||||
1000
|
||||
);
|
||||
assert_eq!(
|
||||
decoder
|
||||
.get_tag(Tag::ModelPixelScaleTag)
|
||||
.expect("Cannot get pixel scale")
|
||||
.into_f64_vec()
|
||||
.expect("Cannot get pixel scale"),
|
||||
vec![60.0, 60.0, 0.0]
|
||||
);
|
||||
let DecodingResult::I16(data) = decoder.read_image().unwrap() else {
|
||||
panic!("Cannot read band data")
|
||||
};
|
||||
assert_eq!(data.len(), 500);
|
||||
}
|
||||
}
|
||||
@@ -216,6 +216,11 @@ fn issue_69() {
|
||||
//assert!(img_res.is_ok());
|
||||
//}
|
||||
|
||||
#[test]
|
||||
fn test_tiled_gray_i1() {
|
||||
test_image_sum_u8("tiled-gray-i1.tif", ColorType::Gray(1), 30531);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tiled_rgb_u8() {
|
||||
test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948);
|
||||
@@ -510,3 +515,10 @@ fn test_predictor_3_rgb_f32() {
|
||||
fn test_predictor_3_gray_f32() {
|
||||
test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "zstd")]
|
||||
fn test_zstd_compression() {
|
||||
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
|
||||
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
|
||||
}
|
||||
@@ -36,6 +36,47 @@ fn encode_decode() {
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||
|
||||
let mut all_tags = decoder
|
||||
.tag_iter()
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
all_tags.sort_by_key(|(t, _)| t.to_u16());
|
||||
assert_eq!(
|
||||
all_tags,
|
||||
vec![
|
||||
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
|
||||
(Tag::ImageLength, ifd::Value::Unsigned(100)),
|
||||
(
|
||||
Tag::BitsPerSample,
|
||||
ifd::Value::List(vec![
|
||||
ifd::Value::Short(8),
|
||||
ifd::Value::Short(8),
|
||||
ifd::Value::Short(8)
|
||||
])
|
||||
),
|
||||
(Tag::Compression, ifd::Value::Short(1)),
|
||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
||||
(Tag::StripOffsets, ifd::Value::Unsigned(8)),
|
||||
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
|
||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
||||
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
|
||||
(Tag::XResolution, ifd::Value::Rational(1, 1)),
|
||||
(Tag::YResolution, ifd::Value::Rational(1, 1)),
|
||||
(Tag::ResolutionUnit, ifd::Value::Short(1)),
|
||||
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
|
||||
(Tag::Predictor, ifd::Value::Short(1)),
|
||||
(
|
||||
Tag::SampleFormat,
|
||||
ifd::Value::List(vec![
|
||||
ifd::Value::Short(1),
|
||||
ifd::Value::Short(1),
|
||||
ifd::Value::Short(1)
|
||||
])
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
decoder.get_tag(Tag::Artist).unwrap(),
|
||||
ifd::Value::Ascii("Image-tiff".into())
|
||||
@@ -75,6 +116,47 @@ fn encode_decode_big() {
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||
|
||||
let mut all_tags = decoder
|
||||
.tag_iter()
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
all_tags.sort_by_key(|(t, _)| t.to_u16());
|
||||
assert_eq!(
|
||||
all_tags,
|
||||
vec![
|
||||
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
|
||||
(Tag::ImageLength, ifd::Value::Unsigned(100)),
|
||||
(
|
||||
Tag::BitsPerSample,
|
||||
ifd::Value::List(vec![
|
||||
ifd::Value::Short(8),
|
||||
ifd::Value::Short(8),
|
||||
ifd::Value::Short(8)
|
||||
])
|
||||
),
|
||||
(Tag::Compression, ifd::Value::Short(1)),
|
||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
||||
(Tag::StripOffsets, ifd::Value::UnsignedBig(16)),
|
||||
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
|
||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
||||
(Tag::StripByteCounts, ifd::Value::UnsignedBig(30000)),
|
||||
(Tag::XResolution, ifd::Value::Rational(1, 1)),
|
||||
(Tag::YResolution, ifd::Value::Rational(1, 1)),
|
||||
(Tag::ResolutionUnit, ifd::Value::Short(1)),
|
||||
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
|
||||
(Tag::Predictor, ifd::Value::Short(1)),
|
||||
(
|
||||
Tag::SampleFormat,
|
||||
ifd::Value::List(vec![
|
||||
ifd::Value::Short(1),
|
||||
ifd::Value::Short(1),
|
||||
ifd::Value::Short(1)
|
||||
])
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
decoder.get_tag(Tag::Artist).unwrap(),
|
||||
ifd::Value::Ascii("Image-tiff".into())
|
||||
@@ -188,7 +270,9 @@ macro_rules! test_roundtrip {
|
||||
}
|
||||
|
||||
test_roundtrip!(test_u8_roundtrip, U8, u8);
|
||||
test_roundtrip!(test_i8_roundtrip, I8, i8);
|
||||
test_roundtrip!(test_u16_roundtrip, U16, u16);
|
||||
test_roundtrip!(test_i16_roundtrip, I16, i16);
|
||||
test_roundtrip!(test_u32_roundtrip, U32, u32);
|
||||
test_roundtrip!(test_u64_roundtrip, U64, u64);
|
||||
test_roundtrip!(test_f32_roundtrip, F32, f32);
|
||||
@@ -199,6 +283,11 @@ fn test_gray_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i8_roundtrip() {
|
||||
test_i8_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
|
||||
@@ -209,11 +298,21 @@ fn test_cmyk_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmyka_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::CMYKA8>("cmyk-4c-8b.tiff", ColorType::CMYKA(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u16_roundtrip() {
|
||||
test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i16_roundtrip() {
|
||||
test_i16_roundtrip::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u16_roundtrip() {
|
||||
test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
|
||||
|
||||
@@ -6,7 +6,7 @@ use tiff::{
|
||||
encoder::{
|
||||
colortype::{self, ColorType},
|
||||
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 generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
|
||||
|
||||
fn compress<C: Compression, W: Write + Seek>(
|
||||
&self,
|
||||
encoder: &mut TiffEncoder<W>,
|
||||
compression: C,
|
||||
) where
|
||||
fn compress<W: Write + Seek>(&self, encoder: &mut TiffEncoder<W>)
|
||||
where
|
||||
[<Self::Color as ColorType>::Inner]: TiffValue,
|
||||
{
|
||||
let image = encoder
|
||||
.new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression)
|
||||
.new_image::<Self::Color>(Self::WIDTH, Self::HEIGHT)
|
||||
.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 image_rgb = TestImageColor::generate();
|
||||
@@ -103,9 +100,11 @@ fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
|
||||
// Encode tiff with compression
|
||||
{
|
||||
// Create a multipage image with 2 images
|
||||
let mut encoder = TiffEncoder::new(&mut data).unwrap();
|
||||
image_rgb.compress(&mut encoder, compression.clone());
|
||||
image_grayscale.compress(&mut encoder, compression);
|
||||
let mut encoder = TiffEncoder::new(&mut data)
|
||||
.unwrap()
|
||||
.with_compression(compression);
|
||||
image_rgb.compress(&mut encoder);
|
||||
image_grayscale.compress(&mut encoder);
|
||||
}
|
||||
|
||||
// Decode tiff
|
||||
@@ -136,22 +135,24 @@ fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
|
||||
|
||||
#[test]
|
||||
fn encode_decode_without_compression() {
|
||||
encode_decode_with_compression(Uncompressed::default());
|
||||
encode_decode_with_compression(Compression::Uncompressed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "lzw")]
|
||||
fn encode_decode_with_lzw() {
|
||||
encode_decode_with_compression(Lzw::default());
|
||||
encode_decode_with_compression(Compression::Lzw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "deflate")]
|
||||
fn encode_decode_with_deflate() {
|
||||
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast));
|
||||
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced));
|
||||
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best));
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_decode_with_packbits() {
|
||||
encode_decode_with_compression(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.
@@ -0,0 +1,231 @@
|
||||
extern crate tiff;
|
||||
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
|
||||
use tiff::ColorType;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Seek, SeekFrom};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
||||
|
||||
macro_rules! test_predict {
|
||||
($name:ident, $buffer:ident, $buffer_ty:ty) => {
|
||||
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
|
||||
file: &str,
|
||||
expected_type: ColorType,
|
||||
) {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(file).expect("Cannot create decoder!");
|
||||
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
let image_data = match decoder.read_image().unwrap() {
|
||||
DecodingResult::$buffer(res) => res,
|
||||
_ => panic!("Wrong data type"),
|
||||
};
|
||||
|
||||
let mut predicted = Vec::with_capacity(image_data.len());
|
||||
C::horizontal_predict(&image_data, &mut predicted);
|
||||
|
||||
let sample_size = C::SAMPLE_FORMAT.len();
|
||||
|
||||
(0..sample_size).for_each(|i| {
|
||||
assert_eq!(predicted[i], image_data[i]);
|
||||
});
|
||||
|
||||
(sample_size..image_data.len()).for_each(|i| {
|
||||
predicted[i] = predicted[i].wrapping_add(predicted[i - sample_size]);
|
||||
assert_eq!(predicted[i], image_data[i]);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_predict!(test_u8_predict, U8, u8);
|
||||
test_predict!(test_i8_predict, I8, i8);
|
||||
test_predict!(test_u16_predict, U16, u16);
|
||||
test_predict!(test_i16_predict, I16, i16);
|
||||
test_predict!(test_u32_predict, U32, u32);
|
||||
test_predict!(test_u64_predict, U64, u64);
|
||||
|
||||
#[test]
|
||||
fn test_gray_u8_predict() {
|
||||
test_u8_predict::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i8_predict() {
|
||||
test_i8_predict::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u8_predict() {
|
||||
test_u8_predict::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmyk_u8_predict() {
|
||||
test_u8_predict::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u16_predict() {
|
||||
test_u16_predict::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i16_predict() {
|
||||
test_i16_predict::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u16_predict() {
|
||||
test_u16_predict::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmyk_u16_predict() {
|
||||
test_u16_predict::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u32_predict() {
|
||||
test_u32_predict::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u32_predict() {
|
||||
test_u32_predict::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u64_predict() {
|
||||
test_u64_predict::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u64_predict() {
|
||||
test_u64_predict::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ycbcr_u8_predict() {
|
||||
test_u8_predict::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
||||
}
|
||||
|
||||
macro_rules! test_predict_roundtrip {
|
||||
($name:ident, $buffer:ident, $buffer_ty:ty) => {
|
||||
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
|
||||
file: &str,
|
||||
expected_type: ColorType,
|
||||
) {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
|
||||
let image_data = match decoder.read_image().unwrap() {
|
||||
DecodingResult::$buffer(res) => res,
|
||||
_ => panic!("Wrong data type"),
|
||||
};
|
||||
|
||||
let mut file = Cursor::new(Vec::new());
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut file)
|
||||
.unwrap()
|
||||
.with_predictor(Predictor::Horizontal);
|
||||
|
||||
let (width, height) = decoder.dimensions().unwrap();
|
||||
tiff.write_image::<C>(width, height, &image_data).unwrap();
|
||||
}
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
if let DecodingResult::$buffer(img_res) =
|
||||
decoder.read_image().expect("Decoding image failed")
|
||||
{
|
||||
assert_eq!(image_data, img_res);
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_predict_roundtrip!(test_u8_predict_roundtrip, U8, u8);
|
||||
test_predict_roundtrip!(test_i8_predict_roundtrip, I8, i8);
|
||||
test_predict_roundtrip!(test_u16_predict_roundtrip, U16, u16);
|
||||
test_predict_roundtrip!(test_i16_predict_roundtrip, I16, i16);
|
||||
test_predict_roundtrip!(test_u32_predict_roundtrip, U32, u32);
|
||||
test_predict_roundtrip!(test_u64_predict_roundtrip, U64, u64);
|
||||
|
||||
#[test]
|
||||
fn test_gray_u8_predict_roundtrip() {
|
||||
test_u8_predict_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i8_predict_roundtrip() {
|
||||
test_i8_predict_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u8_predict_roundtrip() {
|
||||
test_u8_predict_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmyk_u8_predict_roundtrip() {
|
||||
test_u8_predict_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u16_predict_roundtrip() {
|
||||
test_u16_predict_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_i16_predict_roundtrip() {
|
||||
test_i16_predict_roundtrip::<colortype::GrayI16>(
|
||||
"minisblack-1c-i16b.tiff",
|
||||
ColorType::Gray(16),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u16_predict_roundtrip() {
|
||||
test_u16_predict_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cmyk_u16_predict_roundtrip() {
|
||||
test_u16_predict_roundtrip::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u32_predict_roundtrip() {
|
||||
test_u32_predict_roundtrip::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u32_predict_roundtrip() {
|
||||
test_u32_predict_roundtrip::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gray_u64_predict_roundtrip() {
|
||||
test_u64_predict_roundtrip::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb_u64_predict_roundtrip() {
|
||||
test_u64_predict_roundtrip::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ycbcr_u8_predict_roundtrip() {
|
||||
test_u8_predict_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
||||
}
|
||||
Reference in new issue
Block a user