Compare commits

..
158 Commits
Author SHA1 Message Date
Andreas Molzer 369c602952 Adjust supported features in Readme 2020-10-11 18:17:53 +02:00
Andreas Molzer ed22865f04 Remove unused and incorrect Wrapping impl
This is used in the predictor and should be reversible but for floating
point the simply addition is not. It should instead be some variant of
byte casting but it hasn't been implemented yet.
2020-10-11 15:09:46 +02:00
Andreas Molzer 459b4f3908 Update meta data 2020-10-11 15:07:56 +02:00
HeroicKatora e72401d4ac Merge pull request #95 from ruffson/support_modern_jpeg
Support decoding of modern JPEG compression
2020-09-26 18:50:39 +02:00
ruffson 6281360f10 Fix error case 2020-09-26 18:45:58 +02:00
ruffson d00ab0cc61 Fix formatting 2020-09-26 17:22:59 +02:00
ruffson 6353f71e28 Satisfy the borrow checker 2020-09-26 17:14:17 +02:00
ruffson 7c4ee96aee Check for size when reading strip bytes 2020-09-26 16:50:51 +02:00
ruffson cb5319c426 Deal with empty jpeg_tables 2020-09-26 14:35:06 +02:00
ruffson 53aefea6a4 Fix typos 2020-09-26 14:14:09 +02:00
ruffson cb900819d9 Remove tile processing and add error handling
I removed the code that checked the tile attributes. Because I would
rather later on PR something that works than a half-baked solution.
2020-09-26 14:07:27 +02:00
ruffson 35ea719d9e Remove get_all_tags() 2020-09-26 13:44:53 +02:00
ruffson 41050c14ed Change accesibility of JpegReader 2020-09-26 12:38:02 +02:00
ruffson 5fd0534ea2 Fix error handling 2020-09-26 12:23:40 +02:00
ruffson 72474b569e Add byte expected error and cleanup 2020-09-25 20:06:52 +02:00
ruffson 63f09b267a Reorganize tags and cleanup 2020-09-25 19:48:22 +02:00
ruffson b455bd9f2b Fix test 2020-09-25 19:47:35 +02:00
ruffson 46918b395c Condense tile and strip reading code 2020-09-25 19:47:35 +02:00
ruffson a510b03e1c Apply auto-format to modified files 2020-09-25 19:47:33 +02:00
ruffson 2cb57a4d04 Add initial support for modern JPEG compression
Some things still need to be done:
- Check RGB images
- Check small images
- Check huffman-only jpeg tables
- Check empty jpeg tables
- Add tests
2020-09-25 19:42:29 +02:00
ruffson da9bdac8a2 Read out byte-sized arrays 2020-09-25 19:39:09 +02:00
raph 647de77b70 Add support for UNDEFINED type
According to TIFF spec this is an byte-array that may contain arbitrary
data.
2020-09-25 19:37:24 +02:00
HeroicKatora 727d78a57c Merge pull request #94 from image-rs/clippy-with-manual-touchup
rust fmt with manual touchup
2020-09-13 15:17:40 +02:00
Atul Bhosale 49da46bbd0 Add cargo fmt to build config 2020-09-12 01:15:53 +02:00
Andreas Molzer 7b41c823e9 Some slight manual touch up where style was bad 2020-09-12 01:15:45 +02:00
Andreas Molzer bf24b3f935 Do a clippy pass 2020-09-12 01:15:08 +02:00
Andreas Molzer 2b12c1b162 Rewrite tests to be less repetitive 2020-09-12 01:13:09 +02:00
HeroicKatora 82c00a84d8 Merge pull request #93 from image-rs/remove-pipfile
Remove a Pipfile?
2020-09-12 00:38:25 +02:00
Andreas Molzer ec5b4b9a9e Remove a Pipfile?
This slipped through in some merge but I it shouldn't be necessary for
anything? Probably committed/not ignored on accident.
2020-09-11 23:27:46 +02:00
HeroicKatora f4f11e6399 Merge pull request #90 from image-rs/weezl
Replace lzw decoder with weezl
2020-09-05 23:14:29 +02:00
Andreas Molzer 49b1f78ce5 Replace lzw decoder with weezl 2020-09-05 21:57:51 +02:00
Andreas Molzer e48691ca32 Add decoding benchmarks 2020-09-05 21:57:51 +02:00
HeroicKatora 7ad6b0e70d Merge pull request #91 from image-rs/github-actions
Switch to Github Actions for CI checks
2020-09-05 21:56:33 +02:00
Andreas Molzer 7cc5b98d9c Switch to Github Actions for CI checks
More customizable, faster, more integration. We already did that for a
number of other crates with good outcome.
2020-09-05 21:54:15 +02:00
HeroicKatora 7928c219dd Merge pull request #70 from aschampion/features/32-bit-float-samples
Floating point sample support
2020-09-01 17:28:10 +02:00
Andreas Molzer 44c75a66ab Merge remote-tracking branch 'origin/master' into features/32-bit-float-samples 2020-08-31 20:50:16 +02:00
Andreas Molzer d8bfa84d06 Add notes for unsupported predictors 2020-08-31 19:53:06 +02:00
HeroicKatora 1492087426 Merge pull request #76 from sdroege/miniz_oxide-0.4
Update to miniz_oxide 0.4
2020-08-22 18:13:36 +02:00
Sebastian Dröge 23090a3513 Update to miniz_oxide 0.4.1 2020-08-22 18:03:46 +02:00
Etienne Prothon 200355d3bc Mark decoding buffer f32 and f64 as unimplemented 2020-08-17 18:32:43 +02:00
HeroicKatora 469883aa79 Merge pull request #88 from image-rs/group-unsafety
Move all bytecast unsafety to one module
2020-08-16 18:13:28 +02:00
Andreas Molzer 99c038c929 Move all bytecast unsafety to one module 2020-08-16 16:24:47 +02:00
HeroicKatora f3eebee1e9 Merge pull request #87 from nabijaczleweli/byteorder
Drop byteorder dependency
2020-08-16 16:20:40 +02:00
наб b853d841f3 Drop byteorder dependency
Ref: https://github.com/image-rs/image/issues/1000
2020-08-14 16:52:51 +02:00
HeroicKatora 418979f14b Merge pull request #86 from image-rs/release-0.6.0-alpha
Update release notes for alpha release for BigTIFF
2020-08-13 20:37:22 +02:00
Andreas Molzer 2635bdd7d5 Update release notes for alpha release for BigTIFF 2020-08-13 18:02:26 +02:00
Farkal ff874612ba Allow to decode LONG8 (u64)
TileOffsets and TileByteCounts have the LONG8 type on bigtiff.
2020-08-10 15:32:47 +02:00
HeroicKatora ea217dc238 Merge pull request #83 from Farkal/big-tiff
Big tiff decoding support
2020-08-08 12:33:54 +02:00
Etienne Prothon 768e0d4af7 feat: Add tag to single f32 or f64 2020-08-05 15:34:28 +02:00
Etienne Prothon 3b0da4d1ae feat: Add decoding value and get tags as f32 or f64 2020-08-05 14:25:02 +02:00
Etienne Prothon 54c3ebf358 Merge with master and remove duplicate SampleFormat tag 2020-08-04 16:55:48 +02:00
Etienne Prothon 5f48444fa0 Merge branch 'master' of github.com:Farkal/image-tiff into features/32-bit-float-samples 2020-08-04 16:43:01 +02:00
Etienne Prothon b2da2f2333 Merge branch 'master' of github.com:Farkal/image-tiff into big-tiff 2020-07-27 12:01:55 +02:00
Farkal ba1daa18b2 Allow custom encoding process (#84)
Simplify encoding of tags
2020-07-27 11:29:09 +02:00
Etienne Prothon ba3f6e604a feat: Add BigTiff enum values 2020-07-27 10:52:43 +02:00
HeroicKatora a471591f06 Merge pull request #77 from Farkal/read_ascii_in_offset
Read ascii encoded in offset
2020-07-24 02:25:17 +02:00
Etienne Prothon dacc2864e8 feat: better compatibility with old api 2020-07-24 01:22:33 +02:00
Etienne Prothon 797e3dca07 fix: Big tiff decoding and add tests 2020-07-24 00:47:14 +02:00
Etienne Prothon 23751bd8c5 feat: Support decoding big tiff 2020-07-23 01:33:41 +02:00
HeroicKatora 7fb68dbbe7 Merge pull request #78 from Farkal/support_tiled_data_tags
Support tile and sample format tags
2020-07-22 18:52:57 +02:00
HeroicKatora 4afd7ed462 Merge pull request #81 from spenczar/patch-1
Update link to TIFF specification in docs
2020-07-21 18:30:27 +02:00
Spencer Nelson aa49e7d6f0 Update link to TIFF specification in docs
Adobe refactored their site and the specification has moved.
2020-07-21 09:23:12 -07:00
Etienne Prothon cbb41fd479 feat: Support tile and sample format tags 2020-07-15 16:55:34 +02:00
Etienne Prothon 9edc0e656e fix: remove unwrap 2020-07-15 16:39:00 +02:00
Etienne Prothon 9f4873bae8 fix: Read ascii encoded in offset 2020-07-15 16:21:09 +02:00
HeroicKatora 472cf6d07d Merge pull request #75 from image-rs/release-0.5
Update notes and metadata for 0.5.0
2020-06-01 20:13:42 +02:00
Andreas Molzer ee73341bbf Update notes and metadata for 0.5.0 2020-06-01 20:05:59 +02:00
HeroicKatora 6c7cc7ec30 Merge pull request #71 from aschampion/issues/69-u16-lzw-error
Fix #69
2020-03-31 16:55:39 +02:00
Andrew Champion 71f3aa8b15 Include unknown variant for sample format
Since it is known that libtiff produces other sample format variants we
do not support, represent these as variants. This also allows returning
the sample formats in errors so that composition of formats and known
and unknown formats are clearly reported.
2020-02-10 13:21:16 +00:00
Andrew Champion 0ec8e14910 Tags: generate from_u16_exhaustive for all tags with unknown variants 2020-02-10 13:21:16 +00:00
Andrew Champion d79d8c3353 Make sample format FIXME a TODO (minor) 2020-02-10 13:21:16 +00:00
Andrew Champion ae4ae9c36e Fix capitalization case of sample format enum 2020-02-10 13:21:16 +00:00
Andrew Champion 8d4bdce7aa Add support for IEEEFP 32 and 64 bit encoding 2020-02-10 13:21:16 +00:00
Andrew Champion a41e33e42a Fix wrong inner types for color types 2020-02-10 13:21:16 +00:00
Andrew Champion c2084835fa Add support for IEEEFP Gray/RGB/RGBA/CMYK(64) decoding 2020-02-10 13:21:16 +00:00
Andrew Champion 97c888f166 Add support for IEEEFP RGB/RGBA/CMYK(32) decoding 2020-02-10 13:21:13 +00:00
Andrew Champion 24d347ecf5 Tests: add regression test for issue 69 2020-02-09 18:37:16 +00:00
Andrew Champion a439baa948 Decoder: remove test make wrong assumption
The byte count for strips is the compressed length. The compressed
length may be larger than the uncompressed length, especially with poor
compression choices (multi-byte samples with packbits) or small images.
2020-02-09 18:37:16 +00:00
Andrew Champion 6e23274bea Decoder: max uncompressed length for LZW must respect buffer byte length
Fixes #69.
2020-02-09 18:24:22 +00:00
Andrew Champion bbc49291e4 Add support for IEEEFP Gray(32) decoding 2020-02-06 18:40:41 +00:00
HeroicKatora 119dec5068 Merge pull request #67 from aschampion/issues/misc-cleanup
Refactoring, cleanup, safety, docs & CMYK(16|32|64) support
2020-02-06 17:55:26 +01:00
Andrew Champion ec0477125f Tags: restore primitive conversions as inherent methods 2020-02-01 18:25:05 +00:00
Andrew Champion 1c8e98bca5 Squash to cast checking 2020-01-02 12:47:39 +00:00
Andrew Champion e6985063ac Fix formatting in README table 2019-12-21 15:57:24 +00:00
Andrew Champion 42c94ac3d9 Add support for CMYK(16|32|64) encoding 2019-12-21 15:57:24 +00:00
Andrew Champion eadec8f0b9 Remove most unchecked size/index casts
Convert most unchecked size/index `as` casts to checked casts with
`TryFrom`, included another top-level error variant.

This has several motivations:
- Making the library as robust as possible, including to platform
differences.
- Unchecked casts are susceptible to breakage during refactorings.
- Preparing for BigTIFF support, which will require the introduction of
generics and associated types for some parts of the de/encoders.
Unchecked primitive casts cannot be bound, while TryFrom can, and
overflowing conversions become more likely.
- Remaining u16 casts may be prone in failure in existing practice and
not just in theory during future changes.
2019-12-21 15:57:24 +00:00
Andrew Champion 462697e373 Add support for CMYK(16|32|64) decoding 2019-12-21 15:57:24 +00:00
Andrew Champion c537f5cc64 Add decode test for CMYK(8) 2019-12-21 15:57:24 +00:00
Andrew Champion 8f61196d70 Note 32 and 64 bit support in README 2019-12-21 15:57:24 +00:00
Andrew Champion 298dc8841d Remove unchecked casts and magic values for some tags 2019-12-21 15:57:24 +00:00
Andrew Champion f92be13f6c Factor tags into own module
Tag representations are not decoder- or encoder-specific, so should live
in their own module accessible to both for reuse. The decoder::ifd
module remains since this module is mainly dedicated to decoding the
IFD.
2019-12-21 15:57:24 +00:00
Andrew Champion f9cace84da Deduplicate primitive conversions for tag enum 2019-12-21 15:57:24 +00:00
Andrew Champion d510a9f4ab Strictly handle expected tag value types
Resolve several TODOs where tag values were cast from u32 without checks
by providing generic methods that error when the value fails casting to
the desired type. In addition to being more correct, this simplifies and
shortens decoding logic.

Remove `find_tag_u32` and `find_tag_u32_vec` as they are now redundant
and a GitHub code search found no usages outside copies of
`image-tiff`.
2019-12-21 15:57:24 +00:00
Andrew Champion 1fafcccb36 Remove needless cast (minor) 2019-12-21 15:57:24 +00:00
Andrew Champion 8b96793671 Simplify expression (minor) 2019-12-21 15:57:24 +00:00
Andrew Champion 27ae65160d Deduplicate decoding buffer construction 2019-12-21 15:57:24 +00:00
Andrew Champion 29bb8c2dfe Summarize supported features in README 2019-12-21 15:57:24 +00:00
Andrew Champion c5fcba45a0 Fix typo in error display message 2019-12-21 15:57:24 +00:00
HeroicKatora cbaa9dbc5b Merge pull request #68 from aschampion/refactors/test-dedup
Deduplicate tests and remove tempfile dev-dependency
2019-12-21 16:47:42 +01:00
Andrew Champion 959254aee9 Deduplicate encoding tests
Also removes dev dependency on tempfile.
2019-12-21 15:12:25 +00:00
Andrew Champion 3f14098060 Deduplicate decoding tests 2019-12-21 15:11:47 +00:00
Andrew Champion 57783ba43f Merge pull request #66 from aschampion/features/32-bit
Support 32- and 64-bit integer sample grayscale and RGB(A) images
2019-12-19 13:19:35 +00:00
Andrew Champion abd6aee19f Remove outdate and ambiguous TODO (minor) 2019-12-16 22:10:57 +00:00
Andrew Champion c72a8d364d Add tests for exotic 12-bit sample images
These images should correctly report their color depths, but fail to
decode as they are unsupported.
2019-12-16 18:53:44 +00:00
Andrew Champion e5d2e7862c Parse all homogeneous bit depth RGB(A) color types
Make the behavior of RGB and RGBA like Gray in returning ColorTypes for
all homogeneous bit depths, even if those depths are not 8, 16, 32 or
64. Note that this does not change which color types can actually be
decoded.
2019-12-11 15:03:05 +00:00
Andrew Champion 6e61de81b6 Simplify color type matching 2019-12-11 12:56:11 +00:00
Andrew Champion 8c4cea5d97 Support 64-bit encoding
Note that this adds the LONG8 data type from BigTIFF because of bounds
requiring TiffValue. However, this datatype should not actually be
written to tags.
2019-12-10 20:18:53 +00:00
Andrew Champion 0c53e1066a Support 64-bit rgb and rgba decoding 2019-12-10 20:18:53 +00:00
Andrew Champion f4e985e0bd Support 64-bit grayscale decoding 2019-12-10 20:18:53 +00:00
Andrew Champion af010edcfd Support 32-bit encoding 2019-12-10 20:18:53 +00:00
Andrew Champion f32b3b9d10 Fix warnings in encoding test 2019-12-10 20:18:53 +00:00
Andrew Champion 1f92c562bc Support 32-bit rgb and rgba decoding 2019-12-10 20:18:51 +00:00
Andrew Champion fca4e1f88a Support 32-bit grayscale decoding 2019-12-10 20:18:23 +00:00
HeroicKatora ff574b4ca2 Merge pull request #64 from image-rs/release-0.4
Document changes for release 0.4
2019-11-23 19:43:33 +01:00
HeroicKatora 6f87228458 Merge pull request #65 from image-rs/error-type
Improve the error types
2019-11-21 19:36:38 +01:00
Andreas Molzer 15929f8b8e Document changes for release 0.4
Fixes: #60
2019-11-21 19:23:03 +01:00
Andreas Molzer f13492e41d Fix a doc typo found in PR review 2019-11-21 18:46:55 +01:00
Andreas Molzer 1d9944c5c5 Mark both detailed error types as non-exhaustive
This makes it cheaper to introduce new features that might require
amending the list of variants by allowing us to introduce previously
uncovered causes or more specialized representations.
2019-11-20 20:58:45 +01:00
Andreas Molzer a14b23473b Export the inflate error publicly
It encapsulates the implementation detail of holding a miniz_oxide
status within. The derived traits are not too extensive for other
possible representations as well.
2019-11-20 20:02:55 +01:00
Andreas Molzer 101940335c Fix public constructor from miniz_oxide status
Avoids leaking the dependency on `miniz_oxide` into the public interface
by making a constructor taking a status argument crate internal. It
would otherwise not be possible to replace the deflate decoder without
breaking that interface.
2019-11-20 19:56:13 +01:00
HeroicKatora 8e1c67ee53 Merge pull request #63 from image-rs/tagged
Replace FromPrimitive with our own tag macro
2019-11-12 20:14:25 +01:00
Andreas Molzer 7b672998db Get rid of num-traits crate
Turns out the only use was as a private bound which was unused within
that method.
2019-11-12 20:06:02 +01:00
HeroicKatora 62fee106a7 Merge pull request #62 from onnotechno/multipage_image_patch
Multipage image patch
2019-11-12 13:13:57 +01:00
Onno Kammeraad fd789dc420 Cursor::new(Vec::new()) requires a &mut instead of just & 2019-11-12 13:09:09 +01:00
Andreas Molzer 07e8501ad6 Prefer matching over if-else-chain in tags macro
Thanks to the review comment for pointing this style issue out.
2019-11-12 10:53:38 +01:00
OnnoandHeroicKatora f70447325e Update tests/encode_images.rs
Co-Authored-By: HeroicKatora <HeroicKatora@users.noreply.github.com>
2019-11-12 10:40:19 +01:00
Onno Kammeraad b2eb480c44 add multipage testcase, to validate the multipage image handling 2019-11-12 09:44:27 +01:00
Onno Kammeraad 686e9b2648 added a missing offset increase while adding padding 2019-11-11 16:15:01 +01:00
Andreas Molzer 517638a38f Prefer explicit conversion over as
The discriminant of some enum types can be retrieved with the `as`
operator and in C style enums it is also possible to set a specific
discriminant. However, that may silently change—breaking the value
written to the file. It is better to use that technique mostly for ffi
purposes.
2019-11-10 23:20:19 +01:00
Andreas Molzer a6696ab02f fixup! Move everything to the tags macro 2019-11-10 19:24:22 +01:00
Andreas Molzer 258edf4a46 Remove the num-derive dependency 2019-11-10 18:02:43 +01:00
Andreas Molzer f88c9f00c3 Move everything to the tags macro 2019-11-10 18:02:42 +01:00
Andreas Molzer f80f4a06d5 Make the Unknown field optional
Several other uses that currently rely on num-traits for conversion have
no Unknown representation. Thus, the converter now outputs only known
variants or an error and the exposing function then can put the error
value into an Unknown variant instead.
2019-11-10 18:02:08 +01:00
Onno Kammeraad cbae42ea94 fixed support for multipage images, the IDF offset is added during the new and also at the end of the internal_finish. This should only be done once 2019-11-06 14:04:18 +01:00
Onno Kammeraad 80b8ec5cdb Somehow the current offset was invalid when using multipage images. Now using the seek function to get the current offset 2019-11-06 13:58:29 +01:00
Andreas Molzer ffdeb62b9f Rework the tag macro to make it reusable
Factor out parts that are unique to instantiations such as the name, the
documentation, the underlying type, the public conversion methods.

Also adds the ability to document individual items and remodels the
syntax to more closely resemble ordinary enum definitions.
2019-11-01 01:35:18 +01:00
HeroicKatora 6aea7b33c1 Merge pull request #49 from Masterchef365/deflate
Old-style DEFLATE compression support
2019-11-01 00:42:18 +01:00
HeroicKatora 770262cb02 Merge pull request #25 from birktj/new_tests
Test decoder output
2019-10-15 04:35:06 +02:00
HeroicKatora 1f957029af Merge pull request #55 from trolleyman/master
Make Limits struct fields publicly accessible
2019-10-14 21:07:11 +02:00
Masterchef365 f8d4b57120 Change implementation details for InflateError, add uncompressed cap 2019-10-04 18:52:39 -07:00
Masterchef365 6f9f2adfbf Improved error messages for inflate decompression 2019-10-02 21:06:11 -07:00
Masterchef365 205626bd62 Switch to miniz_oxide crate for DEFLATE decompression 2019-10-02 18:51:47 -07:00
Masterchef365 ce73d39d57 Add old-style DEFLATE support 2019-10-02 18:51:07 -07:00
Masterchef365 b79128d766 Add Deflate and OldDeflate compression type recognition 2019-10-02 18:51:07 -07:00
HeroicKatora c8f384d6a6 Merge pull request #58 from paolobarbolini/update-num-derive
update num-derive to 0.3, bump rustc to 1.34.2
2019-10-01 21:01:21 +02:00
Paolo Barbolini ce1d4cb289 update num-derive to 0.3 change minimal rustc to 1.34.2 2019-10-01 20:31:43 +02:00
HeroicKatora b95e5b8f95 Merge pull request #56 from trolleyman/invalid_test_names
Renamed test files
2019-09-22 23:43:51 +02:00
Callum Tolley bc05e0b7c6 Renamed files 2019-09-22 22:25:29 +01:00
Callum Tolley 3868f78067 Make Limits struct fields publicly accessible 2019-09-22 22:11:24 +01:00
HeroicKatora f6a91065d9 Merge pull request #53 from JohnTitor/remove-try-macros
Remove `try!` macros
2019-08-29 21:40:27 +02:00
Yuki Okushi ea19f0f686 Remove try! macros 2019-08-29 19:10:27 +09:00
HeroicKatora 3b85b68ae9 Merge pull request #51 from 51systems/tiff6-signed
Signed value support
2019-08-16 16:52:27 +02:00
Dustin Thomson 30eb8487a6 Signed value support
Add support for SBYTE, SSHORT, SLONG, SRATIONAL as per the Tiff 6.0 spec
Add support for multiple BYTE
2019-08-14 21:22:56 -06:00
HeroicKatora 1c2e57a22a Merge pull request #45 from image-rs/release-0.3.1
Bump version to 0.3.1
2019-05-26 03:23:55 +02:00
Andreas Molzer 2c7f329115 Bump version to 0.3.1
There are no function changes from 0.3 but the compiler requirements
have been lowered. This avoids having to make a decision on `image`
right now, which would delay valuable bug fixes and improvements.
2019-05-26 02:01:12 +02:00
HeroicKatora c9675360cf Merge pull request #44 from birktj/rustc-version
Change minimal rustc version to 1.24.1
2019-05-26 01:35:47 +02:00
Birk Tjelmeland 514b7cddaa Change minimal rustc version to 1.24.1 2019-05-03 23:12:29 +02:00
Birk Tjelmeland 7fb354fe43 Test decoder output
These tests are based on the assumtion that the current decoder is
correct and are to make sure that future changes to the decoder
wont break it.
2019-02-01 11:19:21 +01:00
165 changed files with 2893 additions and 694 deletions

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
name: Rust CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
rust: ["1.34.2", stable, beta, nightly]
command: [build, test]
steps:
- uses: actions/checkout@v2
- run: rustup default ${{ matrix.rust }}
- name: build
run: >
cargo build --verbose --no-default-features --features "$FEATURES"
- name: test
run: >
cargo test --tests --benches --no-default-features --features "$FEATURES"
if: ${{ matrix.rust != '1.34.2' }}
env:
FEATURES: ${{ matrix.features }}
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
components: rustfmt
- name: Run rustfmt check
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
-5
View File
@@ -1,5 +0,0 @@
language: rust
rust:
- stable
- beta
- nightly
+38
View File
@@ -0,0 +1,38 @@
# Version 0.6.0-alpha
New features:
* Support for decoding BigTIFF with 64-bit offsets
* The value types byte, `f32`, `f64` are now recognized
* Support for Modern JPEG encoded images
Improvements:
* Better support for adding auxiliary tags before encoding image data
* Switched to lzw decoder library `weezl` for performance
* The `ColorType` trait now supports `SAMPLE_ENCODING` hints
Fixes:
* Fixed decoding of inline ASCII in tags
* Fixed handling after null terminator in ASCII data
* Recognize tile and sample format tags
# Version 0.5.0
* Added support for 32-bit and 64-bit decoded values.
* Added CMYK(16|32|64) color type support.
* Check many internal integer conversions to increase stability. This should
only lead to images being reported as faulty that would previously silently
break platform limits. If there are any false positives, please report them.
* Remove an erroneous check of decoded length in lzw compressed images.
# Version 0.4.0
* Several enumerations are now non_exhaustive for future extensions.
These are `Tag`, `Type`, `Value`, `PhotometricInterpretation`,
`CompressionMethod`, `Predictor`.
* Enums gained a dedicated method to convert to their TIFF variant value with
the specified type. Performing these conversions by casting the discriminant
with `as` is not guaranteed to be stable, except where documented explicitly.
* Removed the num-derive and num dependencies.
* Added support for decoding `deflate` compressed images.
* Make the decoder `Limits` customizable by exposing members.
* Fixed multi-page TIFF encoding writing incorrect offsets.
+12 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "tiff"
version = "0.3.0"
version = "0.6.0"
authors = [
"ccgn",
"bvssvni <bvssvni@gmail.com>",
@@ -17,10 +17,16 @@ repository = "https://github.com/image-rs/image-tiff"
exclude = ["tests/images/*", "tests/fuzz_images/*"]
[dependencies]
byteorder = "1.2"
lzw = "0.10"
num-derive = "0.2"
num-traits = "0.2"
weezl = "0.1.0"
jpeg = { package = "jpeg-decoder", version = "0.1.17", default-features = false }
[dependencies.miniz_oxide]
version = "0.4.1"
features = ["no_extern_crate_alloc"]
[dev-dependencies]
tempfile = "3.0"
criterion = "0.3.1"
[[bench]]
name = "lzw"
harness = false
+40
View File
@@ -4,3 +4,43 @@
[![Further crate info](https://img.shields.io/crates/v/tiff.svg)](https://crates.io/crates/tiff)
TIFF decoding and encoding library in pure Rust
## Supported
### Features
- Baseline spec (other than formats and tags listed below as not supported)
- Multipage
- BigTIFF
- Incremental decoding
### Formats
This table lists photometric interpretations and sample formats which are supported for encoding and decoding. The entries are `ColorType` variants for which sample bit depths are supported. Only samples where all bit depths are equal are currently supported. For example, `RGB(8)` means that the bit depth [8, 8, 8] is supported and will be interpreted as an 8 bit per channel RGB color type.
| `PhotometricInterpretation` | UINT Format | IEEEFP Format |
|-|-|-|
| `WhiteIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
| `BlackIsZero` | Gray(8\|16\|32\|64) | Gray(32\|64) |
| `RGB` | RGB(8\|16\|32\|64), RGBA(8\|16\|32\|64) | RGB(32\|64), RGBA(32\|64) |
| `RGBPalette` | | |
| `Mask` | | |
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
| `YCbCr` | | |
| `CIELab` | | |
### Compressions
| | Decoding | Encoding |
|-|-|-|
| None | ✓ | ✓ |
| LZW | ✓ | |
| Deflate | ✓ | |
| PackBits | ✓ | |
## Not yet supported
Formats and interpretations not listed above or with empty entries are unsupported.
- Baseline tags
- `ExtraSamples`
- Extension tags
+82
View File
@@ -0,0 +1,82 @@
extern crate criterion;
extern crate tiff;
use criterion::{
black_box, measurement::Measurement, BenchmarkGroup, BenchmarkId, Criterion, Throughput,
};
use tiff::decoder::Decoder;
fn read_image(image: &[u8]) {
let image = std::io::Cursor::new(image);
let decoder = Decoder::new(black_box(image));
let mut reader = decoder.unwrap();
while {
reader.read_image().unwrap();
reader.more_images()
} {}
}
fn main() {
struct BenchDef {
data: &'static [u8],
id: &'static str,
sample_size: usize,
}
fn run_bench_def<M: Measurement>(group: &mut BenchmarkGroup<M>, def: BenchDef) {
group
.sample_size(def.sample_size)
.throughput(Throughput::Bytes(def.data.len() as u64))
.bench_with_input(
BenchmarkId::new(def.id, def.data.len()),
def.data,
|b, input| b.iter(|| read_image(input)),
);
};
let mut c = Criterion::default().configure_from_args();
let mut group = c.benchmark_group("tiff-lzw");
macro_rules! data_file {
($name:literal) => {
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), $name))
};
}
run_bench_def(
&mut group,
BenchDef {
data: data_file!("/tests/images/issue_69_lzw.tiff"),
id: "issue-69-lzw.tif",
sample_size: 500,
},
);
run_bench_def(
&mut group,
BenchDef {
data: data_file!("/tests/benches/kodim02-lzw.tif"),
id: "kodim02-lzw.tif",
sample_size: 20,
},
);
run_bench_def(
&mut group,
BenchDef {
data: data_file!("/tests/benches/kodim07-lzw.tif"),
id: "kodim07-lzw.tif",
sample_size: 20,
},
);
run_bench_def(
&mut group,
BenchDef {
data: data_file!("/tests/benches/Transparency-lzw.tif"),
id: "Transparency-lzw.tif",
sample_size: 20,
},
);
}
+33
View File
@@ -0,0 +1,33 @@
//! Trivial, internal byte transmutation.
//!
//! A dependency like bytemuck would give us extra assurance of the safety but overall would not
//! reduce the amount of total unsafety. We don't use it in the interface where the traits would
//! really become useful.
//!
//! SAFETY: These are benign casts as we apply them to fixed size integer types only. All of them
//! are naturally aligned, valid for all bit patterns and their alignment is surely at most their
//! size (we assert the latter fact since it is 'implementation defined' if following the letter of
//! the unsafe code guidelines).
//!
//! TODO: Would like to use std-lib here.
use std::{mem, slice};
macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => {
pub(crate) fn $const(slice: &[$int]) -> &[u8] {
assert!(mem::align_of::<$int>() <= mem::size_of::<$int>());
unsafe { slice::from_raw_parts(slice.as_ptr() as *const u8, mem::size_of_val(slice)) }
}
$(pub(crate) fn $mut(slice: &mut [$int]) -> &mut [u8] {
assert!(mem::align_of::<$int>() <= mem::size_of::<$int>());
unsafe { slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, mem::size_of_val(slice)) }
})*
}}
integral_slice_as_bytes!(i8, i8_as_ne_bytes);
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!(u32, u32_as_ne_bytes, u32_as_ne_mut_bytes);
integral_slice_as_bytes!(i32, i32_as_ne_bytes);
integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes);
integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes);
integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes);
+433 -149
View File
@@ -1,114 +1,105 @@
//! Function for reading TIFF tags
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::io::{self, Read, Seek};
use std::mem;
use super::stream::{ByteOrder, EndianReader, SmartReader};
use tags::{Tag, Type};
use {TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
use self::Value::{Ascii, List, Rational, Unsigned};
macro_rules! tags {
{$(
$tag:ident
$val:expr;
)*} => {
/// TIFF tag
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum Tag {
$($tag,)*
Unknown(u16)
}
impl Tag {
pub fn from_u16(n: u16) -> Tag {
$(if n == $val { Tag::$tag } else)* {
Tag::Unknown(n)
}
}
pub fn to_u16(&self) -> u16 {
match self {
$( Tag::$tag => $val, )*
Tag::Unknown(n) => *n,
}
}
}
}
}
// Note: These tags appear in the order they are mentioned in the TIFF reference
tags! {
// Baseline tags:
Artist 315;
// grayscale images PhotometricInterpretation 1 or 3
BitsPerSample 258;
CellLength 265; // TODO add support
CellWidth 264; // TODO add support
// palette-color images (PhotometricInterpretation 3)
ColorMap 320; // TODO add support
Compression 259; // TODO add support for 2 and 32773
Copyright 33_432;
DateTime 306;
ExtraSamples 338; // TODO add support
FillOrder 266; // TODO add support
FreeByteCounts 289; // TODO add support
FreeOffsets 288; // TODO add support
GrayResponseCurve 291; // TODO add support
GrayResponseUnit 290; // TODO add support
HostComputer 316;
ImageDescription 270;
ImageLength 257;
ImageWidth 256;
Make 271;
MaxSampleValue 281; // TODO add support
MinSampleValue 280; // TODO add support
Model 272;
NewSubfileType 254; // TODO add support
Orientation 274; // TODO add support
PhotometricInterpretation 262;
PlanarConfiguration 284;
ResolutionUnit 296; // TODO add support
RowsPerStrip 278;
SamplesPerPixel 277;
Software 305;
StripByteCounts 279;
StripOffsets 273;
SubfileType 255; // TODO add support
Threshholding 263; // TODO add support
XResolution 282;
YResolution 283;
// Advanced tags
Predictor 317;
}
#[derive(Clone, Copy, Debug, FromPrimitive)]
pub enum Type {
BYTE = 1,
ASCII = 2,
SHORT = 3,
LONG = 4,
RATIONAL = 5,
}
use self::Value::{
Ascii, Byte, Double, Float, List, Rational, RationalBig, SRational, SRationalBig, Signed,
SignedBig, Unsigned, UnsignedBig,
};
#[allow(unused_qualifications)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
//Signed(i32),
Byte(u8),
Signed(i32),
SignedBig(i64),
Unsigned(u32),
UnsignedBig(u64),
Float(f32),
Double(f64),
List(Vec<Value>),
Rational(u32, u32),
RationalBig(u64, u64),
SRational(i32, i32),
SRationalBig(i64, i64),
Ascii(String),
#[doc(hidden)] // Do not match against this.
__NonExhaustive,
}
impl Value {
pub fn into_u8(self) -> TiffResult<u8> {
match self {
Byte(val) => Ok(val),
val => Err(TiffError::FormatError(TiffFormatError::ByteExpected(val))),
}
}
pub fn into_u32(self) -> TiffResult<u32> {
match self {
Unsigned(val) => Ok(val),
UnsignedBig(val) => Ok(u32::try_from(val)?),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
}
}
pub fn into_i32(self) -> TiffResult<i32> {
match self {
Signed(val) => Ok(val),
SignedBig(val) => Ok(i32::try_from(val)?),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
}
}
pub fn into_u64(self) -> TiffResult<u64> {
match self {
Unsigned(val) => Ok(val.into()),
UnsignedBig(val) => Ok(val),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
}
}
pub fn into_i64(self) -> TiffResult<i64> {
match self {
Signed(val) => Ok(val.into()),
SignedBig(val) => Ok(val),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
}
}
pub fn into_f32(self) -> TiffResult<f32> {
match self {
Float(val) => Ok(val),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
}
}
pub fn into_f64(self) -> TiffResult<f64> {
match self {
Double(val) => Ok(val),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
}
}
pub fn into_u32_vec(self) -> TiffResult<Vec<u32>> {
match self {
List(vec) => {
@@ -119,8 +110,144 @@ impl Value {
Ok(new_vec)
}
Unsigned(val) => Ok(vec![val]),
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
Ascii(val) => Ok(val.chars().map(|x| x as u32).collect()),
RationalBig(numerator, denominator) => {
Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?])
}
Ascii(val) => Ok(val.chars().map(u32::from).collect()),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
}
}
pub fn into_u8_vec(self) -> TiffResult<Vec<u8>> {
match self {
List(vec) => {
let mut new_vec = Vec::with_capacity(vec.len());
for v in vec {
new_vec.push(v.into_u8()?)
}
Ok(new_vec)
}
Byte(val) => Ok(vec![val]),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
}
}
pub fn into_i32_vec(self) -> TiffResult<Vec<i32>> {
match self {
List(vec) => {
let mut new_vec = Vec::with_capacity(vec.len());
for v in vec {
match v {
SRational(numerator, denominator) => {
new_vec.push(numerator);
new_vec.push(denominator);
}
SRationalBig(numerator, denominator) => {
new_vec.push(i32::try_from(numerator)?);
new_vec.push(i32::try_from(denominator)?);
}
_ => new_vec.push(v.into_i32()?),
}
}
Ok(new_vec)
}
Signed(val) => Ok(vec![val]),
SignedBig(val) => Ok(vec![i32::try_from(val)?]),
SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
SRationalBig(numerator, denominator) => {
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
}
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
}
}
pub fn into_f32_vec(self) -> TiffResult<Vec<f32>> {
match self {
List(vec) => {
let mut new_vec = Vec::with_capacity(vec.len());
for v in vec {
new_vec.push(v.into_f32()?)
}
Ok(new_vec)
}
Float(val) => Ok(vec![val]),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
}
}
pub fn into_f64_vec(self) -> TiffResult<Vec<f64>> {
match self {
List(vec) => {
let mut new_vec = Vec::with_capacity(vec.len());
for v in vec {
new_vec.push(v.into_f64()?)
}
Ok(new_vec)
}
Double(val) => Ok(vec![val]),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
}
}
pub fn into_u64_vec(self) -> TiffResult<Vec<u64>> {
match self {
List(vec) => {
let mut new_vec = Vec::with_capacity(vec.len());
for v in vec {
new_vec.push(v.into_u64()?)
}
Ok(new_vec)
}
Unsigned(val) => Ok(vec![val.into()]),
UnsignedBig(val) => Ok(vec![val]),
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
}
}
pub fn into_i64_vec(self) -> TiffResult<Vec<i64>> {
match self {
List(vec) => {
let mut new_vec = Vec::with_capacity(vec.len());
for v in vec {
match v {
SRational(numerator, denominator) => {
new_vec.push(numerator.into());
new_vec.push(denominator.into());
}
SRationalBig(numerator, denominator) => {
new_vec.push(numerator);
new_vec.push(denominator);
}
_ => new_vec.push(v.into_i64()?),
}
}
Ok(new_vec)
}
Signed(val) => Ok(vec![val.into()]),
SignedBig(val) => Ok(vec![val]),
SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
}
}
}
@@ -128,8 +255,8 @@ impl Value {
#[derive(Clone)]
pub struct Entry {
type_: Type,
count: u32,
offset: [u8; 4],
count: u64,
offset: [u8; 8],
}
impl ::std::fmt::Debug for Entry {
@@ -143,6 +270,12 @@ impl ::std::fmt::Debug for Entry {
impl Entry {
pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry {
let mut offset = offset.to_vec();
offset.append(&mut vec![0; 4]);
Entry::new_u64(type_, count.into(), offset[..].try_into().unwrap())
}
pub fn new_u64(type_: Type, count: u64, offset: [u8; 8]) -> Entry {
Entry {
type_,
count,
@@ -161,73 +294,224 @@ impl Entry {
decoder: &mut super::Decoder<R>,
) -> TiffResult<Value> {
let bo = decoder.byte_order();
match (self.type_, self.count) {
// TODO check if this could give wrong results
// at a different endianess of file/computer.
(Type::BYTE, 1) => Ok(Unsigned(u32::from(self.offset[0]))),
(Type::SHORT, 1) => Ok(Unsigned(u32::from(self.r(bo).read_u16()?))),
(Type::SHORT, 2) => {
let mut r = self.r(bo);
Ok(List(vec![
Unsigned(u32::from(r.read_u16()?)),
Unsigned(u32::from(r.read_u16()?)),
]))
let bigtiff_value = if decoder.bigtiff {
match (self.type_, self.count) {
(Type::BYTE, 5) => Some(offset_to_bytes(5, self)?),
(Type::BYTE, 6) => Some(offset_to_bytes(6, self)?),
(Type::BYTE, 7) => Some(offset_to_bytes(7, self)?),
(Type::BYTE, 8) => Some(offset_to_bytes(8, self)?),
(Type::SBYTE, 5) => Some(offset_to_sbytes(5, self)?),
(Type::SBYTE, 6) => Some(offset_to_sbytes(6, self)?),
(Type::SBYTE, 7) => Some(offset_to_sbytes(7, self)?),
(Type::SBYTE, 8) => Some(offset_to_sbytes(8, self)?),
(Type::SHORT, 3) => {
let mut r = self.r(bo);
Some(List(vec![
Unsigned(u32::from(r.read_u16()?)),
Unsigned(u32::from(r.read_u16()?)),
Unsigned(u32::from(r.read_u16()?)),
]))
}
(Type::SSHORT, 3) => {
let mut r = self.r(bo);
Some(List(vec![
Signed(i32::from(r.read_i16()?)),
Signed(i32::from(r.read_i16()?)),
Signed(i32::from(r.read_i16()?)),
]))
}
(Type::SHORT, 4) => {
let mut r = self.r(bo);
Some(List(vec![
Unsigned(u32::from(r.read_u16()?)),
Unsigned(u32::from(r.read_u16()?)),
Unsigned(u32::from(r.read_u16()?)),
Unsigned(u32::from(r.read_u16()?)),
]))
}
(Type::SSHORT, 4) => {
let mut r = self.r(bo);
Some(List(vec![
Signed(i32::from(r.read_i16()?)),
Signed(i32::from(r.read_i16()?)),
Signed(i32::from(r.read_i16()?)),
Signed(i32::from(r.read_i16()?)),
]))
}
(Type::LONG, 2) => {
let mut r = self.r(bo);
Some(List(vec![Unsigned(r.read_u32()?), Unsigned(r.read_u32()?)]))
}
(Type::SLONG, 2) => {
let mut r = self.r(bo);
Some(List(vec![Signed(r.read_i32()?), Signed(r.read_i32()?)]))
}
(Type::RATIONAL, 1) => {
let mut r = self.r(bo);
let numerator = r.read_u32()?;
let denominator = r.read_u32()?;
Some(Rational(numerator, denominator))
}
(Type::SRATIONAL, 1) => {
let mut r = self.r(bo);
let numerator = r.read_i32()?;
let denominator = r.read_i32()?;
Some(SRational(numerator, denominator))
}
_ => None,
}
(Type::SHORT, n) => {
if n as usize > limits.decoding_buffer_size / std::mem::size_of::<Value>() {
return Err(TiffError::LimitsExceeded);
} else {
None
};
if let Some(v) = bigtiff_value {
Ok(v)
} else {
match (self.type_, self.count) {
// TODO check if this could give wrong results
// at a different endianess of file/computer.
(Type::BYTE, 1) => Ok(Unsigned(u32::from(self.offset[0]))),
(Type::BYTE, 2) => offset_to_bytes(2, self),
(Type::BYTE, 3) => offset_to_bytes(3, self),
(Type::BYTE, 4) => offset_to_bytes(4, self),
(Type::BYTE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(UnsignedBig(u64::from(decoder.read_byte()?)))
}),
(Type::SBYTE, 1) => Ok(Signed(i32::from(self.offset[0] as i8))),
(Type::SBYTE, 2) => offset_to_sbytes(2, self),
(Type::SBYTE, 3) => offset_to_sbytes(3, self),
(Type::SBYTE, 4) => offset_to_sbytes(4, self),
(Type::SBYTE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(SignedBig(i64::from(decoder.read_byte()? as i8)))
}),
(Type::SHORT, 1) => Ok(Unsigned(u32::from(self.r(bo).read_u16()?))),
(Type::SSHORT, 1) => Ok(Signed(i32::from(self.r(bo).read_i16()?))),
(Type::SHORT, 2) => {
let mut r = self.r(bo);
Ok(List(vec![
Unsigned(u32::from(r.read_u16()?)),
Unsigned(u32::from(r.read_u16()?)),
]))
}
let mut v = Vec::with_capacity(n as usize);
try!(decoder.goto_offset(try!(self.r(bo).read_u32())));
for _ in 0..n {
v.push(Unsigned(u32::from(decoder.read_short()?)))
(Type::SSHORT, 2) => {
let mut r = self.r(bo);
Ok(List(vec![
Signed(i32::from(r.read_i16()?)),
Signed(i32::from(r.read_i16()?)),
]))
}
Ok(List(v))
(Type::SHORT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(UnsignedBig(u64::from(decoder.read_short()?)))
}),
(Type::SSHORT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(SignedBig(i64::from(decoder.read_sshort()?)))
}),
(Type::LONG, 1) => Ok(Unsigned(self.r(bo).read_u32()?)),
(Type::SLONG, 1) => Ok(Signed(self.r(bo).read_i32()?)),
(Type::LONG, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(Unsigned(decoder.read_long()?))
}),
(Type::SLONG, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(Signed(decoder.read_slong()?))
}),
(Type::FLOAT, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(Float(decoder.read_float()?))
}),
(Type::DOUBLE, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(Double(decoder.read_double()?))
}),
(Type::RATIONAL, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(Rational(decoder.read_long()?, decoder.read_long()?))
}),
(Type::SRATIONAL, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(SRational(decoder.read_slong()?, decoder.read_slong()?))
}),
(Type::LONG8, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(UnsignedBig(decoder.read_long8()?))
}),
(Type::ASCII, n) => {
let n = usize::try_from(n)?;
if n > limits.decoding_buffer_size {
return Err(TiffError::LimitsExceeded);
}
if (n <= 4 && !decoder.bigtiff) || (n <= 8 && decoder.bigtiff) {
let mut buf = vec![0; n];
self.r(bo).read_exact(&mut buf)?;
let v = String::from_utf8(buf)?;
let v = v.trim_matches(char::from(0));
Ok(Ascii(v.into()))
} else {
if decoder.bigtiff {
decoder.goto_offset_u64(self.r(bo).read_u64()?)?
} else {
decoder.goto_offset(self.r(bo).read_u32()?)?
}
let string = decoder.read_string(n)?;
Ok(Ascii(string))
}
}
(Type::UNDEFINED, n) => self.decode_offset(n, bo, limits, decoder, |decoder| {
Ok(Byte(u8::from(decoder.read_byte()?)))
}),
_ => Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedDataType,
)),
}
(Type::LONG, 1) => Ok(Unsigned(try!(self.r(bo).read_u32()))),
(Type::LONG, n) => {
if n as usize > limits.decoding_buffer_size / std::mem::size_of::<Value>() {
return Err(TiffError::LimitsExceeded);
}
let mut v = Vec::with_capacity(n as usize);
try!(decoder.goto_offset(try!(self.r(bo).read_u32())));
for _ in 0..n {
v.push(Unsigned(try!(decoder.read_long())))
}
Ok(List(v))
}
(Type::RATIONAL, 1) => {
try!(decoder.goto_offset(try!(self.r(bo).read_u32())));
let numerator = try!(decoder.read_long());
let denominator = try!(decoder.read_long());
Ok(Rational(numerator, denominator))
}
(Type::RATIONAL, n) => {
if n as usize > limits.decoding_buffer_size / std::mem::size_of::<Value>() {
return Err(TiffError::LimitsExceeded);
}
let mut v = Vec::with_capacity(n as usize);
try!(decoder.goto_offset(try!(self.r(bo).read_u32())));
for _ in 0..n {
let numerator = try!(decoder.read_long());
let denominator = try!(decoder.read_long());
v.push(Rational(numerator, denominator))
}
Ok(List(v))
}
(Type::ASCII, n) => {
if n as usize > limits.decoding_buffer_size {
return Err(TiffError::LimitsExceeded);
}
try!(decoder.goto_offset(try!(self.r(bo).read_u32())));
let string = try!(decoder.read_string(n as usize));
Ok(Ascii(string))
}
_ => Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedDataType,
)),
}
}
#[inline]
fn decode_offset<R, F>(
&self,
value_count: u64,
bo: ByteOrder,
limits: &super::Limits,
decoder: &mut super::Decoder<R>,
decode_fn: F,
) -> TiffResult<Value>
where
R: Read + Seek,
F: Fn(&mut super::Decoder<R>) -> TiffResult<Value>,
{
let value_count = usize::try_from(value_count)?;
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
return Err(TiffError::LimitsExceeded);
}
let mut v = Vec::with_capacity(value_count);
if decoder.bigtiff {
decoder.goto_offset_u64(self.r(bo).read_u64()?)?
} else {
decoder.goto_offset(self.r(bo).read_u32()?)?
}
for _ in 0..value_count {
v.push(decode_fn(decoder)?)
}
Ok(List(v))
}
}
/// Extracts a list of BYTE tags stored in an offset
#[inline]
fn offset_to_bytes(n: usize, entry: &Entry) -> TiffResult<Value> {
Ok(List(
entry.offset[0..n]
.iter()
.map(|&e| Unsigned(u32::from(e)))
.collect(),
))
}
/// Extracts a list of SBYTE tags stored in an offset
#[inline]
fn offset_to_sbytes(n: usize, entry: &Entry) -> TiffResult<Value> {
Ok(List(
entry.offset[0..n]
.iter()
.map(|&e| Signed(i32::from(e as i8)))
.collect(),
))
}
/// Type representing an Image File Directory
+586 -237
View File
File diff suppressed because it is too large. Load diff
+311 -18
View File
@@ -1,7 +1,8 @@
//! All IO functionality needed for TIFF decoding
use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
use lzw;
use crate::bytecast;
use crate::error::{TiffError, TiffResult};
use miniz_oxide::inflate;
use std::io::{self, Read, Seek};
/// Byte order of the TIFF file.
@@ -21,34 +22,229 @@ pub trait EndianReader: Read {
/// Reads an u16
#[inline(always)]
fn read_u16(&mut self) -> Result<u16, io::Error> {
match self.byte_order() {
ByteOrder::LittleEndian => <Self as ReadBytesExt>::read_u16::<LittleEndian>(self),
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_u16::<BigEndian>(self),
}
let mut n = [0u8; 2];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
ByteOrder::LittleEndian => u16::from_le_bytes(n),
ByteOrder::BigEndian => u16::from_be_bytes(n),
})
}
#[inline(always)]
fn read_u16_into(&mut self, buffer: &mut [u16]) -> Result<(), io::Error> {
self.read_exact(bytecast::u16_as_ne_mut_bytes(buffer))?;
match self.byte_order() {
ByteOrder::LittleEndian => {
<Self as ReadBytesExt>::read_u16_into::<LittleEndian>(self, buffer)
for n in buffer {
*n = u16::from_le(*n);
}
}
ByteOrder::BigEndian => {
<Self as ReadBytesExt>::read_u16_into::<BigEndian>(self, buffer)
for n in buffer {
*n = u16::from_be(*n);
}
}
}
Ok(())
}
/// Reads an i16
#[inline(always)]
fn read_i16(&mut self) -> Result<i16, io::Error> {
let mut n = [0u8; 2];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
ByteOrder::LittleEndian => i16::from_le_bytes(n),
ByteOrder::BigEndian => i16::from_be_bytes(n),
})
}
/// Reads an u32
#[inline(always)]
fn read_u32(&mut self) -> Result<u32, io::Error> {
let mut n = [0u8; 4];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
ByteOrder::LittleEndian => u32::from_le_bytes(n),
ByteOrder::BigEndian => u32::from_be_bytes(n),
})
}
#[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 => <Self as ReadBytesExt>::read_u32::<LittleEndian>(self),
ByteOrder::BigEndian => <Self as ReadBytesExt>::read_u32::<BigEndian>(self),
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
#[inline(always)]
fn read_i32(&mut self) -> Result<i32, io::Error> {
let mut n = [0u8; 4];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
ByteOrder::LittleEndian => i32::from_le_bytes(n),
ByteOrder::BigEndian => i32::from_be_bytes(n),
})
}
/// Reads an u64
#[inline(always)]
fn read_u64(&mut self) -> Result<u64, io::Error> {
let mut n = [0u8; 8];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
ByteOrder::LittleEndian => u64::from_le_bytes(n),
ByteOrder::BigEndian => u64::from_be_bytes(n),
})
}
#[inline(always)]
fn read_u64_into(&mut self, buffer: &mut [u64]) -> Result<(), io::Error> {
self.read_exact(bytecast::u64_as_ne_mut_bytes(buffer))?;
match self.byte_order() {
ByteOrder::LittleEndian => {
for n in buffer {
*n = u64::from_le(*n);
}
}
ByteOrder::BigEndian => {
for n in buffer {
*n = u64::from_be(*n);
}
}
}
Ok(())
}
/// Reads an f32
#[inline(always)]
fn read_f32(&mut self) -> Result<f32, io::Error> {
let mut n = [0u8; 4];
self.read_exact(&mut n)?;
Ok(f32::from_bits(match self.byte_order() {
ByteOrder::LittleEndian => u32::from_le_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
#[inline(always)]
fn read_f64(&mut self) -> Result<f64, io::Error> {
let mut n = [0u8; 8];
self.read_exact(&mut n)?;
Ok(f64::from_bits(match self.byte_order() {
ByteOrder::LittleEndian => u64::from_le_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(())
}
}
///
/// # READERS
///
///
/// ## Deflate Reader
///
/// Reader that decompresses DEFLATE streams
pub struct DeflateReader {
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
///
/// Reader that decompresses LZW streams
pub struct LZWReader {
buffer: io::Cursor<Vec<u8>>,
@@ -67,14 +263,34 @@ impl LZWReader {
{
let order = reader.byte_order;
let mut compressed = vec![0; compressed_length as usize];
try!(reader.read_exact(&mut compressed[..]));
reader.read_exact(&mut compressed[..])?;
let mut uncompressed = Vec::with_capacity(max_uncompressed_length);
let mut decoder = lzw::DecoderEarlyChange::new(lzw::MsbReader::new(), 8);
let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
let mut bytes_read = 0;
while bytes_read < compressed_length && uncompressed.len() < max_uncompressed_length {
let (len, bytes) = try!(decoder.decode_bytes(&compressed[bytes_read..]));
bytes_read += len;
uncompressed.extend_from_slice(bytes);
let bytes_written = uncompressed.len();
uncompressed.reserve(1 << 12);
let buffer_space = uncompressed.capacity();
uncompressed.resize(buffer_space, 0u8);
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 {
Ok(weezl::LzwStatus::Ok) => {}
Ok(weezl::LzwStatus::Done) => break,
Ok(weezl::LzwStatus::NoProgress) => {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"no lzw end code found",
))
}
Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)),
}
}
let bytes = uncompressed.len();
@@ -102,6 +318,79 @@ impl EndianReader for LZWReader {
}
}
///
/// ## 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
}
}
///
/// ## PackBits Reader
///
/// Reader that unpacks Apple's `PackBits` format
pub struct PackBitsReader {
buffer: io::Cursor<Vec<u8>>,
@@ -137,7 +426,7 @@ impl PackBitsReader {
fn read_packbits_run<R: Read + Seek>(reader: &mut R, buffer: &mut Vec<u8>) -> io::Result<usize> {
let mut header: [u8; 1] = [0];
let bytes = try!(reader.read(&mut header));
let bytes = reader.read(&mut header)?;
match bytes {
0 => Ok(0),
@@ -145,7 +434,7 @@ fn read_packbits_run<R: Read + Seek>(reader: &mut R, buffer: &mut Vec<u8>) -> io
-128 => Ok(1),
h if h >= -127 && h <= -1 => {
let new_len = buffer.len() + (1 - h as isize) as usize;
try!(reader.read_exact(&mut header));
reader.read_exact(&mut header)?;
buffer.resize(new_len, header[0]);
Ok(2)
}
@@ -153,7 +442,7 @@ fn read_packbits_run<R: Read + Seek>(reader: &mut R, buffer: &mut Vec<u8>) -> io
let num_vals = h as usize + 1;
let start = buffer.len();
buffer.resize(start + num_vals, 0);
try!(reader.read_exact(&mut buffer[start..]));
reader.read_exact(&mut buffer[start..])?;
Ok(num_vals + 1)
}
},
@@ -174,6 +463,10 @@ impl EndianReader for PackBitsReader {
}
}
///
/// ## SmartReader Reader
///
/// Reader that is aware of the byte order.
#[derive(Debug)]
pub struct SmartReader<R>
+146 -1
View File
@@ -1,4 +1,4 @@
use crate::decoder::PhotometricInterpretation;
use tags::{PhotometricInterpretation, SampleFormat};
/// Trait for different colortypes that can be encoded.
pub trait ColorType {
@@ -8,6 +8,8 @@ pub trait ColorType {
const TIFF_VALUE: PhotometricInterpretation;
/// The value of the tiff tag `BitsPerSample`
const BITS_PER_SAMPLE: &'static [u16];
/// The value of the tiff tag `SampleFormat`
const SAMPLE_FORMAT: &'static [SampleFormat];
}
pub struct Gray8;
@@ -15,6 +17,7 @@ impl ColorType for Gray8 {
type Inner = u8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
}
pub struct Gray16;
@@ -22,6 +25,39 @@ impl ColorType for Gray16 {
type Inner = u16;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
}
pub struct Gray32;
impl ColorType for Gray32 {
type Inner = u32;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
}
pub struct Gray32Float;
impl ColorType for Gray32Float {
type Inner = f32;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
}
pub struct Gray64;
impl ColorType for Gray64 {
type Inner = u64;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
}
pub struct Gray64Float;
impl ColorType for Gray64Float {
type Inner = f64;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
}
pub struct RGB8;
@@ -29,6 +65,7 @@ impl ColorType for RGB8 {
type Inner = u8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
}
pub struct RGB16;
@@ -36,6 +73,39 @@ impl ColorType for RGB16 {
type Inner = u16;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
}
pub struct RGB32;
impl ColorType for RGB32 {
type Inner = u32;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
}
pub struct RGB32Float;
impl ColorType for RGB32Float {
type Inner = f32;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
}
pub struct RGB64;
impl ColorType for RGB64 {
type Inner = u64;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
}
pub struct RGB64Float;
impl ColorType for RGB64Float {
type Inner = f64;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
}
pub struct RGBA8;
@@ -43,6 +113,7 @@ impl ColorType for RGBA8 {
type Inner = u8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
}
pub struct RGBA16;
@@ -50,6 +121,39 @@ impl ColorType for RGBA16 {
type Inner = u16;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
}
pub struct RGBA32;
impl ColorType for RGBA32 {
type Inner = u32;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
}
pub struct RGBA32Float;
impl ColorType for RGBA32Float {
type Inner = f32;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
}
pub struct RGBA64;
impl ColorType for RGBA64 {
type Inner = u64;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
}
pub struct RGBA64Float;
impl ColorType for RGBA64Float {
type Inner = f64;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
}
pub struct CMYK8;
@@ -57,4 +161,45 @@ impl ColorType for CMYK8 {
type Inner = u8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
}
pub struct CMYK16;
impl ColorType for CMYK16 {
type Inner = u16;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
}
pub struct CMYK32;
impl ColorType for CMYK32 {
type Inner = u32;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
}
pub struct CMYK32Float;
impl ColorType for CMYK32Float {
type Inner = f32;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
}
pub struct CMYK64;
impl ColorType for CMYK64 {
type Inner = u64;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
}
pub struct CMYK64Float;
impl ColorType for CMYK64Float {
type Inner = f64;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
}
+343 -98
View File
@@ -1,9 +1,11 @@
use byteorder::NativeEndian;
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::io::{Seek, Write};
use std::{cmp, io, mem};
use crate::decoder::ifd::{self, Tag};
use crate::error::{TiffError, TiffFormatError, TiffResult};
use bytecast;
use error::{TiffError, TiffFormatError, TiffResult};
use tags::{self, ResolutionUnit, Tag, Type};
pub mod colortype;
mod writer;
@@ -18,17 +20,16 @@ pub struct Rational {
pub d: u32,
}
/// Type to represent resolution units
pub enum ResolutionUnit {
None = 1,
Inch = 2,
Centimeter = 3,
/// Type to represent tiff values of type `SRATIONAL`
pub struct SRational {
pub n: i32,
pub d: i32,
}
/// Trait for types that can be encoded in a tiff file
pub trait TiffValue {
const BYTE_LEN: u32;
const FIELD_TYPE: ifd::Type;
const FIELD_TYPE: Type;
fn count(&self) -> u32;
fn bytes(&self) -> u32 {
self.count() * Self::BYTE_LEN
@@ -38,7 +39,7 @@ pub trait TiffValue {
impl TiffValue for [u8] {
const BYTE_LEN: u32 = 1;
const FIELD_TYPE: ifd::Type = ifd::Type::BYTE;
const FIELD_TYPE: Type = Type::BYTE;
fn count(&self) -> u32 {
self.len() as u32
@@ -50,18 +51,46 @@ impl TiffValue for [u8] {
}
}
impl TiffValue for [u16] {
const BYTE_LEN: u32 = 2;
const FIELD_TYPE: ifd::Type = ifd::Type::SHORT;
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<()> {
// We write using nativeedian so this sould be safe
let slice =
unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * 2) };
let slice = bytecast::i8_as_ne_bytes(self);
writer.write_bytes(slice)?;
Ok(())
}
}
impl TiffValue for [u16] {
const BYTE_LEN: u32 = 2;
const FIELD_TYPE: Type = Type::SHORT;
fn count(&self) -> u32 {
self.len() as u32
}
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(())
}
@@ -69,7 +98,52 @@ impl TiffValue for [u16] {
impl TiffValue for [u32] {
const BYTE_LEN: u32 = 4;
const FIELD_TYPE: ifd::Type = ifd::Type::LONG;
const FIELD_TYPE: Type = Type::LONG;
fn count(&self) -> u32 {
self.len() as u32
}
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 {
self.len() as u32
}
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
let slice = bytecast::i32_as_ne_bytes(self);
writer.write_bytes(slice)?;
Ok(())
}
}
impl TiffValue for [u64] {
const BYTE_LEN: u32 = 8;
const FIELD_TYPE: Type = Type::LONG8;
fn count(&self) -> u32 {
self.len() as u32
}
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
@@ -77,8 +151,23 @@ impl TiffValue for [u32] {
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
// We write using nativeedian so this sould be safe
let slice =
unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, self.len() * 4) };
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(())
}
@@ -86,7 +175,23 @@ impl TiffValue for [u32] {
impl TiffValue for [Rational] {
const BYTE_LEN: u32 = 8;
const FIELD_TYPE: ifd::Type = ifd::Type::RATIONAL;
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
@@ -102,7 +207,7 @@ impl TiffValue for [Rational] {
impl TiffValue for u8 {
const BYTE_LEN: u32 = 1;
const FIELD_TYPE: ifd::Type = ifd::Type::BYTE;
const FIELD_TYPE: Type = Type::BYTE;
fn count(&self) -> u32 {
1
@@ -114,9 +219,23 @@ impl TiffValue for u8 {
}
}
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: ifd::Type = ifd::Type::SHORT;
const FIELD_TYPE: Type = Type::SHORT;
fn count(&self) -> u32 {
1
@@ -128,9 +247,23 @@ impl TiffValue for u16 {
}
}
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: ifd::Type = ifd::Type::LONG;
const FIELD_TYPE: Type = Type::LONG;
fn count(&self) -> u32 {
1
@@ -142,9 +275,65 @@ impl TiffValue for u32 {
}
}
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: ifd::Type = ifd::Type::RATIONAL;
const FIELD_TYPE: Type = Type::RATIONAL;
fn count(&self) -> u32 {
1
@@ -157,9 +346,24 @@ impl TiffValue for Rational {
}
}
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: ifd::Type = ifd::Type::ASCII;
const FIELD_TYPE: Type = Type::ASCII;
fn count(&self) -> u32 {
self.len() as u32 + 1
@@ -178,7 +382,7 @@ impl TiffValue for str {
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
const BYTE_LEN: u32 = T::BYTE_LEN;
const FIELD_TYPE: ifd::Type = T::FIELD_TYPE;
const FIELD_TYPE: Type = T::FIELD_TYPE;
fn count(&self) -> u32 {
(*self).count()
@@ -198,14 +402,16 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
///
/// # Examples
/// ```
/// # extern crate tempfile;
/// # let mut file = tempfile::tempfile().unwrap();
/// # extern crate tiff;
/// # fn main() {
/// # let mut file = std::io::Cursor::new(Vec::new());
/// # let image_data = vec![0; 100*100*3];
/// use tiff::encoder::*;
///
/// let mut tiff = TiffEncoder::new(&mut file).unwrap();
///
/// tiff.write_image::<colortype::RGB8>(100, 100, &image_data).unwrap();
/// # }
/// ```
pub struct TiffEncoder<W> {
writer: TiffWriter<W>,
@@ -217,7 +423,9 @@ impl<W: Write + Seek> TiffEncoder<W> {
writer: TiffWriter::new(writer),
};
NativeEndian::write_header(&mut encoder.writer)?;
write_tiff_header(&mut encoder.writer)?;
// blank the IFD offset location
encoder.writer.write_u32(0)?;
Ok(encoder)
}
@@ -247,26 +455,9 @@ impl<W: Write + Seek> TiffEncoder<W> {
where
[C::Inner]: TiffValue,
{
let num_pix = (width as usize).checked_mul(height as usize)
.ok_or_else(|| std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Image width * height exceeds usize"))?;
if data.len() < num_pix {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Input data slice is undersized for provided dimensions").into());
}
let encoder = DirectoryEncoder::new(&mut self.writer)?;
let mut image: ImageEncoder<W, C> = ImageEncoder::new(encoder, width, height)?;
let mut idx = 0;
while image.next_strip_sample_count() > 0 {
let sample_count = image.next_strip_sample_count() as usize;
image.write_strip(&data[idx..idx + sample_count])?;
idx += sample_count;
}
image.finish()
let image: ImageEncoder<W, C> = ImageEncoder::new(encoder, width, height)?;
image.write_data(data)
}
}
@@ -274,7 +465,7 @@ impl<W: Write + Seek> TiffEncoder<W> {
///
/// You should call `finish` on this when you are finished with it.
/// Encoding can silently fail while this is dropping.
pub struct DirectoryEncoder<'a, W: Write + Seek> {
pub struct DirectoryEncoder<'a, W: 'a + Write + Seek> {
writer: &'a mut TiffWriter<W>,
dropped: bool,
// We use BTreeMap to make sure tags are written in correct order
@@ -282,11 +473,11 @@ pub struct DirectoryEncoder<'a, W: Write + Seek> {
ifd: BTreeMap<u16, (u16, u32, Vec<u8>)>,
}
impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> {
impl<'a, W: 'a + Write + Seek> DirectoryEncoder<'a, W> {
fn new(writer: &'a mut TiffWriter<W>) -> TiffResult<DirectoryEncoder<'a, W>> {
// the previous word is the IFD offset position
let ifd_pointer_pos = writer.offset() - mem::size_of::<u32>() as u64;
writer.pad_word_boundary()?;
let ifd_pointer_pos = writer.offset();
writer.write_u32(0)?;
Ok(DirectoryEncoder {
writer,
dropped: false,
@@ -296,27 +487,31 @@ impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> {
}
/// Write a single ifd tag.
pub fn write_tag<T: TiffValue>(&mut self, tag: Tag, value: T) {
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(len as usize);
let mut bytes = Vec::with_capacity(usize::try_from(len)?);
{
let mut writer = TiffWriter::new(&mut bytes);
value.write(&mut writer).unwrap();
value.write(&mut writer)?;
}
self.ifd
.insert(tag.to_u16(), (<T>::FIELD_TYPE as u16, value.count(), bytes));
self.ifd.insert(
tag.to_u16(),
(<T>::FIELD_TYPE.to_u16(), value.count(), bytes),
);
Ok(())
}
fn write_directory(&mut self) -> TiffResult<u64> {
// Start by writing out all values
for (_, _, ref mut bytes) in self.ifd.values_mut() {
for &mut (_, _, ref mut bytes) in self.ifd.values_mut() {
if bytes.len() > 4 {
let offset = self.writer.offset();
self.writer.write_bytes(bytes)?;
*bytes = vec![0, 0, 0, 0];
let mut writer = TiffWriter::new(bytes as &mut [u8]);
writer.write_u32(offset as u32)?;
writer.write_u32(u32::try_from(offset)?)?;
} else {
while bytes.len() < 4 {
bytes.push(0);
@@ -326,8 +521,8 @@ impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> {
let offset = self.writer.offset();
self.writer.write_u16(self.ifd.len() as u16)?;
for (tag, (field_type, count, offset)) in self.ifd.iter() {
self.writer.write_u16(u16::try_from(self.ifd.len())?)?;
for (tag, &(ref field_type, ref count, ref offset)) in self.ifd.iter() {
self.writer.write_u16(*tag)?;
self.writer.write_u16(*field_type)?;
self.writer.write_u32(*count)?;
@@ -351,7 +546,7 @@ impl<'a, W: Write + Seek> DirectoryEncoder<'a, W> {
let curr_pos = self.writer.offset();
self.writer.goto_offset(self.ifd_pointer_pos)?;
self.writer.write_u32(ifd_pointer as u32)?;
self.writer.write_u32(u32::try_from(ifd_pointer)?)?;
self.writer.goto_offset(curr_pos)?;
self.writer.write_u32(0)?;
@@ -381,14 +576,19 @@ impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> {
///
/// # Examples
/// ```
/// # extern crate tempfile;
/// # let mut file = tempfile::tempfile().unwrap();
/// # extern crate tiff;
/// # fn main() {
/// # let mut file = std::io::Cursor::new(Vec::new());
/// # let image_data = vec![0; 100*100*3];
/// use tiff::encoder::*;
/// use tiff::tags::Tag;
///
/// let mut tiff = TiffEncoder::new(&mut file).unwrap();
/// let mut image = tiff.new_image::<colortype::RGB8>(100, 100).unwrap();
///
/// // You can encode tags here
/// image.encoder().write_tag(Tag::Artist, "Image-tiff").unwrap();
///
/// let mut idx = 0;
/// while image.next_strip_sample_count() > 0 {
/// let sample_count = image.next_strip_sample_count() as usize;
@@ -396,27 +596,30 @@ impl<'a, W: Write + Seek> Drop for DirectoryEncoder<'a, W> {
/// idx += sample_count;
/// }
/// image.finish().unwrap();
/// # }
/// ```
pub struct ImageEncoder<'a, W: Write + Seek, C: ColorType> {
/// You can also call write_data function wich will encode by strip and finish
pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType> {
encoder: DirectoryEncoder<'a, W>,
strip_idx: u64,
strip_count: u64,
row_samples: u64,
width: u32,
height: u32,
rows_per_strip: u64,
strip_offsets: Vec<u32>,
strip_byte_count: Vec<u32>,
dropped: bool,
_phantom: std::marker::PhantomData<C>,
_phantom: ::std::marker::PhantomData<C>,
}
impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
impl<'a, W: 'a + Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
fn new(
mut encoder: DirectoryEncoder<'a, W>,
width: u32,
height: u32,
) -> TiffResult<ImageEncoder<'a, W, T>> {
let row_samples = u64::from(width) * <T>::BITS_PER_SAMPLE.len() as u64;
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);
// As per tiff spec each strip should be about 8k long
@@ -424,19 +627,24 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
encoder.write_tag(Tag::ImageWidth, width);
encoder.write_tag(Tag::ImageLength, height);
encoder.write_tag(Tag::Compression, 1u16);
encoder.write_tag(Tag::ImageWidth, width)?;
encoder.write_tag(Tag::ImageLength, height)?;
encoder.write_tag(Tag::Compression, tags::CompressionMethod::None.to_u16())?;
encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE);
encoder.write_tag(Tag::PhotometricInterpretation, <T>::TIFF_VALUE as u16);
encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
encoder.write_tag(Tag::SampleFormat, &sample_format[..])?;
encoder.write_tag(Tag::PhotometricInterpretation, <T>::TIFF_VALUE.to_u16())?;
encoder.write_tag(Tag::RowsPerStrip, rows_per_strip as u32);
encoder.write_tag(Tag::RowsPerStrip, u32::try_from(rows_per_strip)?)?;
encoder.write_tag(Tag::SamplesPerPixel, <T>::BITS_PER_SAMPLE.len() as u16);
encoder.write_tag(Tag::XResolution, Rational { n: 1, d: 1 });
encoder.write_tag(Tag::YResolution, Rational { n: 1, d: 1 });
encoder.write_tag(Tag::ResolutionUnit, 1u16);
encoder.write_tag(
Tag::SamplesPerPixel,
u16::try_from(<T>::BITS_PER_SAMPLE.len())?,
)?;
encoder.write_tag(Tag::XResolution, Rational { n: 1, d: 1 })?;
encoder.write_tag(Tag::YResolution, Rational { n: 1, d: 1 })?;
encoder.write_tag(Tag::ResolutionUnit, ResolutionUnit::None.to_u16())?;
Ok(ImageEncoder {
encoder,
@@ -444,11 +652,12 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
strip_idx: 0,
row_samples,
rows_per_strip,
width,
height,
strip_offsets: Vec::new(),
strip_byte_count: Vec::new(),
dropped: false,
_phantom: std::marker::PhantomData,
_phantom: ::std::marker::PhantomData,
})
}
@@ -458,11 +667,9 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
return 0;
}
let start_row = std::cmp::min(u64::from(self.height), self.strip_idx * self.rows_per_strip);
let end_row = std::cmp::min(
u64::from(self.height),
(self.strip_idx + 1) * self.rows_per_strip,
);
let raw_start_row = self.strip_idx * self.rows_per_strip;
let start_row = cmp::min(u64::from(self.height), raw_start_row);
let end_row = cmp::min(u64::from(self.height), raw_start_row + self.rows_per_strip);
(end_row - start_row) * self.row_samples
}
@@ -474,47 +681,85 @@ impl<'a, W: Write + Seek, T: ColorType> ImageEncoder<'a, W, T> {
{
// TODO: Compression
let samples = self.next_strip_sample_count();
if value.len() as u64 != samples {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Slice is wrong size for strip").into());
if u64::try_from(value.len())? != samples {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Slice is wrong size for strip",
)
.into());
}
let offset = self.encoder.write_data(value)?;
self.strip_offsets.push(offset as u32);
self.strip_byte_count.push(value.bytes() as u32);
self.strip_offsets.push(u32::try_from(offset)?);
self.strip_byte_count.push(value.bytes());
self.strip_idx += 1;
Ok(())
}
/// Write strips from data
pub fn write_data(mut self, data: &[T::Inner]) -> TiffResult<()>
where
[T::Inner]: TiffValue,
{
let num_pix = usize::try_from(self.width)?
.checked_mul(usize::try_from(self.height)?)
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"Image width * height exceeds usize",
)
})?;
if data.len() < num_pix {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Input data slice is undersized for provided dimensions",
)
.into());
}
let mut idx = 0;
while self.next_strip_sample_count() > 0 {
let sample_count = usize::try_from(self.next_strip_sample_count())?;
self.write_strip(&data[idx..idx + sample_count])?;
idx += sample_count;
}
self.finish()?;
Ok(())
}
/// Set image resolution
pub fn resolution(&mut self, unit: ResolutionUnit, value: Rational) {
self.encoder.write_tag(Tag::ResolutionUnit, unit as u16);
self.encoder.write_tag(Tag::XResolution, value.clone());
self.encoder.write_tag(Tag::YResolution, value);
self.encoder
.write_tag(Tag::ResolutionUnit, unit.to_u16())
.unwrap();
self.encoder
.write_tag(Tag::XResolution, value.clone())
.unwrap();
self.encoder.write_tag(Tag::YResolution, value).unwrap();
}
/// Set image resolution unit
pub fn resolution_unit(&mut self, unit: ResolutionUnit) {
self.encoder.write_tag(Tag::ResolutionUnit, unit as u16);
self.encoder
.write_tag(Tag::ResolutionUnit, unit.to_u16())
.unwrap();
}
/// Set image x-resolution
pub fn x_resolution(&mut self, value: Rational) {
self.encoder.write_tag(Tag::XResolution, value);
self.encoder.write_tag(Tag::XResolution, value).unwrap();
}
/// Set image y-resolution
pub fn y_resolution(&mut self, value: Rational) {
self.encoder.write_tag(Tag::YResolution, value);
self.encoder.write_tag(Tag::YResolution, value).unwrap();
}
fn finish_internal(&mut self) -> TiffResult<()> {
self.encoder
.write_tag(Tag::StripOffsets, &*self.strip_offsets);
.write_tag(Tag::StripOffsets, &*self.strip_offsets)?;
self.encoder
.write_tag(Tag::StripByteCounts, &*self.strip_byte_count);
.write_tag(Tag::StripByteCounts, &*self.strip_byte_count)?;
self.dropped = true;
self.encoder.finish_internal()
+49 -26
View File
@@ -1,29 +1,17 @@
use crate::error::TiffResult;
use byteorder::{BigEndian, ByteOrder, LittleEndian, NativeEndian, WriteBytesExt};
use error::TiffResult;
use std::io::{self, Seek, SeekFrom, Write};
pub trait TiffByteOrder: ByteOrder {
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()>;
}
pub fn write_tiff_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;
impl TiffByteOrder for LittleEndian {
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.writer.write_u16::<LittleEndian>(0x4949)?;
writer.writer.write_u16::<LittleEndian>(42)?;
writer.offset += 4;
writer.writer.write_all(&[boi, boi])?;
writer.writer.write_all(&42u16.to_ne_bytes())?;
writer.offset += 4;
Ok(())
}
}
impl TiffByteOrder for BigEndian {
fn write_header<W: Write>(writer: &mut TiffWriter<W>) -> TiffResult<()> {
writer.writer.write_u16::<BigEndian>(0x4d4d)?;
writer.writer.write_u16::<BigEndian>(42)?;
writer.offset += 4;
Ok(())
}
Ok(())
}
pub struct TiffWriter<W> {
@@ -47,27 +35,61 @@ impl<W: Write> TiffWriter<W> {
}
pub fn write_u8(&mut self, n: u8) -> Result<(), io::Error> {
self.writer.write_u8(n)?;
self.writer.write_all(&n.to_ne_bytes())?;
self.offset += 1;
Ok(())
}
pub fn write_i8(&mut self, n: i8) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?;
self.offset += 1;
Ok(())
}
pub fn write_u16(&mut self, n: u16) -> Result<(), io::Error> {
self.writer.write_u16::<NativeEndian>(n)?;
self.writer.write_all(&n.to_ne_bytes())?;
self.offset += 2;
Ok(())
}
pub fn write_i16(&mut self, n: i16) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?;
self.offset += 2;
Ok(())
}
pub fn write_u32(&mut self, n: u32) -> Result<(), io::Error> {
self.writer.write_u32::<NativeEndian>(n)?;
self.writer.write_all(&n.to_ne_bytes())?;
self.offset += 4;
Ok(())
}
pub fn write_i32(&mut self, n: i32) -> Result<(), io::Error> {
self.writer.write_all(&n.to_ne_bytes())?;
self.offset += 4;
Ok(())
}
pub fn write_u64(&mut self, n: u64) -> Result<(), io::Error> {
self.writer.write_u64::<NativeEndian>(n)?;
self.writer.write_all(&n.to_ne_bytes())?;
self.offset += 8;
Ok(())
}
pub fn write_f32(&mut self, n: f32) -> Result<(), io::Error> {
self.writer.write_all(&u32::to_ne_bytes(n.to_bits()))?;
self.offset += 4;
Ok(())
}
pub fn write_f64(&mut self, n: f64) -> Result<(), io::Error> {
self.writer.write_all(&u64::to_ne_bytes(n.to_bits()))?;
self.offset += 8;
Ok(())
@@ -78,6 +100,7 @@ impl<W: Write> TiffWriter<W> {
let padding = [0, 0, 0];
let padd_len = 4 - (self.offset % 4);
self.writer.write_all(&padding[..padd_len as usize])?;
self.offset += padd_len;
}
Ok(())
+96 -10
View File
@@ -3,36 +3,57 @@ use std::fmt;
use std::io;
use std::string;
use decoder::ifd::{Tag, Value};
use decoder::{CompressionMethod, PhotometricInterpretation, PlanarConfiguration};
use decoder::ifd::Value;
use miniz_oxide::inflate::TINFLStatus;
use tags::{CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag};
use ColorType;
/// Tiff error kinds.
#[derive(Debug)]
pub enum TiffError {
/// The Image is not formatted properly
/// The Image is not formatted properly.
FormatError(TiffFormatError),
/// The Decoder does not support this image format
/// The Decoder does not support features required by the image.
UnsupportedError(TiffUnsupportedError),
/// An I/O Error occurred while decoding the image
/// An I/O Error occurred while decoding the image.
IoError(io::Error),
/// The Limits of the Decoder is exceeded,
/// The Limits of the Decoder is exceeded.
LimitsExceeded,
/// An integer conversion to or from a platform size failed, either due to
/// limits of the platform size or limits of the format.
IntSizeError,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
/// The image is not formatted properly.
///
/// This indicates that the encoder producing the image might behave incorrectly or that the input
/// file has been corrupted.
///
/// The list of variants may grow to incorporate errors of future features. Matching against this
/// exhaustively is not covered by interface stability guarantees.
#[derive(Debug, Clone, PartialEq)]
pub enum TiffFormatError {
TiffSignatureNotFound,
TiffSignatureInvalid,
ImageFileDirectoryNotFound,
InconsistentSizesEncountered,
InvalidTag,
InvalidTagValueType(Tag),
RequiredTagNotFound(Tag),
UnknownPredictor(u32),
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,
}
impl fmt::Display for TiffFormatError {
@@ -44,17 +65,54 @@ impl fmt::Display for TiffFormatError {
ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."),
InvalidTag => write!(fmt, "Image contains invalid tag."),
InvalidTagValueType(ref tag) => {
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
}
RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag),
UnknownPredictor(ref predictor) => {
write!(fmt, "Unknown predictor “{}” encountered", predictor)
}
ByteExpected(ref val) => write!(fmt, "Expected byte, {:?} found.", val),
UnsignedIntegerExpected(ref val) => {
write!(fmt, "Expected unsigned integer, {:?} found.", val)
}
SignedIntegerExpected(ref val) => {
write!(fmt, "Expected signed integer, {:?} found.", val)
}
InflateError(_) => write!(fmt, "Failed to decode inflate data."),
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
__NonExhaustive => unreachable!(),
}
}
}
/// Decompression failed due to faulty compressed data.
#[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.
///
/// This only captures known failures for which the standard either does not require support or an
/// implementation has been planned but not yet completed. Some variants may become unused over
/// time and will then get deprecated before being removed.
///
/// The list of variants may grow. Matching against this exhaustively is not covered by interface
/// stability guarantees.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TiffUnsupportedError {
HorizontalPredictor(ColorType),
@@ -63,10 +121,14 @@ pub enum TiffUnsupportedError {
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,
}
impl fmt::Display for TiffUnsupportedError {
@@ -92,7 +154,10 @@ impl fmt::Display for TiffUnsupportedError {
write!(fmt, "Compression method {:?} is unsupported", method)
}
UnsupportedSampleDepth(samples) => {
write!(fmt, "{} samples per pixel is supported.", samples)
write!(fmt, "{} samples per pixel is unsupported.", samples)
}
UnsupportedSampleFormat(ref formats) => {
write!(fmt, "Sample format {:?} is unsupported.", formats)
}
UnsupportedColorType(color_type) => {
write!(fmt, "Color type {:?} is unsupported", color_type)
@@ -104,6 +169,7 @@ impl fmt::Display for TiffUnsupportedError {
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
}
UnsupportedDataType => write!(fmt, "Unsupported data type."),
__NonExhaustive => unreachable!(),
}
}
}
@@ -120,6 +186,7 @@ impl fmt::Display for TiffError {
),
TiffError::IoError(ref e) => e.fmt(fmt),
TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"),
TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"),
}
}
}
@@ -131,10 +198,11 @@ impl Error for TiffError {
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<&Error> {
fn cause(&self) -> Option<&dyn Error> {
match *self {
TiffError::IoError(ref e) => Some(e),
_ => None,
@@ -154,5 +222,23 @@ impl From<string::FromUtf8Error> for TiffError {
}
}
impl From<TiffFormatError> for TiffError {
fn from(err: TiffFormatError) -> TiffError {
TiffError::FormatError(err)
}
}
impl From<TiffUnsupportedError> for TiffError {
fn from(err: TiffUnsupportedError) -> TiffError {
TiffError::UnsupportedError(err)
}
}
impl From<std::num::TryFromIntError> for TiffError {
fn from(_err: std::num::TryFromIntError) -> TiffError {
TiffError::IntSizeError
}
}
/// Result of an image decoding/encoding process
pub type TiffResult<T> = Result<T, TiffError>;
+7 -7
View File
@@ -4,19 +4,19 @@
//! lossless and lossy compression.
//!
//! # Related Links
//! * <http://partners.adobe.com/public/developer/tiff/index.html> - The TIFF specification
//! * <https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
extern crate byteorder;
extern crate lzw;
#[macro_use]
extern crate num_derive;
extern crate num_traits;
extern crate jpeg;
extern crate miniz_oxide;
extern crate weezl;
mod bytecast;
pub mod decoder;
pub mod encoder;
mod error;
pub mod tags;
pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
pub use self::error::{InflateError, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
/// An enumeration over supported color types and their bit depths
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
+207
View File
@@ -0,0 +1,207 @@
macro_rules! tags {
{
// Permit arbitrary meta items, which include documentation.
$( #[$enum_attr:meta] )*
$vis:vis enum $name:ident($ty:tt) $(unknown($unknown_doc:literal))* {
// Each of the `Name = Val,` permitting documentation.
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
}
} => {
$( #[$enum_attr] )*
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum $name {
$($(#[$ident_attr])* $tag,)*
// FIXME: switch to non_exhaustive once stabilized and compiler requirement new enough
#[doc(hidden)]
__NonExhaustive,
$(
#[doc = $unknown_doc]
Unknown($ty),
)*
}
impl $name {
#[inline(always)]
fn __from_inner_type(n: $ty) -> Result<Self, $ty> {
match n {
$( $val => Ok($name::$tag), )*
n => Err(n),
}
}
#[inline(always)]
fn __to_inner_type(&self) -> $ty {
match *self {
$( $name::$tag => $val, )*
$( $name::Unknown(n) => { $unknown_doc; n }, )*
$name::__NonExhaustive => unreachable!(),
}
}
}
tags!($name, $ty, $($unknown_doc)*);
};
// For u16 tags, provide direct inherent primitive conversion methods.
($name:tt, u16, $($unknown_doc:literal)*) => {
impl $name {
#[inline(always)]
pub fn from_u16(val: u16) -> Option<Self> {
Self::__from_inner_type(val).ok()
}
$(
#[inline(always)]
pub fn from_u16_exhaustive(val: u16) -> Self {
$unknown_doc;
Self::__from_inner_type(val).unwrap_or_else(|_| $name::Unknown(val))
}
)*
#[inline(always)]
pub fn to_u16(&self) -> u16 {
Self::__to_inner_type(self)
}
}
};
// For other tag types, do nothing for now. With concat_idents one could
// provide inherent conversion methods for all types.
($name:tt, $ty:tt, $($unknown_doc:literal)*) => {};
}
// Note: These tags appear in the order they are mentioned in the TIFF reference
tags! {
/// TIFF tags
pub enum Tag(u16) unknown("A private or extension tag") {
// Baseline tags:
Artist = 315,
// grayscale images PhotometricInterpretation 1 or 3
BitsPerSample = 258,
CellLength = 265, // TODO add support
CellWidth = 264, // TODO add support
// palette-color images (PhotometricInterpretation 3)
ColorMap = 320, // TODO add support
Compression = 259, // TODO add support for 2 and 32773
Copyright = 33_432,
DateTime = 306,
ExtraSamples = 338, // TODO add support
FillOrder = 266, // TODO add support
FreeByteCounts = 289, // TODO add support
FreeOffsets = 288, // TODO add support
GrayResponseCurve = 291, // TODO add support
GrayResponseUnit = 290, // TODO add support
HostComputer = 316,
ImageDescription = 270,
ImageLength = 257,
ImageWidth = 256,
Make = 271,
MaxSampleValue = 281, // TODO add support
MinSampleValue = 280, // TODO add support
Model = 272,
NewSubfileType = 254, // TODO add support
Orientation = 274, // TODO add support
PhotometricInterpretation = 262,
PlanarConfiguration = 284,
ResolutionUnit = 296, // TODO add support
RowsPerStrip = 278,
SamplesPerPixel = 277,
Software = 305,
StripByteCounts = 279,
StripOffsets = 273,
SubfileType = 255, // TODO add support
Threshholding = 263, // TODO add support
XResolution = 282,
YResolution = 283,
// JPEG
JPEGTables = 347,
// Advanced tags
Predictor = 317,
TileWidth = 322,
TileLength = 323,
TileOffsets = 324,
TileByteCounts = 325,
SampleFormat = 339,
}
}
tags! {
/// The type of an IFD entry (a 2 byte field).
pub enum Type(u16) {
BYTE = 1,
ASCII = 2,
SHORT = 3,
LONG = 4,
RATIONAL = 5,
SBYTE = 6,
UNDEFINED = 7,
SSHORT = 8,
SLONG = 9,
SRATIONAL = 10,
FLOAT = 11,
DOUBLE = 12,
/// BigTIFF 64-bit unsigned integer
LONG8 = 16,
}
}
tags! {
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
/// for reference.
pub enum CompressionMethod(u16) {
None = 1,
Huffman = 2,
Fax3 = 3,
Fax4 = 4,
LZW = 5,
JPEG = 6,
// "Extended JPEG" or "new JPEG" style
ModernJPEG = 7,
Deflate = 8,
OldDeflate = 0x80B2,
PackBits = 0x8005,
}
}
tags! {
pub enum PhotometricInterpretation(u16) {
WhiteIsZero = 0,
BlackIsZero = 1,
RGB = 2,
RGBPalette = 3,
TransparencyMask = 4,
CMYK = 5,
YCbCr = 6,
CIELab = 8,
}
}
tags! {
pub enum PlanarConfiguration(u16) {
Chunky = 1,
Planar = 2,
}
}
tags! {
pub enum Predictor(u16) {
None = 1,
Horizontal = 2,
}
}
tags! {
/// Type to represent resolution units
pub enum ResolutionUnit(u16) {
None = 1,
Inch = 2,
Centimeter = 3,
}
}
tags! {
pub enum SampleFormat(u16) unknown("An unknown extension sample format") {
Uint = 1,
Int = 2,
IEEEFP = 3,
Void = 4,
}
}
+4
View File
@@ -0,0 +1,4 @@
Copyrights:
kodim*.png: Eastman Kodak Company, released for unrestricted use
Transparency.png: Public Domain, according to Wikimedia
Binary file not shown.
Binary file not shown.
Binary file not shown.
+46
View File
@@ -0,0 +1,46 @@
extern crate tiff;
use tiff::decoder::Decoder;
use tiff::tags::Tag;
use tiff::ColorType;
use std::fs::File;
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images/bigtiff";
#[test]
fn test_big_tiff() {
let filenames = ["BigTIFF.tif", "BigTIFFMotorola.tif", "BigTIFFLong.tif"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(64, 64)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::RGB(8)
);
assert_eq!(
decoder
.get_tag_u64(Tag::StripOffsets)
.expect("Cannot get StripOffsets"),
16
);
assert_eq!(
decoder
.get_tag_u64(Tag::RowsPerStrip)
.expect("Cannot get RowsPerStrip"),
64
);
assert_eq!(
decoder
.get_tag_u64(Tag::StripByteCounts)
.expect("Cannot get StripByteCounts"),
12288
)
}
}
+130 -32
View File
@@ -1,62 +1,154 @@
extern crate tiff;
use tiff::decoder::{ifd::Tag, ifd::Value, Decoder, DecodingResult};
use tiff::decoder::{ifd, Decoder, DecodingResult};
use tiff::ColorType;
use std::fs::File;
use std::path::PathBuf;
#[test]
fn test_gray_u8() {
let img_file =
File::open("./tests/images/minisblack-1c-8b.tiff").expect("Cannot find test image!");
const TEST_IMAGE_DIR: &str = "./tests/images/";
macro_rules! test_image_sum {
($name:ident, $buffer:ident, $sum_ty:ty) => {
fn $name(file: &str, expected_type: ColorType, expected_sum: $sum_ty) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let img_res = decoder.read_image().unwrap();
match img_res {
DecodingResult::$buffer(res) => {
let sum: $sum_ty = res.into_iter().map(<$sum_ty>::from).sum();
assert_eq!(sum, expected_sum);
}
_ => panic!("Wrong bit depth"),
}
}
};
}
test_image_sum!(test_image_sum_u8, U8, u64);
test_image_sum!(test_image_sum_u16, U16, 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_f32, F32, f32);
test_image_sum!(test_image_sum_f64, F64, f64);
/// Tests that a decoder can be constructed for an image and the color type
/// read from the IFD and is of the appropriate type, but the type is
/// unsupported.
fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(8));
let img_res = decoder.read_image();
assert!(img_res.is_ok());
assert_eq!(decoder.colortype().unwrap(), expected_type);
assert!(match decoder.read_image() {
Err(tiff::TiffError::UnsupportedError(
tiff::TiffUnsupportedError::UnsupportedColorType(_),
)) => true,
_ => false,
});
}
#[test]
fn test_rgb_u8() {
let img_file = File::open("./tests/images/rgb-3c-8b.tiff").expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
let img_res = decoder.read_image();
assert!(img_res.is_ok());
fn test_cmyk_u8() {
test_image_sum_u8("cmyk-3c-8b.tiff", ColorType::CMYK(8), 8522658);
}
#[test]
fn test_cmyk_u16() {
test_image_sum_u16("cmyk-3c-16b.tiff", ColorType::CMYK(16), 2181426827);
}
#[test]
fn test_cmyk_f32() {
test_image_sum_f32("cmyk-3c-32b-float.tiff", ColorType::CMYK(32), 496.0405);
}
#[test]
fn test_gray_u8() {
test_image_sum_u8("minisblack-1c-8b.tiff", ColorType::Gray(8), 2840893);
}
#[test]
fn test_gray_u12() {
test_image_color_type_unsupported("12bit.cropped.tiff", ColorType::Gray(12));
}
#[test]
fn test_gray_u16() {
let img_file =
File::open("./tests/images/minisblack-1c-16b.tiff").expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(16));
let img_res = decoder.read_image();
assert!(img_res.is_ok());
test_image_sum_u16("minisblack-1c-16b.tiff", ColorType::Gray(16), 733126239);
}
#[test]
fn test_gray_u32() {
test_image_sum_u32("gradient-1c-32b.tiff", ColorType::Gray(32), 549892913787);
}
#[test]
fn test_gray_u64() {
test_image_sum_u64("gradient-1c-64b.tiff", ColorType::Gray(64), 549892913787);
}
#[test]
fn test_gray_f32() {
test_image_sum_f32("gradient-1c-32b-float.tiff", ColorType::Gray(32), 128.03194);
}
#[test]
fn test_gray_f64() {
test_image_sum_f64(
"gradient-1c-64b-float.tiff",
ColorType::Gray(64),
128.0319210877642,
);
}
#[test]
fn test_rgb_u8() {
test_image_sum_u8("rgb-3c-8b.tiff", ColorType::RGB(8), 7842108);
}
#[test]
fn test_rgb_u12() {
test_image_color_type_unsupported("12bit.cropped.rgb.tiff", ColorType::RGB(12));
}
#[test]
fn test_rgb_u16() {
let img_file = File::open("./tests/images/rgb-3c-16b.tiff").expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(16));
let img_res = decoder.read_image();
assert!(img_res.is_ok());
test_image_sum_u16("rgb-3c-16b.tiff", ColorType::RGB(16), 2024349944);
}
#[test]
fn test_rgb_u32() {
test_image_sum_u32("gradient-3c-32b.tiff", ColorType::RGB(32), 2030834111716);
}
#[test]
fn test_rgb_u64() {
test_image_sum_u64("gradient-3c-64b.tiff", ColorType::RGB(64), 2030834111716);
}
#[test]
fn test_rgb_f32() {
test_image_sum_f32("gradient-3c-32b-float.tiff", ColorType::RGB(32), 472.8405);
}
#[test]
fn test_string_tags() {
// these files have null-terminated strings for their Software tag. One has extra bytes after
// the null byte, so we check both to ensure that we're truncating properly
let filenames = vec!["minisblack-1c-16b.tiff", "rgb-3c-16b.tiff"];
let filenames = ["minisblack-1c-16b.tiff", "rgb-3c-16b.tiff"];
for filename in filenames.iter() {
let path = format!("./tests/images/{}", filename);
let img_file = File::open(path).expect("can't open file");
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
let software = decoder.get_tag(Tag::Software).unwrap();
let software = decoder.get_tag(tiff::tags::Tag::Software).unwrap();
match software {
Value::Ascii(s) => assert_eq!(
s,
String::from("GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/")
ifd::Value::Ascii(s) => assert_eq!(
&s,
"GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/"
),
_ => assert!(false),
};
@@ -85,6 +177,12 @@ fn test_decode_data() {
}
}
#[test]
fn issue_69() {
test_image_sum_u16("issue_69_lzw.tiff", ColorType::Gray(16), 1015486);
test_image_sum_u16("issue_69_packbits.tiff", ColorType::Gray(16), 1015486);
}
// TODO: GrayA support
//#[test]
//fn test_gray_alpha_u8()
+290 -105
View File
@@ -1,12 +1,13 @@
extern crate tempfile;
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::encoder::{colortype, TiffEncoder};
use tiff::decoder::{ifd, Decoder, DecodingResult};
use tiff::encoder::{colortype, SRational, TiffEncoder};
use tiff::tags::Tag;
use tiff::ColorType;
use std::fs::File;
use std::io::{Cursor, Seek, SeekFrom};
use std::path::PathBuf;
#[test]
fn encode_decode() {
@@ -19,18 +20,26 @@ fn encode_decode() {
image_data.push(val);
}
}
let mut file = tempfile::tempfile().unwrap();
let mut file = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new(&mut file).unwrap();
tiff.write_image::<colortype::RGB8>(100, 100, &image_data)
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));
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 {
@@ -53,125 +62,301 @@ fn test_encode_undersized_buffer() {
}
}
const TEST_IMAGE_DIR: &str = "./tests/images/";
macro_rules! test_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();
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().unwrap() {
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
}
}
}
};
}
test_roundtrip!(test_u8_roundtrip, U8, u8);
test_roundtrip!(test_u16_roundtrip, U16, u16);
test_roundtrip!(test_u32_roundtrip, U32, u32);
test_roundtrip!(test_u64_roundtrip, U64, u64);
test_roundtrip!(test_f32_roundtrip, F32, f32);
test_roundtrip!(test_f64_roundtrip, F64, f64);
#[test]
fn test_gray_u8_roundtrip() {
let img_file =
File::open("./tests/images/minisblack-1c-8b.tiff").expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(8));
let image_data = match decoder.read_image().unwrap() {
DecodingResult::U8(res) => res,
_ => panic!("Wrong data type"),
};
let mut file = tempfile::tempfile().unwrap();
{
let mut tiff = TiffEncoder::new(&mut file).unwrap();
let (width, height) = decoder.dimensions().unwrap();
tiff.write_image::<colortype::Gray8>(width, height, &image_data)
.unwrap();
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::new(&mut file).unwrap();
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
}
}
test_u8_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_rgb_u8_roundtrip() {
let img_file = File::open("./tests/images/rgb-3c-8b.tiff").expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
}
let image_data = match decoder.read_image().unwrap() {
DecodingResult::U8(res) => res,
_ => panic!("Wrong data type"),
};
let mut file = tempfile::tempfile().unwrap();
{
let mut tiff = TiffEncoder::new(&mut file).unwrap();
let (width, height) = decoder.dimensions().unwrap();
tiff.write_image::<colortype::RGB8>(width, height, &image_data)
.unwrap();
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::new(&mut file).unwrap();
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
}
}
#[test]
fn test_cmyk_u8_roundtrip() {
test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
}
#[test]
fn test_gray_u16_roundtrip() {
let img_file =
File::open("./tests/images/minisblack-1c-16b.tiff").expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(16));
let image_data = match decoder.read_image().unwrap() {
DecodingResult::U16(res) => res,
_ => panic!("Wrong data type"),
};
let mut file = tempfile::tempfile().unwrap();
{
let mut tiff = TiffEncoder::new(&mut file).unwrap();
let (width, height) = decoder.dimensions().unwrap();
tiff.write_image::<colortype::Gray16>(width, height, &image_data)
.unwrap();
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::new(&mut file).unwrap();
if let DecodingResult::U16(img_res) = decoder.read_image().unwrap() {
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
}
}
test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_rgb_u16_roundtrip() {
let img_file = File::open("./tests/images/rgb-3c-16b.tiff").expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(16));
test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
}
let image_data = match decoder.read_image().unwrap() {
DecodingResult::U16(res) => res,
_ => panic!("Wrong data type"),
};
#[test]
fn test_cmyk_u16_roundtrip() {
test_u16_roundtrip::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
}
let mut file = tempfile::tempfile().unwrap();
{
let mut tiff = TiffEncoder::new(&mut file).unwrap();
#[test]
fn test_gray_u32_roundtrip() {
test_u32_roundtrip::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
}
let (width, height) = decoder.dimensions().unwrap();
tiff.write_image::<colortype::RGB16>(width, height, &image_data)
.unwrap();
#[test]
fn test_rgb_u32_roundtrip() {
test_u32_roundtrip::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
}
#[test]
fn test_gray_u64_roundtrip() {
test_u64_roundtrip::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
}
#[test]
fn test_rgb_u64_roundtrip() {
test_u64_roundtrip::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
}
#[test]
fn test_gray_f32_roundtrip() {
test_f32_roundtrip::<colortype::Gray32Float>("gradient-1c-32b-float.tiff", ColorType::Gray(32));
}
#[test]
fn test_rgb_f32_roundtrip() {
test_f32_roundtrip::<colortype::RGB32Float>("gradient-3c-32b-float.tiff", ColorType::RGB(32));
}
#[test]
fn test_cmyk_f32_roundtrip() {
test_f32_roundtrip::<colortype::CMYK32Float>("cmyk-3c-32b-float.tiff", ColorType::CMYK(32));
}
#[test]
fn test_gray_f64_roundtrip() {
test_f64_roundtrip::<colortype::Gray64Float>("gradient-1c-64b-float.tiff", ColorType::Gray(64));
}
trait AssertDecode {
fn assert_tag_u32(&mut self, tag: u16) -> u32;
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
fn assert_tag_i32(&mut self, tag: u16) -> i32;
fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec<i32>;
}
impl<R: std::io::Read + std::io::Seek> AssertDecode for Decoder<R> {
fn assert_tag_u32(&mut self, tag: u16) -> u32 {
self.get_tag(Tag::Unknown(tag)).unwrap().into_u32().unwrap()
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::new(&mut file).unwrap();
if let DecodingResult::U16(img_res) = decoder.read_image().unwrap() {
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
}
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32> {
self.get_tag(Tag::Unknown(tag))
.unwrap()
.into_u32_vec()
.unwrap()
}
fn assert_tag_i32(&mut self, tag: u16) -> i32 {
self.get_tag(Tag::Unknown(tag)).unwrap().into_i32().unwrap()
}
fn assert_tag_i32_vec(&mut self, tag: u16) -> Vec<i32> {
self.get_tag(Tag::Unknown(tag))
.unwrap()
.into_i32_vec()
.unwrap()
}
}
#[test]
fn test_multiple_byte() {
let mut data = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new(&mut data).unwrap();
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
let encoder = image_encoder.encoder();
encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]).unwrap();
encoder
.write_tag(Tag::Unknown(65001), &[1_u8, 2][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65002), &[1_u8, 2, 3][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65003), &[1_u8, 2, 3, 4][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65004), &[1_u8, 2, 3, 4, 5][..])
.unwrap();
}
data.set_position(0);
{
let mut decoder = Decoder::new(&mut data).unwrap();
assert_eq!(decoder.assert_tag_u32_vec(65000), [1]);
assert_eq!(decoder.assert_tag_u32_vec(65001), [1, 2]);
assert_eq!(decoder.assert_tag_u32_vec(65002), [1, 2, 3]);
assert_eq!(decoder.assert_tag_u32_vec(65003), [1, 2, 3, 4]);
assert_eq!(decoder.assert_tag_u32_vec(65004), [1, 2, 3, 4, 5]);
}
}
#[test]
/// Test writing signed tags from TIFF 6.0
fn test_signed() {
let mut data = Cursor::new(Vec::new());
fn make_srational(i: i32) -> SRational {
SRational { n: i, d: 100 }
}
{
let mut tiff = TiffEncoder::new(&mut data).unwrap();
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
let encoder = image_encoder.encoder();
//Use the "reusable" tags section as per the TIFF6 spec
encoder.write_tag(Tag::Unknown(65000), -1_i8).unwrap();
encoder
.write_tag(Tag::Unknown(65001), &[-1_i8][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65002), &[-1_i8, 2][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65003), &[-1_i8, 2, -3][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65004), &[-1_i8, 2, -3, 4][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65005), &[-1_i8, 2, -3, 4, -5][..])
.unwrap();
encoder.write_tag(Tag::Unknown(65010), -1_i16).unwrap();
encoder.write_tag(Tag::Unknown(65011), -1_i16).unwrap();
encoder
.write_tag(Tag::Unknown(65012), &[-1_i16, 2][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65013), &[-1_i16, 2, -3][..])
.unwrap();
encoder.write_tag(Tag::Unknown(65020), -1_i32).unwrap();
encoder
.write_tag(Tag::Unknown(65021), &[-1_i32][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65022), &[-1_i32, 2][..])
.unwrap();
encoder
.write_tag(Tag::Unknown(65030), make_srational(-1))
.unwrap();
encoder
.write_tag(
Tag::Unknown(65031),
&[make_srational(-1), make_srational(2)][..],
)
.unwrap();
}
//Rewind the cursor for reading
data.set_position(0);
{
let mut decoder = Decoder::new(&mut data).unwrap();
assert_eq!(decoder.assert_tag_i32(65000), -1);
assert_eq!(decoder.assert_tag_i32_vec(65001), [-1]);
assert_eq!(decoder.assert_tag_i32_vec(65002), [-1, 2]);
assert_eq!(decoder.assert_tag_i32_vec(65003), [-1, 2, -3]);
assert_eq!(decoder.assert_tag_i32_vec(65004), [-1, 2, -3, 4]);
assert_eq!(decoder.assert_tag_i32_vec(65005), [-1, 2, -3, 4, -5],);
assert_eq!(decoder.assert_tag_i32(65010), -1);
assert_eq!(decoder.assert_tag_i32_vec(65011), [-1]);
assert_eq!(decoder.assert_tag_i32_vec(65012), [-1, 2]);
assert_eq!(decoder.assert_tag_i32_vec(65013), [-1, 2, -3]);
assert_eq!(decoder.assert_tag_i32(65020), -1);
assert_eq!(decoder.assert_tag_i32_vec(65021), [-1]);
assert_eq!(decoder.assert_tag_i32_vec(65022), [-1, 2]);
assert_eq!(decoder.assert_tag_i32_vec(65030), [-1, 100]);
assert_eq!(decoder.assert_tag_i32_vec(65031), [-1_i32, 100, 2, 100]);
}
}
#[test]
/// check multipage image handling
fn test_multipage_image() {
let mut img_file = Cursor::new(Vec::new());
{
// first create a multipage image with 2 images
let mut img_encoder = TiffEncoder::new(&mut img_file).unwrap();
// write first grayscale image (2x2 16-bit)
let img1: Vec<u16> = [1, 2, 3, 4].to_vec();
img_encoder
.write_image::<colortype::Gray16>(2, 2, &img1[..])
.unwrap();
// write second grayscale image (3x3 8-bit)
let img2: Vec<u8> = [9, 8, 7, 6, 5, 4, 3, 2, 1].to_vec();
img_encoder
.write_image::<colortype::Gray8>(3, 3, &img2[..])
.unwrap();
}
// seek to the beginning of the file, so that it can be decoded
img_file.seek(SeekFrom::Start(0)).unwrap();
{
let mut img_decoder = Decoder::new(&mut img_file).unwrap();
// check the dimensions of the image in the first page
assert_eq!(img_decoder.dimensions().unwrap(), (2, 2));
img_decoder.next_image().unwrap();
// check the dimensions of the image in the second page
assert_eq!(img_decoder.dimensions().unwrap(), (3, 3));
}
}
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff. Show more