Compare commits

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

* Also run clippy and verify msrv

* Fix string mismatch

* Have clippy only check for serious errors
2023-08-20 13:44:35 -07:00
Jonathan Behrens d6f6b8a01e Merge pull request #213 from fintelia/fix-clippy
A bunch of clippy fixes
2023-08-20 13:39:27 -07:00
Jonathan Behrens 8ec91b64f6 Cargo fmt 2023-08-20 13:33:35 -07:00
Jonathan Behrens 60f866c119 A bunch of clippy fixes 2023-08-20 13:32:08 -07:00
Jonathan Behrens daa0e3eabf Merge pull request #206 from fintelia/simplify-jpeg
Simplify and avoid allocation in jpeg decompression
2023-06-14 14:04:01 -07:00
Jonathan Behrens 24d203c7c3 Simplify and avoid allocation in jpeg decompression 2023-06-11 12:54:30 -07:00
Jonathan Behrens 473868a7bb Merge pull request #199 from pka/planar-config
Basic support for planar configuration
2023-06-10 12:46:20 -07:00
Andreas Molzer aefea7786f Merge pull request #203 from AlanRace/custom-compression
Don't panic when parsing metadata when custom compression is used
2023-03-08 09:33:03 +01:00
Alan Race d1ad9b2664 Don't panic when parsing metadata when custom compression is used 2023-03-07 09:15:24 +01:00
Andreas Molzer bd1d085189 Merge pull request #201 from image-rs/release-0.8.2
Add changelog and metadata for 0.9.0
2023-02-06 23:58:59 +01:00
Andreas Molzer fcfcecb2df Add changelog and metadata for 0.8.2 2023-02-06 23:37:12 +01:00
Andreas Molzer aa10d27a28 Merge pull request #200 from anfedotoff/master
Fix capacity overflow in JpegReader
2023-02-06 21:22:13 +01:00
fedotoff 8fa175c371 Fix capacity overflow in JpegReader 2023-02-05 17:53:37 +03:00
Pirmin Kalberer 2a15aa6b7e Support also tiled planar images 2023-01-17 11:57:54 +01:00
Pirmin Kalberer 42feeba8da Add basic support for planar configuration 2023-01-12 23:34:31 +01:00
Jonathan Behrens 0da9770916 Merge pull request #190 from kurtkuehnert/YCbCr_support
Added support for the YCbCr PhotometricInterpretation
2023-01-06 23:54:16 -08:00
Kurt Kühnert c30ea911a2 fixed ci 2023-01-05 10:00:10 +01:00
Kurt Kühnert da07c0f4f5 added round trip test
fixed ci
added colortype
2023-01-05 09:34:54 +01:00
Jonathan Behrens 289fab2fcd Merge pull request #197 from pka/fix-padding
Fix padding when image size is multiple of chunk size
2022-12-16 15:49:58 -08:00
Pirmin Kalberer 21252d9d89 Fix padding when image size is multiple of chunk size 2022-12-16 23:37:38 +01:00
Andreas Molzer ffaccf2b52 Merge pull request #196 from image-rs/release-0.8.1
Changelog and metadata for 0.8.1
2022-12-10 16:57:28 +01:00
Andreas Molzer 4dea7c4c81 Changelog and metadata for 0.8.1 2022-12-10 08:25:57 +01:00
Andreas Molzer aa42fcb1bb Merge pull request #193 from image-rs/issue-192-desync-packbits
Fix read in packbits decoder
2022-12-03 18:49:51 +01:00
Andreas Molzer 1f7b25ef55 Fix read in packbits decoder
This assumed that a `read` would succeed for the full length, thus
interpreting in a subsequent call some data bits as a header.

For instance, consider 77 bytes of literal were left and a buffer of
1024 bytes was provided. Then the `read` call might return 60 which was
ignored and assumed to be 77. Thus the next 17 bytes of literal data
were instead interpreted as a header due to setting `self.count` to 0.
2022-12-03 18:42:08 +01:00
Andreas Molzer 6c65e66440 Merge pull request #170 from AlanRace/jpeg-unwrap
Removed `unwrap` and set colour transform with Jpeg compressed tiffs
2022-11-22 16:17:40 +01:00
Alan Race a733ff13d9 Changed to using private constructor rather than From<> JpegDecoderError 2022-11-22 15:53:42 +01:00
Alan Race b1cd459330 Updated error handling 2022-11-22 15:44:10 +01:00
Alan Race 4f1c88e35f Added unsupported error for interpretations not supported by JPEG 2022-11-22 14:38:27 +01:00
Alan Race 7307e96782 Removed unwrap and set colour transform with Jpeg compressed tiffs 2022-11-22 14:31:33 +01:00
Kurt Kühnert a08604b015 added support for the YCbCr PhotometricInterpretation 2022-11-16 17:33:39 +01:00
HeroicKatora 9333d99c92 Merge pull request #187 from image-rs/release-0.8.0
Changelog for 0.8
2022-11-10 21:02:40 +01:00
Andreas Molzer 65bc24d5d9 Add deprecation removals to changelog 2022-11-10 17:49:32 +01:00
Andreas Molzer 7fc75f194a Changelog for 0.8 2022-11-10 17:28:12 +01:00
HeroicKatora 7679be19c1 Merge pull request #189 from image-rs/non-exhaustive-enumeration
Non exhaustive enumerations
2022-11-10 17:27:31 +01:00
Andreas Molzer 0e9f8e3b5b Mark more tag enums as non_exhaustive 2022-11-10 12:45:04 +01:00
Andreas Molzer c900129f2c Change to non_exhaustive attributes 2022-11-10 12:45:04 +01:00
HeroicKatora 61234c0005 Merge pull request #188 from fintelia/decoder-deprecated
Remove deprecated methods on Decoder
2022-11-05 20:22:38 +01:00
Jonathan Behrens 1416af1018 Remove deprecated methods on Decoder 2022-11-05 10:15:31 -07:00
HeroicKatora 87d652ae5e Merge pull request #186 from micahsnyder/bump-jpeg-decoder-dependency
Bump jpeg-decoder version pin to 0.3.0
2022-11-03 08:28:43 +01:00
Micah Snyder b5a78fbec4 CI: Correct the MSRV from 1.48 to 1.61 2022-11-02 15:00:29 -07:00
Micah Snyder 30640d50ff Bump jpeg-decoder version pin to 0.3.0
The jpeg-decoder project has a new version with a number of improvements
plus a fix for a possible panic.
Semantic versioning considers 0.2.4 to be incompatible with 0.3.X because
of the leading zero, so this change bumps the pin to 0.3.0.
2022-11-02 14:25:18 -07:00
HeroicKatora b87770225f Merge pull request #185 from image-rs/release-0.7.4
Changelog for 0.7.4
2022-10-29 12:44:25 +02:00
Andreas Molzer d38b65eeb2 Changelog for 0.7.4 2022-10-28 22:58:18 +02:00
HeroicKatora ee27553cf6 Merge pull request #180 from luksab/patch-1
Update link to the TIFF specification
2022-10-14 20:14:07 +02:00
Lukas Sabatschus 5a809a728e Update link to the TIFF specification
The old link is unavailable since mid-2017, so we link to archive.org
2022-10-14 13:53:29 +02:00
HeroicKatora b0b99fc536 Merge pull request #173 from micahsnyder/issue-168-handle-jpeg-decoder-errors
Handle possible JPEG decoder errors
2022-09-06 21:17:04 +02:00
Jonathan Behrens 3bce92781c Merge pull request #176 from fintelia/zero-size
Return an error for zero-size images
2022-09-05 13:10:30 -07:00
Jonathan Behrens 907bbee576 cargo fmt 2022-09-05 12:54:21 -07:00
Jonathan Behrens fc0dafcde2 Change error kind 2022-09-05 12:45:22 -07:00
Jonathan Behrens 945a02d1e1 Format 2022-09-05 12:44:42 -07:00
Jonathan Behrens ef3294d1fe Return an error for zero-size images 2022-09-05 12:44:42 -07:00
HeroicKatora 0311402999 Merge pull request #177 from fintelia/fix-warnings
Fix some warnings
2022-09-05 12:45:04 +02:00
Jonathan Behrens 361578df00 Fix some warnings 2022-09-04 12:35:15 -07:00
Jonathan Behrens bd23428f52 Validate that images contain the proper number of strips (#174) 2022-09-04 12:00:34 -07:00
Micah Snyder d32adf8ef4 Handle possible JPEG decoder errors
The JPEG decoder may fail and panic the TIFF decoder. This commit
converts the JPEG `Format(String)` error to a TIFF `Format(String)`
error.

Fixes #168
2022-09-02 15:02:23 -07:00
Jonathan Behrens 2ec1ab7489 Add changelog and metadata for 0.7.3 (#167) 2022-07-24 10:31:37 -07:00
Jonathan Behrens 80e7c46151 Merge pull request #163 from AlanRace/access-tile
Add ability to select image directory and tile using index
2022-07-23 15:44:45 -07:00
Jonathan Behrens 5003803c97 cargo fmt 2022-07-23 15:27:02 -07:00
Jonathan Behrens 7c4a6fae08 Update src/decoder/mod.rs 2022-07-18 19:36:35 -07:00
Jonathan Behrens 723ef6431a Update src/decoder/mod.rs 2022-07-18 19:36:28 -07:00
Alan Race 802949f6e1 Removed ChunkInfo and added additional functions on decoder 2022-07-18 17:15:44 +02:00
Alan Race 5cadbffebf Changed to use u32 for chunk_index 2022-07-11 08:56:35 +02:00
Alan Race e1f4161e0b Removed unused functions and make InvalidChunkIndex a UsageError 2022-07-08 11:08:30 +02:00
Alan Race 8c0c13f022 Added read_chunk_at and read_chunk_to_buffer functions 2022-07-07 15:25:01 +02:00
Alan Race d5e3f4aa8a Merge remote-tracking branch 'image-tiff/master' into access-tile 2022-07-07 15:06:56 +02:00
Jonathan Behrens 3c42a6e1f4 Merge pull request #165 from fintelia/improve-predictor
Floating point predictor
2022-06-27 07:36:45 -07:00
Jonathan Behrens 5e7702a874 Suggested edits 2022-06-26 20:45:21 -07:00
Jonathan Behrens 8e5c5b44c5 Support floating point predictor (predictor 3)
Based on work by @TomasKralCZ in #135
2022-06-22 20:14:25 -07:00
Jonathan Behrens 8a6e93f66e Merge fix_endianness and rev_hpredict methods 2022-06-22 20:13:55 -07:00
Alan Race 6e398ce7e3 Remove ChunkInfo from mod.rs that was added through merge 2022-05-09 09:58:51 +02:00
Alan Race 8bd02ac389 Merge remote-tracking branch 'origin/access-tile' into access-tile 2022-05-09 09:57:24 +02:00
Alan Race 0447d81b07 Updated to use new unified chunk handling 2022-05-09 09:56:46 +02:00
Alan Race 54664d553d Added comments for read_tile, read_strip and read_*_at 2022-05-09 09:17:39 +02:00
Alan Race 6bec36ab20 Reverted read_strip and read_tile to previous call signature
Added comments to ChunkInfo
2022-05-09 09:17:39 +02:00
Alan Race e57ca1a926 Added functionality for loading selected strip 2022-05-09 09:17:27 +02:00
Alan Race ca4da33dd7 Mirrored return value of read_tile to read_strip 2022-05-09 09:16:35 +02:00
Alan Race 6d4c4a49a1 read_tile now returns ChunkInfo 2022-05-09 09:16:13 +02:00
Alan Race d8520bde1f Now using the list of offsets to know how many IFDs we have seen 2022-05-09 09:14:56 +02:00
Alan Race 56c20c2c20 Added ability to select which image (IFD) to load 2022-05-09 09:14:56 +02:00
Alan Race 6efa89c249 Added ability to select which tile to load (based on index)
Also reduced repeated code when calculating padding etc.
2022-05-09 09:14:04 +02:00
HeroicKatora 68d677d1b3 Merge pull request #164 from fintelia/unify-strip-tile
Unify strip and tile decoding
2022-05-05 18:35:53 +02:00
Alan Race 72ca636625 Added comments for read_tile, read_strip and read_*_at 2022-05-03 09:27:23 +02:00
Alan Race 1f501b5acb Reverted read_strip and read_tile to previous call signature
Added comments to ChunkInfo
2022-05-03 09:21:23 +02:00
Jonathan Behrens 697d2877a1 Cargo fmt 2022-05-02 20:37:37 -07:00
Jonathan Behrens 805a1e5cef Rename chunk_byte_range method 2022-05-02 19:47:41 -07:00
Jonathan Behrens dc8311fb99 Merge read_tiled_image and read_stripped_image 2022-05-02 19:46:28 -07:00
Jonathan Behrens f25530adec Unify expand_strip and expand_tile 2022-05-02 19:37:00 -07:00
Alan Race dd8c9e4f2b Added functionality for loading selected strip 2022-05-02 15:59:48 +02:00
Alan Race 8f34748d01 Mirrored return value of read_tile to read_strip 2022-05-02 15:39:14 +02:00
Alan Race 4ce3c2f41f read_tile now returns ChunkInfo 2022-05-02 15:35:48 +02:00
Alan Race f809d7d1bb Fixed compilation issue after merging 2022-05-02 08:47:05 +02:00
Alan Race 902f945375 Merge branch 'tiled-jpeg' into access-tile 2022-05-02 08:41:29 +02:00
Jonathan Behrens d437c2708b Merge pull request #162 from AlanRace/tiled-jpeg
Added ability to decompress tiled jpeg files
2022-05-01 22:31:14 -07:00
Alan Race a133056287 Removed passing of PhotometricInterpretation to create_reader
as this is now handled for JPEG globally
2022-04-30 14:55:52 +02:00
Alan Race ce00bb5a28 Removed old code 2022-04-30 14:35:33 +02:00
Alan Race 623a3d957d Avoid cloning jpeg_tables and instead pass Arc<Vec<u8>> 2022-04-30 14:33:12 +02:00
Alan Race 9e6f9771d3 Updated create_reader to only require &mut SmartReader and not &mut Self 2022-04-28 08:54:32 +02:00
Alan Race b196d19238 Now using the list of offsets to know how many IFDs we have seen 2022-04-27 17:50:34 +02:00
Alan Race 436cfaf69f Added ability to select which image (IFD) to load 2022-04-27 17:38:44 +02:00
Alan Race 8fcf49deac Added ability to select which tile to load (based on index)
Also reduced repeated code when calculating padding etc.
2022-04-27 16:46:48 +02:00
Alan Race 2cf33d634d Clean up read_image 2022-04-27 15:00:22 +02:00
Alan Race eeb79ee07a Reduce number of clones when processing jpeg data 2022-04-27 14:46:52 +02:00
Alan Race 592405268e Added ability to decompress tiled jpeg files 2022-04-27 14:05:41 +02:00
HeroicKatora 9f2508e37e Merge pull request #160 from fintelia/stream-pack-bits
Make PackBitsReader incrementally decompress output
2022-04-16 22:22:28 +02:00
Jonathan Behrens 4f46bacfda Fixes to PackBitsReader 2022-04-16 16:10:09 -04:00
Jonathan Behrens b286457337 Remove dbg! print 2022-04-16 15:52:09 -04:00
Jonathan Behrens 7881291032 Make PackBitsReader incrementally decompress output 2022-04-15 12:25:24 -04:00
HeroicKatora 167088b33d Merge pull request #159 from fintelia/stream-lzw
Make LZWReader incrementally decompress input
2022-04-15 17:35:43 +02:00
Jonathan Behrens ff33343372 Fix formatting 2022-04-15 11:28:56 -04:00
Jonathan Behrens 4097bf37b1 Rename test oom_2 -> fuzzer_testcase4 2022-04-15 10:04:05 -04:00
Jonathan Behrens c910ef8534 Remove redundant limit on LZWReader output length 2022-04-15 10:02:33 -04:00
Jonathan Behrens 1063d6dd27 Have LZWReader internally use a BufReader<Take<R>> 2022-04-15 09:49:18 -04:00
Jonathan Behrens e4eac36c1c Remove now unneeded limit check 2022-04-14 21:40:19 -04:00
Jonathan Behrens 9394f6d043 Make LZWReader incrementally decompress input 2022-04-14 21:00:36 -04:00
HeroicKatora 3399f93620 Merge pull request #158 from image-rs/release-0.7.2
Add changelog and metadata for 0.7.2
2022-04-11 18:48:10 +02:00
Andreas Molzer ef7592cf82 Add changelog and metadata for 0.7.2 2022-04-11 18:22:39 +02:00
Jonathan Behrens 7cdfd154ba Merge pull request #155 from Aaron1011/update/jpeg-decoder
Update jpeg-decoder to 0.2.1
2022-02-09 00:44:56 -05:00
Aaron Hill fa0d6eb99f Update jpeg-decoder to 0.2.1 2022-02-08 19:55:40 -05:00
Jonathan Behrens a780da0b34 Merge pull request #154 from fintelia/decoder-refactor
Decoder refactor
2022-01-15 17:04:11 -05:00
Jonathan Behrens ca56836a79 Add back fuzzer generated testcases 2022-01-15 15:11:54 -05:00
Jonathan Behrens 2c7c8891f4 Remove use half-open range pattern which is unstable on rustc 1.48 2022-01-12 20:46:17 -05:00
Jonathan Behrens 01df985ef8 Fix formatting 2022-01-12 20:13:23 -05:00
Jonathan Behrens 593479e5e3 Avoid reading tags in read_jpeg 2022-01-12 19:56:08 -05:00
Jonathan Behrens bc2055243a Add consistency check between StripOffsets and StripByteCounts 2022-01-12 19:56:08 -05:00
Jonathan Behrens 7a7d4f0305 Avoid unused warning 2022-01-12 19:56:08 -05:00
Jonathan Behrens 720e6586b9 Make read_ifd and read_entry stateless 2022-01-12 19:56:08 -05:00
Jonathan Behrens 47080293db Move image loading to Image::from_reader 2022-01-12 19:56:02 -05:00
Jonathan Behrens b385a6e752 Move some decoder fields into a new Image struct 2022-01-12 19:50:06 -05:00
Jonathan Behrens 6ba5553d20 Compute tile attributes on demand 2022-01-12 19:50:06 -05:00
Jonathan Behrens a36cd6bd90 chunk_{offsets,bytes} to replace {tile,strip}_{offsets,bytes} 2022-01-12 19:50:06 -05:00
Jonathan Behrens 63e6ef8bba Add current_chunk to replace strip_index and current_tile 2022-01-12 19:50:06 -05:00
Jonathan Behrens 2d10cff97e Reading fields shouldn't need the entire Decoder 2022-01-12 19:50:06 -05:00
Jonathan Behrens 23fe194ad9 Remove read_ifd_offset_nonrepeating 2022-01-12 19:50:06 -05:00
Jonathan Behrens 2d572c0370 Remove Decoder::byte_order 2022-01-12 19:50:06 -05:00
Jonathan Behrens 4c942ca690 Read JPEGTables ahead of time 2022-01-12 19:50:06 -05:00
Jonathan Behrens a19d35233a Read predictor ahead of time 2022-01-12 19:50:06 -05:00
Jonathan Behrens dc95ce10ac Remove some extremely brittle tests 2022-01-12 19:49:55 -05:00
Jonathan Behrens 31da6737b9 More refactoring 2022-01-12 19:45:41 -05:00
Jonathan Behrens e9aa39d895 Initialize Decoder in new instead of init 2022-01-12 19:45:41 -05:00
HeroicKatora 77f15b5c0a Merge pull request #153 from mindQ-GmbH-Co-KG/compression-release
fixed deflate and packbits
2022-01-11 18:53:12 +01:00
Pretorer 6f89033fd3 moved namespace qualifier 2022-01-11 15:51:34 +01:00
Pretorer 9df18a1674 fixed deflate and packbits 2022-01-11 15:43:00 +01:00
HeroicKatora 37cfcf82aa Merge pull request #151 from mindQ-GmbH-Co-KG/compression-release
Add TIFF compression during encoding and corresponding tests
2022-01-10 22:30:19 +01:00
Pretorer 1d8cbbc6a1 add Packbits compression 2022-01-10 11:57:59 +01:00
Pretorer 9f1e3d7a89 moved compression into TiffWriter 2022-01-10 11:55:08 +01:00
Jonathan Behrens fb4c119b88 Merge pull request #152 from fintelia/refactor-decode
Refactor strip/tile decoding
2022-01-05 21:48:14 -05:00
Jonathan Behrens 3242d328d1 Reformat 2021-12-22 12:06:12 -05:00
Jonathan Behrens d6c42dd60a Some refactoring of strip/tile decoding 2021-12-22 12:05:38 -05:00
Christopher Gundler de95ac35a2 Remove "CompressionError" 2021-12-22 12:29:39 +01:00
Christopher Gundler 30bf508983 Avoid allocations 2021-12-22 11:10:13 +01:00
Christopher Gundler bc7be68d0f Simplify tests 2021-12-22 10:48:52 +01:00
Christopher Gundler 99b2aa9b3a Add TIFF compression and corresponding tests 2021-12-21 16:47:11 +01:00
HeroicKatora d7d63e648b Merge pull request #149 from image-rs/release-0.7.1
Release 0.7.1
2021-09-22 17:47:02 +02:00
Andreas Molzer b8459f413a Add fuzzing work and bugs to release notes 2021-09-22 01:20:00 +02:00
Andreas Molzer 26e760e303 Add notes on MSRV 2021-09-22 01:13:10 +02:00
Andreas Molzer a4878e9658 Release notes for 0.7.1 2021-09-22 01:05:25 +02:00
HeroicKatora 73285481cd Merge pull request #141 from 5225225/oom
Fix out of memory issues / misc other decoding bugs.
2021-09-18 15:51:33 +02:00
5225225 b065539c07 Add fuzzing instructions to readme, bump CI version to 1.48 2021-09-18 14:42:53 +01:00
5225225 1c5800daa0 Infinitely try to read off images rather than only trying 128 2021-09-18 12:14:17 +01:00
5225225 885d762def Implement cycle checking in IFD offsets 2021-09-08 18:08:10 +01:00
5225225 2c9e02d347 Merge remote-tracking branch 'origin/master' into oom 2021-09-06 17:53:16 +01:00
Jonathan Behrens b318481c55 Merge pull request #143 from Christopher22/reset_tag_values
Do not carry tag values from previous images
2021-08-15 11:43:44 -04:00
5225225 1774b362d3 x 2021-07-15 18:08:40 +01:00
Jonathan Behrens f2fec92283 Merge pull request #146 from fintelia/encode-signed-int
Support encoding signed integer formats
2021-06-20 18:39:08 -04:00
Jonathan Behrens be0619d6a1 Support encoding signed integer formats 2021-06-20 14:33:13 -04:00
Tomáš Král e92cf219a9 Fix the mandrill.tiff regression (#145)
* fix mandrill.tiff regression from upstream

* use contains_key(), added StripTileTagConflict error

* updated test_div_zero, added test_no_rows_per_strip
2021-06-17 09:22:07 -04:00
Christopher Gundler 3e5f977460 Do not carry tag values from previous images 2021-06-14 20:21:51 +02:00
5225225 f0666aebbf Fix overflow when calculating strip height for multiple images 2021-05-29 22:52:42 +01:00
5225225 65a1fbe8ae Test for short JPEG tag 2021-05-29 22:12:35 +01:00
5225225 03b6ed413b Fix out of bounds subtraction with invalid JPEG table 2021-05-29 22:12:35 +01:00
5225225 5b7814c994 Check for OOM when creating LZWReader 2021-05-29 22:12:35 +01:00
5225225 a15d619471 Fix OOM in Jpeg decoding buffer allocation 2021-05-29 22:12:35 +01:00
HeroicKatora 458a713827 Merge pull request #140 from 5225225/fuzzer
Add fuzzer with 1MB size limits decoding image
2021-05-29 23:12:04 +02:00
5225225 2f6ad07f28 Try to read multiple images in fuzzer 2021-05-29 22:00:36 +01:00
HeroicKatora 153ed48ee7 Merge pull request #136 from TomasKralCZ/padding_skip
remove padding_buffer
2021-05-29 15:13:20 +02:00
5225225 420667955a Add fuzzer with 1MB size limits decoding image 2021-05-29 13:18:35 +01:00
HeroicKatora 3bf19822a2 Merge pull request #139 from 5225225/value-bytes-overflow
Check for various arithmetic overflows
2021-05-29 13:48:03 +02:00
5225225 2bb37a4ecd Do checked multiplication for samples per strip 2021-05-29 12:37:15 +01:00
5225225 b41ac105ab Use checked multiplication when calculating buffer size 2021-05-29 12:36:59 +01:00
5225225 1e10b9803b Add check for an invalid strip count 2021-05-29 12:36:50 +01:00
5225225 2f4e491874 Check for where tag count * tag size overflows 2021-05-28 20:32:30 +01:00
HeroicKatora 285b881e27 Merge pull request #138 from 5225225/divide_by_zero
Fix division by zero issue with malformed file
2021-05-28 19:59:37 +02:00
5225225 bdfe2f55fd Check error returned from parsing invalid image 2021-05-28 15:46:55 +01:00
5225225 eda5a8ac30 Fix division by zero issue with malformed file 2021-05-28 14:44:50 +01:00
HeroicKatora bd1e37d78b Merge pull request #130 from phil-opp/release
Prepare 0.7.0 release
2021-05-25 21:03:44 +02:00
Andreas Molzer e8bc935618 Mention changed interpretation of MinIsWhite 2021-05-25 20:27:46 +02:00
Philipp Oppermann 150ff8303b Add changes of #132 and #134 2021-05-25 20:19:01 +02:00
Philipp Oppermann e44b98ddf9 Prepare 0.7.0 release 2021-05-25 20:19:01 +02:00
Tomáš Král ad0f75222a remove padding_buffer 2021-05-21 18:06:05 +02:00
HeroicKatora 4df26011d9 Merge pull request #133 from TomasKralCZ/inversion
expand_strip() and expand_tile() cleanup
2021-05-18 18:37:25 +02:00
Tomáš Král 4dc7c2a986 add comment in expand_strip() 2021-05-18 18:31:17 +02:00
Tomáš Král b7c4f668ae remove redundant subrange and outdated comments 2021-05-16 12:00:15 +02:00
Jonathan Behrens 9fe9b2e0a0 Merge pull request #134 from RReverser/remove-inflate-error
Remove InflateError
2021-05-15 16:50:36 -04:00
Ingvar Stepanyan b7e8f210d1 Remove InflateError
It was left in but marked as deprecated as part of #132 to avoid breaking API changes.

However, #130 is going to mark a 0.7.0 release, so we can remove it altogether for simplicity.
2021-05-15 21:43:42 +01:00
Tomáš Král 371a9b53c3 fix u64::MAX error on older compilers 2021-05-15 21:31:11 +02:00
Tomáš Král d0e4f37a28 implement subrange for DecodingBuffer 2021-05-15 21:14:00 +02:00
Tomáš Král 46c7efd836 remove unneeded mutability 2021-05-15 19:55:48 +02:00
Tomáš Král 65832e01bd Merge branch 'master' into inversion 2021-05-15 19:49:01 +02:00
Tomáš Král b9590e0842 remove WhiteIsZero i8 test 2021-05-15 18:15:24 +02:00
Tomáš Král 521b58dc07 expand_strip and expand_tile cleanup 2021-05-15 18:12:34 +02:00
Jonathan Behrens e70121efbb Merge pull request #132 from RReverser/deflate-improvements
Improve deflate support
2021-05-14 17:58:11 -04:00
Ingvar Stepanyan a6fb855121 Improve deflate support
- Switch to streaming decompression via `flate2`. Aside from performance improvements and lower RAM consumption, this fixes a bug where `max_uncompressed_length` was precalculated for a single tile but then used as a hard limit on the whole data, failing to decompress any tiled images.
 - Deprecate `InflateError` - this was used for wrapping `miniz_oxide` raw status, but `flate2` already reports them as `io::Error` instead. This makes errors from both Lzw and Deflate modes more consistent - they're all now just `io::Error`.
 - Add support for new `Deflate` tag in addition to `OldDeflate` - format-wise it's same Zlib, and on practice decodes successfully with the same underlying code.
 - Slightly simplify partial reads from the `Reader` by using the `std::io::copy` helper instead of doing it manually.

Fixes #131.
2021-05-08 15:34:14 +01:00
HeroicKatora a874b9c3a5 Merge pull request #125 from TomasKralCZ/tiles
Added basic tile decoding
2021-04-18 18:26:53 +02:00
Tomáš Král d4ce174236 Merge remote-tracking branch 'upstream/master' into tiles 2021-04-14 13:32:56 +02:00
HeroicKatora a43aa1fbc7 Merge pull request #126 from fintelia/better-decompress
Simplify de-compression logic
2021-04-12 18:36:07 +02:00
HeroicKatora 69c34ac33c Merge pull request #128 from fintelia/simplify-expand3
Simplify expand_strip
2021-04-12 18:35:48 +02:00
Tomáš Král 00e0eb9006 incremental tile decoding, refactoring 2021-04-12 15:09:39 +02:00
Tomáš Král ebd0e266f7 Merge remote-tracking branch 'upstream/master' into tiles 2021-04-12 14:52:38 +02:00
HeroicKatora 9b94e9de88 Merge pull request #122 from phil-opp/encode-bigtiff
Add support for encoding BigTiff
2021-04-12 12:46:59 +02:00
Philipp Oppermann 47dfa0aa3e Merge branch 'master' into encode-bigtiff 2021-04-12 11:34:41 +02:00
Jonathan Behrens 1eb9c4fab7 fix borrow check issue on rustc 1.34.2 2021-04-10 21:45:04 -04:00
Jonathan Behrens 201b95f749 Improve expand_strip and remove read_{u16, u32, ...}_into 2021-04-10 21:34:57 -04:00
Jonathan Behrens 7641dcbd3f Refactoring to simplify expand_strip function 2021-04-10 15:02:16 -04:00
Jonathan Behrens 60c6e0cd90 Simplify de-compression logic 2021-04-10 14:49:04 -04:00
Tomáš Král 65a1c553af TileAttributes doc comments, padding buffer limit check 2021-04-06 20:42:48 +02:00
Tomáš Král fddc048849 changed test photos to avoid coyright, added whiteiszero test 2021-04-06 13:58:33 +02:00
Tomáš Král 68f2182918 fixed WhiteIsZero processing 2021-04-04 19:34:14 +02:00
Tomáš Král b2a7e35c85 added basic tiled-based decoding 2021-04-04 18:15:28 +02:00
HeroicKatora 91fe529859 Merge pull request #123 from kalcutter/add-s-minmax-samplevalue-tags
Add `SMinSampleValue` and `SMaxSampleValue` tags
2021-04-03 18:53:27 +02:00
Kal Conley 5ca2730416 Add SMinSampleValue and SMaxSampleValue tags 2021-04-03 18:45:01 +02:00
HeroicKatora 8fcacc829f Merge pull request #119 from kalcutter/missing-tags-and-i64
Implement missing tag types and signed 64-bit support
2021-03-24 12:25:01 +01:00
Philipp Oppermann 59d0b7389d Run rustfmt 2021-03-24 11:11:56 +01:00
Philipp Oppermann 0ac444b63f Improve documentation 2021-03-24 11:10:42 +01:00
Philipp Oppermann ce1ee6776f Move TiffValue trait to separate module 2021-03-24 10:47:23 +01:00
Philipp Oppermann 29a020e5ec Clarify TODO 2021-03-23 14:44:39 +01:00
Philipp Oppermann 9021985477 Provide documentation for TiffKind helper trait 2021-03-23 14:42:05 +01:00
Philipp Oppermann d4c958f915 Add a test for bigtiff encoding 2021-03-23 14:29:56 +01:00
Philipp Oppermann dc04282290 Fix: Use the correctly parametrized directory and image encoder types 2021-03-23 14:29:45 +01:00
Philipp Oppermann 4cfed1c7b1 Make encoders generic to support BigTiff too 2021-03-23 13:49:54 +01:00
Philipp Oppermann 745ed62808 Change associated const types of TiffValue to usize/u8
This allows us to reuse the trait for BigTiff encoding. It also makes things safer for normal Tiff since we properly error on 32 bit overflows now.
2021-03-23 10:56:33 +01:00
HeroicKatora 6fe9b78509 Merge pull request #121 from emmanuelantony2000/ascii_tag_validation
Validation for ASCII tag type
2021-03-20 13:28:32 +01:00
Emmanuel Antony f5754159cb Modified the validation for ascii tag type 2021-03-20 08:47:47 +05:30
HeroicKatora 53d0256585 Merge pull request #120 from atouchet/ga
Replace Travis badge with GitHub Actions
2021-03-20 00:45:18 +01:00
Alex Touchet 09aa28d699 Replace Travis badge with GitHub Actions 2021-03-19 16:33:30 -07:00
Kal Conley d086e289dd Implement missing tag types and signed 64-bit support 2021-03-17 01:24:32 +01:00
Jonathan Behrens 2cba04dfe7 Merge pull request #118 from satarsa/master
Open tif files containing i32 arrays
2021-02-24 09:44:37 -05:00
Vadim Dyadkin 3de87a7001 Open tif files containing i32 arrays 2021-02-20 18:25:29 +01:00
HeroicKatora 4900c82871 Merge pull request #114 from HagaiHargil/int16_support
Int8/16 support
2020-12-14 00:21:51 +01:00
HagaiHargil 8a8e3ed753 Rustfmt 2020-12-13 12:03:53 +02:00
HagaiHargil cd2f4f6462 Added tests and finalized i8 support 2020-12-13 12:00:48 +02:00
HagaiHargil aa13e31894 Initial i8 support 2020-12-13 11:37:56 +02:00
HeroicKatora 6c211b9179 Merge pull request #115 from image-rs/release-0.6.1
Change lot and meta data for 0.6.1
2020-12-12 20:24:40 +01:00
Andreas Molzer 332ab1dc4d Use WhiteIsZero interpretation of libtiff
Note that the variants are even named `MINISWHITE` in their code.
Address the remaining review nits.
2020-12-11 20:36:33 +01:00
HagaiHargil ec98e32a3b Removed unused wrapping f32 and f64 2020-12-08 13:46:09 +02:00
HagaiHargil 7ec2b75c70 Added int16 tests 2020-12-08 13:42:08 +02:00
HagaiHargil 9788085786 Int16 support 2020-12-08 13:33:25 +02:00
60 changed files with 5047 additions and 1861 deletions

No files matched your search

+42 -17
View File
@@ -9,32 +9,57 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
rust: ["1.34.2", stable, beta, nightly] rust: ["1.74.0", stable, beta, nightly]
command: [build, test]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- run: rustup default ${{ matrix.rust }}
- uses: dtolnay/rust-toolchain@nightly
if: ${{ matrix.rust == '1.74.0' }}
- name: Generate Cargo.lock with minimal-version dependencies
if: ${{ matrix.rust == '1.74.0' }}
run: cargo -Zminimal-versions generate-lockfile
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ matrix.rust }}
- name: Cache Cargo Dependencies
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- name: build - name: build
run: > run: cargo build -v
cargo build --verbose --no-default-features --features "$FEATURES"
- name: test - name: test
run: > if: ${{ matrix.rust != '1.74.0' }}
cargo test --tests --benches --no-default-features --features "$FEATURES" run: cargo test -v && cargo doc -v
if: ${{ matrix.rust != '1.34.2' }}
env: features:
FEATURES: ${{ matrix.features }} runs-on: ubuntu-latest
strategy:
matrix:
feature: ["", deflate, jpeg, lzw, zstd, default, "default,zstd"]
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cargo check
run: cargo check --features "${{ matrix.feature }}" --no-default-features
rustfmt: rustfmt:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable
override: true
components: rustfmt components: rustfmt
- name: Run rustfmt check - name: Run rustfmt check
uses: actions-rs/cargo@v1 run: cargo fmt -- --check
with:
command: fmt
args: -- --check
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo clippy --all-features # TODO: enable "-- -D warnings" once all outstanding clippy lints are fixed
+172
View File
@@ -1,3 +1,175 @@
# Version 0.10.0
New features:
- Added `ColorType::Multiband` and `ColorType::CMYKA`.
- Added `Value::Byte` and `Value::SignedByte` to represent `u8` and `i8` tags.
- Encoding can now use horizontal predictor.
- The `Decoder::tag_iter` method allows iterating over all tags in the image.
- Half precision floating point values are now supported during decoding.
- ZStandard compression has been added as an additional compression method
during decoding, but requires the `zstd` feature flag.
Changes:
- Switched `u16` and `i16` values to be intepreted as `Value::Short` and
`Value::SignedShort` respectively.
- Changed JPEG compression to use the `zune-jpeg` crate instead of
`jpeg-decoder`.
- The `deflate`, `jpeg`, and `lzw` compression methods are now controlled by
(enabled by default) feature flags.
Fixes:
- Fixed a bug in 1 bit-per-pixel decoding.
# Version 0.9.1
New features:
- Basic support for planar configuration.
- Allow arbitrary number of samples as long as all have the same bit depth.
Fixes:
- Don't panic when parsing metadata when custom compression is used.
# Version 0.9.0
New features:
* Added support for photometric interpretation `YCbCr` and added related
`ColorType`.
Fixes:
* Decoding tiled images calculates padding correctly when image width or height
is a multiple of tile size. It could previously corrupt the last tile per row
by skipping over data.
# Version 0.8.1
Changes:
* The jpeg decoder gained to ability to utilize the Photometric Interpretation
directly instead of relying on a custom APP segment.
Fixes:
* A spurious error within the PackBits decoder lead to the incorrect results
(wrong bits or errors), based on the maximum size of reads from the
underlying reader.
* Removed a panic path in jpeg decoding, when a feature such as photometric
interpretation is not supported. An error is returned instead.
# Version 0.8.0
Changes:
* The minimum supported rust version is now indicated in `Cargo.toml`.
* The enums `TiffFormatError` and `TiffUnsupportedError` are now
marked with the `#[non_exhaustive]` attribute.
* Additionally, tag related enums `Value`, `Tags`, `Type`, `CompressionMethod`,
`PhotometricInterpretation`, `PlanarConfiguration`, `Predictor`,
`ResolutionUnit`, `SampleFormat` are also changed.
Removals:
* Removed deprecated methods of `Decoder`: `init`, `read_jpeg`,
`read_strip_to_buffer`, `read_strip`, `read_tile`. The implicit chunk (row or
tile) index order could not be easily tracked by the caller. New separate
utility interfaces may be introduced at a later point, for now callers are
obligated to choose their own.
Fixes:
* Update to `jpeg_decoder = 0.3`.
# Version 0.7.4
New features:
* Creating an encoder for invalid, zero-sized images is now rejected.
Fixes:
* Fix panic, in a case where decoding jpeg encoded images did not expect the
jpeg decoder to return an error.
* Fix panic by validating `rows_per_strip` better, fixing a division-by-zero.
# Version 0.7.3
New features:
* Allow decoder to access specific tiles by index.
* Add support for floating point predictor.
* Tiled jpeg file support.
Changes:
* Various refactoring and performance improvements.
# Version 0.7.2
New features:
* Encoding with `ImageEncoder` now takes an optional compressor argument,
allowing compressed encoding. See the methods
`TiffEncoder::{new_image,write_image}_with_compression`.
* `jpeg_decoder` has been upgraded, now supports lossless JPEG.
Changes:
* Decoding now more consistently reads and interprets the initial IFD, instead
of performing _some_ interpretation lazily. (This change prepares fully lazy
and backwards seeking.)
# Version 0.7.1
New features:
* Encoding signed integer formats is now supported.
* Extensive fuzzing with `cargo fuzz`.
Changes:
* Tile decoding should be a little faster, requires one less intermediate buffer.
* Images whose IFDs form a cycle due to their offsets will now raise an error
when the cycle would be entered (jumping back should still be supported).
Fixes:
* Fixed a regression that caused conflict between strips and tile images,
causing errors in decoding some images.
* Use checked integer arithmetic in limit calculations, fixes overflows.
* IFD Tags are now always cleared between images.
* Found by fuzzing: Several memory limit overflows; JPEG now correctly
validates offsets and a minimum size of its table; Check upper limit of strip
byte size correctly;
Notes:
Our CI has warned that this version no longer builds on `1.34.2` out of the
box. We're still committed to the MSRV on this major version yet one
dependency—`flate2`—has already bumped it in a SemVer compatible version of its
own. This is out-of-our-control (cargo's dependency resolution does not allow
us to address this in a reasonable manner).
This can be address this by pinning the version of `flate2` to `1.0.21` in your
own files. However, you should understand that this puts you in considerable
maintenance debt as you will no longer receive any updates for this dependency
and any package that _requires_ a new version of the `1.0` series would be
incompatible with this requirement (cargo might yell at you very loudly).
# Version 0.7.0
New features:
* Support for encoding BigTiff ([#122](https://github.com/image-rs/image-tiff/pull/122))
* _Breaking:_ Encoder types now have a generic parameter to differentiate BigTiff and standard Tiff encoding. Defaults to standard Tiff.
* Basic tile decoding ([#125](https://github.com/image-rs/image-tiff/pull/125))
* _Breaking:_ There is a new `TiffError::UsageError` variant.
* Support for datatypes `Int8` and `Int16` ([#114](https://github.com/image-rs/image-tiff/pull/114))
* _Breaking:_ `DecodingResult` and `DecodingBuffer` have the two new variants `I8` and `I16`.
* Support for `i32` arrays ([#118](https://github.com/image-rs/image-tiff/pull/118/files))
* _Breaking:_ `DecodingResult` and `DecodingBuffer` have a new `I32` variant.
* Support for `Ifd` and `IfdBig` tag types and `I64` data type ([#119](https://github.com/image-rs/image-tiff/pull/119))
* _Breaking:_ `DecodingResult` and `DecodingBuffer` have a new `I64` variant.
* Add `SMinSampleValue` and `SMaxSampleValue` ([#123](https://github.com/image-rs/image-tiff/pull/123))
Changes:
* Improve deflate support ([#132](https://github.com/image-rs/image-tiff/pull/132))
* Switch to streaming decompression via `flate2`. Aside from performance improvements and lower RAM consumption, this fixes a bug where `max_uncompressed_length` was precalculated for a single tile but then used as a hard limit on the whole data, failing to decompress any tiled images.
* Add support for new `Deflate` tag in addition to `OldDeflate`.
* _Breaking:_ Remove `InflateError`, which is no longer needed with `flate2` ([#134](https://github.com/image-rs/image-tiff/pull/134))
* _Breaking:_ Support for `MinIsWhite` is restricted to unsigned and floating
point values. This is expected to be be re-added once some contradictory
interpretation regarding semantics for signed values is resolved.
Fixes:
* Validate that ASCII tags are valid ASCII and end with a null byte ([#121](https://github.com/image-rs/image-tiff/pull/121))
Internal:
* Simplify decompression logic ([#126](https://github.com/image-rs/image-tiff/pull/126))
* Simplify `expand_strip` ([#128](https://github.com/image-rs/image-tiff/pull/128))
# Version 0.6.1 # Version 0.6.1
New features: New features:
+26 -17
View File
@@ -1,33 +1,42 @@
[package] [package]
name = "tiff" name = "tiff"
version = "0.6.1" version = "0.10.0"
authors = [ edition = "2021"
"ccgn", resolver = "2"
"bvssvni <bvssvni@gmail.com>",
"nwin", # note: when changed, also update test runner in `.github/workflows/rust.yml`
"TyOverby <ty@pre-alpha.com>", rust-version = "1.74.0"
"HeroicKatora",
"Calum",
"CensoredUsername <cens.username@gmail.com>",
"Robzz",
"birktj"]
license = "MIT" license = "MIT"
description = "TIFF decoding and encoding library in pure Rust" description = "TIFF decoding and encoding library in pure Rust"
authors = ["The image-rs Developers"]
repository = "https://github.com/image-rs/image-tiff" repository = "https://github.com/image-rs/image-tiff"
categories = ["multimedia::images", "multimedia::encoding"]
exclude = ["tests/images/*", "tests/fuzz_images/*"] exclude = ["tests/images/*", "tests/fuzz_images/*"]
edition = "2018"
[dependencies] [dependencies]
weezl = "0.1.0" half = { version = "2.4.1" }
jpeg = { package = "jpeg-decoder", version = "0.1.17", default-features = false } quick-error = "2.0.1"
[dependencies.miniz_oxide] flate2 = { version = "1.0.20", optional = true }
version = "0.4.1" weezl = { version = "0.1.0", optional = true }
features = ["no_extern_crate_alloc"] zstd = { version = "0.13", optional = true }
zune-jpeg = { version = "0.4.17", optional = true }
[dev-dependencies] [dev-dependencies]
criterion = "0.3.1" criterion = "0.3.1"
[features]
default = ["deflate", "jpeg", "lzw"]
# Compression algorithms
deflate = ["dep:flate2"]
jpeg = ["dep:zune-jpeg"]
lzw = ["dep:weezl"]
zstd = ["dep:zstd"]
[[bench]] [[bench]]
name = "lzw" name = "lzw"
harness = false harness = false
+17 -6
View File
@@ -1,5 +1,5 @@
# image-tiff # image-tiff
[![Build Status](https://travis-ci.org/image-rs/image-tiff.svg?branch=master)](https://travis-ci.org/image-rs/image-tiff) [![Build Status](https://github.com/image-rs/image-tiff/workflows/Rust%20CI/badge.svg)](https://github.com/image-rs/image-tiff/actions)
[![Documentation](https://docs.rs/tiff/badge.svg)](https://docs.rs/tiff) [![Documentation](https://docs.rs/tiff/badge.svg)](https://docs.rs/tiff)
[![Further crate info](https://img.shields.io/crates/v/tiff.svg)](https://crates.io/crates/tiff) [![Further crate info](https://img.shields.io/crates/v/tiff.svg)](https://crates.io/crates/tiff)
@@ -17,24 +17,27 @@ TIFF decoding and encoding library in pure Rust
This table lists photometric interpretations and sample formats which are supported for encoding and decoding. The entries are `ColorType` variants for which sample bit depths are supported. Only samples where all bit depths are equal are currently supported. For example, `RGB(8)` means that the bit depth [8, 8, 8] is supported and will be interpreted as an 8 bit per channel RGB color type. This table lists photometric interpretations and sample formats which are supported for encoding and decoding. The entries are `ColorType` variants for which sample bit depths are supported. Only samples where all bit depths are equal are currently supported. For example, `RGB(8)` means that the bit depth [8, 8, 8] is supported and will be interpreted as an 8 bit per channel RGB color type.
| `PhotometricInterpretation` | UINT Format | IEEEFP Format | | `PhotometricInterpretation` | UINT Format | IEEEFP Format |
|-|-|-| | --------------------------- | --------------------------------------- | ------------------------- |
| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | | `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) | | `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) | | `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) |
| `RGBPalette` | | | | `RGBPalette` | | |
| `Mask` | | | | `Mask` | | |
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) | | `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
| `CMYKA` | CMYKA(8) | |
| `YCbCr` | | | | `YCbCr` | | |
| `CIELab` | | | | `CIELab` | | |
### Compressions ### Compressions
| | Decoding | Encoding | | | Decoding | Encoding |
|-|-|-| | -------- | -------- | -------- |
| None | ✓ | ✓ | | None | ✓ | ✓ |
| LZW | ✓ | | | LZW | ✓ | ✓ |
| Deflate | ✓ | | | Deflate | ✓ | ✓ |
| PackBits | ✓ | | | PackBits | ✓ | ✓ |
| JPEG | ✓ | not yet |
| ZSTD | ✓ | not yet |
## Not yet supported ## Not yet supported
@@ -44,3 +47,11 @@ Formats and interpretations not listed above or with empty entries are unsupport
- Baseline tags - Baseline tags
- `ExtraSamples` - `ExtraSamples`
- Extension tags - Extension tags
## Fuzzing
This crate uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz) in order to test the image parser.
After installing it with `cargo install cargo-fuzz` on a nightly rustc, the
fuzzing harness can be run with recommended settings using
`cargo fuzz run decode_image -snone -- -timeout=5`.
+1 -1
View File
@@ -33,7 +33,7 @@ fn main() {
def.data, def.data,
|b, input| b.iter(|| read_image(input)), |b, input| b.iter(|| read_image(input)),
); );
}; }
let mut c = Criterion::default().configure_from_args(); let mut c = Criterion::default().configure_from_args();
let mut group = c.benchmark_group("tiff-lzw"); let mut group = c.benchmark_group("tiff-lzw");
+3
View File
@@ -0,0 +1,3 @@
target
corpus
artifacts
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "tiff-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
[dependencies.tiff]
path = ".."
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "decode_image"
path = "fuzz_targets/decode_image.rs"
test = false
doc = false
+31
View File
@@ -0,0 +1,31 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let mut decoder = if let Ok(d) = tiff::decoder::Decoder::new(std::io::Cursor::new(data)) {
d
} else {
return;
};
let mut limits = tiff::decoder::Limits::default();
limits.decoding_buffer_size = 1_000_000;
limits.ifd_value_size = 1_000_000;
limits.intermediate_buffer_size = 1_000_000;
decoder = decoder.with_limits(limits);
loop {
if let Err(_) = decoder.read_image() {
break;
}
if !decoder.more_images() {
break;
}
if let Err(_) = decoder.next_image() {
break;
}
}
});
+7 -3
View File
@@ -12,6 +12,8 @@
//! TODO: Would like to use std-lib here. //! TODO: Would like to use std-lib here.
use std::{mem, slice}; use std::{mem, slice};
use half::f16;
macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => { macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => {
pub(crate) fn $const(slice: &[$int]) -> &[u8] { pub(crate) fn $const(slice: &[$int]) -> &[u8] {
assert!(mem::align_of::<$int>() <= mem::size_of::<$int>()); assert!(mem::align_of::<$int>() <= mem::size_of::<$int>());
@@ -23,11 +25,13 @@ macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) =>
})* })*
}} }}
integral_slice_as_bytes!(i8, i8_as_ne_bytes); integral_slice_as_bytes!(i8, i8_as_ne_bytes, i8_as_ne_mut_bytes);
integral_slice_as_bytes!(u16, u16_as_ne_bytes, u16_as_ne_mut_bytes); integral_slice_as_bytes!(u16, u16_as_ne_bytes, u16_as_ne_mut_bytes);
integral_slice_as_bytes!(i16, i16_as_ne_bytes); integral_slice_as_bytes!(i16, i16_as_ne_bytes, i16_as_ne_mut_bytes);
integral_slice_as_bytes!(u32, u32_as_ne_bytes, u32_as_ne_mut_bytes); integral_slice_as_bytes!(u32, u32_as_ne_bytes, u32_as_ne_mut_bytes);
integral_slice_as_bytes!(i32, i32_as_ne_bytes); integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes);
integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes); integral_slice_as_bytes!(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!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes);
integral_slice_as_bytes!(f16, f16_as_ne_bytes, f16_as_ne_mut_bytes);
integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes); integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes);
+205 -126
View File
@@ -1,24 +1,27 @@
//! Function for reading TIFF tags //! Function for reading TIFF tags
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::io::{self, Read, Seek}; use std::io::{self, Read, Seek};
use std::mem; use std::mem;
use std::str;
use super::stream::{ByteOrder, EndianReader, SmartReader}; use super::stream::{ByteOrder, EndianReader};
use crate::tags::{Tag, Type}; use crate::tags::{Tag, Type};
use crate::{TiffError, TiffFormatError, TiffResult}; use crate::{TiffError, TiffFormatError, TiffResult};
use self::Value::{ use self::Value::{
Ascii, Byte, Double, Float, List, Rational, RationalBig, SRational, SRationalBig, Short, Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
Signed, SignedBig, Unsigned, UnsignedBig, Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig,
}; };
#[allow(unused_qualifications)] #[allow(unused_qualifications)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Value { pub enum Value {
Byte(u8), Byte(u8),
Short(u16), Short(u16),
SignedByte(i8),
SignedShort(i16),
Signed(i32), Signed(i32),
SignedBig(i64), SignedBig(i64),
Unsigned(u32), Unsigned(u32),
@@ -31,95 +34,106 @@ pub enum Value {
SRational(i32, i32), SRational(i32, i32),
SRationalBig(i64, i64), SRationalBig(i64, i64),
Ascii(String), Ascii(String),
#[doc(hidden)] // Do not match against this. Ifd(u32),
__NonExhaustive, IfdBig(u64),
} }
impl Value { impl Value {
pub fn into_u8(self) -> TiffResult<u8> { pub fn into_u8(self) -> TiffResult<u8> {
match self { match self {
Byte(val) => Ok(val), Byte(val) => Ok(val),
val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))), _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_i8(self) -> TiffResult<i8> {
match self {
SignedByte(val) => Ok(val),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
} }
} }
pub fn into_u16(self) -> TiffResult<u16> { pub fn into_u16(self) -> TiffResult<u16> {
match self { match self {
Byte(val) => Ok(val.into()),
Short(val) => Ok(val), Short(val) => Ok(val),
Unsigned(val) => Ok(u16::try_from(val)?), Unsigned(val) => Ok(u16::try_from(val)?),
UnsignedBig(val) => Ok(u16::try_from(val)?), UnsignedBig(val) => Ok(u16::try_from(val)?),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val), }
)), }
pub fn into_i16(self) -> TiffResult<i16> {
match self {
SignedByte(val) => Ok(val.into()),
SignedShort(val) => Ok(val),
Signed(val) => Ok(i16::try_from(val)?),
SignedBig(val) => Ok(i16::try_from(val)?),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
} }
} }
pub fn into_u32(self) -> TiffResult<u32> { pub fn into_u32(self) -> TiffResult<u32> {
match self { match self {
Byte(val) => Ok(val.into()),
Short(val) => Ok(val.into()), Short(val) => Ok(val.into()),
Unsigned(val) => Ok(val), Unsigned(val) => Ok(val),
UnsignedBig(val) => Ok(u32::try_from(val)?), UnsignedBig(val) => Ok(u32::try_from(val)?),
val => Err(TiffError::FormatError( Ifd(val) => Ok(val),
TiffFormatError::UnsignedIntegerExpected(val), IfdBig(val) => Ok(u32::try_from(val)?),
)), _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
} }
} }
pub fn into_i32(self) -> TiffResult<i32> { pub fn into_i32(self) -> TiffResult<i32> {
match self { match self {
SignedByte(val) => Ok(val.into()),
SignedShort(val) => Ok(val.into()),
Signed(val) => Ok(val), Signed(val) => Ok(val),
SignedBig(val) => Ok(i32::try_from(val)?), SignedBig(val) => Ok(i32::try_from(val)?),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
pub fn into_u64(self) -> TiffResult<u64> { pub fn into_u64(self) -> TiffResult<u64> {
match self { match self {
Byte(val) => Ok(val.into()),
Short(val) => Ok(val.into()), Short(val) => Ok(val.into()),
Unsigned(val) => Ok(val.into()), Unsigned(val) => Ok(val.into()),
UnsignedBig(val) => Ok(val), UnsignedBig(val) => Ok(val),
val => Err(TiffError::FormatError( Ifd(val) => Ok(val.into()),
TiffFormatError::UnsignedIntegerExpected(val), IfdBig(val) => Ok(val),
)), _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
} }
} }
pub fn into_i64(self) -> TiffResult<i64> { pub fn into_i64(self) -> TiffResult<i64> {
match self { match self {
SignedByte(val) => Ok(val.into()),
SignedShort(val) => Ok(val.into()),
Signed(val) => Ok(val.into()), Signed(val) => Ok(val.into()),
SignedBig(val) => Ok(val), SignedBig(val) => Ok(val),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
pub fn into_f32(self) -> TiffResult<f32> { pub fn into_f32(self) -> TiffResult<f32> {
match self { match self {
Float(val) => Ok(val), Float(val) => Ok(val),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
pub fn into_f64(self) -> TiffResult<f64> { pub fn into_f64(self) -> TiffResult<f64> {
match self { match self {
Double(val) => Ok(val), Double(val) => Ok(val),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
pub fn into_string(self) -> TiffResult<String> { pub fn into_string(self) -> TiffResult<String> {
match self { match self {
Ascii(val) => Ok(val), Ascii(val) => Ok(val),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
@@ -132,16 +146,18 @@ impl Value {
} }
Ok(new_vec) Ok(new_vec)
} }
Byte(val) => Ok(vec![val.into()]),
Short(val) => Ok(vec![val.into()]),
Unsigned(val) => Ok(vec![val]), Unsigned(val) => Ok(vec![val]),
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]), UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
Rational(numerator, denominator) => Ok(vec![numerator, denominator]), Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
RationalBig(numerator, denominator) => { RationalBig(numerator, denominator) => {
Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?]) Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?])
} }
Ifd(val) => Ok(vec![val]),
IfdBig(val) => Ok(vec![u32::try_from(val)?]),
Ascii(val) => Ok(val.chars().map(u32::from).collect()), Ascii(val) => Ok(val.chars().map(u32::from).collect()),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -156,9 +172,7 @@ impl Value {
} }
Byte(val) => Ok(vec![val]), Byte(val) => Ok(vec![val]),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -171,10 +185,9 @@ impl Value {
} }
Ok(new_vec) Ok(new_vec)
} }
Byte(val) => Ok(vec![val.into()]),
Short(val) => Ok(vec![val]), Short(val) => Ok(vec![val]),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -197,15 +210,15 @@ impl Value {
} }
Ok(new_vec) Ok(new_vec)
} }
SignedByte(val) => Ok(vec![val.into()]),
SignedShort(val) => Ok(vec![val.into()]),
Signed(val) => Ok(vec![val]), Signed(val) => Ok(vec![val]),
SignedBig(val) => Ok(vec![i32::try_from(val)?]), SignedBig(val) => Ok(vec![i32::try_from(val)?]),
SRational(numerator, denominator) => Ok(vec![numerator, denominator]), SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
SRationalBig(numerator, denominator) => { SRationalBig(numerator, denominator) => {
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?]) Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
} }
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
@@ -219,9 +232,7 @@ impl Value {
Ok(new_vec) Ok(new_vec)
} }
Float(val) => Ok(vec![val]), Float(val) => Ok(vec![val]),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -235,9 +246,7 @@ impl Value {
Ok(new_vec) Ok(new_vec)
} }
Double(val) => Ok(vec![val]), Double(val) => Ok(vec![val]),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -250,14 +259,16 @@ impl Value {
} }
Ok(new_vec) Ok(new_vec)
} }
Byte(val) => Ok(vec![val.into()]),
Short(val) => Ok(vec![val.into()]),
Unsigned(val) => Ok(vec![val.into()]), Unsigned(val) => Ok(vec![val.into()]),
UnsignedBig(val) => Ok(vec![val]), UnsignedBig(val) => Ok(vec![val]),
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]), Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]), RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
Ifd(val) => Ok(vec![val.into()]),
IfdBig(val) => Ok(vec![val]),
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()), Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::UnsignedIntegerExpected(val),
)),
} }
} }
@@ -280,13 +291,13 @@ impl Value {
} }
Ok(new_vec) Ok(new_vec)
} }
SignedByte(val) => Ok(vec![val.into()]),
SignedShort(val) => Ok(vec![val.into()]),
Signed(val) => Ok(vec![val.into()]), Signed(val) => Ok(vec![val.into()]),
SignedBig(val) => Ok(vec![val]), SignedBig(val) => Ok(vec![val]),
SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]), SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]), SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
val => Err(TiffError::FormatError( _ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
TiffFormatError::SignedIntegerExpected(val),
)),
} }
} }
} }
@@ -323,36 +334,49 @@ impl Entry {
} }
/// Returns a mem_reader for the offset/value field /// Returns a mem_reader for the offset/value field
fn r(&self, byte_order: ByteOrder) -> SmartReader<io::Cursor<Vec<u8>>> { fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
SmartReader::wrap(io::Cursor::new(self.offset.to_vec()), byte_order) EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
} }
pub fn val<R: Read + Seek>( pub(crate) fn val<R: Read + Seek>(
&self, &self,
limits: &super::Limits, limits: &super::Limits,
decoder: &mut super::Decoder<R>, bigtiff: bool,
reader: &mut EndianReader<R>,
) -> TiffResult<Value> { ) -> TiffResult<Value> {
// Case 1: there are no values so we can return immediately. // Case 1: there are no values so we can return immediately.
if self.count == 0 { if self.count == 0 {
return Ok(List(Vec::new())); return Ok(List(Vec::new()));
} }
let bo = decoder.byte_order(); let bo = reader.byte_order;
let value_bytes = self.count
* match self.type_ { let tag_size = match self.type_ {
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
Type::SHORT | Type::SSHORT => 2, Type::SHORT | Type::SSHORT => 2,
Type::LONG | Type::SLONG | Type::FLOAT => 4, Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
Type::LONG8 | Type::DOUBLE | Type::RATIONAL | Type::SRATIONAL => 8, Type::LONG8
Type::__NonExhaustive => unreachable!(), | Type::SLONG8
| Type::DOUBLE
| Type::RATIONAL
| Type::SRATIONAL
| Type::IFD8 => 8,
};
let value_bytes = match self.count.checked_mul(tag_size) {
Some(n) => n,
None => {
return Err(TiffError::LimitsExceeded);
}
}; };
// Case 2: there is one value. // Case 2: there is one value.
if self.count == 1 { if self.count == 1 {
// 2a: the value is 5-8 bytes and we're in BigTiff mode. // 2a: the value is 5-8 bytes and we're in BigTiff mode.
if decoder.bigtiff && value_bytes > 4 && value_bytes <= 8 { if bigtiff && value_bytes > 4 && value_bytes <= 8 {
return Ok(match self.type_ { return Ok(match self.type_ {
Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?), Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?),
Type::SLONG8 => SignedBig(self.r(bo).read_i64()?),
Type::DOUBLE => Double(self.r(bo).read_f64()?), Type::DOUBLE => Double(self.r(bo).read_f64()?),
Type::RATIONAL => { Type::RATIONAL => {
let mut r = self.r(bo); let mut r = self.r(bo);
@@ -362,6 +386,7 @@ impl Entry {
let mut r = self.r(bo); let mut r = self.r(bo);
SRational(r.read_i32()?, r.read_i32()?) SRational(r.read_i32()?, r.read_i32()?)
} }
Type::IFD8 => IfdBig(self.r(bo).read_u64()?),
Type::BYTE Type::BYTE
| Type::SBYTE | Type::SBYTE
| Type::ASCII | Type::ASCII
@@ -370,18 +395,18 @@ impl Entry {
| Type::SSHORT | Type::SSHORT
| Type::LONG | Type::LONG
| Type::SLONG | Type::SLONG
| Type::FLOAT => unreachable!(), | Type::FLOAT
Type::__NonExhaustive => unreachable!(), | Type::IFD => unreachable!(),
}); });
} }
// 2b: the value is at most 4 bytes or doesn't fit in the offset field. // 2b: the value is at most 4 bytes or doesn't fit in the offset field.
return Ok(match self.type_ { return Ok(match self.type_ {
Type::BYTE => Unsigned(u32::from(self.offset[0])), Type::BYTE => Byte(self.offset[0]),
Type::SBYTE => Signed(i32::from(self.offset[0] as i8)), Type::SBYTE => SignedByte(i8::from(self.offset[0] as i8)),
Type::UNDEFINED => Byte(self.offset[0]), Type::UNDEFINED => Byte(self.offset[0]),
Type::SHORT => Unsigned(u32::from(self.r(bo).read_u16()?)), Type::SHORT => Short(self.r(bo).read_u16()?),
Type::SSHORT => Signed(i32::from(self.r(bo).read_i16()?)), Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
Type::LONG => Unsigned(self.r(bo).read_u32()?), Type::LONG => Unsigned(self.r(bo).read_u32()?),
Type::SLONG => Signed(self.r(bo).read_i32()?), Type::SLONG => Signed(self.r(bo).read_i32()?),
Type::FLOAT => Float(self.r(bo).read_f32()?), Type::FLOAT => Float(self.r(bo).read_f32()?),
@@ -393,36 +418,48 @@ impl Entry {
} }
} }
Type::LONG8 => { Type::LONG8 => {
decoder.goto_offset(self.r(bo).read_u32()?)?; reader.goto_offset(self.r(bo).read_u32()?.into())?;
UnsignedBig(decoder.read_long8()?) UnsignedBig(reader.read_u64()?)
}
Type::SLONG8 => {
reader.goto_offset(self.r(bo).read_u32()?.into())?;
SignedBig(reader.read_i64()?)
} }
Type::DOUBLE => { Type::DOUBLE => {
decoder.goto_offset(self.r(bo).read_u32()?)?; reader.goto_offset(self.r(bo).read_u32()?.into())?;
Double(decoder.read_double()?) Double(reader.read_f64()?)
} }
Type::RATIONAL => { Type::RATIONAL => {
decoder.goto_offset(self.r(bo).read_u32()?)?; reader.goto_offset(self.r(bo).read_u32()?.into())?;
Rational(decoder.read_long()?, decoder.read_long()?) Rational(reader.read_u32()?, reader.read_u32()?)
} }
Type::SRATIONAL => { Type::SRATIONAL => {
decoder.goto_offset(self.r(bo).read_u32()?)?; reader.goto_offset(self.r(bo).read_u32()?.into())?;
SRational(decoder.read_slong()?, decoder.read_slong()?) SRational(reader.read_i32()?, reader.read_i32()?)
}
Type::IFD => Ifd(self.r(bo).read_u32()?),
Type::IFD8 => {
reader.goto_offset(self.r(bo).read_u32()?.into())?;
IfdBig(reader.read_u64()?)
} }
Type::__NonExhaustive => unreachable!(),
}); });
} }
// Case 3: There is more than one value, but it fits in the offset field. // Case 3: There is more than one value, but it fits in the offset field.
if value_bytes <= 4 || decoder.bigtiff && value_bytes <= 8 { if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
match self.type_ { match self.type_ {
Type::BYTE => return Ok(offset_to_bytes(self.count as usize, self)?), Type::BYTE => return offset_to_bytes(self.count as usize, self),
Type::SBYTE => return Ok(offset_to_sbytes(self.count as usize, self)?), Type::SBYTE => return offset_to_sbytes(self.count as usize, self),
Type::ASCII => { Type::ASCII => {
let mut buf = vec![0; self.count as usize]; let mut buf = vec![0; self.count as usize];
self.r(bo).read_exact(&mut buf)?; buf.copy_from_slice(&self.offset[..self.count as usize]);
let v = String::from_utf8(buf)?; if buf.is_ascii() && buf.ends_with(&[0]) {
let v = str::from_utf8(&buf)?;
let v = v.trim_matches(char::from(0)); let v = v.trim_matches(char::from(0));
return Ok(Ascii(v.into())); return Ok(Ascii(v.into()));
} else {
return Err(TiffError::FormatError(TiffFormatError::InvalidTag));
}
} }
Type::UNDEFINED => { Type::UNDEFINED => {
return Ok(List( return Ok(List(
@@ -444,7 +481,7 @@ impl Entry {
let mut r = self.r(bo); let mut r = self.r(bo);
let mut v = Vec::new(); let mut v = Vec::new();
for _ in 0..self.count { for _ in 0..self.count {
v.push(Signed(i32::from(r.read_i16()?))); v.push(SignedShort(r.read_i16()?));
} }
return Ok(List(v)); return Ok(List(v));
} }
@@ -472,8 +509,22 @@ impl Entry {
} }
return Ok(List(v)); return Ok(List(v));
} }
Type::LONG8 | Type::RATIONAL | Type::SRATIONAL | Type::DOUBLE => unreachable!(), Type::IFD => {
Type::__NonExhaustive => unreachable!(), let mut r = self.r(bo);
let mut v = Vec::new();
for _ in 0..self.count {
v.push(Ifd(r.read_u32()?));
}
return Ok(List(v));
}
Type::LONG8
| Type::SLONG8
| Type::RATIONAL
| Type::SRATIONAL
| Type::DOUBLE
| Type::IFD8 => {
unreachable!()
}
} }
} }
@@ -481,57 +532,81 @@ impl Entry {
match self.type_ { match self.type_ {
// TODO check if this could give wrong results // TODO check if this could give wrong results
// at a different endianess of file/computer. // at a different endianess of file/computer.
Type::BYTE => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(UnsignedBig(u64::from(decoder.read_byte()?))) let mut buf = [0; 1];
reader.inner().read_exact(&mut buf)?;
Ok(UnsignedBig(u64::from(buf[0])))
}), }),
Type::SBYTE => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SignedBig(i64::from(decoder.read_byte()? as i8))) Ok(SignedBig(i64::from(reader.read_i8()?)))
}), }),
Type::SHORT => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(UnsignedBig(u64::from(decoder.read_short()?))) Ok(Short(reader.read_u16()?))
}), }),
Type::SSHORT => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SignedBig(i64::from(decoder.read_sshort()?))) Ok(SignedShort(reader.read_i16()?))
}), }),
Type::LONG => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Unsigned(decoder.read_long()?)) Ok(Unsigned(reader.read_u32()?))
}), }),
Type::SLONG => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::SLONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Signed(decoder.read_slong()?)) Ok(Signed(reader.read_i32()?))
}), }),
Type::FLOAT => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::FLOAT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Float(decoder.read_float()?)) Ok(Float(reader.read_f32()?))
}), }),
Type::DOUBLE => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::DOUBLE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Double(decoder.read_double()?)) Ok(Double(reader.read_f64()?))
}), }),
Type::RATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::RATIONAL => {
Ok(Rational(decoder.read_long()?, decoder.read_long()?)) self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Rational(reader.read_u32()?, reader.read_u32()?))
})
}
Type::SRATIONAL => {
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SRational(reader.read_i32()?, reader.read_i32()?))
})
}
Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(UnsignedBig(reader.read_u64()?))
}), }),
Type::SRATIONAL => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SRational(decoder.read_slong()?, decoder.read_slong()?)) Ok(SignedBig(reader.read_i64()?))
}), }),
Type::LONG8 => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(UnsignedBig(decoder.read_long8()?)) Ok(Ifd(reader.read_u32()?))
}), }),
Type::UNDEFINED => self.decode_offset(self.count, bo, limits, decoder, |decoder| { Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Byte(u8::from(decoder.read_byte()?))) Ok(IfdBig(reader.read_u64()?))
}), }),
Type::UNDEFINED => {
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
let mut buf = [0; 1];
reader.inner().read_exact(&mut buf)?;
Ok(Byte(buf[0]))
})
}
Type::ASCII => { Type::ASCII => {
let n = usize::try_from(self.count)?; let n = usize::try_from(self.count)?;
if n > limits.decoding_buffer_size { if n > limits.decoding_buffer_size {
return Err(TiffError::LimitsExceeded); return Err(TiffError::LimitsExceeded);
} }
if decoder.bigtiff { if bigtiff {
decoder.goto_offset_u64(self.r(bo).read_u64()?)? reader.goto_offset(self.r(bo).read_u64()?)?
} else { } else {
decoder.goto_offset(self.r(bo).read_u32()?)? reader.goto_offset(self.r(bo).read_u32()?.into())?
} }
let string = decoder.read_string(n)?;
Ok(Ascii(string)) let mut out = vec![0; n];
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);
}
Ok(Ascii(String::from_utf8(out)?))
} }
Type::__NonExhaustive => unreachable!(),
} }
} }
@@ -540,13 +615,14 @@ impl Entry {
&self, &self,
value_count: u64, value_count: u64,
bo: ByteOrder, bo: ByteOrder,
bigtiff: bool,
limits: &super::Limits, limits: &super::Limits,
decoder: &mut super::Decoder<R>, reader: &mut EndianReader<R>,
decode_fn: F, decode_fn: F,
) -> TiffResult<Value> ) -> TiffResult<Value>
where where
R: Read + Seek, R: Read + Seek,
F: Fn(&mut super::Decoder<R>) -> TiffResult<Value>, F: Fn(&mut EndianReader<R>) -> TiffResult<Value>,
{ {
let value_count = usize::try_from(value_count)?; let value_count = usize::try_from(value_count)?;
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() { if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
@@ -554,13 +630,16 @@ impl Entry {
} }
let mut v = Vec::with_capacity(value_count); let mut v = Vec::with_capacity(value_count);
if decoder.bigtiff {
decoder.goto_offset_u64(self.r(bo).read_u64()?)? let offset = if bigtiff {
self.r(bo).read_u64()?
} else { } else {
decoder.goto_offset(self.r(bo).read_u32()?)? self.r(bo).read_u32()?.into()
} };
reader.goto_offset(offset)?;
for _ in 0..value_count { for _ in 0..value_count {
v.push(decode_fn(decoder)?) v.push(decode_fn(reader)?)
} }
Ok(List(v)) Ok(List(v))
} }
+726
View File
@@ -0,0 +1,726 @@
use super::ifd::{Directory, Value};
use super::stream::{ByteOrder, PackBitsReader};
use super::tag_reader::TagReader;
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::io::{self, Cursor, Read, Seek};
use std::sync::Arc;
#[derive(Debug)]
pub(crate) struct StripDecodeState {
pub rows_per_strip: u32,
}
#[derive(Debug)]
/// Computed values useful for tile decoding
pub(crate) struct TileAttributes {
pub image_width: usize,
pub image_height: usize,
pub tile_width: usize,
pub tile_length: usize,
}
impl TileAttributes {
pub fn tiles_across(&self) -> usize {
(self.image_width + self.tile_width - 1) / self.tile_width
}
pub fn tiles_down(&self) -> usize {
(self.image_height + self.tile_length - 1) / self.tile_length
}
fn padding_right(&self) -> usize {
(self.tile_width - self.image_width % self.tile_width) % self.tile_width
}
fn padding_down(&self) -> usize {
(self.tile_length - self.image_height % self.tile_length) % self.tile_length
}
pub fn get_padding(&self, tile: usize) -> (usize, usize) {
let row = tile / self.tiles_across();
let column = tile % self.tiles_across();
let padding_right = if column == self.tiles_across() - 1 {
self.padding_right()
} else {
0
};
let padding_down = if row == self.tiles_down() - 1 {
self.padding_down()
} else {
0
};
(padding_right, padding_down)
}
}
#[derive(Debug)]
pub(crate) struct Image {
pub ifd: Option<Directory>,
pub width: u32,
pub height: u32,
pub bits_per_sample: u8,
pub samples: u16,
pub sample_format: SampleFormat,
pub photometric_interpretation: PhotometricInterpretation,
pub compression_method: CompressionMethod,
pub predictor: Predictor,
pub jpeg_tables: Option<Arc<Vec<u8>>>,
pub chunk_type: ChunkType,
pub planar_config: PlanarConfiguration,
pub strip_decoder: Option<StripDecodeState>,
pub tile_attributes: Option<TileAttributes>,
pub chunk_offsets: Vec<u64>,
pub chunk_bytes: Vec<u64>,
}
impl Image {
pub fn from_reader<R: Read + Seek>(
reader: &mut EndianReader<R>,
ifd: Directory,
limits: &Limits,
bigtiff: bool,
) -> TiffResult<Image> {
let mut tag_reader = TagReader {
reader,
limits,
ifd: &ifd,
bigtiff,
};
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
if width == 0 || height == 0 {
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
width, height,
)));
}
let photometric_interpretation = tag_reader
.find_tag(Tag::PhotometricInterpretation)?
.map(Value::into_u16)
.transpose()?
.and_then(PhotometricInterpretation::from_u16)
.ok_or(TiffUnsupportedError::UnknownInterpretation)?;
// Try to parse both the compression method and the number, format, and bits of the included samples.
// If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
let compression_method = match tag_reader.find_tag(Tag::Compression)? {
Some(val) => CompressionMethod::from_u16_exhaustive(val.into_u16()?),
None => CompressionMethod::None,
};
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
&& ifd.contains_key(&Tag::JPEGTables)
{
let vec = tag_reader
.find_tag(Tag::JPEGTables)?
.unwrap()
.into_u8_vec()?;
if vec.len() < 2 {
return Err(TiffError::FormatError(
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
));
}
Some(Arc::new(vec))
} else {
None
};
let samples: u16 = tag_reader
.find_tag(Tag::SamplesPerPixel)?
.map(Value::into_u16)
.transpose()?
.unwrap_or(1);
if samples == 0 {
return Err(TiffFormatError::SamplesPerPixelIsZero.into());
}
let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
Some(vals) => {
let sample_format: Vec<_> = vals
.into_iter()
.map(SampleFormat::from_u16_exhaustive)
.collect();
// TODO: for now, only homogenous formats across samples are supported.
if !sample_format.windows(2).all(|s| s[0] == s[1]) {
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
}
sample_format[0]
}
None => SampleFormat::Uint,
};
let bits_per_sample: Vec<u8> = tag_reader
.find_tag_uint_vec(Tag::BitsPerSample)?
.unwrap_or_else(|| vec![1]);
// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows
// it to be a single value that applies to all samples.
if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 {
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
}
// 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());
}
let predictor = tag_reader
.find_tag(Tag::Predictor)?
.map(Value::into_u16)
.transpose()?
.map(|p| {
Predictor::from_u16(p)
.ok_or(TiffError::FormatError(TiffFormatError::UnknownPredictor(p)))
})
.transpose()?
.unwrap_or(Predictor::None);
let planar_config = tag_reader
.find_tag(Tag::PlanarConfiguration)?
.map(Value::into_u16)
.transpose()?
.map(|p| {
PlanarConfiguration::from_u16(p).ok_or(TiffError::FormatError(
TiffFormatError::UnknownPlanarConfiguration(p),
))
})
.transpose()?
.unwrap_or(PlanarConfiguration::Chunky);
let planes = match planar_config {
PlanarConfiguration::Chunky => 1,
PlanarConfiguration::Planar => samples,
};
let chunk_type;
let chunk_offsets;
let chunk_bytes;
let strip_decoder;
let tile_attributes;
match (
ifd.contains_key(&Tag::StripByteCounts),
ifd.contains_key(&Tag::StripOffsets),
ifd.contains_key(&Tag::TileByteCounts),
ifd.contains_key(&Tag::TileOffsets),
) {
(true, true, false, false) => {
chunk_type = ChunkType::Strip;
chunk_offsets = tag_reader
.find_tag(Tag::StripOffsets)?
.unwrap()
.into_u64_vec()?;
chunk_bytes = tag_reader
.find_tag(Tag::StripByteCounts)?
.unwrap()
.into_u64_vec()?;
let rows_per_strip = tag_reader
.find_tag(Tag::RowsPerStrip)?
.map(Value::into_u32)
.transpose()?
.unwrap_or(height);
strip_decoder = Some(StripDecodeState { rows_per_strip });
tile_attributes = None;
if chunk_offsets.len() != chunk_bytes.len()
|| rows_per_strip == 0
|| u32::try_from(chunk_offsets.len())?
!= (height.saturating_sub(1) / rows_per_strip + 1) * planes as u32
{
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
}
}
(false, false, true, true) => {
chunk_type = ChunkType::Tile;
let tile_width =
usize::try_from(tag_reader.require_tag(Tag::TileWidth)?.into_u32()?)?;
let tile_length =
usize::try_from(tag_reader.require_tag(Tag::TileLength)?.into_u32()?)?;
if tile_width == 0 {
return Err(TiffFormatError::InvalidTagValueType(Tag::TileWidth).into());
} else if tile_length == 0 {
return Err(TiffFormatError::InvalidTagValueType(Tag::TileLength).into());
}
strip_decoder = None;
tile_attributes = Some(TileAttributes {
image_width: usize::try_from(width)?,
image_height: usize::try_from(height)?,
tile_width,
tile_length,
});
chunk_offsets = tag_reader
.find_tag(Tag::TileOffsets)?
.unwrap()
.into_u64_vec()?;
chunk_bytes = tag_reader
.find_tag(Tag::TileByteCounts)?
.unwrap()
.into_u64_vec()?;
let tile = tile_attributes.as_ref().unwrap();
if chunk_offsets.len() != chunk_bytes.len()
|| chunk_offsets.len()
!= tile.tiles_down() * tile.tiles_across() * planes as usize
{
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
}
}
(_, _, _, _) => {
return Err(TiffError::FormatError(
TiffFormatError::StripTileTagConflict,
))
}
};
Ok(Image {
ifd: Some(ifd),
width,
height,
bits_per_sample: bits_per_sample[0],
samples,
sample_format,
photometric_interpretation,
compression_method,
jpeg_tables,
predictor,
chunk_type,
planar_config,
strip_decoder,
tile_attributes,
chunk_offsets,
chunk_bytes,
})
}
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
match self.photometric_interpretation {
PhotometricInterpretation::RGB => match self.samples {
3 => Ok(ColorType::RGB(self.bits_per_sample)),
4 => Ok(ColorType::RGBA(self.bits_per_sample)),
// FIXME: We should _ignore_ other components. In particular:
// > Beware of extra components. Some TIFF files may have more components per pixel
// than you think. A Baseline TIFF reader must skip over them gracefully,using the
// values of the SamplesPerPixel and BitsPerSample fields.
// > -- TIFF 6.0 Specification, Section 7, Additional Baseline requirements.
_ => Err(TiffError::UnsupportedError(
TiffUnsupportedError::InterpretationWithBits(
self.photometric_interpretation,
vec![self.bits_per_sample; self.samples as usize],
),
)),
},
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,
vec![self.bits_per_sample; self.samples as usize],
),
)),
},
PhotometricInterpretation::YCbCr => match self.samples {
3 => Ok(ColorType::YCbCr(self.bits_per_sample)),
_ => Err(TiffError::UnsupportedError(
TiffUnsupportedError::InterpretationWithBits(
self.photometric_interpretation,
vec![self.bits_per_sample; self.samples as usize],
),
)),
},
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
PhotometricInterpretation::RGBPalette
| PhotometricInterpretation::TransparencyMask
| PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError(
TiffUnsupportedError::InterpretationWithBits(
self.photometric_interpretation,
vec![self.bits_per_sample; self.samples as usize],
),
)),
}
}
fn create_reader<'r, R: 'r + Read>(
reader: R,
#[allow(unused_variables)] photometric_interpretation: PhotometricInterpretation,
compression_method: CompressionMethod,
compressed_length: u64,
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
) -> TiffResult<Box<dyn Read + 'r>> {
Ok(match compression_method {
CompressionMethod::None => Box::new(reader),
#[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(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),
));
}
// Construct new jpeg_reader wrapping a SmartReader.
//
// JPEG compression in TIFF allows saving quantization and/or huffman tables in one
// central location. These `jpeg_tables` are simply prepended to the remaining jpeg image data.
// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
// which is also at the beginning of the remaining JPEG image data and would
// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
// this has to be removed as well (last two bytes of `jpeg_tables`).
let mut jpeg_reader = match jpeg_tables {
Some(jpeg_tables) => {
let mut reader = reader.take(compressed_length);
reader.read_exact(&mut [0; 2])?;
Box::new(
Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2])
.chain(reader.take(compressed_length)),
) as Box<dyn Read>
}
None => Box::new(reader.take(compressed_length)),
};
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 => {
options =
options.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::RGB);
}
PhotometricInterpretation::CMYK => {
options = options
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::CMYK);
}
PhotometricInterpretation::YCbCr => {
options = options
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::YCbCr);
}
PhotometricInterpretation::WhiteIsZero
| PhotometricInterpretation::BlackIsZero
| PhotometricInterpretation::TransparencyMask => {}
PhotometricInterpretation::RGBPalette | PhotometricInterpretation::CIELab => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedInterpretation(
photometric_interpretation,
),
));
}
}
decoder.set_options(options);
let data = decoder.decode()?;
Box::new(Cursor::new(data))
}
method => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedCompressionMethod(method),
))
}
})
}
/// Samples per pixel within chunk.
///
/// In planar config, samples are stored in separate strips/chunks, also called bands.
///
/// Example with `bits_per_sample = [8, 8, 8]` and `PhotometricInterpretation::RGB`:
/// * `PlanarConfiguration::Chunky` -> 3 (RGBRGBRGB...)
/// * `PlanarConfiguration::Planar` -> 1 (RRR...) (GGG...) (BBB...)
pub(crate) fn samples_per_pixel(&self) -> usize {
match self.planar_config {
PlanarConfiguration::Chunky => self.samples.into(),
PlanarConfiguration::Planar => 1,
}
}
/// Number of strips per pixel.
pub(crate) fn strips_per_pixel(&self) -> usize {
match self.planar_config {
PlanarConfiguration::Chunky => 1,
PlanarConfiguration::Planar => self.samples.into(),
}
}
pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> {
let file_offset = self
.chunk_offsets
.get(chunk as usize)
.ok_or(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
))?;
let compressed_bytes =
self.chunk_bytes
.get(chunk as usize)
.ok_or(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
))?;
Ok((*file_offset, *compressed_bytes))
}
pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> {
match self.chunk_type {
ChunkType::Strip => {
let strip_attrs = self.strip_decoder.as_ref().unwrap();
Ok((self.width, strip_attrs.rows_per_strip))
}
ChunkType::Tile => {
let tile_attrs = self.tile_attributes.as_ref().unwrap();
Ok((
u32::try_from(tile_attrs.tile_width)?,
u32::try_from(tile_attrs.tile_length)?,
))
}
}
}
pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
let dims = self.chunk_dimensions()?;
match self.chunk_type {
ChunkType::Strip => {
let strip_attrs = self.strip_decoder.as_ref().unwrap();
let strips_per_band =
self.height.saturating_sub(1) / strip_attrs.rows_per_strip + 1;
let strip_height_without_padding = (chunk_index % strips_per_band)
.checked_mul(dims.1)
.and_then(|x| self.height.checked_sub(x))
.ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex(
chunk_index,
)))?;
// Ignore potential vertical padding on the bottommost strip
let strip_height = dims.1.min(strip_height_without_padding);
Ok((dims.0, strip_height))
}
ChunkType::Tile => {
let tile_attrs = self.tile_attributes.as_ref().unwrap();
let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize);
let tile_width = tile_attrs.tile_width - padding_right;
let tile_length = tile_attrs.tile_length - padding_down;
Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?))
}
}
}
pub(crate) fn expand_chunk(
&self,
reader: impl Read,
buf: &mut [u8],
output_row_stride: usize,
byte_order: ByteOrder,
chunk_index: u32,
limits: &Limits,
) -> TiffResult<()> {
// Validate that the color type is supported.
let color_type = self.colortype()?;
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(
TiffUnsupportedError::FloatingPointPredictor(color_type),
));
}
},
type_ => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedColorType(type_),
));
}
}
// Validate that the predictor is supported for the sample type.
match (self.predictor, self.sample_format) {
(
Predictor::Horizontal,
SampleFormat::Int | SampleFormat::Uint | SampleFormat::IEEEFP,
) => {}
(Predictor::Horizontal, _) => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::HorizontalPredictor(color_type),
));
}
(Predictor::FloatingPoint, SampleFormat::IEEEFP) => {}
(Predictor::FloatingPoint, _) => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::FloatingPointPredictor(color_type),
));
}
_ => {}
}
let compressed_bytes =
self.chunk_bytes
.get(chunk_index as usize)
.ok_or(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
))?;
if *compressed_bytes > limits.intermediate_buffer_size as u64 {
return Err(TiffError::LimitsExceeded);
}
let compression_method = self.compression_method;
let photometric_interpretation = self.photometric_interpretation;
let predictor = self.predictor;
let samples = self.samples_per_pixel();
let chunk_dims = self.chunk_dimensions()?;
let data_dims = self.chunk_data_dimensions(chunk_index)?;
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,
photometric_interpretation,
compression_method,
*compressed_bytes,
self.jpeg_tables.as_deref().map(|a| &**a),
)?;
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 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(tile, color_type, self.sample_format);
}
} 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_row_bytes];
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
reader.read_exact(&mut encoded)?;
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(row, color_type, self.sample_format);
}
}
} else {
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 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())?;
}
super::fix_endianness_and_predict(
row,
color_type.bit_depth(),
samples,
byte_order,
predictor,
);
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(row, color_type, self.sample_format);
}
}
}
Ok(())
}
}
+626 -636
View File
File diff suppressed because it is too large. Load diff
+143 -384
View File
@@ -1,9 +1,6 @@
//! All IO functionality needed for TIFF decoding //! All IO functionality needed for TIFF decoding
use crate::bytecast; use std::io::{self, BufRead, BufReader, Read, Seek, Take};
use crate::error::{TiffError, TiffResult};
use miniz_oxide::inflate;
use std::io::{self, Read, Seek};
/// Byte order of the TIFF file. /// Byte order of the TIFF file.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@@ -15,45 +12,54 @@ pub enum ByteOrder {
} }
/// Reader that is aware of the byte order. /// Reader that is aware of the byte order.
pub trait EndianReader: Read { #[derive(Debug)]
/// Byte order that should be adhered to pub struct EndianReader<R> {
fn byte_order(&self) -> ByteOrder; reader: R,
pub(crate) byte_order: ByteOrder,
}
impl<R: Read + Seek> EndianReader<R> {
pub fn new(reader: R, byte_order: ByteOrder) -> Self {
Self { reader, byte_order }
}
pub fn inner(&mut self) -> &mut R {
&mut self.reader
}
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
self.reader.seek(io::SeekFrom::Start(offset))?;
Ok(())
}
/// Reads an u16 /// Reads an u16
#[inline(always)] #[inline(always)]
fn read_u16(&mut self) -> Result<u16, io::Error> { pub fn read_u16(&mut self) -> Result<u16, io::Error> {
let mut n = [0u8; 2]; let mut n = [0u8; 2];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => u16::from_le_bytes(n), ByteOrder::LittleEndian => u16::from_le_bytes(n),
ByteOrder::BigEndian => u16::from_be_bytes(n), ByteOrder::BigEndian => u16::from_be_bytes(n),
}) })
} }
/// Reads an i8
#[inline(always)] #[inline(always)]
fn read_u16_into(&mut self, buffer: &mut [u16]) -> Result<(), io::Error> { pub fn read_i8(&mut self) -> Result<i8, io::Error> {
self.read_exact(bytecast::u16_as_ne_mut_bytes(buffer))?; let mut n = [0u8; 1];
match self.byte_order() { self.reader.read_exact(&mut n)?;
ByteOrder::LittleEndian => { Ok(match self.byte_order {
for n in buffer { ByteOrder::LittleEndian => i8::from_le_bytes(n),
*n = u16::from_le(*n); ByteOrder::BigEndian => i8::from_be_bytes(n),
} })
}
ByteOrder::BigEndian => {
for n in buffer {
*n = u16::from_be(*n);
}
}
}
Ok(())
} }
/// Reads an i16 /// Reads an i16
#[inline(always)] #[inline(always)]
fn read_i16(&mut self) -> Result<i16, io::Error> { pub fn read_i16(&mut self) -> Result<i16, io::Error> {
let mut n = [0u8; 2]; let mut n = [0u8; 2];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => i16::from_le_bytes(n), ByteOrder::LittleEndian => i16::from_le_bytes(n),
ByteOrder::BigEndian => i16::from_be_bytes(n), ByteOrder::BigEndian => i16::from_be_bytes(n),
}) })
@@ -61,39 +67,21 @@ pub trait EndianReader: Read {
/// Reads an u32 /// Reads an u32
#[inline(always)] #[inline(always)]
fn read_u32(&mut self) -> Result<u32, io::Error> { pub fn read_u32(&mut self) -> Result<u32, io::Error> {
let mut n = [0u8; 4]; let mut n = [0u8; 4];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => u32::from_le_bytes(n), ByteOrder::LittleEndian => u32::from_le_bytes(n),
ByteOrder::BigEndian => u32::from_be_bytes(n), ByteOrder::BigEndian => u32::from_be_bytes(n),
}) })
} }
#[inline(always)]
fn read_u32_into(&mut self, buffer: &mut [u32]) -> Result<(), io::Error> {
self.read_exact(bytecast::u32_as_ne_mut_bytes(buffer))?;
match self.byte_order() {
ByteOrder::LittleEndian => {
for n in buffer {
*n = u32::from_le(*n);
}
}
ByteOrder::BigEndian => {
for n in buffer {
*n = u32::from_be(*n);
}
}
}
Ok(())
}
/// Reads an i32 /// Reads an i32
#[inline(always)] #[inline(always)]
fn read_i32(&mut self) -> Result<i32, io::Error> { pub fn read_i32(&mut self) -> Result<i32, io::Error> {
let mut n = [0u8; 4]; let mut n = [0u8; 4];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => i32::from_le_bytes(n), ByteOrder::LittleEndian => i32::from_le_bytes(n),
ByteOrder::BigEndian => i32::from_be_bytes(n), ByteOrder::BigEndian => i32::from_be_bytes(n),
}) })
@@ -101,90 +89,47 @@ pub trait EndianReader: Read {
/// Reads an u64 /// Reads an u64
#[inline(always)] #[inline(always)]
fn read_u64(&mut self) -> Result<u64, io::Error> { pub fn read_u64(&mut self) -> Result<u64, io::Error> {
let mut n = [0u8; 8]; let mut n = [0u8; 8];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(match self.byte_order() { Ok(match self.byte_order {
ByteOrder::LittleEndian => u64::from_le_bytes(n), ByteOrder::LittleEndian => u64::from_le_bytes(n),
ByteOrder::BigEndian => u64::from_be_bytes(n), ByteOrder::BigEndian => u64::from_be_bytes(n),
}) })
} }
/// Reads an i64
#[inline(always)] #[inline(always)]
fn read_u64_into(&mut self, buffer: &mut [u64]) -> Result<(), io::Error> { pub fn read_i64(&mut self) -> Result<i64, io::Error> {
self.read_exact(bytecast::u64_as_ne_mut_bytes(buffer))?; let mut n = [0u8; 8];
match self.byte_order() { self.reader.read_exact(&mut n)?;
ByteOrder::LittleEndian => { Ok(match self.byte_order {
for n in buffer { ByteOrder::LittleEndian => i64::from_le_bytes(n),
*n = u64::from_le(*n); ByteOrder::BigEndian => i64::from_be_bytes(n),
} })
}
ByteOrder::BigEndian => {
for n in buffer {
*n = u64::from_be(*n);
}
}
}
Ok(())
} }
/// Reads an f32 /// Reads an f32
#[inline(always)] #[inline(always)]
fn read_f32(&mut self) -> Result<f32, io::Error> { pub fn read_f32(&mut self) -> Result<f32, io::Error> {
let mut n = [0u8; 4]; let mut n = [0u8; 4];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(f32::from_bits(match self.byte_order() { Ok(f32::from_bits(match self.byte_order {
ByteOrder::LittleEndian => u32::from_le_bytes(n), ByteOrder::LittleEndian => u32::from_le_bytes(n),
ByteOrder::BigEndian => u32::from_be_bytes(n), ByteOrder::BigEndian => u32::from_be_bytes(n),
})) }))
} }
#[inline(always)]
fn read_f32_into(&mut self, buffer: &mut [f32]) -> Result<(), io::Error> {
self.read_exact(bytecast::f32_as_ne_mut_bytes(buffer))?;
match self.byte_order() {
ByteOrder::LittleEndian => {
for n in buffer {
*n = f32::from_bits(u32::from_le(n.to_bits()));
}
}
ByteOrder::BigEndian => {
for n in buffer {
*n = f32::from_bits(u32::from_be(n.to_bits()));
}
}
}
Ok(())
}
/// Reads an f64 /// Reads an f64
#[inline(always)] #[inline(always)]
fn read_f64(&mut self) -> Result<f64, io::Error> { pub fn read_f64(&mut self) -> Result<f64, io::Error> {
let mut n = [0u8; 8]; let mut n = [0u8; 8];
self.read_exact(&mut n)?; self.reader.read_exact(&mut n)?;
Ok(f64::from_bits(match self.byte_order() { Ok(f64::from_bits(match self.byte_order {
ByteOrder::LittleEndian => u64::from_le_bytes(n), ByteOrder::LittleEndian => u64::from_le_bytes(n),
ByteOrder::BigEndian => u64::from_be_bytes(n), ByteOrder::BigEndian => u64::from_be_bytes(n),
})) }))
} }
#[inline(always)]
fn read_f64_into(&mut self, buffer: &mut [f64]) -> Result<(), io::Error> {
self.read_exact(bytecast::f64_as_ne_mut_bytes(buffer))?;
match self.byte_order() {
ByteOrder::LittleEndian => {
for n in buffer {
*n = f64::from_bits(u64::from_le(n.to_bits()));
}
}
ByteOrder::BigEndian => {
for n in buffer {
*n = f64::from_bits(u64::from_be(n.to_bits()));
}
}
}
Ok(())
}
} }
/// ///
@@ -195,196 +140,64 @@ pub trait EndianReader: Read {
/// ## Deflate Reader /// ## Deflate Reader
/// ///
/// Reader that decompresses DEFLATE streams #[cfg(feature = "deflate")]
pub struct DeflateReader { pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
buffer: io::Cursor<Vec<u8>>,
byte_order: ByteOrder,
}
impl DeflateReader {
pub fn new<R: Read + Seek>(
reader: &mut SmartReader<R>,
max_uncompressed_length: usize,
) -> TiffResult<(usize, Self)> {
let byte_order = reader.byte_order;
let mut compressed = Vec::new();
reader.read_to_end(&mut compressed)?;
// TODO: Implement streaming compression, and remove this (temporary) and somewhat
// misleading workaround.
if compressed.len() > max_uncompressed_length {
return Err(TiffError::LimitsExceeded);
}
let uncompressed =
inflate::decompress_to_vec_zlib(&compressed).map_err(TiffError::from_inflate_status)?;
Ok((
uncompressed.len(),
Self {
byte_order,
buffer: io::Cursor::new(uncompressed),
},
))
}
}
impl Read for DeflateReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.buffer.read(buf)
}
}
impl EndianReader for DeflateReader {
fn byte_order(&self) -> ByteOrder {
self.byte_order
}
}
/// ///
/// ## LZW Reader /// ## LZW Reader
/// ///
/// Reader that decompresses LZW streams /// Reader that decompresses LZW streams
pub struct LZWReader { #[cfg(feature = "lzw")]
buffer: io::Cursor<Vec<u8>>, pub struct LZWReader<R: Read> {
byte_order: ByteOrder, reader: BufReader<Take<R>>,
decoder: weezl::decode::Decoder,
} }
impl LZWReader { #[cfg(feature = "lzw")]
impl<R: Read> LZWReader<R> {
/// Wraps a reader /// Wraps a reader
pub fn new<R>( pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
reader: &mut SmartReader<R>, Self {
compressed_length: usize, reader: BufReader::with_capacity(
max_uncompressed_length: usize, (32 * 1024).min(compressed_length),
) -> io::Result<(usize, LZWReader)> reader.take(u64::try_from(compressed_length).unwrap()),
where ),
R: Read + Seek, decoder: weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8),
{ }
let order = reader.byte_order; }
let mut compressed = vec![0; compressed_length as usize]; }
reader.read_exact(&mut compressed[..])?;
let mut uncompressed = Vec::with_capacity(max_uncompressed_length);
let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
let mut bytes_read = 0;
while uncompressed.len() < max_uncompressed_length { #[cfg(feature = "lzw")]
let bytes_written = uncompressed.len(); impl<R: Read> Read for LZWReader<R> {
uncompressed.reserve(1 << 12); fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let buffer_space = uncompressed.capacity().min(max_uncompressed_length); loop {
uncompressed.resize(buffer_space, 0u8); let result = self.decoder.decode_bytes(self.reader.fill_buf()?, buf);
self.reader.consume(result.consumed_in);
let result = decoder.decode_bytes(
&compressed[bytes_read..],
&mut uncompressed[bytes_written..],
);
bytes_read += result.consumed_in;
uncompressed.truncate(bytes_written + result.consumed_out);
match result.status { match result.status {
Ok(weezl::LzwStatus::Ok) => {} Ok(weezl::LzwStatus::Ok) => {
Ok(weezl::LzwStatus::Done) => break, if result.consumed_out == 0 {
continue;
} else {
return Ok(result.consumed_out);
}
}
Ok(weezl::LzwStatus::NoProgress) => { Ok(weezl::LzwStatus::NoProgress) => {
assert_eq!(result.consumed_in, 0);
assert_eq!(result.consumed_out, 0);
assert!(self.reader.buffer().is_empty());
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::UnexpectedEof, io::ErrorKind::UnexpectedEof,
"no lzw end code found", "no lzw end code found",
)) ));
}
Ok(weezl::LzwStatus::Done) => {
return Ok(result.consumed_out);
} }
Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)), Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)),
} }
} }
let bytes = uncompressed.len();
Ok((
bytes,
LZWReader {
buffer: io::Cursor::new(uncompressed),
byte_order: order,
},
))
}
}
impl Read for LZWReader {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.buffer.read(buf)
}
}
impl EndianReader for LZWReader {
#[inline(always)]
fn byte_order(&self) -> ByteOrder {
self.byte_order
}
}
///
/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7))
///
pub(crate) struct JpegReader {
buffer: io::Cursor<Vec<u8>>,
byte_order: ByteOrder,
}
impl JpegReader {
/// Constructs new JpegReader wrapping a SmartReader.
/// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one
/// central location, the constructor accepts this data as `jpeg_tables` here containing either
/// or both.
/// These `jpeg_tables` are simply prepended to the remaining jpeg image data.
/// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
/// which is also at the beginning of the remaining JPEG image data and would
/// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
/// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
/// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
/// this has to be removed as well (last two bytes of `jpeg_tables`).
pub fn new<R>(
reader: &mut SmartReader<R>,
length: u32,
jpeg_tables: &Option<Vec<u8>>,
) -> io::Result<JpegReader>
where
R: Read + Seek,
{
let order = reader.byte_order;
// Read jpeg image data
let mut segment = vec![0; length as usize];
reader.read_exact(&mut segment[..])?;
match jpeg_tables {
Some(tables) => {
let mut jpeg_data = tables.clone();
let truncated_length = jpeg_data.len() - 2;
jpeg_data.truncate(truncated_length);
jpeg_data.extend_from_slice(&mut segment[2..]);
Ok(JpegReader {
buffer: io::Cursor::new(jpeg_data),
byte_order: order,
})
}
None => Ok(JpegReader {
buffer: io::Cursor::new(segment),
byte_order: order,
}),
}
}
}
impl Read for JpegReader {
// #[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.buffer.read(buf)
}
}
impl EndianReader for JpegReader {
#[inline(always)]
fn byte_order(&self) -> ByteOrder {
self.byte_order
} }
} }
@@ -392,123 +205,70 @@ impl EndianReader for JpegReader {
/// ## PackBits Reader /// ## PackBits Reader
/// ///
enum PackBitsReaderState {
Header,
Literal,
Repeat { value: u8 },
}
/// Reader that unpacks Apple's `PackBits` format /// Reader that unpacks Apple's `PackBits` format
pub struct PackBitsReader { pub struct PackBitsReader<R: Read> {
buffer: io::Cursor<Vec<u8>>, reader: Take<R>,
byte_order: ByteOrder, state: PackBitsReaderState,
count: usize,
} }
impl PackBitsReader { impl<R: Read> PackBitsReader<R> {
/// Wraps a reader /// Wraps a reader
pub fn new<R: Read + Seek>( pub fn new(reader: R, length: u64) -> Self {
mut reader: R, Self {
byte_order: ByteOrder, reader: reader.take(length),
length: usize, state: PackBitsReaderState::Header,
) -> io::Result<(usize, PackBitsReader)> { count: 0,
let mut buffer = Vec::new();
let mut read: usize = 0;
while read < length {
let lread = read_packbits_run(&mut reader, &mut buffer)?;
if lread == 0 {
return Err(io::ErrorKind::UnexpectedEof.into());
} }
read += lread;
}
Ok((
buffer.len(),
PackBitsReader {
buffer: io::Cursor::new(buffer),
byte_order,
},
))
} }
} }
fn read_packbits_run<R: Read + Seek>(reader: &mut R, buffer: &mut Vec<u8>) -> io::Result<usize> { impl<R: Read> Read for PackBitsReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
while let PackBitsReaderState::Header = self.state {
if self.reader.limit() == 0 {
return Ok(0);
}
let mut header: [u8; 1] = [0]; let mut header: [u8; 1] = [0];
self.reader.read_exact(&mut header)?;
let bytes = reader.read(&mut header)?; let h = header[0] as i8;
if (-127..=-1).contains(&h) {
match bytes { let mut data: [u8; 1] = [0];
0 => Ok(0), self.reader.read_exact(&mut data)?;
_ => match header[0] as i8 { self.state = PackBitsReaderState::Repeat { value: data[0] };
-128 => Ok(1), self.count = (1 - h as isize) as usize;
h if h >= -127 && h <= -1 => { } else if h >= 0 {
let new_len = buffer.len() + (1 - h as isize) as usize; self.state = PackBitsReaderState::Literal;
reader.read_exact(&mut header)?; self.count = h as usize + 1;
buffer.resize(new_len, header[0]); } else {
Ok(2) // h = -128 is a no-op.
}
h => {
let num_vals = h as usize + 1;
let start = buffer.len();
buffer.resize(start + num_vals, 0);
reader.read_exact(&mut buffer[start..])?;
Ok(num_vals + 1)
}
},
} }
} }
impl Read for PackBitsReader { let length = buf.len().min(self.count);
#[inline] let actual = match self.state {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { PackBitsReaderState::Literal => self.reader.read(&mut buf[..length])?,
self.buffer.read(buf) PackBitsReaderState::Repeat { value } => {
} for b in &mut buf[..length] {
*b = value;
} }
impl EndianReader for PackBitsReader { length
#[inline(always)]
fn byte_order(&self) -> ByteOrder {
self.byte_order
}
} }
PackBitsReaderState::Header => unreachable!(),
};
/// self.count -= actual;
/// ## SmartReader Reader if self.count == 0 {
/// self.state = PackBitsReaderState::Header;
/// Reader that is aware of the byte order.
#[derive(Debug)]
pub struct SmartReader<R>
where
R: Read + Seek,
{
reader: R,
pub byte_order: ByteOrder,
} }
Ok(actual)
impl<R> SmartReader<R>
where
R: Read + Seek,
{
/// Wraps a reader
pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> {
SmartReader { reader, byte_order }
}
}
impl<R> EndianReader for SmartReader<R>
where
R: Read + Seek,
{
#[inline(always)]
fn byte_order(&self) -> ByteOrder {
self.byte_order
}
}
impl<R: Read + Seek> Read for SmartReader<R> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
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)
} }
} }
@@ -525,8 +285,7 @@ mod test {
let encoded_len = encoded.len(); let encoded_len = encoded.len();
let buff = io::Cursor::new(encoded); let buff = io::Cursor::new(encoded);
let (_, mut decoder) = let mut decoder = PackBitsReader::new(buff, encoded_len as u64);
PackBitsReader::new(buff, ByteOrder::LittleEndian, encoded_len).unwrap();
let mut decoded = Vec::new(); let mut decoded = Vec::new();
decoder.read_to_end(&mut decoded).unwrap(); decoder.read_to_end(&mut decoded).unwrap();
+44
View File
@@ -0,0 +1,44 @@
use std::io::{Read, Seek};
use crate::tags::Tag;
use crate::{TiffError, TiffFormatError, TiffResult};
use super::ifd::{Directory, Value};
use super::stream::EndianReader;
use super::Limits;
pub(crate) struct TagReader<'a, R: Read + Seek> {
pub reader: &'a mut EndianReader<R>,
pub ifd: &'a Directory,
pub limits: &'a Limits,
pub bigtiff: bool,
}
impl<'a, R: Read + Seek> TagReader<'a, R> {
pub(crate) fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<Value>> {
Ok(match self.ifd.get(&tag) {
Some(entry) => Some(entry.clone().val(self.limits, self.bigtiff, self.reader)?),
None => None,
})
}
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
match self.find_tag(tag)? {
Some(val) => Ok(val),
None => Err(TiffError::FormatError(
TiffFormatError::RequiredTagNotFound(tag),
)),
}
}
pub fn find_tag_uint_vec<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<Option<Vec<T>>> {
self.find_tag(tag)?
.map(|v| v.into_u64_vec())
.transpose()?
.map(|v| {
v.into_iter()
.map(|u| {
T::try_from(u).map_err(|_| TiffFormatError::InvalidTagValueType(tag).into())
})
.collect()
})
.transpose()
}
}
+148
View File
@@ -1,5 +1,31 @@
use crate::tags::{PhotometricInterpretation, SampleFormat}; use crate::tags::{PhotometricInterpretation, SampleFormat};
macro_rules! integer_horizontal_predict {
() => {
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
let sample_size = Self::SAMPLE_FORMAT.len();
if row.len() < sample_size {
debug_assert!(false);
return;
}
let (start, rest) = row.split_at(sample_size);
result.extend_from_slice(start);
if result.capacity() - result.len() < rest.len() {
return;
}
result.extend(
row.into_iter()
.zip(rest)
.map(|(prev, current)| current.wrapping_sub(*prev)),
);
}
};
}
/// Trait for different colortypes that can be encoded. /// Trait for different colortypes that can be encoded.
pub trait ColorType { pub trait ColorType {
/// The type of each sample of this colortype /// The type of each sample of this colortype
@@ -10,6 +36,8 @@ pub trait ColorType {
const BITS_PER_SAMPLE: &'static [u16]; const BITS_PER_SAMPLE: &'static [u16];
/// The value of the tiff tag `SampleFormat` /// The value of the tiff tag `SampleFormat`
const SAMPLE_FORMAT: &'static [SampleFormat]; const SAMPLE_FORMAT: &'static [SampleFormat];
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
} }
pub struct Gray8; pub struct Gray8;
@@ -18,6 +46,18 @@ impl ColorType for Gray8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[8]; const BITS_PER_SAMPLE: &'static [u16] = &[8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
}
pub struct GrayI8;
impl ColorType for GrayI8 {
type Inner = i8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray16; pub struct Gray16;
@@ -26,6 +66,18 @@ impl ColorType for Gray16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[16]; const BITS_PER_SAMPLE: &'static [u16] = &[16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
}
pub struct GrayI16;
impl ColorType for GrayI16 {
type Inner = i16;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray32; pub struct Gray32;
@@ -34,6 +86,18 @@ impl ColorType for Gray32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32]; const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
}
pub struct GrayI32;
impl ColorType for GrayI32 {
type Inner = i32;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray32Float; pub struct Gray32Float;
@@ -42,6 +106,10 @@ impl ColorType for Gray32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32]; const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct Gray64; pub struct Gray64;
@@ -50,6 +118,18 @@ impl ColorType for Gray64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64]; const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
}
pub struct GrayI64;
impl ColorType for GrayI64 {
type Inner = i64;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
} }
pub struct Gray64Float; pub struct Gray64Float;
@@ -58,6 +138,10 @@ impl ColorType for Gray64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64]; const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGB8; pub struct RGB8;
@@ -66,6 +150,8 @@ impl ColorType for RGB8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB16; pub struct RGB16;
@@ -74,6 +160,8 @@ impl ColorType for RGB16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16]; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB32; pub struct RGB32;
@@ -82,6 +170,8 @@ impl ColorType for RGB32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB32Float; pub struct RGB32Float;
@@ -90,6 +180,9 @@ impl ColorType for RGB32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGB64; pub struct RGB64;
@@ -98,6 +191,8 @@ impl ColorType for RGB64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
} }
pub struct RGB64Float; pub struct RGB64Float;
@@ -106,6 +201,9 @@ impl ColorType for RGB64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGBA8; pub struct RGBA8;
@@ -114,6 +212,8 @@ impl ColorType for RGBA8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA16; pub struct RGBA16;
@@ -122,6 +222,8 @@ impl ColorType for RGBA16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA32; pub struct RGBA32;
@@ -130,6 +232,8 @@ impl ColorType for RGBA32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA32Float; pub struct RGBA32Float;
@@ -138,6 +242,9 @@ impl ColorType for RGBA32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct RGBA64; pub struct RGBA64;
@@ -146,6 +253,8 @@ impl ColorType for RGBA64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct RGBA64Float; pub struct RGBA64Float;
@@ -154,6 +263,9 @@ impl ColorType for RGBA64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct CMYK8; pub struct CMYK8;
@@ -162,6 +274,8 @@ impl ColorType for CMYK8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8]; const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK16; pub struct CMYK16;
@@ -170,6 +284,8 @@ impl ColorType for CMYK16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16]; const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK32; pub struct CMYK32;
@@ -178,6 +294,8 @@ impl ColorType for CMYK32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK32Float; pub struct CMYK32Float;
@@ -186,6 +304,10 @@ impl ColorType for CMYK32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32]; const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
} }
pub struct CMYK64; pub struct CMYK64;
@@ -194,6 +316,8 @@ impl ColorType for CMYK64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
} }
pub struct CMYK64Float; pub struct CMYK64Float;
@@ -202,4 +326,28 @@ impl ColorType for CMYK64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK; const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64]; const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4]; const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
}
pub struct YCbCr8;
impl ColorType for YCbCr8 {
type Inner = u8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
}
pub struct CMYKA8;
impl ColorType for CMYKA8 {
type Inner = u8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 5];
integer_horizontal_predict!();
} }
+82
View File
@@ -0,0 +1,82 @@
use crate::encoder::compression::*;
use flate2::{write::ZlibEncoder, Compression as FlateCompression};
/// The Deflate algorithm used to compress image data in TIFF files.
#[derive(Debug, Clone, Copy)]
pub struct Deflate {
level: FlateCompression,
}
/// The level of compression used by the Deflate algorithm.
/// It allows trading compression ratio for compression speed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum DeflateLevel {
/// The fastest possible compression mode.
Fast = 1,
/// The conserative choice between speed and ratio.
Balanced = 6,
/// The best compression available with Deflate.
Best = 9,
}
impl Default for DeflateLevel {
fn default() -> Self {
DeflateLevel::Balanced
}
}
impl Deflate {
/// Create a new deflate compressor with a specific level of compression.
pub fn with_level(level: DeflateLevel) -> Self {
Self {
level: FlateCompression::new(level as u32),
}
}
}
impl Default for Deflate {
fn default() -> Self {
Self::with_level(DeflateLevel::default())
}
}
impl Compression for Deflate {
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::Deflate;
fn get_algorithm(&self) -> Compressor {
Compressor::Deflate(*self)
}
}
impl CompressionAlgorithm for Deflate {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
let mut encoder = ZlibEncoder::new(writer, self.level);
encoder.write_all(bytes)?;
encoder.try_finish()?;
Ok(encoder.total_out())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::compression::tests::TEST_DATA;
use std::io::Cursor;
#[test]
fn test_deflate() {
const EXPECTED_COMPRESSED_DATA: [u8; 64] = [
0x78, 0x9C, 0x15, 0xC7, 0xD1, 0x0D, 0x80, 0x20, 0x0C, 0x04, 0xD0, 0x55, 0x6E, 0x02,
0xA7, 0x71, 0x81, 0xA6, 0x41, 0xDA, 0x28, 0xD4, 0xF4, 0xD0, 0xF9, 0x81, 0xE4, 0xFD,
0xBC, 0xD3, 0x9C, 0x58, 0x04, 0x1C, 0xE9, 0xBD, 0xE2, 0x8A, 0x84, 0x5A, 0xD1, 0x7B,
0xE7, 0x97, 0xF4, 0xF8, 0x08, 0x8D, 0xF6, 0x66, 0x21, 0x3D, 0x3A, 0xE4, 0xA9, 0x91,
0x3E, 0xAC, 0xF1, 0x98, 0xB9, 0x70, 0x17, 0x13,
];
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Deflate::default().write_to(&mut writer, TEST_DATA).unwrap();
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
}
}
+46
View File
@@ -0,0 +1,46 @@
use crate::encoder::compression::*;
use weezl::encode::Encoder as LZWEncoder;
/// The LZW algorithm used to compress image data in TIFF files.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Lzw;
impl Compression for Lzw {
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::LZW;
fn get_algorithm(&self) -> Compressor {
Compressor::Lzw(*self)
}
}
impl CompressionAlgorithm for Lzw {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
let mut encoder = LZWEncoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
let result = encoder.into_stream(writer).encode_all(bytes);
let byte_count = result.bytes_written as u64;
result.status.map(|_| byte_count)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::compression::tests::TEST_DATA;
use std::io::Cursor;
#[test]
fn test_lzw() {
const EXPECTED_COMPRESSED_DATA: [u8; 63] = [
0x80, 0x15, 0x0D, 0x06, 0x93, 0x98, 0x82, 0x08, 0x20, 0x30, 0x88, 0x0E, 0x67, 0x43,
0x91, 0xA4, 0xDC, 0x67, 0x10, 0x19, 0x8D, 0xE7, 0x21, 0x01, 0x8C, 0xD0, 0x65, 0x31,
0x9A, 0xE1, 0xD1, 0x03, 0xB1, 0x86, 0x1A, 0x6F, 0x3A, 0xC1, 0x4C, 0x66, 0xF3, 0x69,
0xC0, 0xE4, 0x65, 0x39, 0x9C, 0xCD, 0x26, 0xF3, 0x74, 0x20, 0xD8, 0x67, 0x89, 0x9A,
0x4E, 0x86, 0x83, 0x69, 0xCC, 0x5D, 0x01,
];
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Lzw.write_to(&mut writer, TEST_DATA).unwrap();
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
}
}
+70
View File
@@ -0,0 +1,70 @@
use crate::tags::CompressionMethod;
use std::io::{self, Write};
#[cfg(feature = "deflate")]
mod deflate;
#[cfg(feature = "lzw")]
mod lzw;
mod packbits;
mod uncompressed;
#[cfg(feature = "deflate")]
pub use self::deflate::{Deflate, DeflateLevel};
#[cfg(feature = "lzw")]
pub use self::lzw::Lzw;
pub use self::packbits::Packbits;
pub use self::uncompressed::Uncompressed;
/// An algorithm used for compression
pub trait CompressionAlgorithm {
/// The algorithm writes data directly into the writer.
/// It returns the total number of bytes written.
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error>;
}
/// An algorithm used for compression with associated enums and optional configurations.
pub trait Compression: CompressionAlgorithm {
/// The corresponding tag to the algorithm.
const COMPRESSION_METHOD: CompressionMethod;
/// Method to optain a type that can store each variant of comression algorithm.
fn get_algorithm(&self) -> Compressor;
}
/// An enum to store each compression algorithm.
#[non_exhaustive]
pub enum Compressor {
Uncompressed(Uncompressed),
#[cfg(feature = "lzw")]
Lzw(Lzw),
#[cfg(feature = "deflate")]
Deflate(Deflate),
Packbits(Packbits),
}
impl Default for Compressor {
/// The default compression strategy does not apply any compression.
fn default() -> Self {
Compressor::Uncompressed(Uncompressed)
}
}
impl CompressionAlgorithm for Compressor {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
match self {
Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes),
#[cfg(feature = "lzw")]
Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes),
#[cfg(feature = "deflate")]
Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes),
Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
}
}
}
#[cfg(test)]
mod tests {
pub const TEST_DATA: &[u8] = b"This is a string for checking various compression algorithms.";
}
+204
View File
@@ -0,0 +1,204 @@
use crate::encoder::compression::*;
use std::io::{BufWriter, Error, ErrorKind};
/// Compressor that uses the Packbits[^note] algorithm to compress bytes.
///
/// [^note]: PackBits is often ineffective on continuous tone images,
/// including many grayscale images. In such cases, it is better
/// to leave the image uncompressed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Packbits;
impl Compression for Packbits {
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::PackBits;
fn get_algorithm(&self) -> Compressor {
Compressor::Packbits(*self)
}
}
impl CompressionAlgorithm for Packbits {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
// Inspired by https://github.com/skirridsystems/packbits
const MIN_REPT: u8 = 3; // Minimum run to compress between differ blocks
const MAX_BYTES: u8 = 128; // Maximum number of bytes that can be encoded in a header byte
// Encoding for header byte based on number of bytes represented.
fn encode_diff(n: u8) -> u8 {
n - 1
}
fn encode_rept(n: u8) -> u8 {
let var = 256 - (n - 1) as u16;
var as u8
}
fn write_u8<W: Write>(writer: &mut W, byte: u8) -> Result<u64, Error> {
writer.write(&[byte]).map(|byte_count| byte_count as u64)
}
let mut bufwriter = BufWriter::new(writer);
let mut bytes_written = 0u64; // The number of bytes written into the writer
let mut offset: Option<u64> = None; // The index of the first byte written into the writer
let mut src_index: usize = 0; // Index of the current byte
let mut src_count = bytes.len(); //The number of bytes remaining to be compressed
let mut in_run = false; // Indication whether counting of similar bytes is performed
let mut run_index = 0u8; // Distance into pending bytes that a run starts
let mut bytes_pending = 0u8; // Bytes looked at but not yet output
let mut pending_index = 0usize; // Index of the first pending byte
let mut curr_byte: u8; // Byte currently being considered
let mut last_byte: u8; // Previous byte
// Need at least one byte to compress
if src_count == 0 {
return Err(Error::new(ErrorKind::WriteZero, "write zero"));
}
// Prime compressor with first character.
last_byte = bytes[src_index];
src_index += 1;
bytes_pending += 1;
while src_count - 1 != 0 {
src_count -= 1;
curr_byte = bytes[src_index];
src_index += 1;
bytes_pending += 1;
if in_run {
if (curr_byte != last_byte) || (bytes_pending > MAX_BYTES) {
offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending - 1))?);
write_u8(&mut bufwriter, last_byte)?;
bytes_written += 2;
bytes_pending = 1;
pending_index = src_index - 1;
run_index = 0;
in_run = false;
}
} else if bytes_pending > MAX_BYTES {
// We have as much differing data as we can output in one chunk.
// Output MAX_BYTES leaving one byte.
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(MAX_BYTES))?);
bufwriter.write_all(&bytes[pending_index..pending_index + MAX_BYTES as usize])?;
bytes_written += 1 + MAX_BYTES as u64;
pending_index += MAX_BYTES as usize;
bytes_pending -= MAX_BYTES;
run_index = bytes_pending - 1; // A run could start here
} else if curr_byte == last_byte {
if (bytes_pending - run_index >= MIN_REPT) || (run_index == 0) {
// This is a worthwhile run
if run_index != 0 {
// Flush differing data out of input buffer
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(run_index))?);
bufwriter
.write_all(&bytes[pending_index..pending_index + run_index as usize])?;
bytes_written += 1 + run_index as u64;
}
bytes_pending -= run_index; // Length of run
in_run = true;
}
} else {
run_index = bytes_pending - 1; // A run could start here
}
last_byte = curr_byte;
}
// Output the remainder
if in_run {
bytes_written += 2;
offset.get_or_insert(write_u8(&mut bufwriter, encode_rept(bytes_pending))?);
write_u8(&mut bufwriter, last_byte)?;
} else {
bytes_written += 1 + bytes_pending as u64;
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(bytes_pending))?);
bufwriter.write_all(&bytes[pending_index..pending_index + bytes_pending as usize])?;
}
bufwriter.flush()?;
Ok(bytes_written)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::compression::tests::TEST_DATA;
use std::io::Cursor;
#[test]
fn test_packbits_single_byte() {
// compress single byte
const UNCOMPRESSED_DATA: [u8; 1] = [0x3F];
const EXPECTED_COMPRESSED_DATA: [u8; 2] = [0x00, 0x3F];
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits.write_to(&mut writer, &UNCOMPRESSED_DATA).unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
#[test]
fn test_packbits_rept() {
// compress buffer with repetitive sequence
const UNCOMPRESSED_DATA: &[u8] =
b"This strrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrring hangs.";
const EXPECTED_COMPRESSED_DATA: &[u8] = b"\x06This st\xD1r\x09ing hangs.";
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits.write_to(&mut writer, UNCOMPRESSED_DATA).unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
#[test]
fn test_packbits_large_rept_nonrept() {
// compress buffer with large repetitive and non-repetitive sequence
let mut data = b"This st".to_vec();
for _i in 0..158 {
data.push(b'r');
}
data.extend_from_slice(b"ing hangs.");
for i in 0..158 {
data.push(i);
}
const EXPECTED_COMPRESSED_DATA: [u8; 182] = [
0x06, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x74, 0x81, 0x72, 0xE3, 0x72, 0x7F, 0x69,
0x6E, 0x67, 0x20, 0x68, 0x61, 0x6E, 0x67, 0x73, 0x2E, 0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A,
0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74,
0x75, 0x27, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81,
0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
];
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits.write_to(&mut writer, data.as_slice()).unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
#[test]
fn test_packbits() {
// compress teststring
const EXPECTED_COMPRESSED_DATA: &[u8] =
b"\x3CThis is a string for checking various compression algorithms.";
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits.write_to(&mut writer, TEST_DATA).unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
}
+34
View File
@@ -0,0 +1,34 @@
use crate::encoder::compression::*;
/// The default algorithm which does not compress at all.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Uncompressed;
impl Compression for Uncompressed {
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::None;
fn get_algorithm(&self) -> Compressor {
Compressor::Uncompressed(*self)
}
}
impl CompressionAlgorithm for Uncompressed {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
writer.write(bytes).map(|byte_count| byte_count as u64)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encoder::compression::tests::TEST_DATA;
use std::io::Cursor;
#[test]
fn test_no_compression() {
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Uncompressed.write_to(&mut writer, TEST_DATA).unwrap();
assert_eq!(TEST_DATA, compressed_data);
}
}
+355 -427
View File
@@ -1,402 +1,86 @@
use std::collections::BTreeMap; pub use tiff_value::*;
use std::convert::TryFrom;
use std::io::{Seek, Write};
use std::{cmp, io, mem};
use crate::bytecast; use std::{
use crate::error::{TiffError, TiffFormatError, TiffResult}; cmp,
use crate::tags::{self, ResolutionUnit, Tag, Type}; collections::BTreeMap,
io::{self, Seek, Write},
marker::PhantomData,
mem,
num::TryFromIntError,
};
use crate::{
error::{TiffResult, UsageError},
tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag},
TiffError, TiffFormatError,
};
pub mod colortype; pub mod colortype;
pub mod compression;
mod tiff_value;
mod writer; mod writer;
use self::colortype::*; use self::colortype::*;
use self::compression::Compression as Comp;
use self::compression::*;
use self::writer::*; use self::writer::*;
/// Type to represent tiff values of type `RATIONAL` /// Type of prediction to prepare the image with.
#[derive(Clone)] ///
pub struct Rational { /// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
pub n: u32, /// passes ran over the image data to prepare it for compression. This is mostly used for LZW
pub d: u32, /// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
} /// ratio over the unpredicted compression !
///
/// Type to represent tiff values of type `SRATIONAL` /// [Predictor::FloatingPoint] is currently not supported.
pub struct SRational { pub type Predictor = crate::tags::Predictor;
pub n: i32, #[cfg(feature = "deflate")]
pub d: i32, pub type DeflateLevel = compression::DeflateLevel;
}
/// Trait for types that can be encoded in a tiff file
pub trait TiffValue {
const BYTE_LEN: u32;
const FIELD_TYPE: Type;
fn count(&self) -> u32;
fn bytes(&self) -> u32 {
self.count() * Self::BYTE_LEN
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()>;
}
impl TiffValue for [u8] {
const BYTE_LEN: u32 = 1;
const FIELD_TYPE: Type = Type::BYTE;
fn count(&self) -> u32 {
self.len() as u32
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_bytes(self)?;
Ok(())
}
}
impl TiffValue for [i8] {
const BYTE_LEN: u32 = 1;
const FIELD_TYPE: Type = Type::SBYTE;
fn count(&self) -> u32 {
self.len() as u32
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
let slice = bytecast::i8_as_ne_bytes(self);
writer.write_bytes(slice)?;
Ok(())
}
}
impl TiffValue for [u16] { #[derive(Clone, Copy, PartialEq)]
const BYTE_LEN: u32 = 2; pub enum Compression {
const FIELD_TYPE: Type = Type::SHORT; Uncompressed,
#[cfg(feature = "lzw")]
fn count(&self) -> u32 { Lzw,
self.len() as u32 #[cfg(feature = "deflate")]
} Deflate(DeflateLevel),
Packbits,
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
let slice = bytecast::u16_as_ne_bytes(self);
writer.write_bytes(slice)?;
Ok(())
}
}
impl TiffValue for [i16] {
const BYTE_LEN: u32 = 2;
const FIELD_TYPE: Type = Type::SSHORT;
fn count(&self) -> u32 {
self.len() as u32
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
let slice = bytecast::i16_as_ne_bytes(self);
writer.write_bytes(slice)?;
Ok(())
} }
}
impl TiffValue for [u32] {
const BYTE_LEN: u32 = 4;
const FIELD_TYPE: Type = Type::LONG;
fn count(&self) -> u32 { impl Default for Compression {
self.len() as u32 fn default() -> Self {
Self::Uncompressed
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
let slice = bytecast::u32_as_ne_bytes(self);
writer.write_bytes(slice)?;
Ok(())
} }
}
impl TiffValue for [i32] {
const BYTE_LEN: u32 = 4;
const FIELD_TYPE: Type = Type::SLONG;
fn count(&self) -> u32 { impl Compression {
self.len() as u32 fn tag(&self) -> CompressionMethod {
} match self {
Compression::Uncompressed => CompressionMethod::None,
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> { #[cfg(feature = "lzw")]
let slice = bytecast::i32_as_ne_bytes(self); Compression::Lzw => CompressionMethod::LZW,
writer.write_bytes(slice)?; #[cfg(feature = "deflate")]
Ok(()) Compression::Deflate(_) => CompressionMethod::Deflate,
Compression::Packbits => CompressionMethod::PackBits,
} }
} }
impl TiffValue for [u64] { fn get_algorithm(&self) -> Compressor {
const BYTE_LEN: u32 = 8; match self {
const FIELD_TYPE: Type = Type::LONG8; Compression::Uncompressed => compression::Uncompressed {}.get_algorithm(),
#[cfg(feature = "lzw")]
fn count(&self) -> u32 { Compression::Lzw => compression::Lzw {}.get_algorithm(),
self.len() as u32 #[cfg(feature = "deflate")]
Compression::Deflate(level) => compression::Deflate::with_level(*level).get_algorithm(),
Compression::Packbits => compression::Packbits {}.get_algorithm(),
} }
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
let slice = bytecast::u64_as_ne_bytes(self);
writer.write_bytes(slice)?;
Ok(())
}
}
impl TiffValue for [f32] {
const BYTE_LEN: u32 = 4;
const FIELD_TYPE: Type = Type::FLOAT;
fn count(&self) -> u32 {
self.len() as u32
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
// We write using nativeedian so this sould be safe
let slice = bytecast::f32_as_ne_bytes(self);
writer.write_bytes(slice)?;
Ok(())
}
}
impl TiffValue for [f64] {
const BYTE_LEN: u32 = 8;
const FIELD_TYPE: Type = Type::DOUBLE;
fn count(&self) -> u32 {
self.len() as u32
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
// We write using nativeedian so this sould be safe
let slice = bytecast::f64_as_ne_bytes(self);
writer.write_bytes(slice)?;
Ok(())
}
}
impl TiffValue for [Rational] {
const BYTE_LEN: u32 = 8;
const FIELD_TYPE: Type = Type::RATIONAL;
fn count(&self) -> u32 {
self.len() as u32
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
for x in self {
x.write(writer)?;
}
Ok(())
}
}
impl TiffValue for [SRational] {
const BYTE_LEN: u32 = 8;
const FIELD_TYPE: Type = Type::SRATIONAL;
fn count(&self) -> u32 {
self.len() as u32
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
for x in self {
x.write(writer)?;
}
Ok(())
}
}
impl TiffValue for u8 {
const BYTE_LEN: u32 = 1;
const FIELD_TYPE: Type = Type::BYTE;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u8(*self)?;
Ok(())
}
}
impl TiffValue for i8 {
const BYTE_LEN: u32 = 1;
const FIELD_TYPE: Type = Type::SBYTE;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_i8(*self)?;
Ok(())
}
}
impl TiffValue for u16 {
const BYTE_LEN: u32 = 2;
const FIELD_TYPE: Type = Type::SHORT;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u16(*self)?;
Ok(())
}
}
impl TiffValue for i16 {
const BYTE_LEN: u32 = 2;
const FIELD_TYPE: Type = Type::SSHORT;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_i16(*self)?;
Ok(())
}
}
impl TiffValue for u32 {
const BYTE_LEN: u32 = 4;
const FIELD_TYPE: Type = Type::LONG;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u32(*self)?;
Ok(())
}
}
impl TiffValue for i32 {
const BYTE_LEN: u32 = 4;
const FIELD_TYPE: Type = Type::SLONG;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_i32(*self)?;
Ok(())
}
}
impl TiffValue for u64 {
const BYTE_LEN: u32 = 8;
const FIELD_TYPE: Type = Type::LONG8;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u64(*self)?;
Ok(())
}
}
impl TiffValue for f32 {
const BYTE_LEN: u32 = 4;
const FIELD_TYPE: Type = Type::FLOAT;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_f32(*self)?;
Ok(())
}
}
impl TiffValue for f64 {
const BYTE_LEN: u32 = 8;
const FIELD_TYPE: Type = Type::DOUBLE;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_f64(*self)?;
Ok(())
}
}
impl TiffValue for Rational {
const BYTE_LEN: u32 = 8;
const FIELD_TYPE: Type = Type::RATIONAL;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u32(self.n)?;
writer.write_u32(self.d)?;
Ok(())
}
}
impl TiffValue for SRational {
const BYTE_LEN: u32 = 8;
const FIELD_TYPE: Type = Type::SRATIONAL;
fn count(&self) -> u32 {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_i32(self.n)?;
writer.write_i32(self.d)?;
Ok(())
}
}
impl TiffValue for str {
const BYTE_LEN: u32 = 1;
const FIELD_TYPE: Type = Type::ASCII;
fn count(&self) -> u32 {
self.len() as u32 + 1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
writer.write_bytes(self.as_bytes())?;
writer.write_u8(0)?;
Ok(())
} else {
Err(TiffError::FormatError(TiffFormatError::InvalidTag))
}
}
}
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
const BYTE_LEN: u32 = T::BYTE_LEN;
const FIELD_TYPE: Type = T::FIELD_TYPE;
fn count(&self) -> u32 {
(*self).count()
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
(*self).write(writer)
} }
} }
/// Tiff encoder. /// Encoder for Tiff and BigTiff files.
/// ///
/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder` /// With this type you can get a `DirectoryEncoder` or a `ImageEncoder`
/// to encode tiff ifd directories with images. /// to encode Tiff/BigTiff ifd directories with images.
/// ///
/// See `DirectoryEncoder` and `ImageEncoder`. /// See `DirectoryEncoder` and `ImageEncoder`.
/// ///
@@ -408,41 +92,91 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
/// # let image_data = vec![0; 100*100*3]; /// # let image_data = vec![0; 100*100*3];
/// use tiff::encoder::*; /// use tiff::encoder::*;
/// ///
/// // create a standard Tiff file
/// let mut tiff = TiffEncoder::new(&mut file).unwrap(); /// let mut tiff = TiffEncoder::new(&mut file).unwrap();
///
/// tiff.write_image::<colortype::RGB8>(100, 100, &image_data).unwrap(); /// tiff.write_image::<colortype::RGB8>(100, 100, &image_data).unwrap();
///
/// // create a BigTiff file
/// let mut bigtiff = TiffEncoder::new_big(&mut file).unwrap();
/// bigtiff.write_image::<colortype::RGB8>(100, 100, &image_data).unwrap();
///
/// # } /// # }
/// ``` /// ```
pub struct TiffEncoder<W> { pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> {
writer: TiffWriter<W>, writer: TiffWriter<W>,
kind: PhantomData<K>,
predictor: Predictor,
compression: Compression,
} }
/// Constructor functions to create standard Tiff files.
impl<W: Write + Seek> TiffEncoder<W> { impl<W: Write + Seek> TiffEncoder<W> {
pub fn new(writer: W) -> TiffResult<TiffEncoder<W>> { /// Creates a new encoder for standard Tiff files.
///
/// To create BigTiff files, use [`new_big`][TiffEncoder::new_big] or
/// [`new_generic`][TiffEncoder::new_generic].
pub fn new(writer: W) -> TiffResult<TiffEncoder<W, TiffKindStandard>> {
TiffEncoder::new_generic(writer)
}
}
/// Constructor functions to create BigTiff files.
impl<W: Write + Seek> TiffEncoder<W, TiffKindBig> {
/// Creates a new encoder for BigTiff files.
///
/// To create standard Tiff files, use [`new`][TiffEncoder::new] or
/// [`new_generic`][TiffEncoder::new_generic].
pub fn new_big(writer: W) -> TiffResult<Self> {
TiffEncoder::new_generic(writer)
}
}
/// Generic functions that are available for both Tiff and BigTiff encoders.
impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
/// Creates a new Tiff or BigTiff encoder, inferred from the return type.
pub fn new_generic(writer: W) -> TiffResult<Self> {
let mut encoder = TiffEncoder { let mut encoder = TiffEncoder {
writer: TiffWriter::new(writer), writer: TiffWriter::new(writer),
kind: PhantomData,
predictor: Predictor::None,
compression: Compression::Uncompressed,
}; };
write_tiff_header(&mut encoder.writer)?; K::write_header(&mut encoder.writer)?;
// blank the IFD offset location
encoder.writer.write_u32(0)?;
Ok(encoder) Ok(encoder)
} }
/// Create a `DirectoryEncoder` to encode an ifd directory. /// Set the predictor to use
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W>> { ///
/// 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) DirectoryEncoder::new(&mut self.writer)
} }
/// Create an 'ImageEncoder' to encode an image one slice at a time. /// Create an [`ImageEncoder`] to encode an image one slice at a time.
pub fn new_image<C: ColorType>( pub fn new_image<C: ColorType>(
&mut self, &mut self,
width: u32, width: u32,
height: u32, height: u32,
) -> TiffResult<ImageEncoder<W, C>> { ) -> TiffResult<ImageEncoder<W, C, K>> {
let encoder = DirectoryEncoder::new(&mut self.writer)?; let encoder = DirectoryEncoder::new(&mut self.writer)?;
ImageEncoder::new(encoder, width, height) ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
} }
/// Convenience function to write an entire image from memory. /// Convenience function to write an entire image from memory.
@@ -456,7 +190,8 @@ impl<W: Write + Seek> TiffEncoder<W> {
[C::Inner]: TiffValue, [C::Inner]: TiffValue,
{ {
let encoder = DirectoryEncoder::new(&mut self.writer)?; let encoder = DirectoryEncoder::new(&mut self.writer)?;
let image: ImageEncoder<W, C> = ImageEncoder::new(encoder, width, height)?; let image: ImageEncoder<W, C, K> =
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?;
image.write_data(data) image.write_data(data)
} }
} }
@@ -465,19 +200,19 @@ impl<W: Write + Seek> TiffEncoder<W> {
/// ///
/// You should call `finish` on this when you are finished with it. /// You should call `finish` on this when you are finished with it.
/// Encoding can silently fail while this is dropping. /// Encoding can silently fail while this is dropping.
pub struct DirectoryEncoder<'a, W: 'a + Write + Seek> { pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> {
writer: &'a mut TiffWriter<W>, writer: &'a mut TiffWriter<W>,
dropped: bool, dropped: bool,
// We use BTreeMap to make sure tags are written in correct order // We use BTreeMap to make sure tags are written in correct order
ifd_pointer_pos: u64, ifd_pointer_pos: u64,
ifd: BTreeMap<u16, (u16, u32, Vec<u8>)>, ifd: BTreeMap<u16, DirectoryEntry<K::OffsetType>>,
} }
impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> { impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
fn new(writer: &'a mut TiffWriter<W>) -> TiffResult<DirectoryEncoder<'a, W>> { fn new(writer: &'a mut TiffWriter<W>) -> TiffResult<Self> {
// the previous word is the IFD offset position // the previous word is the IFD offset position
let ifd_pointer_pos = writer.offset() - mem::size_of::<u32>() as u64; let ifd_pointer_pos = writer.offset() - mem::size_of::<K::OffsetType>() as u64;
writer.pad_word_boundary()?; writer.pad_word_boundary()?; // TODO: Do we need to adjust this for BigTiff?
Ok(DirectoryEncoder { Ok(DirectoryEncoder {
writer, writer,
dropped: false, dropped: false,
@@ -488,8 +223,7 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
/// Write a single ifd tag. /// Write a single ifd tag.
pub fn write_tag<T: TiffValue>(&mut self, tag: Tag, value: T) -> TiffResult<()> { pub fn write_tag<T: TiffValue>(&mut self, tag: Tag, value: T) -> TiffResult<()> {
let len = <T>::BYTE_LEN * value.count(); let mut bytes = Vec::with_capacity(value.bytes());
let mut bytes = Vec::with_capacity(usize::try_from(len)?);
{ {
let mut writer = TiffWriter::new(&mut bytes); let mut writer = TiffWriter::new(&mut bytes);
value.write(&mut writer)?; value.write(&mut writer)?;
@@ -497,7 +231,11 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
self.ifd.insert( self.ifd.insert(
tag.to_u16(), tag.to_u16(),
(<T>::FIELD_TYPE.to_u16(), value.count(), bytes), DirectoryEntry {
data_type: <T>::FIELD_TYPE.to_u16(),
count: value.count().try_into()?,
data: bytes,
},
); );
Ok(()) Ok(())
@@ -505,15 +243,21 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
fn write_directory(&mut self) -> TiffResult<u64> { fn write_directory(&mut self) -> TiffResult<u64> {
// Start by writing out all values // Start by writing out all values
for &mut (_, _, ref mut bytes) in self.ifd.values_mut() { for &mut DirectoryEntry {
if bytes.len() > 4 { data: ref mut bytes,
..
} in self.ifd.values_mut()
{
let data_bytes = mem::size_of::<K::OffsetType>();
if bytes.len() > data_bytes {
let offset = self.writer.offset(); let offset = self.writer.offset();
self.writer.write_bytes(bytes)?; self.writer.write_bytes(bytes)?;
*bytes = vec![0, 0, 0, 0]; *bytes = vec![0; data_bytes];
let mut writer = TiffWriter::new(bytes as &mut [u8]); let mut writer = TiffWriter::new(bytes as &mut [u8]);
writer.write_u32(u32::try_from(offset)?)?; K::write_offset(&mut writer, offset)?;
} else { } else {
while bytes.len() < 4 { while bytes.len() < data_bytes {
bytes.push(0); bytes.push(0);
} }
} }
@@ -521,11 +265,19 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
let offset = self.writer.offset(); let offset = self.writer.offset();
self.writer.write_u16(u16::try_from(self.ifd.len())?)?; K::write_entry_count(self.writer, self.ifd.len())?;
for (tag, &(ref field_type, ref count, ref offset)) in self.ifd.iter() { for (
tag,
DirectoryEntry {
data_type: field_type,
count,
data: offset,
},
) in self.ifd.iter()
{
self.writer.write_u16(*tag)?; self.writer.write_u16(*tag)?;
self.writer.write_u16(*field_type)?; self.writer.write_u16(*field_type)?;
self.writer.write_u32(*count)?; (*count).write(self.writer)?;
self.writer.write_bytes(offset)?; self.writer.write_bytes(offset)?;
} }
@@ -537,18 +289,23 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
/// This could be used to write tiff strips. /// This could be used to write tiff strips.
pub fn write_data<T: TiffValue>(&mut self, value: T) -> TiffResult<u64> { pub fn write_data<T: TiffValue>(&mut self, value: T) -> TiffResult<u64> {
let offset = self.writer.offset(); let offset = self.writer.offset();
value.write(&mut self.writer)?; value.write(self.writer)?;
Ok(offset) Ok(offset)
} }
/// Provides the number of bytes written by the underlying TiffWriter during the last call.
fn last_written(&self) -> u64 {
self.writer.last_written()
}
fn finish_internal(&mut self) -> TiffResult<()> { fn finish_internal(&mut self) -> TiffResult<()> {
let ifd_pointer = self.write_directory()?; let ifd_pointer = self.write_directory()?;
let curr_pos = self.writer.offset(); let curr_pos = self.writer.offset();
self.writer.goto_offset(self.ifd_pointer_pos)?; self.writer.goto_offset(self.ifd_pointer_pos)?;
self.writer.write_u32(u32::try_from(ifd_pointer)?)?; K::write_offset(self.writer, ifd_pointer)?;
self.writer.goto_offset(curr_pos)?; self.writer.goto_offset(curr_pos)?;
self.writer.write_u32(0)?; K::write_offset(self.writer, 0)?;
self.dropped = true; self.dropped = true;
@@ -561,7 +318,7 @@ impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
} }
} }
impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> { impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> {
fn drop(&mut self) { fn drop(&mut self) {
if !self.dropped { if !self.dropped {
let _ = self.finish_internal(); let _ = self.finish_internal();
@@ -602,38 +359,68 @@ impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> {
/// # } /// # }
/// ``` /// ```
/// You can also call write_data function wich will encode by strip and finish /// You can also call write_data function wich will encode by strip and finish
pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType> { pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
encoder: DirectoryEncoder<'a, W>, encoder: DirectoryEncoder<'a, W, K>,
strip_idx: u64, strip_idx: u64,
strip_count: u64, strip_count: u64,
row_samples: u64, row_samples: u64,
width: u32, width: u32,
height: u32, height: u32,
rows_per_strip: u64, rows_per_strip: u64,
strip_offsets: Vec<u32>, strip_offsets: Vec<K::OffsetType>,
strip_byte_count: Vec<u32>, strip_byte_count: Vec<K::OffsetType>,
dropped: bool, dropped: bool,
compression: Compression,
predictor: Predictor,
_phantom: ::std::marker::PhantomData<C>, _phantom: ::std::marker::PhantomData<C>,
} }
impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> { 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 new( fn new(
mut encoder: DirectoryEncoder<'a, W>, mut encoder: DirectoryEncoder<'a, W, K>,
width: u32, width: u32,
height: u32, height: u32,
) -> TiffResult<ImageEncoder<'a, W, T>> { compression: Compression,
predictor: Predictor,
) -> TiffResult<Self> {
if width == 0 || height == 0 {
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
width, height,
)));
}
Self::sanity_check(compression, predictor)?;
let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?; let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN); let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
// Limit the strip size to prevent potential memory and security issues. // Limit the strip size to prevent potential memory and security issues.
// Also keep the multiple strip handling 'oiled' // Also keep the multiple strip handling 'oiled'
let rows_per_strip = (1_000_000 + row_bytes - 1) / row_bytes; let rows_per_strip = {
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,
}
};
let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip; let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
encoder.write_tag(Tag::ImageWidth, width)?; encoder.write_tag(Tag::ImageWidth, width)?;
encoder.write_tag(Tag::ImageLength, height)?; encoder.write_tag(Tag::ImageLength, height)?;
encoder.write_tag(Tag::Compression, tags::CompressionMethod::None.to_u16())?; encoder.write_tag(Tag::Compression, compression.tag().to_u16())?;
encoder.write_tag(Tag::Predictor, predictor.to_u16())?;
encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?; encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect(); let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
@@ -661,6 +448,8 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
strip_offsets: Vec::new(), strip_offsets: Vec::new(),
strip_byte_count: Vec::new(), strip_byte_count: Vec::new(),
dropped: false, dropped: false,
compression,
predictor,
_phantom: ::std::marker::PhantomData, _phantom: ::std::marker::PhantomData,
}) })
} }
@@ -683,7 +472,6 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
where where
[T::Inner]: TiffValue, [T::Inner]: TiffValue,
{ {
// TODO: Compression
let samples = self.next_strip_sample_count(); let samples = self.next_strip_sample_count();
if u64::try_from(value.len())? != samples { if u64::try_from(value.len())? != samples {
return Err(io::Error::new( return Err(io::Error::new(
@@ -693,9 +481,23 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
.into()); .into());
} }
let offset = self.encoder.write_data(value)?; // Write the (possible compressed) data to the encoder.
self.strip_offsets.push(u32::try_from(offset)?); let offset = match self.predictor {
self.strip_byte_count.push(value.bytes()); 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)?);
self.strip_byte_count.push(byte_count.try_into()?);
self.strip_idx += 1; self.strip_idx += 1;
Ok(()) Ok(())
@@ -721,12 +523,19 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
) )
.into()); .into());
} }
self.encoder
.writer
.set_compression(self.compression.get_algorithm());
let mut idx = 0; let mut idx = 0;
while self.next_strip_sample_count() > 0 { while self.next_strip_sample_count() > 0 {
let sample_count = usize::try_from(self.next_strip_sample_count())?; let sample_count = usize::try_from(self.next_strip_sample_count())?;
self.write_strip(&data[idx..idx + sample_count])?; self.write_strip(&data[idx..idx + sample_count])?;
idx += sample_count; idx += sample_count;
} }
self.encoder.writer.reset_compression();
self.finish()?; self.finish()?;
Ok(()) Ok(())
} }
@@ -783,16 +592,18 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
fn finish_internal(&mut self) -> TiffResult<()> { fn finish_internal(&mut self) -> TiffResult<()> {
self.encoder self.encoder
.write_tag(Tag::StripOffsets, &*self.strip_offsets)?; .write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?;
self.encoder self.encoder.write_tag(
.write_tag(Tag::StripByteCounts, &*self.strip_byte_count)?; Tag::StripByteCounts,
K::convert_slice(&self.strip_byte_count),
)?;
self.dropped = true; self.dropped = true;
self.encoder.finish_internal() self.encoder.finish_internal()
} }
/// Get a reference of the underlying `DirectoryEncoder` /// Get a reference of the underlying `DirectoryEncoder`
pub fn encoder(&mut self) -> &mut DirectoryEncoder<'a, W> { pub fn encoder(&mut self) -> &mut DirectoryEncoder<'a, W, K> {
&mut self.encoder &mut self.encoder
} }
@@ -802,10 +613,127 @@ impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
} }
} }
impl<'a, W: Write + Seek, C: ColorType> Drop for ImageEncoder<'a, W, C> { impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W, C, K> {
fn drop(&mut self) { fn drop(&mut self) {
if !self.dropped { if !self.dropped {
let _ = self.finish_internal(); let _ = self.finish_internal();
} }
} }
} }
struct DirectoryEntry<S> {
data_type: u16,
count: S,
data: Vec<u8>,
}
/// Trait to abstract over Tiff/BigTiff differences.
///
/// Implemented for [`TiffKindStandard`] and [`TiffKindBig`].
pub trait TiffKind {
/// The type of offset fields, `u32` for normal Tiff, `u64` for BigTiff.
type OffsetType: TryFrom<usize, Error = TryFromIntError> + Into<u64> + TiffValue;
/// Needed for the `convert_slice` method.
type OffsetArrayType: ?Sized + TiffValue;
/// Write the (Big)Tiff header.
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()>;
/// Convert a file offset to `Self::OffsetType`.
///
/// This returns an error for normal Tiff if the offset is larger than `u32::MAX`.
fn convert_offset(offset: u64) -> TiffResult<Self::OffsetType>;
/// Write an offset value to the given writer.
///
/// Like `convert_offset`, this errors if `offset > u32::MAX` for normal Tiff.
fn write_offset<W: Write>(writer: &mut TiffWriter<W>, offset: u64) -> TiffResult<()>;
/// Write the IFD entry count field with the given `count` value.
///
/// The entry count field is an `u16` for normal Tiff and `u64` for BigTiff. Errors
/// if the given `usize` is larger than the representable values.
fn write_entry_count<W: Write>(writer: &mut TiffWriter<W>, count: usize) -> TiffResult<()>;
/// Internal helper method for satisfying Rust's type checker.
///
/// The `TiffValue` trait is implemented for both primitive values (e.g. `u8`, `u32`) and
/// slices of primitive values (e.g. `[u8]`, `[u32]`). However, this is not represented in
/// the type system, so there is no guarantee that that for all `T: TiffValue` there is also
/// an implementation of `TiffValue` for `[T]`. This method works around that problem by
/// providing a conversion from `[T]` to some value that implements `TiffValue`, thereby
/// making all slices of `OffsetType` usable with `write_tag` and similar methods.
///
/// Implementations of this trait should always set `OffsetArrayType` to `[OffsetType]`.
fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType;
}
/// Create a standard Tiff file.
pub struct TiffKindStandard;
impl TiffKind for TiffKindStandard {
type OffsetType = u32;
type OffsetArrayType = [u32];
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
write_tiff_header(writer)?;
// blank the IFD offset location
writer.write_u32(0)?;
Ok(())
}
fn convert_offset(offset: u64) -> TiffResult<Self::OffsetType> {
Ok(Self::OffsetType::try_from(offset)?)
}
fn write_offset<W: Write>(writer: &mut TiffWriter<W>, offset: u64) -> TiffResult<()> {
writer.write_u32(u32::try_from(offset)?)?;
Ok(())
}
fn write_entry_count<W: Write>(writer: &mut TiffWriter<W>, count: usize) -> TiffResult<()> {
writer.write_u16(u16::try_from(count)?)?;
Ok(())
}
fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType {
slice
}
}
/// Create a BigTiff file.
pub struct TiffKindBig;
impl TiffKind for TiffKindBig {
type OffsetType = u64;
type OffsetArrayType = [u64];
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
write_bigtiff_header(writer)?;
// blank the IFD offset location
writer.write_u64(0)?;
Ok(())
}
fn convert_offset(offset: u64) -> TiffResult<Self::OffsetType> {
Ok(offset)
}
fn write_offset<W: Write>(writer: &mut TiffWriter<W>, offset: u64) -> TiffResult<()> {
writer.write_u64(offset)?;
Ok(())
}
fn write_entry_count<W: Write>(writer: &mut TiffWriter<W>, count: usize) -> TiffResult<()> {
writer.write_u64(u64::try_from(count)?)?;
Ok(())
}
fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType {
slice
}
}
+523
View File
@@ -0,0 +1,523 @@
use std::{borrow::Cow, io::Write, slice::from_ref};
use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult};
use super::writer::TiffWriter;
/// Trait for types that can be encoded in a tiff file
pub trait TiffValue {
const BYTE_LEN: u8;
const FIELD_TYPE: Type;
fn count(&self) -> usize;
fn bytes(&self) -> usize {
self.count() * usize::from(Self::BYTE_LEN)
}
/// Access this value as an contiguous sequence of bytes.
/// If their is no trivial representation, allocate it on the heap.
fn data(&self) -> Cow<[u8]>;
/// Write this value to a TiffWriter.
/// While the default implementation will work in all cases, it may require unnecessary allocations.
/// The written bytes of any custom implementation MUST be the same as yielded by `self.data()`.
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_bytes(&self.data())?;
Ok(())
}
}
impl TiffValue for [u8] {
const BYTE_LEN: u8 = 1;
const FIELD_TYPE: Type = Type::BYTE;
fn count(&self) -> usize {
self.len()
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(self)
}
}
impl TiffValue for [i8] {
const BYTE_LEN: u8 = 1;
const FIELD_TYPE: Type = Type::SBYTE;
fn count(&self) -> usize {
self.len()
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
}
}
impl TiffValue for [u16] {
const BYTE_LEN: u8 = 2;
const FIELD_TYPE: Type = Type::SHORT;
fn count(&self) -> usize {
self.len()
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
}
}
impl TiffValue for [i16] {
const BYTE_LEN: u8 = 2;
const FIELD_TYPE: Type = Type::SSHORT;
fn count(&self) -> usize {
self.len()
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
}
}
impl TiffValue for [u32] {
const BYTE_LEN: u8 = 4;
const FIELD_TYPE: Type = Type::LONG;
fn count(&self) -> usize {
self.len()
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
}
}
impl TiffValue for [i32] {
const BYTE_LEN: u8 = 4;
const FIELD_TYPE: Type = Type::SLONG;
fn count(&self) -> usize {
self.len()
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
}
}
impl TiffValue for [u64] {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::LONG8;
fn count(&self) -> usize {
self.len()
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
}
}
impl TiffValue for [i64] {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::SLONG8;
fn count(&self) -> usize {
self.len()
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
}
}
impl TiffValue for [f32] {
const BYTE_LEN: u8 = 4;
const FIELD_TYPE: Type = Type::FLOAT;
fn count(&self) -> usize {
self.len()
}
fn data(&self) -> Cow<[u8]> {
// We write using native endian so this should be safe
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
}
}
impl TiffValue for [f64] {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::DOUBLE;
fn count(&self) -> usize {
self.len()
}
fn data(&self) -> Cow<[u8]> {
// We write using native endian so this should be safe
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
}
}
impl TiffValue for u8 {
const BYTE_LEN: u8 = 1;
const FIELD_TYPE: Type = Type::BYTE;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u8(*self)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(from_ref(self))
}
}
impl TiffValue for i8 {
const BYTE_LEN: u8 = 1;
const FIELD_TYPE: Type = Type::SBYTE;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_i8(*self)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
}
}
impl TiffValue for u16 {
const BYTE_LEN: u8 = 2;
const FIELD_TYPE: Type = Type::SHORT;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u16(*self)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
}
}
impl TiffValue for i16 {
const BYTE_LEN: u8 = 2;
const FIELD_TYPE: Type = Type::SSHORT;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_i16(*self)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
}
}
impl TiffValue for u32 {
const BYTE_LEN: u8 = 4;
const FIELD_TYPE: Type = Type::LONG;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u32(*self)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
}
}
impl TiffValue for i32 {
const BYTE_LEN: u8 = 4;
const FIELD_TYPE: Type = Type::SLONG;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_i32(*self)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
}
}
impl TiffValue for u64 {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::LONG8;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u64(*self)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
}
}
impl TiffValue for i64 {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::SLONG8;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_i64(*self)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
}
}
impl TiffValue for f32 {
const BYTE_LEN: u8 = 4;
const FIELD_TYPE: Type = Type::FLOAT;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_f32(*self)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
}
}
impl TiffValue for f64 {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::DOUBLE;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_f64(*self)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
}
}
impl TiffValue for Ifd {
const BYTE_LEN: u8 = 4;
const FIELD_TYPE: Type = Type::IFD;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u32(self.0)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
}
}
impl TiffValue for Ifd8 {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::IFD8;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u64(self.0)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
}
}
impl TiffValue for Rational {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::RATIONAL;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_u32(self.n)?;
writer.write_u32(self.d)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Owned({
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
[first_dword, second_dword].concat()
})
}
}
impl TiffValue for SRational {
const BYTE_LEN: u8 = 8;
const FIELD_TYPE: Type = Type::SRATIONAL;
fn count(&self) -> usize {
1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.write_i32(self.n)?;
writer.write_i32(self.d)?;
Ok(())
}
fn data(&self) -> Cow<[u8]> {
Cow::Owned({
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
[first_dword, second_dword].concat()
})
}
}
impl TiffValue for str {
const BYTE_LEN: u8 = 1;
const FIELD_TYPE: Type = Type::ASCII;
fn count(&self) -> usize {
self.len() + 1
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
writer.write_bytes(self.as_bytes())?;
writer.write_u8(0)?;
Ok(())
} else {
Err(TiffError::FormatError(TiffFormatError::InvalidTag))
}
}
fn data(&self) -> Cow<[u8]> {
Cow::Owned({
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
let bytes: &[u8] = self.as_bytes();
[bytes, &[0]].concat()
} else {
vec![]
}
})
}
}
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
const BYTE_LEN: u8 = T::BYTE_LEN;
const FIELD_TYPE: Type = T::FIELD_TYPE;
fn count(&self) -> usize {
(*self).count()
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
(*self).write(writer)
}
fn data(&self) -> Cow<[u8]> {
T::data(self)
}
}
macro_rules! impl_tiff_value_for_contiguous_sequence {
($inner_type:ty; $bytes:expr; $field_type:expr) => {
impl $crate::encoder::TiffValue for [$inner_type] {
const BYTE_LEN: u8 = $bytes;
const FIELD_TYPE: Type = $field_type;
fn count(&self) -> usize {
self.len()
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
for x in self {
x.write(writer)?;
}
Ok(())
}
fn data(&self) -> Cow<[u8]> {
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
for x in self {
buf.extend_from_slice(&x.data());
}
Cow::Owned(buf)
}
}
};
}
impl_tiff_value_for_contiguous_sequence!(Ifd; 4; Type::IFD);
impl_tiff_value_for_contiguous_sequence!(Ifd8; 8; Type::IFD8);
impl_tiff_value_for_contiguous_sequence!(Rational; 8; Type::RATIONAL);
impl_tiff_value_for_contiguous_sequence!(SRational; 8; Type::SRATIONAL);
/// Type to represent tiff values of type `IFD`
#[derive(Clone)]
pub struct Ifd(pub u32);
/// Type to represent tiff values of type `IFD8`
#[derive(Clone)]
pub struct Ifd8(pub u64);
/// Type to represent tiff values of type `RATIONAL`
#[derive(Clone)]
pub struct Rational {
pub n: u32,
pub d: u32,
}
/// Type to represent tiff values of type `SRATIONAL`
#[derive(Clone)]
pub struct SRational {
pub n: i32,
pub d: i32,
}
+94 -22
View File
@@ -1,3 +1,4 @@
use crate::encoder::compression::*;
use crate::error::TiffResult; use crate::error::TiffResult;
use std::io::{self, Seek, SeekFrom, Write}; use std::io::{self, Seek, SeekFrom, Write};
@@ -14,83 +15,154 @@ pub fn write_tiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()>
Ok(()) Ok(())
} }
/// Writes a BigTiff header, excluding the IFD offset field.
///
/// Writes the byte order, version number, offset byte size, and zero constant fields. Does
// _not_ write the offset to the first IFD, this should be done by the caller.
pub fn write_bigtiff_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
#[cfg(target_endian = "little")]
let boi: u8 = 0x49;
#[cfg(not(target_endian = "little"))]
let boi: u8 = 0x4d;
// byte order indication
writer.writer.write_all(&[boi, boi])?;
// version number
writer.writer.write_all(&43u16.to_ne_bytes())?;
// bytesize of offsets (pointer size)
writer.writer.write_all(&8u16.to_ne_bytes())?;
// always 0
writer.writer.write_all(&0u16.to_ne_bytes())?;
// we wrote 8 bytes, so set the internal offset accordingly
writer.offset += 8;
Ok(())
}
pub struct TiffWriter<W> { pub struct TiffWriter<W> {
writer: W, writer: W,
offset: u64, offset: u64,
byte_count: u64,
compressor: Compressor,
} }
impl<W: Write> TiffWriter<W> { impl<W: Write> TiffWriter<W> {
pub fn new(writer: W) -> Self { pub fn new(writer: W) -> Self {
Self { writer, offset: 0 } Self {
writer,
offset: 0,
byte_count: 0,
compressor: Compressor::default(),
}
}
pub fn set_compression(&mut self, compressor: Compressor) {
self.compressor = compressor;
}
pub fn reset_compression(&mut self) {
self.compressor = Compressor::default();
} }
pub fn offset(&self) -> u64 { pub fn offset(&self) -> u64 {
self.offset self.offset
} }
pub fn last_written(&self) -> u64 {
self.byte_count
}
pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> { pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), io::Error> {
self.writer.write_all(bytes)?; self.byte_count = self.compressor.write_to(&mut self.writer, bytes)?;
self.offset += bytes.len() as u64; self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> { pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 1; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> { pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 1; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> { pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 2; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> { pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 2; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> { pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 4; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> { pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 4; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> { pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?; self.byte_count = self
self.offset += 8; .compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(())
}
pub fn write_i64(&mut self, n: i64) -> Result<(), io::Error> {
self.byte_count = self
.compressor
.write_to(&mut self.writer, &n.to_ne_bytes())?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> { pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> {
self.writer.write_all(&u32::to_ne_bytes(n.to_bits()))?; self.byte_count = self
self.offset += 4; .compressor
.write_to(&mut self.writer, &u32::to_ne_bytes(n.to_bits()))?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> { pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> {
self.writer.write_all(&u64::to_ne_bytes(n.to_bits()))?; self.byte_count = self
self.offset += 8; .compressor
.write_to(&mut self.writer, &u64::to_ne_bytes(n.to_bits()))?;
self.offset += self.byte_count;
Ok(()) Ok(())
} }
@@ -110,7 +182,7 @@ impl<W: Write> TiffWriter<W> {
impl<W: Seek> TiffWriter<W> { impl<W: Seek> TiffWriter<W> {
pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> { pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> {
self.offset = offset; self.offset = offset;
self.writer.seek(SeekFrom::Start(offset as u64))?; self.writer.seek(SeekFrom::Start(offset))?;
Ok(()) Ok(())
} }
} }
+152 -203
View File
@@ -1,248 +1,195 @@
use std::error::Error;
use std::fmt;
use std::io; use std::io;
use std::str;
use std::string; use std::string;
use crate::decoder::ifd::Value; use quick_error::quick_error;
use crate::decoder::ChunkType;
use crate::tags::{ use crate::tags::{
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag, CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
}; };
use crate::ColorType; use crate::ColorType;
use miniz_oxide::inflate::TINFLStatus;
quick_error! {
/// Tiff error kinds. /// Tiff error kinds.
#[derive(Debug)] #[derive(Debug)]
pub enum TiffError { pub enum TiffError {
/// The Image is not formatted properly. /// The Image is not formatted properly.
FormatError(TiffFormatError), FormatError(err: TiffFormatError) {
display("format error: {err}")
from()
source(err)
}
/// The Decoder does not support features required by the image. /// The Decoder does not support features required by the image.
UnsupportedError(TiffUnsupportedError), UnsupportedError(err: TiffUnsupportedError) {
display("unsupported error: {err}")
from()
source(err)
}
/// An I/O Error occurred while decoding the image. /// An I/O Error occurred while decoding the image.
IoError(io::Error), IoError(err: io::Error) {
display("{err}")
from()
source(err)
}
/// The Limits of the Decoder is exceeded. /// The Limits of the Decoder is exceeded.
LimitsExceeded, LimitsExceeded {
display("decoder limits exceeded")
/// An integer conversion to or from a platform size failed, either due to }
/// limits of the platform size or limits of the format. /// An integer conversion to or from a platform size failed.
IntSizeError, IntSizeError {
display("platform or format size limits exceeded")
}
/// The image does not support the requested operation
UsageError(err: UsageError) {
display("usage error: {err}")
from()
source(err)
}
}
} }
quick_error! {
/// The image is not formatted properly. /// The image is not formatted properly.
/// ///
/// This indicates that the encoder producing the image might behave incorrectly or that the input /// This indicates that the encoder producing the image might behave incorrectly or that the
/// file has been corrupted. /// input file has been corrupted.
///
/// The list of variants may grow to incorporate errors of future features. Matching against this
/// exhaustively is not covered by interface stability guarantees.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum TiffFormatError { pub enum TiffFormatError {
TiffSignatureNotFound, TiffSignatureNotFound {
TiffSignatureInvalid, display("TIFF signature not found")
ImageFileDirectoryNotFound,
InconsistentSizesEncountered,
UnexpectedCompressedData {
actual_bytes: usize,
required_bytes: usize,
},
InconsistentStripSamples {
actual_samples: usize,
required_samples: usize,
},
InvalidTag,
InvalidTagValueType(Tag),
RequiredTagNotFound(Tag),
UnknownPredictor(u16),
ByteExpected(Value),
UnsignedIntegerExpected(Value),
SignedIntegerExpected(Value),
InflateError(InflateError),
Format(String),
RequiredTagEmpty(Tag),
#[doc(hidden)]
/// Do not match against this variant. It may get removed.
__NonExhaustive,
} }
TiffSignatureInvalid {
impl fmt::Display for TiffFormatError { display("TIFF signature invalid")
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::TiffFormatError::*;
match *self {
TiffSignatureNotFound => write!(fmt, "TIFF signature not found."),
TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."),
ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."),
UnexpectedCompressedData {
actual_bytes,
required_bytes,
} => {
write!(
fmt,
"Decompression returned different amount of bytes than expected: got {}, expected {}.",
actual_bytes, required_bytes
)
} }
InconsistentStripSamples { ImageFileDirectoryNotFound {
actual_samples, display("image file directory not found")
required_samples,
} => {
write!(
fmt,
"Inconsistent elements in strip: got {}, expected {}.",
actual_samples, required_samples
)
} }
InvalidTag => write!(fmt, "Image contains invalid tag."), InconsistentSizesEncountered {
InvalidTagValueType(ref tag) => { display("inconsistent sizes encountered")
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
} }
RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag), InvalidDimensions(width: u32, height: u32) {
UnknownPredictor(ref predictor) => { display("invalid dimensions: {width}x{height}")
write!(fmt, "Unknown predictor “{}” encountered", predictor)
} }
ByteExpected(ref val) => write!(fmt, "Expected byte, {:?} found.", val), InvalidTag {
UnsignedIntegerExpected(ref val) => { display("image contains invalid tag")
write!(fmt, "Expected unsigned integer, {:?} found.", val)
} }
SignedIntegerExpected(ref val) => { InvalidTagValueType(tag: Tag) {
write!(fmt, "Expected signed integer, {:?} found.", val) display("tag `{tag:?}` did not have the expected value type")
} }
InflateError(_) => write!(fmt, "Failed to decode inflate data."), RequiredTagNotFound(tag: Tag) {
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val), display("required tag `{tag:?}` not found")
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val), }
__NonExhaustive => unreachable!(), 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}")
} }
} }
} }
/// Decompression failed due to faulty compressed data. quick_error! {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InflateError {
status: TINFLStatus,
}
impl InflateError {
pub(crate) fn new(status: TINFLStatus) -> Self {
Self { status }
}
}
impl TiffError {
pub(crate) fn from_inflate_status(status: TINFLStatus) -> Self {
TiffError::FormatError(TiffFormatError::InflateError(InflateError::new(status)))
}
}
/// The Decoder does not support features required by the image. /// The Decoder does not support features required by the image.
/// ///
/// This only captures known failures for which the standard either does not require support or an /// This only captures known failures for which the standard either does not require support or an
/// implementation has been planned but not yet completed. Some variants may become unused over /// implementation has been planned but not yet completed. Some variants may become unused over
/// time and will then get deprecated before being removed. /// time and will then get deprecated before being removed.
///
/// The list of variants may grow. Matching against this exhaustively is not covered by interface
/// stability guarantees.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TiffUnsupportedError { pub enum TiffUnsupportedError {
HorizontalPredictor(ColorType), FloatingPointPredictor(color_type: ColorType) {
InterpretationWithBits(PhotometricInterpretation, Vec<u8>), display("floating point predictor for {color_type:?} is unsupported")
UnknownInterpretation,
UnknownCompressionMethod,
UnsupportedCompressionMethod(CompressionMethod),
UnsupportedSampleDepth(u8),
UnsupportedSampleFormat(Vec<SampleFormat>),
UnsupportedColorType(ColorType),
UnsupportedBitsPerChannel(u8),
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
UnsupportedDataType,
#[doc(hidden)]
/// Do not match against this variant. It may get removed.
__NonExhaustive,
} }
HorizontalPredictor(color_type: ColorType) {
impl fmt::Display for TiffUnsupportedError { display("horizontal predictor for {color_type:?} is unsupported")
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::TiffUnsupportedError::*;
match *self {
HorizontalPredictor(color_type) => write!(
fmt,
"Horizontal predictor for {:?} is unsupported.",
color_type
),
InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => write!(
fmt,
"{:?} with {:?} bits per sample is unsupported",
photometric_interpretation, bits_per_sample
),
UnknownInterpretation => write!(
fmt,
"The image is using an unknown photometric interpretation."
),
UnknownCompressionMethod => write!(fmt, "Unknown compression method."),
UnsupportedCompressionMethod(method) => {
write!(fmt, "Compression method {:?} is unsupported", method)
} }
UnsupportedSampleDepth(samples) => { InconsistentBitsPerSample(bits_per_sample: Vec<u8>) {
write!(fmt, "{} samples per pixel is unsupported.", samples) display("inconsistent bits per sample: {bits_per_sample:?}")
} }
UnsupportedSampleFormat(ref formats) => { InterpretationWithBits(interpretation: PhotometricInterpretation, bits_per_sample: Vec<u8>) {
write!(fmt, "Sample format {:?} is unsupported.", formats) display("Photometric interpretation {interpretation:?} with bits per sample {bits_per_sample:?} is unsupported")
} }
UnsupportedColorType(color_type) => { UnknownInterpretation {
write!(fmt, "Color type {:?} is unsupported", color_type) display("unknown photometric interpretation")
} }
UnsupportedBitsPerChannel(bits) => { UnknownCompressionMethod {
write!(fmt, "{} bits per channel not supported", bits) display("unknown compression method")
} }
UnsupportedPlanarConfig(config) => { UnsupportedCompressionMethod(method: CompressionMethod) {
write!(fmt, "Unsupported planar configuration “{:?}”.", config) display("compression method {method:?} is unsupported")
} }
UnsupportedDataType => write!(fmt, "Unsupported data type."), UnsupportedSampleDepth(depth: u8) {
__NonExhaustive => unreachable!(), display("{depth} samples per pixel is unsupported")
}
UnsupportedSampleFormat(sample_format: Vec<SampleFormat>) {
display("sample format {sample_format:?} is unsupported")
}
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")
} }
} }
} }
impl fmt::Display for TiffError { quick_error! {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { /// User attempted to use the Decoder in a way that is incompatible with a specific image.
match *self { ///
TiffError::FormatError(ref e) => write!(fmt, "Format error: {}", e), /// For example: attempting to read a tile from a stripped image.
TiffError::UnsupportedError(ref f) => write!( #[derive(Debug)]
fmt, #[non_exhaustive]
"The Decoder does not support the \ pub enum UsageError {
image format `{}`", InvalidChunkType(expected: ChunkType, actual: ChunkType) {
f display("requested operation is only valid for images with chunk encoding of type {expected:?} but got {actual:?}")
), }
TiffError::IoError(ref e) => e.fmt(fmt), InvalidChunkIndex(index: u32) {
TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"), display("invalid chunk index ({index}) requested")
TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"), }
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 Error for TiffError { impl From<str::Utf8Error> for TiffError {
fn description(&self) -> &str { fn from(_err: str::Utf8Error) -> TiffError {
match *self { TiffError::FormatError(TiffFormatError::InvalidTag)
TiffError::FormatError(..) => "Format error",
TiffError::UnsupportedError(..) => "Unsupported error",
TiffError::IoError(..) => "IO error",
TiffError::LimitsExceeded => "Decoder limits exceeded",
TiffError::IntSizeError => "Platform or format size limits exceeded",
}
}
fn cause(&self) -> Option<&dyn Error> {
match *self {
TiffError::IoError(ref e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for TiffError {
fn from(err: io::Error) -> TiffError {
TiffError::IoError(err)
} }
} }
@@ -252,23 +199,25 @@ impl From<string::FromUtf8Error> for TiffError {
} }
} }
impl From<TiffFormatError> for TiffError {
fn from(err: TiffFormatError) -> TiffError {
TiffError::FormatError(err)
}
}
impl From<TiffUnsupportedError> for TiffError {
fn from(err: TiffUnsupportedError) -> TiffError {
TiffError::UnsupportedError(err)
}
}
impl From<std::num::TryFromIntError> for TiffError { impl From<std::num::TryFromIntError> for TiffError {
fn from(_err: std::num::TryFromIntError) -> TiffError { fn from(_err: std::num::TryFromIntError) -> TiffError {
TiffError::IntSizeError TiffError::IntSizeError
} }
} }
#[cfg(feature = "lzw")]
impl From<weezl::LzwError> for TiffError {
fn from(err: weezl::LzwError) -> TiffError {
TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
}
}
#[cfg(feature = "jpeg")]
impl From<zune_jpeg::errors::DecodeErrors> for TiffError {
fn from(err: zune_jpeg::errors::DecodeErrors) -> Self {
TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
}
}
/// Result of an image decoding/encoding process /// Result of an image decoding/encoding process
pub type TiffResult<T> = Result<T, TiffError>; pub type TiffResult<T> = Result<T, TiffError>;
+27 -6
View File
@@ -4,11 +4,7 @@
//! lossless and lossy compression. //! lossless and lossy compression.
//! //!
//! # Related Links //! # Related Links
//! * <https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification //! * <https://web.archive.org/web/20210108073850/https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
extern crate jpeg;
extern crate miniz_oxide;
extern crate weezl;
mod bytecast; mod bytecast;
pub mod decoder; pub mod decoder;
@@ -16,10 +12,11 @@ pub mod encoder;
mod error; mod error;
pub mod tags; pub mod tags;
pub use self::error::{InflateError, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError}; pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
/// An enumeration over supported color types and their bit depths /// An enumeration over supported color types and their bit depths
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)] #[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
#[non_exhaustive]
pub enum ColorType { pub enum ColorType {
/// Pixel is grayscale /// Pixel is grayscale
Gray(u8), Gray(u8),
@@ -38,4 +35,28 @@ pub enum ColorType {
/// Pixel is CMYK /// Pixel is CMYK
CMYK(u8), CMYK(u8),
/// Pixel is CMYK with an alpha channel
CMYKA(u8),
/// Pixel is YCbCr
YCbCr(u8),
/// Pixel has multiple bands/channels
Multiband { bit_depth: u8, num_samples: u16 },
}
impl ColorType {
fn bit_depth(&self) -> u8 {
match *self {
ColorType::Gray(b)
| ColorType::RGB(b)
| ColorType::Palette(b)
| ColorType::GrayA(b)
| ColorType::RGBA(b)
| ColorType::CMYK(b)
| ColorType::CMYKA(b)
| ColorType::YCbCr(b)
| ColorType::Multiband { bit_depth: b, .. } => b,
}
}
} }
+35 -7
View File
@@ -9,11 +9,9 @@ macro_rules! tags {
} => { } => {
$( #[$enum_attr] )* $( #[$enum_attr] )*
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[non_exhaustive]
pub enum $name { pub enum $name {
$($(#[$ident_attr])* $tag,)* $($(#[$ident_attr])* $tag,)*
// FIXME: switch to non_exhaustive once stabilized and compiler requirement new enough
#[doc(hidden)]
__NonExhaustive,
$( $(
#[doc = $unknown_doc] #[doc = $unknown_doc]
Unknown($ty), Unknown($ty),
@@ -34,7 +32,6 @@ macro_rules! tags {
match *self { match *self {
$( $name::$tag => $val, )* $( $name::$tag => $val, )*
$( $name::Unknown(n) => { $unknown_doc; n }, )* $( $name::Unknown(n) => { $unknown_doc; n }, )*
$name::__NonExhaustive => unreachable!(),
} }
} }
} }
@@ -111,15 +108,18 @@ pub enum Tag(u16) unknown("A private or extension tag") {
Threshholding = 263, // TODO add support Threshholding = 263, // TODO add support
XResolution = 282, XResolution = 282,
YResolution = 283, YResolution = 283,
// JPEG
JPEGTables = 347,
// Advanced tags // Advanced tags
Predictor = 317, Predictor = 317,
TileWidth = 322, TileWidth = 322,
TileLength = 323, TileLength = 323,
TileOffsets = 324, TileOffsets = 324,
TileByteCounts = 325, TileByteCounts = 325,
// Data Sample Format
SampleFormat = 339, SampleFormat = 339,
SMinSampleValue = 340, // TODO add support
SMaxSampleValue = 341, // TODO add support
// JPEG
JPEGTables = 347,
// GeoTIFF // GeoTIFF
ModelPixelScaleTag = 33550, // (SoftDesk) ModelPixelScaleTag = 33550, // (SoftDesk)
ModelTransformationTag = 34264, // (JPL Carto Group) ModelTransformationTag = 34264, // (JPL Carto Group)
@@ -134,27 +134,45 @@ pub enum Tag(u16) unknown("A private or extension tag") {
tags! { tags! {
/// The type of an IFD entry (a 2 byte field). /// The type of an IFD entry (a 2 byte field).
pub enum Type(u16) { pub enum Type(u16) {
/// 8-bit unsigned integer
BYTE = 1, BYTE = 1,
/// 8-bit byte that contains a 7-bit ASCII code; the last byte must be zero
ASCII = 2, ASCII = 2,
/// 16-bit unsigned integer
SHORT = 3, SHORT = 3,
/// 32-bit unsigned integer
LONG = 4, LONG = 4,
/// Fraction stored as two 32-bit unsigned integers
RATIONAL = 5, RATIONAL = 5,
/// 8-bit signed integer
SBYTE = 6, SBYTE = 6,
/// 8-bit byte that may contain anything, depending on the field
UNDEFINED = 7, UNDEFINED = 7,
/// 16-bit signed integer
SSHORT = 8, SSHORT = 8,
/// 32-bit signed integer
SLONG = 9, SLONG = 9,
/// Fraction stored as two 32-bit signed integers
SRATIONAL = 10, SRATIONAL = 10,
/// 32-bit IEEE floating point
FLOAT = 11, FLOAT = 11,
/// 64-bit IEEE floating point
DOUBLE = 12, DOUBLE = 12,
/// 32-bit unsigned integer (offset)
IFD = 13,
/// BigTIFF 64-bit unsigned integer /// BigTIFF 64-bit unsigned integer
LONG8 = 16, LONG8 = 16,
/// BigTIFF 64-bit signed integer
SLONG8 = 17,
/// BigTIFF 64-bit unsigned integer (offset)
IFD8 = 18,
} }
} }
tags! { tags! {
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html) /// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
/// for reference. /// for reference.
pub enum CompressionMethod(u16) { pub enum CompressionMethod(u16) unknown("A custom compression method") {
None = 1, None = 1,
Huffman = 2, Huffman = 2,
Fax3 = 3, Fax3 = 3,
@@ -166,6 +184,9 @@ pub enum CompressionMethod(u16) {
Deflate = 8, Deflate = 8,
OldDeflate = 0x80B2, OldDeflate = 0x80B2,
PackBits = 0x8005, PackBits = 0x8005,
// Self-assigned by libtiff
ZSTD = 0xC350,
} }
} }
@@ -191,8 +212,15 @@ pub enum PlanarConfiguration(u16) {
tags! { tags! {
pub enum Predictor(u16) { pub enum Predictor(u16) {
/// No changes were made to the data
None = 1, None = 1,
/// The images' rows were processed to contain the difference of each pixel from the previous one.
///
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
Horizontal = 2, Horizontal = 2,
/// Not currently supported
FloatingPoint = 3,
} }
} }
+195
View File
@@ -0,0 +1,195 @@
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::ColorType;
use std::fs::File;
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images/";
/// Test a basic all white image
#[test]
fn test_white_ieee_fp16() {
let filenames = ["white-fp16.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for p in img {
assert!(p == half::f16::from_f32_const(1.0));
}
} else {
panic!("Wrong data type");
}
}
}
/// Test a single black pixel, to make sure scaling is ok
#[test]
fn test_one_black_pixel_ieee_fp16() {
let filenames = ["single-black-fp16.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for (i, p) in img.iter().enumerate() {
if i == 0 {
assert!(p < &half::f16::from_f32_const(0.001));
} else {
assert!(p == &half::f16::from_f32_const(1.0));
}
}
} else {
panic!("Wrong data type");
}
}
}
/// Test white with horizontal differencing predictor
#[test]
fn test_pattern_horizontal_differencing_ieee_fp16() {
let filenames = ["white-fp16-pred2.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
// 0, 2, 5, 8, 12, 16, 255 are black
let black = [0, 2, 5, 8, 12, 16, 255];
for (i, p) in img.iter().enumerate() {
if black.contains(&i) {
assert!(p < &half::f16::from_f32_const(0.001));
} else {
assert!(p == &half::f16::from_f32_const(1.0));
}
}
} else {
panic!("Wrong data type");
}
}
}
/// Test white with floating point predictor
#[test]
fn test_pattern_predictor_ieee_fp16() {
let filenames = ["white-fp16-pred3.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
// 0, 2, 5, 8, 12, 16, 255 are black
let black = [0, 2, 5, 8, 12, 16, 255];
for (i, p) in img.iter().enumerate() {
if black.contains(&i) {
assert!(p < &half::f16::from_f32_const(0.001));
} else {
assert!(p == &half::f16::from_f32_const(1.0));
}
}
} else {
panic!("Wrong data type");
}
}
}
/// Test several random images
/// we'rell compare against a pnm file, that scales from 0 (for 0.0) to 65767 (for 1.0)
#[test]
fn test_predictor_ieee_fp16() {
// first parse pnm, skip the first 4 \n
let pnm_path = PathBuf::from(TEST_IMAGE_DIR).join("random-fp16.pgm");
let pnm_bytes = std::fs::read(pnm_path).expect("Failed to read expected PNM file");
// PGM looks like this:
// ---
// P5
// #Created with GIMP
// 16 16
// 65535
// ... <big-endian bytes>
// ---
// get index of 4th \n
let byte_start = pnm_bytes
.iter()
.enumerate()
.filter(|(_, &v)| v == b'\n')
.map(|(i, _)| i)
.nth(3)
.expect("Must be 4 \\n's");
let pnm_values: Vec<f32> = pnm_bytes[(byte_start + 1)..]
.chunks(2)
.map(|slice| {
let bts = [slice[0], slice[1]];
(u16::from_be_bytes(bts) as f32) / (u16::MAX as f32)
})
.collect();
assert!(pnm_values.len() == 256);
let filenames = [
"random-fp16-pred2.tiff",
"random-fp16-pred3.tiff",
"random-fp16.tiff",
];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(16, 16)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) {
assert!((exp - found.to_f32()).abs() < 0.0001);
}
} else {
panic!("Wrong data type");
}
}
}
+63
View File
@@ -0,0 +1,63 @@
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::tags::Tag;
use tiff::ColorType;
use std::fs::File;
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images";
#[test]
fn test_geo_tiff() {
let filenames = ["geo-5b.tif"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
decoder = decoder.with_limits(tiff::decoder::Limits::unlimited());
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(10, 10)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Multiband {
bit_depth: 16,
num_samples: 5
}
);
assert_eq!(
decoder
.get_tag_u64(Tag::StripOffsets)
.expect("Cannot get StripOffsets"),
418
);
assert_eq!(
decoder
.get_tag_u64(Tag::RowsPerStrip)
.expect("Cannot get RowsPerStrip"),
10
);
assert_eq!(
decoder
.get_tag_u64(Tag::StripByteCounts)
.expect("Cannot get StripByteCounts"),
1000
);
assert_eq!(
decoder
.get_tag(Tag::ModelPixelScaleTag)
.expect("Cannot get pixel scale")
.into_f64_vec()
.expect("Cannot get pixel scale"),
vec![60.0, 60.0, 0.0]
);
let DecodingResult::I16(data) = decoder.read_image().unwrap() else {
panic!("Cannot read band data")
};
assert_eq!(data.len(), 500);
}
}
+329
View File
@@ -29,7 +29,9 @@ macro_rules! test_image_sum {
} }
test_image_sum!(test_image_sum_u8, U8, u64); test_image_sum!(test_image_sum_u8, U8, u64);
test_image_sum!(test_image_sum_i8, I8, i64);
test_image_sum!(test_image_sum_u16, U16, u64); test_image_sum!(test_image_sum_u16, U16, u64);
test_image_sum!(test_image_sum_i16, I16, i64);
test_image_sum!(test_image_sum_u32, U32, u64); test_image_sum!(test_image_sum_u32, U32, u64);
test_image_sum!(test_image_sum_u64, U64, u64); test_image_sum!(test_image_sum_u64, U64, u64);
test_image_sum!(test_image_sum_f32, F32, f32); test_image_sum!(test_image_sum_f32, F32, f32);
@@ -135,6 +137,26 @@ fn test_rgb_f32() {
test_image_sum_f32("gradient-3c-32b-float.tiff", ColorType::RGB(32), 472.8405); test_image_sum_f32("gradient-3c-32b-float.tiff", ColorType::RGB(32), 472.8405);
} }
#[test]
fn test_int8() {
test_image_sum_i8("int8.tif", ColorType::Gray(8), 3111)
}
#[test]
fn test_int8_rgb() {
test_image_sum_i8("int8_rgb.tif", ColorType::RGB(8), -10344)
}
#[test]
fn test_int16() {
test_image_sum_i16("int16.tif", ColorType::Gray(16), 354396);
}
#[test]
fn test_int16_rgb() {
test_image_sum_i16("int16_rgb.tif", ColorType::RGB(16), 1063188);
}
#[test] #[test]
fn test_string_tags() { fn test_string_tags() {
// these files have null-terminated strings for their Software tag. One has extra bytes after // these files have null-terminated strings for their Software tag. One has extra bytes after
@@ -193,3 +215,310 @@ fn issue_69() {
//let img_res = decoder.read_image(); //let img_res = decoder.read_image();
//assert!(img_res.is_ok()); //assert!(img_res.is_ok());
//} //}
#[test]
fn test_tiled_gray_i1() {
test_image_sum_u8("tiled-gray-i1.tif", ColorType::Gray(1), 30531);
}
#[test]
fn test_tiled_rgb_u8() {
test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948);
}
#[test]
fn test_tiled_rect_rgb_u8() {
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
}
/* #[test]
fn test_tiled_jpeg_rgb_u8() {
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
} */
#[test]
fn test_tiled_oversize_gray_i8() {
test_image_sum_i8("tiled-oversize-gray-i8.tif", ColorType::Gray(8), 1214996);
}
#[test]
fn test_tiled_cmyk_i8() {
test_image_sum_i8("tiled-cmyk-i8.tif", ColorType::CMYK(8), 1759101);
}
#[test]
fn test_tiled_incremental() {
let file = "tiled-rgb-u8.tif";
let expected_type = ColorType::RGB(8);
let sums = [
188760, 195639, 108148, 81986, 665088, 366140, 705317, 423366, 172033, 324455, 244102,
81853, 181258, 247971, 129486, 55600, 565625, 422102, 730888, 379271, 232142, 292549,
244045, 86866, 188141, 115036, 150785, 84389, 353170, 459325, 719619, 329594, 278663,
220474, 243048, 113563, 189152, 109684, 179391, 122188, 279651, 622093, 724682, 302459,
268428, 204499, 224255, 124674, 170668, 121868, 192768, 183367, 378029, 585651, 657712,
296790, 241444, 197083, 198429, 134869, 182318, 86034, 203655, 182338, 297255, 601284,
633813, 242531, 228578, 206441, 193552, 125412, 181527, 165439, 202531, 159538, 268388,
565790, 611382, 272967, 236497, 215154, 158881, 90806, 106114, 182342, 191824, 186138,
215174, 393193, 701228, 198866, 227944, 193830, 166330, 49008, 55719, 122820, 197316,
161969, 203152, 170986, 624427, 188605, 186187, 111064, 115192, 39538, 48626, 163929,
144682, 135796, 194141, 154198, 584125, 180255, 153524, 121433, 132641, 35743, 47798,
152343, 162874, 167664, 160175, 133038, 659882, 138339, 166470, 124173, 118929, 51317,
45267, 155776, 161331, 161006, 130052, 137618, 337291, 106481, 161999, 127343, 87724,
59540, 63907, 155677, 140668, 141523, 108061, 168657, 186482, 98599, 147614, 139963, 90444,
56602, 92547, 125644, 134212, 126569, 144153, 179800, 174516, 133969, 129399, 117681,
83305, 55075, 110737, 115108, 128572, 128911, 130922, 179986, 143288, 145884, 155856,
96683, 94057, 56238, 79649, 71651, 70182, 75010, 77009, 98855, 78979, 74341, 83482, 53403,
59842, 30305,
];
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let tiles = decoder.tile_count().unwrap();
assert_eq!(tiles as usize, sums.len());
for tile in 0..tiles {
match decoder.read_chunk(tile).unwrap() {
DecodingResult::U8(res) => {
let sum: u64 = res.into_iter().map(<u64>::from).sum();
assert_eq!(sum, sums[tile as usize]);
}
_ => panic!("Wrong bit depth"),
}
}
}
#[test]
fn test_planar_rgb_u8() {
// gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
let file = "planar-rgb-u8.tif";
let expected_type = ColorType::RGB(8);
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let chunks = decoder.strip_count().unwrap();
assert_eq!(chunks as usize, 72);
// convert -quiet planar-rgb-u8.tif[0] -crop 1x1+0+0 txt:
// 0,0: (73,51,30) #49331E srgb(73,51,30)
// 1st band (red)
match decoder.read_chunk(0).unwrap() {
DecodingResult::U8(chunk) => {
assert_eq!(chunk[0], 73);
}
_ => panic!("Wrong bit depth"),
}
// 2nd band (green)
match decoder.read_chunk(chunks / 3).unwrap() {
DecodingResult::U8(chunk) => {
assert_eq!(chunk[0], 51);
}
_ => panic!("Wrong bit depth"),
}
// 3rd band (blue)
match decoder.read_chunk(chunks / 3 * 2).unwrap() {
DecodingResult::U8(chunk) => {
assert_eq!(chunk[0], 30);
}
_ => panic!("Wrong bit depth"),
}
test_image_sum_u8(file, ColorType::RGB(8), 15417630);
}
#[test]
fn test_div_zero() {
use tiff::{TiffError, TiffFormatError};
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 39, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 158, 0, 0, 251, 67, 1, 3, 0,
1, 0, 0, 0, 40, 0, 0, 0, 66, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 178, 178, 178, 178,
178, 178, 178,
];
let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
match err {
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
}
#[test]
fn test_too_many_value_bytes() {
let image = [
73, 73, 43, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 8, 0, 0, 0,
23, 0, 12, 0, 0, 65, 4, 0, 1, 6, 0, 0, 1, 16, 0, 1, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0,
0, 0, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 59, 73, 84, 186, 202, 83, 240, 66, 1, 53, 22, 56, 47,
0, 0, 0, 0, 0, 0, 1, 222, 4, 0, 58, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 4, 0, 0, 100, 0,
0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18,
];
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
match error {
tiff::TiffError::LimitsExceeded => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
}
#[test]
fn fuzzer_testcase5() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0,
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 246, 16, 0, 0, 22, 1, 4, 0, 1,
0, 0, 0, 40, 0, 251, 255, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 178, 178, 178, 178,
178, 178, 178,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
}
#[test]
fn fuzzer_testcase1() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1,
0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0,
0, 0, 0, 0, 0, 17, 1, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0,
22, 1, 4, 0, 1, 0, 0, 0, 44, 0, 0, 0, 23, 1, 4, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 1, 0, 178,
178,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
}
#[test]
fn fuzzer_testcase6() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0,
0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0,
0, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, 22,
1, 4, 0, 1, 0, 0, 0, 44, 0, 248, 255, 23, 1, 4, 0, 1, 0, 0, 0, 178, 178, 178, 0, 1, 178,
178, 178,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
}
#[test]
fn oom() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0,
17, 1, 4, 0, 1, 0, 0, 0, 3, 77, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
}
#[test]
fn fuzzer_testcase4() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1,
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
}
#[test]
fn fuzzer_testcase2() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0,
0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0,
73, 42, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 158, 0, 0, 251, 3, 1,
3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0,
0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 4, 61, 1, 18, 0, 1, 0, 0, 0, 202, 0, 0, 0, 17,
1, 100, 0, 129, 0, 0, 0, 0, 0, 0, 0, 232, 254, 252, 255, 254, 255, 255, 255, 1, 29, 0, 0,
22, 1, 3, 0, 1, 0, 0, 0, 16, 0, 0, 0, 23, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 123, 73, 254, 0,
73,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
}
#[test]
fn invalid_jpeg_tag_2() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 242, 0, 1, 4, 0, 1, 0,
0, 0, 0, 129, 16, 0, 1, 1, 4, 0, 1, 0, 0, 0, 214, 0, 0, 248, 253, 1, 3, 0, 1, 0, 0, 0, 64,
0, 0, 0, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 64, 14, 1, 0,
2, 0, 0, 148, 0, 206, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 22, 1, 4, 0, 17, 0, 0, 201, 1, 0, 0, 0, 23, 1, 2, 0, 20, 0, 0, 0, 194, 0, 0, 0,
91, 1, 7, 0, 5, 0, 0, 0, 64, 0, 0, 0, 237, 254, 65, 255, 255, 255, 255, 255, 1, 0, 0, 0,
22, 1, 4, 0, 1, 0, 0, 0, 42, 0, 0, 0, 23, 1, 255, 255, 255, 255, 255, 36, 36, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
}
#[test]
fn fuzzer_testcase3() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22,
128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0,
0, 1, 1, 3, 0, 1, 0, 0, 0, 17, 1, 0, 231, 22, 1, 1, 0, 1, 0, 0, 0, 130, 0, 0, 0, 23, 1, 4,
0, 14, 0, 0, 0, 0, 0, 0, 0, 133, 133, 133, 77, 77, 77, 0, 0, 22, 128, 0, 255, 255, 255,
255, 255,
];
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
}
#[test]
fn timeout() {
use tiff::{TiffError, TiffFormatError};
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 68, 0, 0, 0, 2, 0, 32, 254, 252, 0, 109,
0, 129, 0, 0, 0, 32, 0, 58, 0, 1, 4, 0, 1, 0, 6, 0, 0, 0, 8, 0, 0, 1, 73, 73, 42, 0, 8, 0,
0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 21, 0, 0, 0, 61, 1, 255, 128, 9, 0, 0, 8, 0, 1, 113, 2,
3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 112, 0, 0, 36, 0, 0,
0, 112, 56, 200, 0, 5, 0, 0, 64, 0, 0, 1, 0, 4, 0, 0, 0, 2, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0,
0, 0, 4, 17, 1, 1, 0, 93, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 3, 6, 0, 231, 22, 1,
1, 0, 1, 0, 0, 0, 2, 64, 118, 36, 23, 1, 1, 0, 43, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 8,
0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32,
];
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
match error {
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
e => panic!("Unexpected error {:?}", e),
}
}
#[test]
fn test_no_rows_per_strip() {
test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840);
}
#[test]
fn test_predictor_3_rgb_f32() {
test_image_sum_f32("predictor-3-rgb-f32.tif", ColorType::RGB(32), 54004.33);
}
#[test]
fn test_predictor_3_gray_f32() {
test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275);
}
#[test]
#[cfg(feature = "zstd")]
fn test_zstd_compression() {
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
}
+233 -6
View File
@@ -1,7 +1,7 @@
extern crate tiff; extern crate tiff;
use tiff::decoder::{ifd, Decoder, DecodingResult}; use tiff::decoder::{ifd, Decoder, DecodingResult};
use tiff::encoder::{colortype, SRational, TiffEncoder}; use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder};
use tiff::tags::Tag; use tiff::tags::Tag;
use tiff::ColorType; use tiff::ColorType;
@@ -36,6 +36,47 @@ fn encode_decode() {
let mut decoder = Decoder::new(&mut file).unwrap(); let mut decoder = Decoder::new(&mut file).unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
assert_eq!(decoder.dimensions().unwrap(), (100, 100)); assert_eq!(decoder.dimensions().unwrap(), (100, 100));
let mut all_tags = decoder
.tag_iter()
.filter_map(Result::ok)
.collect::<Vec<_>>();
all_tags.sort_by_key(|(t, _)| t.to_u16());
assert_eq!(
all_tags,
vec![
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
(Tag::ImageLength, ifd::Value::Unsigned(100)),
(
Tag::BitsPerSample,
ifd::Value::List(vec![
ifd::Value::Short(8),
ifd::Value::Short(8),
ifd::Value::Short(8)
])
),
(Tag::Compression, ifd::Value::Short(1)),
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
(Tag::StripOffsets, ifd::Value::Unsigned(8)),
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
(Tag::XResolution, ifd::Value::Rational(1, 1)),
(Tag::YResolution, ifd::Value::Rational(1, 1)),
(Tag::ResolutionUnit, ifd::Value::Short(1)),
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
(Tag::Predictor, ifd::Value::Short(1)),
(
Tag::SampleFormat,
ifd::Value::List(vec![
ifd::Value::Short(1),
ifd::Value::Short(1),
ifd::Value::Short(1)
])
),
]
);
assert_eq!( assert_eq!(
decoder.get_tag(Tag::Artist).unwrap(), decoder.get_tag(Tag::Artist).unwrap(),
ifd::Value::Ascii("Image-tiff".into()) ifd::Value::Ascii("Image-tiff".into())
@@ -48,6 +89,134 @@ fn encode_decode() {
} }
} }
#[test]
fn encode_decode_big() {
let mut image_data = Vec::new();
for x in 0..100 {
for y in 0..100u8 {
let val = x + y;
image_data.push(val);
image_data.push(val);
image_data.push(val);
}
}
let mut file = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new_big(&mut file).unwrap();
let mut image = tiff.new_image::<colortype::RGB8>(100, 100).unwrap();
image
.encoder()
.write_tag(Tag::Artist, "Image-tiff")
.unwrap();
image.write_data(&image_data).unwrap();
}
{
file.seek(SeekFrom::Start(0)).unwrap();
let mut decoder = Decoder::new(&mut file).unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
let mut all_tags = decoder
.tag_iter()
.filter_map(Result::ok)
.collect::<Vec<_>>();
all_tags.sort_by_key(|(t, _)| t.to_u16());
assert_eq!(
all_tags,
vec![
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
(Tag::ImageLength, ifd::Value::Unsigned(100)),
(
Tag::BitsPerSample,
ifd::Value::List(vec![
ifd::Value::Short(8),
ifd::Value::Short(8),
ifd::Value::Short(8)
])
),
(Tag::Compression, ifd::Value::Short(1)),
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
(Tag::StripOffsets, ifd::Value::UnsignedBig(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())
);
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
}
}
}
#[test]
fn test_encode_ifd() {
let mut data = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new(&mut data).unwrap();
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
image_encoder.write_strip(&[1]).unwrap();
let encoder = image_encoder.encoder();
// Use the "reusable" tags section as per the TIFF6 spec
encoder.write_tag(Tag::Unknown(65000), Ifd(42u32)).unwrap();
encoder
.write_tag(Tag::Unknown(65001), &[Ifd(100u32)][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65002), &[Ifd(1u32), Ifd(2u32), Ifd(3u32)][..])
.unwrap();
encoder.write_tag(Tag::Unknown(65010), Ifd8(43u64)).unwrap();
encoder
.write_tag(Tag::Unknown(65011), &[Ifd8(100u64)][..])
.unwrap();
encoder
.write_tag(
Tag::Unknown(65012),
&[Ifd8(1u64), Ifd8(2u64), Ifd8(3u64)][..],
)
.unwrap();
}
// Rewind the cursor for reading
data.set_position(0);
{
let mut decoder = Decoder::new(&mut data).unwrap();
assert_eq!(decoder.assert_tag_u32(65000), 42);
assert_eq!(decoder.assert_tag_u32_vec(65000), [42]);
assert_eq!(decoder.assert_tag_u32_vec(65001), [100]);
assert_eq!(decoder.assert_tag_u32_vec(65002), [1, 2, 3]);
assert_eq!(decoder.assert_tag_u64(65010), 43);
assert_eq!(decoder.assert_tag_u64_vec(65010), [43]);
assert_eq!(decoder.assert_tag_u64_vec(65011), [100]);
assert_eq!(decoder.assert_tag_u64_vec(65012), [1, 2, 3]);
}
}
#[test] #[test]
/// Test that attempting to encode when the input buffer is undersized returns /// Test that attempting to encode when the input buffer is undersized returns
/// an error rather than panicking. /// an error rather than panicking.
@@ -101,7 +270,9 @@ macro_rules! test_roundtrip {
} }
test_roundtrip!(test_u8_roundtrip, U8, u8); test_roundtrip!(test_u8_roundtrip, U8, u8);
test_roundtrip!(test_i8_roundtrip, I8, i8);
test_roundtrip!(test_u16_roundtrip, U16, u16); test_roundtrip!(test_u16_roundtrip, U16, u16);
test_roundtrip!(test_i16_roundtrip, I16, i16);
test_roundtrip!(test_u32_roundtrip, U32, u32); test_roundtrip!(test_u32_roundtrip, U32, u32);
test_roundtrip!(test_u64_roundtrip, U64, u64); test_roundtrip!(test_u64_roundtrip, U64, u64);
test_roundtrip!(test_f32_roundtrip, F32, f32); test_roundtrip!(test_f32_roundtrip, F32, f32);
@@ -112,6 +283,11 @@ fn test_gray_u8_roundtrip() {
test_u8_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8)); test_u8_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
} }
#[test]
fn test_gray_i8_roundtrip() {
test_i8_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test] #[test]
fn test_rgb_u8_roundtrip() { fn test_rgb_u8_roundtrip() {
test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8)); test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
@@ -122,11 +298,21 @@ fn test_cmyk_u8_roundtrip() {
test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8)); test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
} }
#[test]
fn test_cmyka_u8_roundtrip() {
test_u8_roundtrip::<colortype::CMYKA8>("cmyk-4c-8b.tiff", ColorType::CMYKA(8));
}
#[test] #[test]
fn test_gray_u16_roundtrip() { fn test_gray_u16_roundtrip() {
test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16)); test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
} }
#[test]
fn test_gray_i16_roundtrip() {
test_i16_roundtrip::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
}
#[test] #[test]
fn test_rgb_u16_roundtrip() { fn test_rgb_u16_roundtrip() {
test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16)); test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
@@ -177,11 +363,20 @@ fn test_gray_f64_roundtrip() {
test_f64_roundtrip::<colortype::Gray64Float>("gradient-1c-64b-float.tiff", ColorType::Gray(64)); test_f64_roundtrip::<colortype::Gray64Float>("gradient-1c-64b-float.tiff", ColorType::Gray(64));
} }
#[test]
fn test_ycbcr_u8_roundtrip() {
test_u8_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}
trait AssertDecode { trait AssertDecode {
fn assert_tag_u32(&mut self, tag: u16) -> u32; fn assert_tag_u32(&mut self, tag: u16) -> u32;
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>; fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
fn assert_tag_i32(&mut self, tag: u16) -> i32; fn assert_tag_i32(&mut self, tag: u16) -> i32;
fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec<i32>; fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec<i32>;
fn assert_tag_u64(&mut self, tag: u16) -> u64;
fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec<u64>;
fn assert_tag_i64(&mut self, tag: u16) -> i64;
fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec<i64>;
} }
impl<R: std::io::Read + std::io::Seek> AssertDecode for Decoder<R> { impl<R: std::io::Read + std::io::Seek> AssertDecode for Decoder<R> {
@@ -203,6 +398,24 @@ impl<R: std::io::Read + std::io::Seek> AssertDecode for Decoder<R> {
.into_i32_vec() .into_i32_vec()
.unwrap() .unwrap()
} }
fn assert_tag_u64(&mut self, tag: u16) -> u64 {
self.get_tag(Tag::Unknown(tag)).unwrap().into_u64().unwrap()
}
fn assert_tag_u64_vec(&mut self, tag: u16) -> Vec<u64> {
self.get_tag(Tag::Unknown(tag))
.unwrap()
.into_u64_vec()
.unwrap()
}
fn assert_tag_i64(&mut self, tag: u16) -> i64 {
self.get_tag(Tag::Unknown(tag)).unwrap().into_i64().unwrap()
}
fn assert_tag_i64_vec(&mut self, tag: u16) -> Vec<i64> {
self.get_tag(Tag::Unknown(tag))
.unwrap()
.into_i64_vec()
.unwrap()
}
} }
#[test] #[test]
@@ -212,6 +425,7 @@ fn test_multiple_byte() {
{ {
let mut tiff = TiffEncoder::new(&mut data).unwrap(); let mut tiff = TiffEncoder::new(&mut data).unwrap();
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap(); let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
image_encoder.write_strip(&[1]).unwrap();
let encoder = image_encoder.encoder(); let encoder = image_encoder.encoder();
encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]).unwrap(); encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]).unwrap();
@@ -252,6 +466,7 @@ fn test_signed() {
{ {
let mut tiff = TiffEncoder::new(&mut data).unwrap(); let mut tiff = TiffEncoder::new(&mut data).unwrap();
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap(); let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
image_encoder.write_strip(&[1]).unwrap();
let encoder = image_encoder.encoder(); let encoder = image_encoder.encoder();
//Use the "reusable" tags section as per the TIFF6 spec //Use the "reusable" tags section as per the TIFF6 spec
@@ -289,12 +504,20 @@ fn test_signed() {
.write_tag(Tag::Unknown(65022), &[-1_i32, 2][..]) .write_tag(Tag::Unknown(65022), &[-1_i32, 2][..])
.unwrap(); .unwrap();
encoder.write_tag(Tag::Unknown(65030), -1_i64).unwrap();
encoder encoder
.write_tag(Tag::Unknown(65030), make_srational(-1)) .write_tag(Tag::Unknown(65031), &[-1_i64][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65032), &[-1_i64, 2][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65040), make_srational(-1))
.unwrap(); .unwrap();
encoder encoder
.write_tag( .write_tag(
Tag::Unknown(65031), Tag::Unknown(65041),
&[make_srational(-1), make_srational(2)][..], &[make_srational(-1), make_srational(2)][..],
) )
.unwrap(); .unwrap();
@@ -321,8 +544,12 @@ fn test_signed() {
assert_eq!(decoder.assert_tag_i32_vec(65021), [-1]); assert_eq!(decoder.assert_tag_i32_vec(65021), [-1]);
assert_eq!(decoder.assert_tag_i32_vec(65022), [-1, 2]); assert_eq!(decoder.assert_tag_i32_vec(65022), [-1, 2]);
assert_eq!(decoder.assert_tag_i32_vec(65030), [-1, 100]); assert_eq!(decoder.assert_tag_i64(65030), -1);
assert_eq!(decoder.assert_tag_i32_vec(65031), [-1_i32, 100, 2, 100]); assert_eq!(decoder.assert_tag_i64_vec(65031), [-1]);
assert_eq!(decoder.assert_tag_i64_vec(65032), [-1, 2]);
assert_eq!(decoder.assert_tag_i32_vec(65040), [-1, 100]);
assert_eq!(decoder.assert_tag_i32_vec(65041), [-1_i32, 100, 2, 100]);
} }
} }
@@ -392,7 +619,7 @@ fn test_rows_per_strip() {
for i in 0..50 { for i in 0..50 {
let img2 = [i; 2 * 100]; let img2 = [i; 2 * 100];
match decoder.read_strip().unwrap() { match decoder.read_chunk(i as u32).unwrap() {
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]), DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
other => panic!("Incorrect strip type {:?}", other), other => panic!("Incorrect strip type {:?}", other),
} }
+158
View File
@@ -0,0 +1,158 @@
extern crate tiff;
use std::io::{Cursor, Seek, Write};
use tiff::{
decoder::{Decoder, DecodingResult},
encoder::{
colortype::{self, ColorType},
compression::*,
Compression, TiffEncoder, TiffValue,
},
};
trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>::Inner>> {
const WIDTH: u32;
const HEIGHT: u32;
type Color: ColorType;
fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
fn compress<W: Write + Seek>(&self, encoder: &mut TiffEncoder<W>)
where
[<Self::Color as ColorType>::Inner]: TiffValue,
{
let image = encoder
.new_image::<Self::Color>(Self::WIDTH, Self::HEIGHT)
.unwrap();
image.write_data(self.reference_data()).unwrap();
}
fn generate() -> Self {
assert_eq!(
Self::Color::BITS_PER_SAMPLE.len(),
NUM_CHANNELS,
"Incompatible color type"
);
let mut data = Vec::with_capacity((Self::WIDTH * Self::HEIGHT) as usize * NUM_CHANNELS);
for x in 0..Self::WIDTH {
for y in 0..Self::HEIGHT {
data.extend(IntoIterator::into_iter(Self::generate_pixel(x, y)));
}
}
Self::from(data)
}
}
struct TestImageColor(Vec<u16>);
impl From<Vec<u16>> for TestImageColor {
fn from(value: Vec<u16>) -> Self {
Self(value)
}
}
impl TestImage<3> for TestImageColor {
const WIDTH: u32 = 1;
const HEIGHT: u32 = 7;
type Color = colortype::RGB16;
fn reference_data(&self) -> &[u16] {
&self.0
}
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 3] {
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
[val as <Self::Color as ColorType>::Inner; 3]
}
}
struct TestImageGrayscale(Vec<u8>);
impl From<Vec<u8>> for TestImageGrayscale {
fn from(value: Vec<u8>) -> Self {
Self(value)
}
}
impl TestImage<1> for TestImageGrayscale {
const WIDTH: u32 = 21;
const HEIGHT: u32 = 10;
type Color = colortype::Gray8;
fn reference_data(&self) -> &[u8] {
&self.0
}
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; 1] {
let val = (x + y) % <Self::Color as ColorType>::Inner::MAX as u32;
[val as <Self::Color as ColorType>::Inner]
}
}
fn encode_decode_with_compression(compression: Compression) {
let mut data = Cursor::new(Vec::new());
let image_rgb = TestImageColor::generate();
let image_grayscale = TestImageGrayscale::generate();
// Encode tiff with compression
{
// Create a multipage image with 2 images
let mut encoder = TiffEncoder::new(&mut data)
.unwrap()
.with_compression(compression);
image_rgb.compress(&mut encoder);
image_grayscale.compress(&mut encoder);
}
// Decode tiff
data.set_position(0);
{
let mut decoder = Decoder::new(data).unwrap();
// Check the RGB image
assert_eq!(
match decoder.read_image() {
Ok(DecodingResult::U16(image_data)) => image_data,
unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
},
image_rgb.reference_data()
);
// Check the grayscale image
decoder.next_image().unwrap();
assert_eq!(
match decoder.read_image() {
Ok(DecodingResult::U8(image_data)) => image_data,
unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
},
image_grayscale.reference_data()
);
}
}
#[test]
fn encode_decode_without_compression() {
encode_decode_with_compression(Compression::Uncompressed);
}
#[test]
#[cfg(feature = "lzw")]
fn encode_decode_with_lzw() {
encode_decode_with_compression(Compression::Lzw);
}
#[test]
#[cfg(feature = "deflate")]
fn encode_decode_with_deflate() {
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
}
#[test]
fn encode_decode_with_packbits() {
encode_decode_with_compression(Compression::Packbits);
}
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+231
View File
@@ -0,0 +1,231 @@
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
use tiff::ColorType;
use std::fs::File;
use std::io::{Cursor, Seek, SeekFrom};
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images/";
macro_rules! test_predict {
($name:ident, $buffer:ident, $buffer_ty:ty) => {
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
file: &str,
expected_type: ColorType,
) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(file).expect("Cannot create decoder!");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
DecodingResult::$buffer(res) => res,
_ => panic!("Wrong data type"),
};
let mut predicted = Vec::with_capacity(image_data.len());
C::horizontal_predict(&image_data, &mut predicted);
let sample_size = C::SAMPLE_FORMAT.len();
(0..sample_size).for_each(|i| {
assert_eq!(predicted[i], image_data[i]);
});
(sample_size..image_data.len()).for_each(|i| {
predicted[i] = predicted[i].wrapping_add(predicted[i - sample_size]);
assert_eq!(predicted[i], image_data[i]);
});
}
};
}
test_predict!(test_u8_predict, U8, u8);
test_predict!(test_i8_predict, I8, i8);
test_predict!(test_u16_predict, U16, u16);
test_predict!(test_i16_predict, I16, i16);
test_predict!(test_u32_predict, U32, u32);
test_predict!(test_u64_predict, U64, u64);
#[test]
fn test_gray_u8_predict() {
test_u8_predict::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_gray_i8_predict() {
test_i8_predict::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_rgb_u8_predict() {
test_u8_predict::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
}
#[test]
fn test_cmyk_u8_predict() {
test_u8_predict::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
}
#[test]
fn test_gray_u16_predict() {
test_u16_predict::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_gray_i16_predict() {
test_i16_predict::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_rgb_u16_predict() {
test_u16_predict::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
}
#[test]
fn test_cmyk_u16_predict() {
test_u16_predict::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
}
#[test]
fn test_gray_u32_predict() {
test_u32_predict::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
}
#[test]
fn test_rgb_u32_predict() {
test_u32_predict::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
}
#[test]
fn test_gray_u64_predict() {
test_u64_predict::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
}
#[test]
fn test_rgb_u64_predict() {
test_u64_predict::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
}
#[test]
fn test_ycbcr_u8_predict() {
test_u8_predict::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}
macro_rules! test_predict_roundtrip {
($name:ident, $buffer:ident, $buffer_ty:ty) => {
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
file: &str,
expected_type: ColorType,
) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
DecodingResult::$buffer(res) => res,
_ => panic!("Wrong data type"),
};
let mut file = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new(&mut file)
.unwrap()
.with_predictor(Predictor::Horizontal);
let (width, height) = decoder.dimensions().unwrap();
tiff.write_image::<C>(width, height, &image_data).unwrap();
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::new(&mut file).unwrap();
if let DecodingResult::$buffer(img_res) =
decoder.read_image().expect("Decoding image failed")
{
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
}
}
}
};
}
test_predict_roundtrip!(test_u8_predict_roundtrip, U8, u8);
test_predict_roundtrip!(test_i8_predict_roundtrip, I8, i8);
test_predict_roundtrip!(test_u16_predict_roundtrip, U16, u16);
test_predict_roundtrip!(test_i16_predict_roundtrip, I16, i16);
test_predict_roundtrip!(test_u32_predict_roundtrip, U32, u32);
test_predict_roundtrip!(test_u64_predict_roundtrip, U64, u64);
#[test]
fn test_gray_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_gray_i8_predict_roundtrip() {
test_i8_predict_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_rgb_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
}
#[test]
fn test_cmyk_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
}
#[test]
fn test_gray_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_gray_i16_predict_roundtrip() {
test_i16_predict_roundtrip::<colortype::GrayI16>(
"minisblack-1c-i16b.tiff",
ColorType::Gray(16),
);
}
#[test]
fn test_rgb_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
}
#[test]
fn test_cmyk_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
}
#[test]
fn test_gray_u32_predict_roundtrip() {
test_u32_predict_roundtrip::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
}
#[test]
fn test_rgb_u32_predict_roundtrip() {
test_u32_predict_roundtrip::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
}
#[test]
fn test_gray_u64_predict_roundtrip() {
test_u64_predict_roundtrip::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
}
#[test]
fn test_rgb_u64_predict_roundtrip() {
test_u64_predict_roundtrip::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
}
#[test]
fn test_ycbcr_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}