Compare commits

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

* Also run clippy and verify msrv

* Fix string mismatch

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

For instance, consider 77 bytes of literal were left and a buffer of
1024 bytes was provided. Then the `read` call might return 60 which was
ignored and assumed to be 77. Thus the next 17 bytes of literal data
were instead interpreted as a header due to setting `self.count` to 0.
2022-12-03 18:42:08 +01:00
Andreas Molzer 6c65e66440 Merge pull request #170 from AlanRace/jpeg-unwrap
Removed `unwrap` and set colour transform with Jpeg compressed tiffs
2022-11-22 16:17:40 +01:00
Alan Race a733ff13d9 Changed to using private constructor rather than From<> JpegDecoderError 2022-11-22 15:53:42 +01:00
Alan Race b1cd459330 Updated error handling 2022-11-22 15:44:10 +01:00
Alan Race 4f1c88e35f Added unsupported error for interpretations not supported by JPEG 2022-11-22 14:38:27 +01:00
Alan Race 7307e96782 Removed unwrap and set colour transform with Jpeg compressed tiffs 2022-11-22 14:31:33 +01:00
Kurt Kühnert a08604b015 added support for the YCbCr PhotometricInterpretation 2022-11-16 17:33:39 +01:00
HeroicKatora 9333d99c92 Merge pull request #187 from image-rs/release-0.8.0
Changelog for 0.8
2022-11-10 21:02:40 +01:00
Andreas Molzer 65bc24d5d9 Add deprecation removals to changelog 2022-11-10 17:49:32 +01:00
Andreas Molzer 7fc75f194a Changelog for 0.8 2022-11-10 17:28:12 +01:00
HeroicKatora 7679be19c1 Merge pull request #189 from image-rs/non-exhaustive-enumeration
Non exhaustive enumerations
2022-11-10 17:27:31 +01:00
Andreas Molzer 0e9f8e3b5b Mark more tag enums as non_exhaustive 2022-11-10 12:45:04 +01:00
Andreas Molzer c900129f2c Change to non_exhaustive attributes 2022-11-10 12:45:04 +01:00
HeroicKatora 61234c0005 Merge pull request #188 from fintelia/decoder-deprecated
Remove deprecated methods on Decoder
2022-11-05 20:22:38 +01:00
Jonathan Behrens 1416af1018 Remove deprecated methods on Decoder 2022-11-05 10:15:31 -07:00
HeroicKatora 87d652ae5e Merge pull request #186 from micahsnyder/bump-jpeg-decoder-dependency
Bump jpeg-decoder version pin to 0.3.0
2022-11-03 08:28:43 +01:00
Micah Snyder b5a78fbec4 CI: Correct the MSRV from 1.48 to 1.61 2022-11-02 15:00:29 -07:00
Micah Snyder 30640d50ff Bump jpeg-decoder version pin to 0.3.0
The jpeg-decoder project has a new version with a number of improvements
plus a fix for a possible panic.
Semantic versioning considers 0.2.4 to be incompatible with 0.3.X because
of the leading zero, so this change bumps the pin to 0.3.0.
2022-11-02 14:25:18 -07:00
HeroicKatora b87770225f Merge pull request #185 from image-rs/release-0.7.4
Changelog for 0.7.4
2022-10-29 12:44:25 +02:00
43 changed files with 1966 additions and 1229 deletions

No files matched your search

+42 -17
View File
@@ -9,32 +9,57 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
rust: ["1.48", stable, beta, nightly]
command: [build, test]
rust: ["1.74.0", stable, beta, nightly]
steps:
- uses: actions/checkout@v2
- run: rustup default ${{ matrix.rust }}
- uses: dtolnay/rust-toolchain@nightly
if: ${{ matrix.rust == '1.74.0' }}
- name: Generate Cargo.lock with minimal-version dependencies
if: ${{ matrix.rust == '1.74.0' }}
run: cargo -Zminimal-versions generate-lockfile
- uses: dtolnay/rust-toolchain@v1
with:
toolchain: ${{ matrix.rust }}
- name: Cache Cargo Dependencies
uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- name: build
run: >
cargo build --verbose --no-default-features --features "$FEATURES"
run: cargo build -v
- name: test
run: >
cargo test --tests --benches --no-default-features --features "$FEATURES"
if: ${{ matrix.rust != '1.48' }}
env:
FEATURES: ${{ matrix.features }}
if: ${{ matrix.rust != '1.74.0' }}
run: cargo test -v && cargo doc -v
features:
runs-on: ubuntu-latest
strategy:
matrix:
feature: ["", deflate, jpeg, lzw, zstd, default, "default,zstd"]
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cargo check
run: cargo check --features "${{ matrix.feature }}" --no-default-features
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
override: true
components: rustfmt
- name: Run rustfmt check
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
run: cargo fmt -- --check
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- run: cargo clippy --all-features # TODO: enable "-- -D warnings" once all outstanding clippy lints are fixed
+75
View File
@@ -1,3 +1,78 @@
# Version 0.10.0
New features:
- Added `ColorType::Multiband` and `ColorType::CMYKA`.
- Added `Value::Byte` and `Value::SignedByte` to represent `u8` and `i8` tags.
- Encoding can now use horizontal predictor.
- The `Decoder::tag_iter` method allows iterating over all tags in the image.
- Half precision floating point values are now supported during decoding.
- ZStandard compression has been added as an additional compression method
during decoding, but requires the `zstd` feature flag.
Changes:
- Switched `u16` and `i16` values to be intepreted as `Value::Short` and
`Value::SignedShort` respectively.
- Changed JPEG compression to use the `zune-jpeg` crate instead of
`jpeg-decoder`.
- The `deflate`, `jpeg`, and `lzw` compression methods are now controlled by
(enabled by default) feature flags.
Fixes:
- Fixed a bug in 1 bit-per-pixel decoding.
# Version 0.9.1
New features:
- Basic support for planar configuration.
- Allow arbitrary number of samples as long as all have the same bit depth.
Fixes:
- Don't panic when parsing metadata when custom compression is used.
# Version 0.9.0
New features:
* Added support for photometric interpretation `YCbCr` and added related
`ColorType`.
Fixes:
* Decoding tiled images calculates padding correctly when image width or height
is a multiple of tile size. It could previously corrupt the last tile per row
by skipping over data.
# Version 0.8.1
Changes:
* The jpeg decoder gained to ability to utilize the Photometric Interpretation
directly instead of relying on a custom APP segment.
Fixes:
* A spurious error within the PackBits decoder lead to the incorrect results
(wrong bits or errors), based on the maximum size of reads from the
underlying reader.
* Removed a panic path in jpeg decoding, when a feature such as photometric
interpretation is not supported. An error is returned instead.
# Version 0.8.0
Changes:
* The minimum supported rust version is now indicated in `Cargo.toml`.
* The enums `TiffFormatError` and `TiffUnsupportedError` are now
marked with the `#[non_exhaustive]` attribute.
* Additionally, tag related enums `Value`, `Tags`, `Type`, `CompressionMethod`,
`PhotometricInterpretation`, `PlanarConfiguration`, `Predictor`,
`ResolutionUnit`, `SampleFormat` are also changed.
Removals:
* Removed deprecated methods of `Decoder`: `init`, `read_jpeg`,
`read_strip_to_buffer`, `read_strip`, `read_tile`. The implicit chunk (row or
tile) index order could not be easily tracked by the caller. New separate
utility interfaces may be introduced at a later point, for now callers are
obligated to choose their own.
Fixes:
* Update to `jpeg_decoder = 0.3`.
# Version 0.7.4
New features:
+27 -6
View File
@@ -1,21 +1,42 @@
[package]
name = "tiff"
version = "0.7.4"
authors = ["The image-rs Developers"]
version = "0.10.0"
edition = "2021"
resolver = "2"
# note: when changed, also update test runner in `.github/workflows/rust.yml`
rust-version = "1.74.0"
license = "MIT"
description = "TIFF decoding and encoding library in pure Rust"
authors = ["The image-rs Developers"]
repository = "https://github.com/image-rs/image-tiff"
categories = ["multimedia::images", "multimedia::encoding"]
exclude = ["tests/images/*", "tests/fuzz_images/*"]
edition = "2018"
[dependencies]
weezl = "0.1.0"
jpeg = { package = "jpeg-decoder", version = "0.2.4", default-features = false }
flate2 = "1.0.20"
half = { version = "2.4.1" }
quick-error = "2.0.1"
flate2 = { version = "1.0.20", optional = true }
weezl = { version = "0.1.0", optional = true }
zstd = { version = "0.13", optional = true }
zune-jpeg = { version = "0.4.17", optional = true }
[dev-dependencies]
criterion = "0.3.1"
[features]
default = ["deflate", "jpeg", "lzw"]
# Compression algorithms
deflate = ["dep:flate2"]
jpeg = ["dep:zune-jpeg"]
lzw = ["dep:weezl"]
zstd = ["dep:zstd"]
[[bench]]
name = "lzw"
harness = false
+3
View File
@@ -24,6 +24,7 @@ This table lists photometric interpretations and sample formats which are suppor
| `RGBPalette` | | |
| `Mask` | | |
| `CMYK` | CMYK(8\|16\|32\|64) | CMYK(32\|64) |
| `CMYKA` | CMYKA(8) | |
| `YCbCr` | | |
| `CIELab` | | |
@@ -35,6 +36,8 @@ This table lists photometric interpretations and sample formats which are suppor
| LZW | ✓ | ✓ |
| Deflate | ✓ | ✓ |
| PackBits | ✓ | ✓ |
| JPEG | ✓ | not yet |
| ZSTD | ✓ | not yet |
## Not yet supported
+3
View File
@@ -12,6 +12,8 @@
//! TODO: Would like to use std-lib here.
use std::{mem, slice};
use half::f16;
macro_rules! integral_slice_as_bytes{($int:ty, $const:ident $(,$mut:ident)*) => {
pub(crate) fn $const(slice: &[$int]) -> &[u8] {
assert!(mem::align_of::<$int>() <= mem::size_of::<$int>());
@@ -31,4 +33,5 @@ integral_slice_as_bytes!(i32, i32_as_ne_bytes, i32_as_ne_mut_bytes);
integral_slice_as_bytes!(u64, u64_as_ne_bytes, u64_as_ne_mut_bytes);
integral_slice_as_bytes!(i64, i64_as_ne_bytes, i64_as_ne_mut_bytes);
integral_slice_as_bytes!(f32, f32_as_ne_bytes, f32_as_ne_mut_bytes);
integral_slice_as_bytes!(f16, f16_as_ne_bytes, f16_as_ne_mut_bytes);
integral_slice_as_bytes!(f64, f64_as_ne_bytes, f64_as_ne_mut_bytes);
+73 -78
View File
@@ -1,25 +1,27 @@
//! Function for reading TIFF tags
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::io::{self, Read, Seek};
use std::mem;
use std::str;
use super::stream::{ByteOrder, EndianReader, SmartReader};
use super::stream::{ByteOrder, EndianReader};
use crate::tags::{Tag, Type};
use crate::{TiffError, TiffFormatError, TiffResult};
use self::Value::{
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
Short, Signed, SignedBig, Unsigned, UnsignedBig,
Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig,
};
#[allow(unused_qualifications)]
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Value {
Byte(u8),
Short(u16),
SignedByte(i8),
SignedShort(i16),
Signed(i32),
SignedBig(i64),
Unsigned(u32),
@@ -34,99 +36,104 @@ pub enum Value {
Ascii(String),
Ifd(u32),
IfdBig(u64),
#[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))),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_i8(self) -> TiffResult<i8> {
match self {
SignedByte(val) => Ok(val),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_u16(self) -> TiffResult<u16> {
match self {
Byte(val) => Ok(val.into()),
Short(val) => Ok(val),
Unsigned(val) => Ok(u16::try_from(val)?),
UnsignedBig(val) => Ok(u16::try_from(val)?),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_i16(self) -> TiffResult<i16> {
match self {
SignedByte(val) => Ok(val.into()),
SignedShort(val) => Ok(val),
Signed(val) => Ok(i16::try_from(val)?),
SignedBig(val) => Ok(i16::try_from(val)?),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_u32(self) -> TiffResult<u32> {
match self {
Byte(val) => Ok(val.into()),
Short(val) => Ok(val.into()),
Unsigned(val) => Ok(val),
UnsignedBig(val) => Ok(u32::try_from(val)?),
Ifd(val) => Ok(val),
IfdBig(val) => Ok(u32::try_from(val)?),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_i32(self) -> TiffResult<i32> {
match self {
SignedByte(val) => Ok(val.into()),
SignedShort(val) => Ok(val.into()),
Signed(val) => Ok(val),
SignedBig(val) => Ok(i32::try_from(val)?),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_u64(self) -> TiffResult<u64> {
match self {
Byte(val) => Ok(val.into()),
Short(val) => Ok(val.into()),
Unsigned(val) => Ok(val.into()),
UnsignedBig(val) => Ok(val),
Ifd(val) => Ok(val.into()),
IfdBig(val) => Ok(val),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_i64(self) -> TiffResult<i64> {
match self {
SignedByte(val) => Ok(val.into()),
SignedShort(val) => Ok(val.into()),
Signed(val) => Ok(val.into()),
SignedBig(val) => Ok(val),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_f32(self) -> TiffResult<f32> {
match self {
Float(val) => Ok(val),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_f64(self) -> TiffResult<f64> {
match self {
Double(val) => Ok(val),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
pub fn into_string(self) -> TiffResult<String> {
match self {
Ascii(val) => Ok(val),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
@@ -139,6 +146,8 @@ impl Value {
}
Ok(new_vec)
}
Byte(val) => Ok(vec![val.into()]),
Short(val) => Ok(vec![val.into()]),
Unsigned(val) => Ok(vec![val]),
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
@@ -148,9 +157,7 @@ impl Value {
Ifd(val) => Ok(vec![val]),
IfdBig(val) => Ok(vec![u32::try_from(val)?]),
Ascii(val) => Ok(val.chars().map(u32::from).collect()),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
@@ -165,9 +172,7 @@ impl Value {
}
Byte(val) => Ok(vec![val]),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
@@ -180,10 +185,9 @@ impl Value {
}
Ok(new_vec)
}
Byte(val) => Ok(vec![val.into()]),
Short(val) => Ok(vec![val]),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
@@ -206,15 +210,15 @@ impl Value {
}
Ok(new_vec)
}
SignedByte(val) => Ok(vec![val.into()]),
SignedShort(val) => Ok(vec![val.into()]),
Signed(val) => Ok(vec![val]),
SignedBig(val) => Ok(vec![i32::try_from(val)?]),
SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
SRationalBig(numerator, denominator) => {
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
}
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
@@ -228,9 +232,7 @@ impl Value {
Ok(new_vec)
}
Float(val) => Ok(vec![val]),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
@@ -244,9 +246,7 @@ impl Value {
Ok(new_vec)
}
Double(val) => Ok(vec![val]),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
@@ -259,6 +259,8 @@ impl Value {
}
Ok(new_vec)
}
Byte(val) => Ok(vec![val.into()]),
Short(val) => Ok(vec![val.into()]),
Unsigned(val) => Ok(vec![val.into()]),
UnsignedBig(val) => Ok(vec![val]),
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
@@ -266,9 +268,7 @@ impl Value {
Ifd(val) => Ok(vec![val.into()]),
IfdBig(val) => Ok(vec![val]),
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
val => Err(TiffError::FormatError(
TiffFormatError::UnsignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
@@ -291,13 +291,13 @@ impl Value {
}
Ok(new_vec)
}
SignedByte(val) => Ok(vec![val.into()]),
SignedShort(val) => Ok(vec![val.into()]),
Signed(val) => Ok(vec![val.into()]),
SignedBig(val) => Ok(vec![val]),
SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
val => Err(TiffError::FormatError(
TiffFormatError::SignedIntegerExpected(val),
)),
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
}
}
}
@@ -334,22 +334,22 @@ impl Entry {
}
/// Returns a mem_reader for the offset/value field
fn r(&self, byte_order: ByteOrder) -> SmartReader<io::Cursor<Vec<u8>>> {
SmartReader::wrap(io::Cursor::new(self.offset.to_vec()), byte_order)
fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
}
pub fn val<R: Read + Seek>(
pub(crate) fn val<R: Read + Seek>(
&self,
limits: &super::Limits,
bigtiff: bool,
reader: &mut SmartReader<R>,
reader: &mut EndianReader<R>,
) -> TiffResult<Value> {
// Case 1: there are no values so we can return immediately.
if self.count == 0 {
return Ok(List(Vec::new()));
}
let bo = reader.byte_order();
let bo = reader.byte_order;
let tag_size = match self.type_ {
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
@@ -361,7 +361,6 @@ impl Entry {
| Type::RATIONAL
| Type::SRATIONAL
| Type::IFD8 => 8,
Type::__NonExhaustive => unreachable!(),
};
let value_bytes = match self.count.checked_mul(tag_size) {
@@ -398,17 +397,16 @@ impl Entry {
| Type::SLONG
| Type::FLOAT
| Type::IFD => unreachable!(),
Type::__NonExhaustive => unreachable!(),
});
}
// 2b: the value is at most 4 bytes or doesn't fit in the offset field.
return Ok(match self.type_ {
Type::BYTE => Unsigned(u32::from(self.offset[0])),
Type::SBYTE => Signed(i32::from(self.offset[0] as i8)),
Type::BYTE => Byte(self.offset[0]),
Type::SBYTE => SignedByte(i8::from(self.offset[0] as i8)),
Type::UNDEFINED => Byte(self.offset[0]),
Type::SHORT => Unsigned(u32::from(self.r(bo).read_u16()?)),
Type::SSHORT => Signed(i32::from(self.r(bo).read_i16()?)),
Type::SHORT => Short(self.r(bo).read_u16()?),
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
Type::LONG => Unsigned(self.r(bo).read_u32()?),
Type::SLONG => Signed(self.r(bo).read_i32()?),
Type::FLOAT => Float(self.r(bo).read_f32()?),
@@ -444,7 +442,6 @@ impl Entry {
reader.goto_offset(self.r(bo).read_u32()?.into())?;
IfdBig(reader.read_u64()?)
}
Type::__NonExhaustive => unreachable!(),
});
}
@@ -455,7 +452,7 @@ impl Entry {
Type::SBYTE => return offset_to_sbytes(self.count as usize, self),
Type::ASCII => {
let mut buf = vec![0; self.count as usize];
self.r(bo).read_exact(&mut buf)?;
buf.copy_from_slice(&self.offset[..self.count as usize]);
if buf.is_ascii() && buf.ends_with(&[0]) {
let v = str::from_utf8(&buf)?;
let v = v.trim_matches(char::from(0));
@@ -484,7 +481,7 @@ impl Entry {
let mut r = self.r(bo);
let mut v = Vec::new();
for _ in 0..self.count {
v.push(Signed(i32::from(r.read_i16()?)));
v.push(SignedShort(r.read_i16()?));
}
return Ok(List(v));
}
@@ -528,7 +525,6 @@ impl Entry {
| Type::IFD8 => {
unreachable!()
}
Type::__NonExhaustive => unreachable!(),
}
}
@@ -538,17 +534,17 @@ impl Entry {
// at a different endianess of file/computer.
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
reader.inner().read_exact(&mut buf)?;
Ok(UnsignedBig(u64::from(buf[0])))
}),
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SignedBig(i64::from(reader.read_i8()? as i8)))
Ok(SignedBig(i64::from(reader.read_i8()?)))
}),
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(UnsignedBig(u64::from(reader.read_u16()?)))
Ok(Short(reader.read_u16()?))
}),
Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(SignedBig(i64::from(reader.read_i16()?)))
Ok(SignedShort(reader.read_i16()?))
}),
Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
Ok(Unsigned(reader.read_u32()?))
@@ -587,7 +583,7 @@ impl Entry {
Type::UNDEFINED => {
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
let mut buf = [0; 1];
reader.read_exact(&mut buf)?;
reader.inner().read_exact(&mut buf)?;
Ok(Byte(buf[0]))
})
}
@@ -604,14 +600,13 @@ impl Entry {
}
let mut out = vec![0; n];
reader.read_exact(&mut out)?;
reader.inner().read_exact(&mut out)?;
// Strings may be null-terminated, so we trim anything downstream of the null byte
if let Some(first) = out.iter().position(|&b| b == 0) {
out.truncate(first);
}
Ok(Ascii(String::from_utf8(out)?))
}
Type::__NonExhaustive => unreachable!(),
}
}
@@ -622,12 +617,12 @@ impl Entry {
bo: ByteOrder,
bigtiff: bool,
limits: &super::Limits,
reader: &mut SmartReader<R>,
reader: &mut EndianReader<R>,
decode_fn: F,
) -> TiffResult<Value>
where
R: Read + Seek,
F: Fn(&mut SmartReader<R>) -> TiffResult<Value>,
F: Fn(&mut EndianReader<R>) -> TiffResult<Value>,
{
let value_count = usize::try_from(value_count)?;
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
+274 -121
View File
@@ -1,14 +1,12 @@
use super::ifd::{Directory, Value};
use super::stream::{
add_app14segment, ByteOrder, DeflateReader, JpegReader, JpegTagApp14Transform, LZWReader,
PackBitsReader,
};
use super::stream::{ByteOrder, PackBitsReader};
use super::tag_reader::TagReader;
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
use super::{stream::SmartReader, ChunkType};
use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag};
use super::{predict_f16, predict_f32, predict_f64, Limits};
use super::{stream::EndianReader, ChunkType};
use crate::tags::{
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
};
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
use std::convert::{TryFrom, TryInto};
use std::io::{self, Cursor, Read, Seek};
use std::sync::Arc;
@@ -35,12 +33,11 @@ impl TileAttributes {
(self.image_height + self.tile_length - 1) / self.tile_length
}
fn padding_right(&self) -> usize {
self.tile_width - self.image_width % self.tile_width
(self.tile_width - self.image_width % self.tile_width) % self.tile_width
}
fn padding_down(&self) -> usize {
self.tile_length - self.image_height % self.tile_length
(self.tile_length - self.image_height % self.tile_length) % self.tile_length
}
pub fn get_padding(&self, tile: usize) -> (usize, usize) {
let row = tile / self.tiles_across();
let column = tile % self.tiles_across();
@@ -66,15 +63,15 @@ pub(crate) struct Image {
pub ifd: Option<Directory>,
pub width: u32,
pub height: u32,
pub bits_per_sample: Vec<u8>,
#[allow(unused)]
pub samples: u8,
pub sample_format: Vec<SampleFormat>,
pub bits_per_sample: u8,
pub samples: u16,
pub sample_format: SampleFormat,
pub photometric_interpretation: PhotometricInterpretation,
pub compression_method: CompressionMethod,
pub predictor: Predictor,
pub jpeg_tables: Option<Arc<Vec<u8>>>,
pub chunk_type: ChunkType,
pub planar_config: PlanarConfiguration,
pub strip_decoder: Option<StripDecodeState>,
pub tile_attributes: Option<TileAttributes>,
pub chunk_offsets: Vec<u64>,
@@ -83,7 +80,7 @@ pub(crate) struct Image {
impl Image {
pub fn from_reader<R: Read + Seek>(
reader: &mut SmartReader<R>,
reader: &mut EndianReader<R>,
ifd: Directory,
limits: &Limits,
bigtiff: bool,
@@ -113,15 +110,14 @@ impl Image {
// Try to parse both the compression method and the number, format, and bits of the included samples.
// If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
let compression_method = match tag_reader.find_tag(Tag::Compression)? {
Some(val) => CompressionMethod::from_u16(val.into_u16()?)
.ok_or(TiffUnsupportedError::UnknownCompressionMethod)?,
Some(val) => CompressionMethod::from_u16_exhaustive(val.into_u16()?),
None => CompressionMethod::None,
};
let jpeg_tables = if compression_method == CompressionMethod::ModernJPEG
&& ifd.contains_key(&Tag::JPEGTables)
{
let mut vec = tag_reader
let vec = tag_reader
.find_tag(Tag::JPEGTables)?
.unwrap()
.into_u8_vec()?;
@@ -131,22 +127,19 @@ impl Image {
));
}
// TODO: Can we avoid this somehow?
if photometric_interpretation == PhotometricInterpretation::RGB {
add_app14segment(&mut vec, JpegTagApp14Transform::App14TransformUnknown)
}
Some(Arc::new(vec))
} else {
None
};
let samples = tag_reader
let samples: u16 = tag_reader
.find_tag(Tag::SamplesPerPixel)?
.map(Value::into_u16)
.transpose()?
.unwrap_or(1)
.try_into()?;
.unwrap_or(1);
if samples == 0 {
return Err(TiffFormatError::SamplesPerPixelIsZero.into());
}
let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
Some(vals) => {
@@ -160,17 +153,28 @@ impl Image {
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
}
sample_format
sample_format[0]
}
None => vec![SampleFormat::Uint],
None => SampleFormat::Uint,
};
let bits_per_sample = match samples {
1 | 3 | 4 => tag_reader
.find_tag_uint_vec(Tag::BitsPerSample)?
.unwrap_or_else(|| vec![1]),
_ => return Err(TiffUnsupportedError::UnsupportedSampleDepth(samples).into()),
};
let bits_per_sample: Vec<u8> = tag_reader
.find_tag_uint_vec(Tag::BitsPerSample)?
.unwrap_or_else(|| vec![1]);
// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows
// it to be a single value that applies to all samples.
if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 {
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
}
// This library (and libtiff) do not support mixed sample formats and zero bits per sample
// doesn't make sense.
if bits_per_sample.iter().any(|&b| b != bits_per_sample[0]) || bits_per_sample[0] == 0 {
return Err(TiffUnsupportedError::InconsistentBitsPerSample(bits_per_sample).into());
}
let predictor = tag_reader
.find_tag(Tag::Predictor)?
@@ -183,6 +187,23 @@ impl Image {
.transpose()?
.unwrap_or(Predictor::None);
let planar_config = tag_reader
.find_tag(Tag::PlanarConfiguration)?
.map(Value::into_u16)
.transpose()?
.map(|p| {
PlanarConfiguration::from_u16(p).ok_or(TiffError::FormatError(
TiffFormatError::UnknownPlanarConfiguration(p),
))
})
.transpose()?
.unwrap_or(PlanarConfiguration::Chunky);
let planes = match planar_config {
PlanarConfiguration::Chunky => 1,
PlanarConfiguration::Planar => samples,
};
let chunk_type;
let chunk_offsets;
let chunk_bytes;
@@ -216,7 +237,7 @@ impl Image {
if chunk_offsets.len() != chunk_bytes.len()
|| rows_per_strip == 0
|| u32::try_from(chunk_offsets.len())?
!= height.saturating_sub(1) / rows_per_strip + 1
!= (height.saturating_sub(1) / rows_per_strip + 1) * planes as u32
{
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
@@ -255,7 +276,8 @@ impl Image {
let tile = tile_attributes.as_ref().unwrap();
if chunk_offsets.len() != chunk_bytes.len()
|| chunk_offsets.len() != tile.tiles_down() * tile.tiles_across()
|| chunk_offsets.len()
!= tile.tiles_down() * tile.tiles_across() * planes as usize
{
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
@@ -273,7 +295,7 @@ impl Image {
ifd: Some(ifd),
width,
height,
bits_per_sample,
bits_per_sample: bits_per_sample[0],
samples,
sample_format,
photometric_interpretation,
@@ -281,6 +303,7 @@ impl Image {
jpeg_tables,
predictor,
chunk_type,
planar_config,
strip_decoder,
tile_attributes,
chunk_offsets,
@@ -290,9 +313,9 @@ impl Image {
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
match self.photometric_interpretation {
PhotometricInterpretation::RGB => match self.bits_per_sample[..] {
[r, g, b] if [r, r] == [g, b] => Ok(ColorType::RGB(r)),
[r, g, b, a] if [r, r, r] == [g, b, a] => Ok(ColorType::RGBA(r)),
PhotometricInterpretation::RGB => match self.samples {
3 => Ok(ColorType::RGB(self.bits_per_sample)),
4 => Ok(ColorType::RGBA(self.bits_per_sample)),
// FIXME: We should _ignore_ other components. In particular:
// > Beware of extra components. Some TIFF files may have more components per pixel
// than you think. A Baseline TIFF reader must skip over them gracefully,using the
@@ -301,30 +324,45 @@ impl Image {
_ => Err(TiffError::UnsupportedError(
TiffUnsupportedError::InterpretationWithBits(
self.photometric_interpretation,
self.bits_per_sample.clone(),
vec![self.bits_per_sample; self.samples as usize],
),
)),
},
PhotometricInterpretation::CMYK => match self.bits_per_sample[..] {
[c, m, y, k] if [c, c, c] == [m, y, k] => Ok(ColorType::CMYK(c)),
PhotometricInterpretation::CMYK => match self.samples {
4 => Ok(ColorType::CMYK(self.bits_per_sample)),
5 => Ok(ColorType::CMYKA(self.bits_per_sample)),
_ => Err(TiffError::UnsupportedError(
TiffUnsupportedError::InterpretationWithBits(
self.photometric_interpretation,
self.bits_per_sample.clone(),
vec![self.bits_per_sample; self.samples as usize],
),
)),
},
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
if self.bits_per_sample.len() == 1 =>
{
Ok(ColorType::Gray(self.bits_per_sample[0]))
PhotometricInterpretation::YCbCr => match self.samples {
3 => Ok(ColorType::YCbCr(self.bits_per_sample)),
_ => Err(TiffError::UnsupportedError(
TiffUnsupportedError::InterpretationWithBits(
self.photometric_interpretation,
vec![self.bits_per_sample; self.samples as usize],
),
)),
},
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
match self.samples {
1 => Ok(ColorType::Gray(self.bits_per_sample)),
_ => Ok(ColorType::Multiband {
bit_depth: self.bits_per_sample,
num_samples: self.samples,
}),
}
}
// TODO: this is bad we should not fail at this point
_ => Err(TiffError::UnsupportedError(
PhotometricInterpretation::RGBPalette
| PhotometricInterpretation::TransparencyMask
| PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError(
TiffUnsupportedError::InterpretationWithBits(
self.photometric_interpretation,
self.bits_per_sample.clone(),
vec![self.bits_per_sample; self.samples as usize],
),
)),
}
@@ -332,37 +370,91 @@ impl Image {
fn create_reader<'r, R: 'r + Read>(
reader: R,
#[allow(unused_variables)] photometric_interpretation: PhotometricInterpretation,
compression_method: CompressionMethod,
compressed_length: u64,
jpeg_tables: Option<Arc<Vec<u8>>>,
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
) -> TiffResult<Box<dyn Read + 'r>> {
Ok(match compression_method {
CompressionMethod::None => Box::new(reader),
CompressionMethod::LZW => {
Box::new(LZWReader::new(reader, usize::try_from(compressed_length)?))
}
#[cfg(feature = "lzw")]
CompressionMethod::LZW => Box::new(super::stream::LZWReader::new(
reader,
usize::try_from(compressed_length)?,
)),
#[cfg(feature = "zstd")]
CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?),
CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)),
#[cfg(feature = "deflate")]
CompressionMethod::Deflate | CompressionMethod::OldDeflate => {
Box::new(DeflateReader::new(reader))
Box::new(super::stream::DeflateReader::new(reader))
}
#[cfg(feature = "jpeg")]
CompressionMethod::ModernJPEG => {
use zune_jpeg::zune_core;
if jpeg_tables.is_some() && compressed_length < 2 {
return Err(TiffError::FormatError(
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
));
}
let jpeg_reader = JpegReader::new(reader, compressed_length, jpeg_tables)?;
let mut decoder = jpeg::Decoder::new(jpeg_reader);
let data = match decoder.decode() {
Ok(data) => data,
Err(e) => {
return Err(TiffError::FormatError(TiffFormatError::Format(
e.to_string(),
)))
// Construct new jpeg_reader wrapping a SmartReader.
//
// JPEG compression in TIFF allows saving quantization and/or huffman tables in one
// central location. These `jpeg_tables` are simply prepended to the remaining jpeg image data.
// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
// which is also at the beginning of the remaining JPEG image data and would
// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
// this has to be removed as well (last two bytes of `jpeg_tables`).
let mut jpeg_reader = match jpeg_tables {
Some(jpeg_tables) => {
let mut reader = reader.take(compressed_length);
reader.read_exact(&mut [0; 2])?;
Box::new(
Cursor::new(&jpeg_tables[..jpeg_tables.len() - 2])
.chain(reader.take(compressed_length)),
) as Box<dyn Read>
}
None => Box::new(reader.take(compressed_length)),
};
let mut jpeg_data = Vec::new();
jpeg_reader.read_to_end(&mut jpeg_data)?;
let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
let mut options: zune_core::options::DecoderOptions = Default::default();
match photometric_interpretation {
PhotometricInterpretation::RGB => {
options =
options.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::RGB);
}
PhotometricInterpretation::CMYK => {
options = options
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::CMYK);
}
PhotometricInterpretation::YCbCr => {
options = options
.jpeg_set_out_colorspace(zune_core::colorspace::ColorSpace::YCbCr);
}
PhotometricInterpretation::WhiteIsZero
| PhotometricInterpretation::BlackIsZero
| PhotometricInterpretation::TransparencyMask => {}
PhotometricInterpretation::RGBPalette | PhotometricInterpretation::CIELab => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedInterpretation(
photometric_interpretation,
),
));
}
}
decoder.set_options(options);
let data = decoder.decode()?;
Box::new(Cursor::new(data))
}
method => {
@@ -373,6 +465,28 @@ impl Image {
})
}
/// Samples per pixel within chunk.
///
/// In planar config, samples are stored in separate strips/chunks, also called bands.
///
/// Example with `bits_per_sample = [8, 8, 8]` and `PhotometricInterpretation::RGB`:
/// * `PlanarConfiguration::Chunky` -> 3 (RGBRGBRGB...)
/// * `PlanarConfiguration::Planar` -> 1 (RRR...) (GGG...) (BBB...)
pub(crate) fn samples_per_pixel(&self) -> usize {
match self.planar_config {
PlanarConfiguration::Chunky => self.samples.into(),
PlanarConfiguration::Planar => 1,
}
}
/// Number of strips per pixel.
pub(crate) fn strips_per_pixel(&self) -> usize {
match self.planar_config {
PlanarConfiguration::Chunky => 1,
PlanarConfiguration::Planar => self.samples.into(),
}
}
pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> {
let file_offset = self
.chunk_offsets
@@ -412,7 +526,10 @@ impl Image {
match self.chunk_type {
ChunkType::Strip => {
let strip_height_without_padding = chunk_index
let strip_attrs = self.strip_decoder.as_ref().unwrap();
let strips_per_band =
self.height.saturating_sub(1) / strip_attrs.rows_per_strip + 1;
let strip_height_without_padding = (chunk_index % strips_per_band)
.checked_mul(dims.1)
.and_then(|x| self.height.checked_sub(x))
.ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex(
@@ -439,50 +556,61 @@ impl Image {
pub(crate) fn expand_chunk(
&self,
reader: impl Read,
mut buffer: DecodingBuffer,
output_width: usize,
buf: &mut [u8],
output_row_stride: usize,
byte_order: ByteOrder,
chunk_index: u32,
limits: &Limits,
) -> TiffResult<()> {
// Validate that the provided buffer is of the expected type.
// Validate that the color type is supported.
let color_type = self.colortype()?;
match (color_type, &buffer) {
(ColorType::RGB(n), _)
| (ColorType::RGBA(n), _)
| (ColorType::CMYK(n), _)
| (ColorType::Gray(n), _)
if usize::from(n) == buffer.byte_len() * 8 => {}
(ColorType::Gray(n), DecodingBuffer::U8(_)) if n < 8 => match self.predictor {
match color_type {
ColorType::RGB(n)
| ColorType::RGBA(n)
| ColorType::CMYK(n)
| ColorType::CMYKA(n)
| ColorType::YCbCr(n)
| ColorType::Gray(n)
| ColorType::Multiband {
bit_depth: n,
num_samples: _,
} if n == 8 || n == 16 || n == 32 || n == 64 => {}
ColorType::Gray(n)
| ColorType::Multiband {
bit_depth: n,
num_samples: _,
} if n < 8 => match self.predictor {
Predictor::None => {}
Predictor::Horizontal => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::HorizontalPredictor(color_type),
))
));
}
Predictor::FloatingPoint => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::FloatingPointPredictor(color_type),
));
}
Predictor::__NonExhaustive => unreachable!(),
},
(type_, _) => {
type_ => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedColorType(type_),
))
));
}
}
// Validate that the predictor is supported for the sample type.
match (self.predictor, &buffer) {
(Predictor::Horizontal, DecodingBuffer::F32(_))
| (Predictor::Horizontal, DecodingBuffer::F64(_)) => {
match (self.predictor, self.sample_format) {
(
Predictor::Horizontal,
SampleFormat::Int | SampleFormat::Uint | SampleFormat::IEEEFP,
) => {}
(Predictor::Horizontal, _) => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::HorizontalPredictor(color_type),
));
}
(Predictor::FloatingPoint, DecodingBuffer::F32(_))
| (Predictor::FloatingPoint, DecodingBuffer::F64(_)) => {}
(Predictor::FloatingPoint, SampleFormat::IEEEFP) => {}
(Predictor::FloatingPoint, _) => {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::FloatingPointPredictor(color_type),
@@ -497,73 +625,98 @@ impl Image {
.ok_or(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
))?;
if *compressed_bytes > limits.intermediate_buffer_size as u64 {
return Err(TiffError::LimitsExceeded);
}
let byte_len = buffer.byte_len();
let compression_method = self.compression_method;
let photometric_interpretation = self.photometric_interpretation;
let predictor = self.predictor;
let samples = self.bits_per_sample.len();
let samples = self.samples_per_pixel();
let chunk_dims = self.chunk_dimensions()?;
let data_dims = self.chunk_data_dimensions(chunk_index)?;
let padding_right = chunk_dims.0 - data_dims.0;
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
let jpeg_tables = self.jpeg_tables.clone();
let mut reader =
Self::create_reader(reader, compression_method, *compressed_bytes, jpeg_tables)?;
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let data_row_bytes: usize = ((data_row_bits + 7) / 8).try_into()?;
if output_width == data_dims.0 as usize && padding_right == 0 {
let total_samples = data_dims.0 as usize * data_dims.1 as usize * samples;
let tile = &mut buffer.as_bytes_mut()[..total_samples * byte_len];
// TODO: Should these return errors instead?
assert!(output_row_stride >= data_row_bytes);
assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
let mut reader = Self::create_reader(
reader,
photometric_interpretation,
compression_method,
*compressed_bytes,
self.jpeg_tables.as_deref().map(|a| &**a),
)?;
if output_row_stride == chunk_row_bytes as usize {
let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
reader.read_exact(tile)?;
for row in 0..data_dims.1 as usize {
let row_start = row as usize * output_width as usize * samples;
let row_end = (row + 1) * output_width as usize * samples;
let row = buffer.subrange(row_start..row_end);
super::fix_endianness_and_predict(row, samples, byte_order, predictor);
for row in tile.chunks_mut(chunk_row_bytes as usize) {
super::fix_endianness_and_predict(
row,
color_type.bit_depth(),
samples,
byte_order,
predictor,
);
}
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(&mut buffer.subrange(0..total_samples), color_type);
super::invert_colors(tile, color_type, self.sample_format);
}
} else if padding_right > 0 && self.predictor == Predictor::FloatingPoint {
} else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint {
// The floating point predictor shuffles the padding bytes into the encoded output, so
// this case is handled specially when needed.
let mut encoded = vec![0u8; chunk_dims.0 as usize * samples * byte_len];
for row in 0..data_dims.1 as usize {
let row_start = row * output_width as usize * samples;
let row_end = row_start + data_dims.0 as usize * samples;
let mut encoded = vec![0u8; chunk_row_bytes];
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
reader.read_exact(&mut encoded)?;
match buffer.subrange(row_start..row_end) {
DecodingBuffer::F32(buf) => fp_predict_f32(&mut encoded, buf, samples),
DecodingBuffer::F64(buf) => fp_predict_f64(&mut encoded, buf, samples),
let row = &mut row[..data_row_bytes];
match color_type.bit_depth() {
16 => predict_f16(&mut encoded, row, samples),
32 => predict_f32(&mut encoded, row, samples),
64 => predict_f64(&mut encoded, row, samples),
_ => unreachable!(),
}
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type);
super::invert_colors(row, color_type, self.sample_format);
}
}
} else {
for row in 0..data_dims.1 as usize {
let row_start = row * output_width as usize * samples;
let row_end = row_start + data_dims.0 as usize * samples;
let row = &mut buffer.as_bytes_mut()[(row_start * byte_len)..(row_end * byte_len)];
for (i, row) in buf
.chunks_mut(output_row_stride)
.take(data_dims.1 as usize)
.enumerate()
{
let row = &mut row[..data_row_bytes];
reader.read_exact(row)?;
// Skip horizontal padding
if padding_right > 0 {
let len = u64::try_from(padding_right as usize * samples * byte_len)?;
if chunk_row_bytes > data_row_bytes {
let len = u64::try_from(chunk_row_bytes - data_row_bytes)?;
io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
}
let mut row = buffer.subrange(row_start..row_end);
super::fix_endianness_and_predict(row.copy(), samples, byte_order, predictor);
super::fix_endianness_and_predict(
row,
color_type.bit_depth(),
samples,
byte_order,
predictor,
);
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
super::invert_colors(&mut row, color_type);
super::invert_colors(row, color_type, self.sample_format);
}
}
}
+234 -322
View File
@@ -1,19 +1,18 @@
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::io::{self, Read, Seek};
use std::ops::Range;
use crate::tags::{
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat,
Tag, Type,
};
use crate::{
bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
};
use half::f16;
use self::ifd::Directory;
use self::image::Image;
use crate::tags::{
CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag, Type,
};
use self::stream::{ByteOrder, EndianReader, SmartReader};
use self::stream::{ByteOrder, EndianReader};
pub mod ifd;
mod image;
@@ -31,6 +30,8 @@ pub enum DecodingResult {
U32(Vec<u32>),
/// A vector of 64 bit unsigned ints
U64(Vec<u64>),
/// A vector of 16 bit IEEE floats (held in u16)
F16(Vec<f16>),
/// A vector of 32 bit IEEE floats
F32(Vec<f32>),
/// A vector of 64 bit IEEE floats
@@ -94,6 +95,14 @@ impl DecodingResult {
}
}
fn new_f16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / std::mem::size_of::<u16>() {
Err(TiffError::LimitsExceeded)
} else {
Ok(DecodingResult::F16(vec![f16::ZERO; size]))
}
}
fn new_i8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
if size > limits.decoding_buffer_size / std::mem::size_of::<i8>() {
Err(TiffError::LimitsExceeded)
@@ -132,6 +141,7 @@ impl DecodingResult {
DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]),
DecodingResult::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[start..]),
DecodingResult::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[start..]),
DecodingResult::F16(ref mut buf) => DecodingBuffer::F16(&mut buf[start..]),
DecodingResult::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[start..]),
DecodingResult::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[start..]),
DecodingResult::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[start..]),
@@ -152,6 +162,8 @@ pub enum DecodingBuffer<'a> {
U32(&'a mut [u32]),
/// A slice of 64 bit unsigned ints
U64(&'a mut [u64]),
/// A slice of 16 bit IEEE floats
F16(&'a mut [f16]),
/// A slice of 32 bit IEEE floats
F32(&'a mut [f32]),
/// A slice of 64 bit IEEE floats
@@ -167,60 +179,9 @@ pub enum DecodingBuffer<'a> {
}
impl<'a> DecodingBuffer<'a> {
fn byte_len(&self) -> usize {
match *self {
DecodingBuffer::U8(_) => 1,
DecodingBuffer::U16(_) => 2,
DecodingBuffer::U32(_) => 4,
DecodingBuffer::U64(_) => 8,
DecodingBuffer::F32(_) => 4,
DecodingBuffer::F64(_) => 8,
DecodingBuffer::I8(_) => 1,
DecodingBuffer::I16(_) => 2,
DecodingBuffer::I32(_) => 4,
DecodingBuffer::I64(_) => 8,
}
}
fn copy<'b>(&'b mut self) -> DecodingBuffer<'b>
where
'a: 'b,
{
match *self {
DecodingBuffer::U8(ref mut buf) => DecodingBuffer::U8(buf),
DecodingBuffer::U16(ref mut buf) => DecodingBuffer::U16(buf),
DecodingBuffer::U32(ref mut buf) => DecodingBuffer::U32(buf),
DecodingBuffer::U64(ref mut buf) => DecodingBuffer::U64(buf),
DecodingBuffer::F32(ref mut buf) => DecodingBuffer::F32(buf),
DecodingBuffer::F64(ref mut buf) => DecodingBuffer::F64(buf),
DecodingBuffer::I8(ref mut buf) => DecodingBuffer::I8(buf),
DecodingBuffer::I16(ref mut buf) => DecodingBuffer::I16(buf),
DecodingBuffer::I32(ref mut buf) => DecodingBuffer::I32(buf),
DecodingBuffer::I64(ref mut buf) => DecodingBuffer::I64(buf),
}
}
fn subrange<'b>(&'b mut self, range: Range<usize>) -> DecodingBuffer<'b>
where
'a: 'b,
{
match *self {
DecodingBuffer::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[range]),
DecodingBuffer::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[range]),
DecodingBuffer::U32(ref mut buf) => DecodingBuffer::U32(&mut buf[range]),
DecodingBuffer::U64(ref mut buf) => DecodingBuffer::U64(&mut buf[range]),
DecodingBuffer::F32(ref mut buf) => DecodingBuffer::F32(&mut buf[range]),
DecodingBuffer::F64(ref mut buf) => DecodingBuffer::F64(&mut buf[range]),
DecodingBuffer::I8(ref mut buf) => DecodingBuffer::I8(&mut buf[range]),
DecodingBuffer::I16(ref mut buf) => DecodingBuffer::I16(&mut buf[range]),
DecodingBuffer::I32(ref mut buf) => DecodingBuffer::I32(&mut buf[range]),
DecodingBuffer::I64(ref mut buf) => DecodingBuffer::I64(&mut buf[range]),
}
}
fn as_bytes_mut(&mut self) -> &mut [u8] {
match self {
DecodingBuffer::U8(buf) => &mut *buf,
DecodingBuffer::U8(ref mut buf) => buf,
DecodingBuffer::I8(buf) => bytecast::i8_as_ne_mut_bytes(buf),
DecodingBuffer::U16(buf) => bytecast::u16_as_ne_mut_bytes(buf),
DecodingBuffer::I16(buf) => bytecast::i16_as_ne_mut_bytes(buf),
@@ -228,6 +189,7 @@ impl<'a> DecodingBuffer<'a> {
DecodingBuffer::I32(buf) => bytecast::i32_as_ne_mut_bytes(buf),
DecodingBuffer::U64(buf) => bytecast::u64_as_ne_mut_bytes(buf),
DecodingBuffer::I64(buf) => bytecast::i64_as_ne_mut_bytes(buf),
DecodingBuffer::F16(buf) => bytecast::f16_as_ne_mut_bytes(buf),
DecodingBuffer::F32(buf) => bytecast::f32_as_ne_mut_bytes(buf),
DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf),
}
@@ -298,217 +260,190 @@ pub struct Decoder<R>
where
R: Read + Seek,
{
reader: SmartReader<R>,
reader: EndianReader<R>,
bigtiff: bool,
limits: Limits,
current_chunk: u32,
next_ifd: Option<u64>,
ifd_offsets: Vec<u64>,
seen_ifds: HashSet<u64>,
image: Image,
}
trait Wrapping {
fn wrapping_add(&self, other: Self) -> Self;
}
impl Wrapping for u8 {
fn wrapping_add(&self, other: Self) -> Self {
u8::wrapping_add(*self, other)
fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
match bit_depth {
0..=8 => {
for i in samples..buf.len() {
buf[i] = buf[i].wrapping_add(buf[i - samples]);
}
}
9..=16 => {
for i in (samples * 2..buf.len()).step_by(2) {
let v = u16::from_ne_bytes(buf[i..][..2].try_into().unwrap());
let p = u16::from_ne_bytes(buf[i - 2 * samples..][..2].try_into().unwrap());
buf[i..][..2].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
17..=32 => {
for i in (samples * 4..buf.len()).step_by(4) {
let v = u32::from_ne_bytes(buf[i..][..4].try_into().unwrap());
let p = u32::from_ne_bytes(buf[i - 4 * samples..][..4].try_into().unwrap());
buf[i..][..4].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
33..=64 => {
for i in (samples * 8..buf.len()).step_by(8) {
let v = u64::from_ne_bytes(buf[i..][..8].try_into().unwrap());
let p = u64::from_ne_bytes(buf[i - 8 * samples..][..8].try_into().unwrap());
buf[i..][..8].copy_from_slice(&(v.wrapping_add(p)).to_ne_bytes());
}
}
_ => {
unreachable!("Caller should have validated arguments. Please file a bug.")
}
}
}
impl Wrapping for u16 {
fn wrapping_add(&self, other: Self) -> Self {
u16::wrapping_add(*self, other)
fn predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) {
for i in samples..input.len() {
input[i] = input[i].wrapping_add(input[i - samples]);
}
}
impl Wrapping for u32 {
fn wrapping_add(&self, other: Self) -> Self {
u32::wrapping_add(*self, other)
}
}
impl Wrapping for u64 {
fn wrapping_add(&self, other: Self) -> Self {
u64::wrapping_add(*self, other)
}
}
impl Wrapping for i8 {
fn wrapping_add(&self, other: Self) -> Self {
i8::wrapping_add(*self, other)
}
}
impl Wrapping for i16 {
fn wrapping_add(&self, other: Self) -> Self {
i16::wrapping_add(*self, other)
}
}
impl Wrapping for i32 {
fn wrapping_add(&self, other: Self) -> Self {
i32::wrapping_add(*self, other)
}
}
impl Wrapping for i64 {
fn wrapping_add(&self, other: Self) -> Self {
i64::wrapping_add(*self, other)
}
}
fn rev_hpredict_nsamp<T: Copy + Wrapping>(image: &mut [T], samples: usize) {
for col in samples..image.len() {
image[col] = image[col].wrapping_add(image[col - samples]);
}
}
pub fn fp_predict_f32(input: &mut [u8], output: &mut [f32], samples: usize) {
rev_hpredict_nsamp(input, samples);
for i in 0..output.len() {
// TODO: use f32::from_be_bytes() when we can (version 1.40)
output[i] = f32::from_bits(u32::from_be_bytes([
input[input.len() / 4 * 0 + i],
input[input.len() / 4 * 1 + i],
for (i, chunk) in output.chunks_mut(4).enumerate() {
chunk.copy_from_slice(&u32::to_ne_bytes(u32::from_be_bytes([
input[i],
input[input.len() / 4 + i],
input[input.len() / 4 * 2 + i],
input[input.len() / 4 * 3 + i],
]));
])));
}
}
pub fn fp_predict_f64(input: &mut [u8], output: &mut [f64], samples: usize) {
rev_hpredict_nsamp(input, samples);
for i in 0..output.len() {
// TODO: use f64::from_be_bytes() when we can (version 1.40)
output[i] = f64::from_bits(u64::from_be_bytes([
input[input.len() / 8 * 0 + i],
input[input.len() / 8 * 1 + i],
fn predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) {
for i in samples..input.len() {
input[i] = input[i].wrapping_add(input[i - samples]);
}
for (i, chunk) in output.chunks_mut(2).enumerate() {
chunk.copy_from_slice(&u16::to_ne_bytes(u16::from_be_bytes([
input[i],
input[input.len() / 2 + i],
])));
}
}
fn predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) {
for i in samples..input.len() {
input[i] = input[i].wrapping_add(input[i - samples]);
}
for (i, chunk) in output.chunks_mut(8).enumerate() {
chunk.copy_from_slice(&u64::to_ne_bytes(u64::from_be_bytes([
input[i],
input[input.len() / 8 + i],
input[input.len() / 8 * 2 + i],
input[input.len() / 8 * 3 + i],
input[input.len() / 8 * 4 + i],
input[input.len() / 8 * 5 + i],
input[input.len() / 8 * 6 + i],
input[input.len() / 8 * 7 + i],
]));
])));
}
}
fn fix_endianness_and_predict(
mut image: DecodingBuffer,
buf: &mut [u8],
bit_depth: u8,
samples: usize,
byte_order: ByteOrder,
predictor: Predictor,
) {
match predictor {
Predictor::None => {
fix_endianness(&mut image, byte_order);
fix_endianness(buf, byte_order, bit_depth);
}
Predictor::Horizontal => {
fix_endianness(&mut image, byte_order);
match image {
DecodingBuffer::U8(buf) => rev_hpredict_nsamp(buf, samples),
DecodingBuffer::U16(buf) => rev_hpredict_nsamp(buf, samples),
DecodingBuffer::U32(buf) => rev_hpredict_nsamp(buf, samples),
DecodingBuffer::U64(buf) => rev_hpredict_nsamp(buf, samples),
DecodingBuffer::I8(buf) => rev_hpredict_nsamp(buf, samples),
DecodingBuffer::I16(buf) => rev_hpredict_nsamp(buf, samples),
DecodingBuffer::I32(buf) => rev_hpredict_nsamp(buf, samples),
DecodingBuffer::I64(buf) => rev_hpredict_nsamp(buf, samples),
DecodingBuffer::F32(_) | DecodingBuffer::F64(_) => {
unreachable!("Caller should have validated arguments. Please file a bug.")
}
}
fix_endianness(buf, byte_order, bit_depth);
rev_hpredict_nsamp(buf, bit_depth, samples);
}
Predictor::FloatingPoint => {
let mut buffer_copy = image.as_bytes_mut().to_vec();
match image {
DecodingBuffer::F32(buf) => fp_predict_f32(&mut buffer_copy, buf, samples),
DecodingBuffer::F64(buf) => fp_predict_f64(&mut buffer_copy, buf, samples),
let mut buffer_copy = buf.to_vec();
match bit_depth {
16 => predict_f16(&mut buffer_copy, buf, samples),
32 => predict_f32(&mut buffer_copy, buf, samples),
64 => predict_f64(&mut buffer_copy, buf, samples),
_ => unreachable!("Caller should have validated arguments. Please file a bug."),
}
}
Predictor::__NonExhaustive => unreachable!(),
}
}
fn invert_colors_unsigned<T>(buffer: &mut [T], max: T)
where
T: std::ops::Sub<T> + std::ops::Sub<Output = T> + Copy,
{
for datum in buffer.iter_mut() {
*datum = max - *datum
}
}
fn invert_colors_fp<T>(buffer: &mut [T], max: T)
where
T: std::ops::Sub<T> + std::ops::Sub<Output = T> + Copy,
{
for datum in buffer.iter_mut() {
// FIXME: assumes [0, 1) range for floats
*datum = max - *datum
}
}
fn invert_colors(buf: &mut DecodingBuffer, color_type: ColorType) {
match (color_type, buf) {
(ColorType::Gray(64), DecodingBuffer::U64(ref mut buffer)) => {
invert_colors_unsigned(buffer, 0xffff_ffff_ffff_ffff);
fn invert_colors(buf: &mut [u8], color_type: ColorType, sample_format: SampleFormat) {
match (color_type, sample_format) {
(ColorType::Gray(8), SampleFormat::Uint) => {
for x in buf {
*x = 0xff - *x;
}
}
(ColorType::Gray(32), DecodingBuffer::U32(ref mut buffer)) => {
invert_colors_unsigned(buffer, 0xffff_ffff);
(ColorType::Gray(16), SampleFormat::Uint) => {
for x in buf.chunks_mut(2) {
let v = u16::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(0xffff - v).to_ne_bytes());
}
}
(ColorType::Gray(16), DecodingBuffer::U16(ref mut buffer)) => {
invert_colors_unsigned(buffer, 0xffff);
(ColorType::Gray(32), SampleFormat::Uint) => {
for x in buf.chunks_mut(4) {
let v = u32::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(0xffff_ffff - v).to_ne_bytes());
}
}
(ColorType::Gray(n), DecodingBuffer::U8(ref mut buffer)) if n <= 8 => {
invert_colors_unsigned(buffer, 0xff);
(ColorType::Gray(64), SampleFormat::Uint) => {
for x in buf.chunks_mut(8) {
let v = u64::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(0xffff_ffff_ffff_ffff - v).to_ne_bytes());
}
}
(ColorType::Gray(32), DecodingBuffer::F32(ref mut buffer)) => {
invert_colors_fp(buffer, 1.0);
(ColorType::Gray(32), SampleFormat::IEEEFP) => {
for x in buf.chunks_mut(4) {
let v = f32::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
}
}
(ColorType::Gray(64), DecodingBuffer::F64(ref mut buffer)) => {
invert_colors_fp(buffer, 1.0);
(ColorType::Gray(64), SampleFormat::IEEEFP) => {
for x in buf.chunks_mut(8) {
let v = f64::from_ne_bytes(x.try_into().unwrap());
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
}
}
_ => {}
}
}
/// Fix endianness. If `byte_order` matches the host, then conversion is a no-op.
fn fix_endianness(buf: &mut DecodingBuffer, byte_order: ByteOrder) {
fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
match byte_order {
ByteOrder::LittleEndian => match buf {
DecodingBuffer::U8(_) | DecodingBuffer::I8(_) => {}
DecodingBuffer::U16(b) => b.iter_mut().for_each(|v| *v = u16::from_le(*v)),
DecodingBuffer::I16(b) => b.iter_mut().for_each(|v| *v = i16::from_le(*v)),
DecodingBuffer::U32(b) => b.iter_mut().for_each(|v| *v = u32::from_le(*v)),
DecodingBuffer::I32(b) => b.iter_mut().for_each(|v| *v = i32::from_le(*v)),
DecodingBuffer::U64(b) => b.iter_mut().for_each(|v| *v = u64::from_le(*v)),
DecodingBuffer::I64(b) => b.iter_mut().for_each(|v| *v = i64::from_le(*v)),
DecodingBuffer::F32(b) => b
.iter_mut()
.for_each(|v| *v = f32::from_bits(u32::from_le(v.to_bits()))),
DecodingBuffer::F64(b) => b
.iter_mut()
.for_each(|v| *v = f64::from_bits(u64::from_le(v.to_bits()))),
ByteOrder::LittleEndian => match bit_depth {
0..=8 => {}
9..=16 => buf.chunks_exact_mut(2).for_each(|v| {
v.copy_from_slice(&u16::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
}),
17..=32 => buf.chunks_exact_mut(4).for_each(|v| {
v.copy_from_slice(&u32::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
}),
_ => buf.chunks_exact_mut(8).for_each(|v| {
v.copy_from_slice(&u64::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes())
}),
},
ByteOrder::BigEndian => match buf {
DecodingBuffer::U8(_) | DecodingBuffer::I8(_) => {}
DecodingBuffer::U16(b) => b.iter_mut().for_each(|v| *v = u16::from_be(*v)),
DecodingBuffer::I16(b) => b.iter_mut().for_each(|v| *v = i16::from_be(*v)),
DecodingBuffer::U32(b) => b.iter_mut().for_each(|v| *v = u32::from_be(*v)),
DecodingBuffer::I32(b) => b.iter_mut().for_each(|v| *v = i32::from_be(*v)),
DecodingBuffer::U64(b) => b.iter_mut().for_each(|v| *v = u64::from_be(*v)),
DecodingBuffer::I64(b) => b.iter_mut().for_each(|v| *v = i64::from_be(*v)),
DecodingBuffer::F32(b) => b
.iter_mut()
.for_each(|v| *v = f32::from_bits(u32::from_be(v.to_bits()))),
DecodingBuffer::F64(b) => b
.iter_mut()
.for_each(|v| *v = f64::from_bits(u64::from_be(v.to_bits()))),
ByteOrder::BigEndian => match bit_depth {
0..=8 => {}
9..=16 => buf.chunks_exact_mut(2).for_each(|v| {
v.copy_from_slice(&u16::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
}),
17..=32 => buf.chunks_exact_mut(4).for_each(|v| {
v.copy_from_slice(&u32::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
}),
_ => buf.chunks_exact_mut(8).for_each(|v| {
v.copy_from_slice(&u64::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes())
}),
},
};
}
@@ -527,7 +462,7 @@ impl<R: Read + Seek> Decoder<R> {
))
}
};
let mut reader = SmartReader::wrap(r, byte_order);
let mut reader = EndianReader::new(r, byte_order);
let bigtiff = match reader.read_u16()? {
42 => false,
@@ -573,20 +508,20 @@ impl<R: Read + Seek> Decoder<R> {
ifd: None,
width: 0,
height: 0,
bits_per_sample: vec![1],
bits_per_sample: 1,
samples: 1,
sample_format: vec![SampleFormat::Uint],
sample_format: SampleFormat::Uint,
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
compression_method: CompressionMethod::None,
jpeg_tables: None,
predictor: Predictor::None,
chunk_type: ChunkType::Strip,
planar_config: PlanarConfiguration::Chunky,
strip_decoder: None,
tile_attributes: None,
chunk_offsets: Vec::new(),
chunk_bytes: Vec::new(),
},
current_chunk: 0,
};
decoder.next_image()?;
Ok(decoder)
@@ -638,7 +573,6 @@ impl<R: Read + Seek> Decoder<R> {
if let Some(ifd_offset) = self.ifd_offsets.get(ifd_index) {
let (ifd, _next_ifd) = Self::read_ifd(&mut self.reader, self.bigtiff, *ifd_offset)?;
self.current_chunk = 0;
self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?;
Ok(())
@@ -649,13 +583,6 @@ impl<R: Read + Seek> Decoder<R> {
}
}
/// Reset the decoder.
#[deprecated = "Never should have been public. Only use Decoder::new()"]
pub fn init(self) -> TiffResult<Decoder<R>> {
let Self { reader, .. } = self;
Self::new(reader.into_inner())
}
fn next_ifd(&mut self) -> TiffResult<(Directory, Option<u64>)> {
if self.next_ifd.is_none() {
return Err(TiffError::FormatError(
@@ -686,7 +613,6 @@ impl<R: Read + Seek> Decoder<R> {
pub fn next_image(&mut self) -> TiffResult<()> {
let (ifd, _next_ifd) = self.next_ifd()?;
self.current_chunk = 0;
self.image = Image::from_reader(&mut self.reader, ifd, &self.limits, self.bigtiff)?;
Ok(())
}
@@ -714,7 +640,7 @@ impl<R: Read + Seek> Decoder<R> {
#[inline]
pub fn read_byte(&mut self) -> Result<u8, io::Error> {
let mut buf = [0; 1];
self.reader.read_exact(&mut buf)?;
self.reader.inner().read_exact(&mut buf)?;
Ok(buf[0])
}
@@ -768,7 +694,7 @@ impl<R: Read + Seek> Decoder<R> {
#[inline]
pub fn read_string(&mut self, length: usize) -> TiffResult<String> {
let mut out = vec![0; length];
self.reader.read_exact(&mut out)?;
self.reader.inner().read_exact(&mut out)?;
// Strings may be null-terminated, so we trim anything downstream of the null byte
if let Some(first) = out.iter().position(|&b| b == 0) {
out.truncate(first);
@@ -785,7 +711,7 @@ impl<R: Read + Seek> Decoder<R> {
));
}
let mut val = [0; 4];
self.reader.read_exact(&mut val)?;
self.reader.inner().read_exact(&mut val)?;
Ok(val)
}
@@ -793,7 +719,7 @@ impl<R: Read + Seek> Decoder<R> {
#[inline]
pub fn read_offset_u64(&mut self) -> Result<[u8; 8], io::Error> {
let mut val = [0; 8];
self.reader.read_exact(&mut val)?;
self.reader.inner().read_exact(&mut val)?;
Ok(val)
}
@@ -805,7 +731,7 @@ impl<R: Read + Seek> Decoder<R> {
#[inline]
pub fn goto_offset_u64(&mut self, offset: u64) -> io::Result<()> {
self.reader.seek(io::SeekFrom::Start(offset)).map(|_| ())
self.reader.goto_offset(offset)
}
/// Reads a IFD entry.
@@ -816,7 +742,7 @@ impl<R: Read + Seek> Decoder<R> {
// Count 4 bytes
// Value 4 bytes either a pointer the value itself
fn read_entry(
reader: &mut SmartReader<R>,
reader: &mut EndianReader<R>,
bigtiff: bool,
) -> TiffResult<Option<(Tag, ifd::Entry)>> {
let tag = Tag::from_u16_exhaustive(reader.read_u16()?);
@@ -833,13 +759,13 @@ impl<R: Read + Seek> Decoder<R> {
let mut offset = [0; 8];
let count = reader.read_u64()?;
reader.read_exact(&mut offset)?;
reader.inner().read_exact(&mut offset)?;
ifd::Entry::new_u64(type_, count, offset)
} else {
let mut offset = [0; 4];
let count = reader.read_u32()?;
reader.read_exact(&mut offset)?;
reader.inner().read_exact(&mut offset)?;
ifd::Entry::new(type_, count, offset)
};
Ok(Some((tag, entry)))
@@ -847,7 +773,7 @@ impl<R: Read + Seek> Decoder<R> {
/// Reads the IFD starting at the indicated location.
fn read_ifd(
reader: &mut SmartReader<R>,
reader: &mut EndianReader<R>,
bigtiff: bool,
ifd_location: u64,
) -> TiffResult<(Directory, Option<u64>)> {
@@ -997,6 +923,15 @@ impl<R: Read + Seek> Decoder<R> {
self.get_tag(tag)?.into_string()
}
/// Returns an iterator over all tags in the current image, along with their values.
pub fn tag_iter(&mut self) -> impl Iterator<Item = TiffResult<(Tag, ifd::Value)>> + '_ {
self.image.ifd.as_ref().unwrap().iter().map(|(tag, entry)| {
entry
.val(&self.limits, self.bigtiff, &mut self.reader)
.map(|value| (*tag, value))
})
}
fn check_chunk_type(&self, expected: ChunkType) -> TiffResult<()> {
if expected != self.image().chunk_type {
return Err(TiffError::UsageError(UsageError::InvalidChunkType(
@@ -1028,7 +963,12 @@ impl<R: Read + Seek> Decoder<R> {
None => return Err(TiffError::IntSizeError),
};
Ok(height / rows_per_strip)
let strips = match self.image().planar_config {
PlanarConfiguration::Chunky => height / rows_per_strip,
PlanarConfiguration::Planar => height / rows_per_strip * self.image().samples as u32,
};
Ok(strips)
}
/// Number of tiles in image
@@ -1037,33 +977,6 @@ impl<R: Read + Seek> Decoder<R> {
Ok(u32::try_from(self.image().chunk_offsets.len())?)
}
#[deprecated = "Use read_image instead"]
pub fn read_jpeg(&mut self) -> TiffResult<DecodingResult> {
self.read_image()
}
#[deprecated = "Use read_chunk_to_buffer instead"]
pub fn read_strip_to_buffer(&mut self, mut buffer: DecodingBuffer) -> TiffResult<()> {
self.check_chunk_type(ChunkType::Strip)?;
let offset = self.image.chunk_file_range(self.current_chunk)?.0;
self.goto_offset_u64(offset)?;
let byte_order = self.reader.byte_order;
let output_width = usize::try_from(self.image().width)?;
self.image.expand_chunk(
&mut self.reader,
buffer.copy(),
output_width,
byte_order,
self.current_chunk,
)?;
self.current_chunk += 1;
Ok(())
}
pub fn read_chunk_to_buffer(
&mut self,
mut buffer: DecodingBuffer,
@@ -1071,43 +984,45 @@ impl<R: Read + Seek> Decoder<R> {
output_width: usize,
) -> TiffResult<()> {
let offset = self.image.chunk_file_range(chunk_index)?.0;
self.goto_offset_u64(offset)?;
self.reader.goto_offset(offset)?;
let byte_order = self.reader.byte_order;
let output_row_stride = (output_width as u64)
.saturating_mul(self.image.samples_per_pixel() as u64)
.saturating_mul(self.image.bits_per_sample as u64)
/ 8;
self.image.expand_chunk(
&mut self.reader,
buffer.copy(),
output_width,
self.reader.inner(),
buffer.as_bytes_mut(),
output_row_stride.try_into()?,
byte_order,
chunk_index,
&self.limits,
)?;
Ok(())
}
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
let buffer_size = match width
.checked_mul(height)
.and_then(|x| x.checked_mul(self.image().bits_per_sample.len()))
{
Some(s) => s,
None => return Err(TiffError::LimitsExceeded),
let bits_per_sample = self.image().bits_per_sample;
let row_samples = if bits_per_sample >= 8 {
width
} else {
((((width as u64) * bits_per_sample as u64) + 7) / 8)
.try_into()
.map_err(|_| TiffError::LimitsExceeded)?
};
let max_sample_bits = self
.image()
.bits_per_sample
.iter()
.cloned()
.max()
.unwrap_or(8);
match self
.image()
.sample_format
.first()
.unwrap_or(&SampleFormat::Uint)
{
let buffer_size = row_samples
.checked_mul(height)
.and_then(|x| x.checked_mul(self.image().samples_per_pixel()))
.ok_or(TiffError::LimitsExceeded)?;
let max_sample_bits = self.image().bits_per_sample;
match self.image().sample_format {
SampleFormat::Uint => match max_sample_bits {
n if n <= 8 => DecodingResult::new_u8(buffer_size, &self.limits),
n if n <= 16 => DecodingResult::new_u16(buffer_size, &self.limits),
@@ -1118,6 +1033,7 @@ impl<R: Read + Seek> Decoder<R> {
)),
},
SampleFormat::IEEEFP => match max_sample_bits {
16 => DecodingResult::new_f16(buffer_size, &self.limits),
32 => DecodingResult::new_f32(buffer_size, &self.limits),
64 => DecodingResult::new_f64(buffer_size, &self.limits),
n => Err(TiffError::UnsupportedError(
@@ -1133,36 +1049,10 @@ impl<R: Read + Seek> Decoder<R> {
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
)),
},
format => {
Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format.clone()]).into())
}
format => Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format]).into()),
}
}
/// Read a single strip from the image and return it as a Vector. This method does not return
/// information on the size of the strip read and so the caller must take care of the final
/// strip in the image as the data read may be less than the expected strip size. This interface
/// may change at a future date to reflect this.
#[deprecated = "Use read_chunk instead"]
pub fn read_strip(&mut self) -> TiffResult<DecodingResult> {
let result = self.read_chunk(self.current_chunk)?;
Ok(result)
}
/// Read a single tile from the image and return it as a Vector. This method does not return
/// information on the size of the tile read and so the caller must take care of tiles on the
/// rightmost column and the bottommost row in the image as the data read may be less than the
/// expected tile size. This interface may change at a future date to reflect this.
#[deprecated = "Use read_chunk instead"]
pub fn read_tile(&mut self) -> TiffResult<DecodingResult> {
let result = self.read_chunk(self.current_chunk)?;
self.current_chunk += 1;
Ok(result)
}
/// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector.
pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<DecodingResult> {
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
@@ -1208,29 +1098,51 @@ impl<R: Read + Seek> Decoder<R> {
));
}
let samples = self.image().bits_per_sample.len();
let samples = self.image().samples_per_pixel();
if samples == 0 {
return Err(TiffError::FormatError(
TiffFormatError::InconsistentSizesEncountered,
));
}
let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize;
let strip_samples = width as usize * chunk_dimensions.1 as usize * samples;
let output_row_bits = (width as u64 * self.image.bits_per_sample as u64)
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let output_row_stride: usize = ((output_row_bits + 7) / 8).try_into()?;
for chunk in 0..self.image().chunk_offsets.len() {
self.goto_offset_u64(self.image().chunk_offsets[chunk])?;
let chunk_row_bits = (chunk_dimensions.0 as u64 * self.image.bits_per_sample as u64)
.checked_mul(samples as u64)
.ok_or(TiffError::LimitsExceeded)?;
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize;
if chunks_across > 1 && chunk_row_bits % 8 != 0 {
return Err(TiffError::UnsupportedError(
TiffUnsupportedError::MisalignedTileBoundaries,
));
}
let image_chunks = self.image().chunk_offsets.len() / self.image().strips_per_pixel();
// For multi-band images, only the first band is read.
// Possible improvements:
// * pass requested band as parameter
// * collect bands to a RGB encoding result in case of RGB bands
for chunk in 0..image_chunks {
self.reader.goto_offset(self.image().chunk_offsets[chunk])?;
let x = chunk % chunks_across;
let y = chunk / chunks_across;
let buffer_offset = y * strip_samples + x * chunk_dimensions.0 as usize * samples;
let buffer_offset =
y * output_row_stride * chunk_dimensions.1 as usize + x * chunk_row_bytes;
let byte_order = self.reader.byte_order;
self.image.expand_chunk(
&mut self.reader,
result.as_buffer(buffer_offset).copy(),
width as usize,
self.reader.inner(),
&mut result.as_buffer(0).as_bytes_mut()[buffer_offset..],
output_row_stride,
byte_order,
chunk as u32,
&self.limits,
)?;
}
+59 -247
View File
@@ -1,8 +1,6 @@
//! All IO functionality needed for TIFF decoding
use std::convert::TryFrom;
use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Take};
use std::sync::Arc;
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
/// Byte order of the TIFF file.
#[derive(Clone, Copy, Debug)]
@@ -14,16 +12,32 @@ pub enum ByteOrder {
}
/// Reader that is aware of the byte order.
pub trait EndianReader: Read {
/// Byte order that should be adhered to
fn byte_order(&self) -> ByteOrder;
#[derive(Debug)]
pub struct EndianReader<R> {
reader: R,
pub(crate) byte_order: ByteOrder,
}
impl<R: Read + Seek> EndianReader<R> {
pub fn new(reader: R, byte_order: ByteOrder) -> Self {
Self { reader, byte_order }
}
pub fn inner(&mut self) -> &mut R {
&mut self.reader
}
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
self.reader.seek(io::SeekFrom::Start(offset))?;
Ok(())
}
/// Reads an u16
#[inline(always)]
fn read_u16(&mut self) -> Result<u16, io::Error> {
pub fn read_u16(&mut self) -> Result<u16, io::Error> {
let mut n = [0u8; 2];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
self.reader.read_exact(&mut n)?;
Ok(match self.byte_order {
ByteOrder::LittleEndian => u16::from_le_bytes(n),
ByteOrder::BigEndian => u16::from_be_bytes(n),
})
@@ -31,10 +45,10 @@ pub trait EndianReader: Read {
/// Reads an i8
#[inline(always)]
fn read_i8(&mut self) -> Result<i8, io::Error> {
pub fn read_i8(&mut self) -> Result<i8, io::Error> {
let mut n = [0u8; 1];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
self.reader.read_exact(&mut n)?;
Ok(match self.byte_order {
ByteOrder::LittleEndian => i8::from_le_bytes(n),
ByteOrder::BigEndian => i8::from_be_bytes(n),
})
@@ -42,10 +56,10 @@ pub trait EndianReader: Read {
/// Reads an i16
#[inline(always)]
fn read_i16(&mut self) -> Result<i16, io::Error> {
pub fn read_i16(&mut self) -> Result<i16, io::Error> {
let mut n = [0u8; 2];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
self.reader.read_exact(&mut n)?;
Ok(match self.byte_order {
ByteOrder::LittleEndian => i16::from_le_bytes(n),
ByteOrder::BigEndian => i16::from_be_bytes(n),
})
@@ -53,10 +67,10 @@ pub trait EndianReader: Read {
/// Reads an u32
#[inline(always)]
fn read_u32(&mut self) -> Result<u32, io::Error> {
pub fn read_u32(&mut self) -> Result<u32, io::Error> {
let mut n = [0u8; 4];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
self.reader.read_exact(&mut n)?;
Ok(match self.byte_order {
ByteOrder::LittleEndian => u32::from_le_bytes(n),
ByteOrder::BigEndian => u32::from_be_bytes(n),
})
@@ -64,10 +78,10 @@ pub trait EndianReader: Read {
/// Reads an i32
#[inline(always)]
fn read_i32(&mut self) -> Result<i32, io::Error> {
pub fn read_i32(&mut self) -> Result<i32, io::Error> {
let mut n = [0u8; 4];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
self.reader.read_exact(&mut n)?;
Ok(match self.byte_order {
ByteOrder::LittleEndian => i32::from_le_bytes(n),
ByteOrder::BigEndian => i32::from_be_bytes(n),
})
@@ -75,10 +89,10 @@ pub trait EndianReader: Read {
/// Reads an u64
#[inline(always)]
fn read_u64(&mut self) -> Result<u64, io::Error> {
pub fn read_u64(&mut self) -> Result<u64, io::Error> {
let mut n = [0u8; 8];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
self.reader.read_exact(&mut n)?;
Ok(match self.byte_order {
ByteOrder::LittleEndian => u64::from_le_bytes(n),
ByteOrder::BigEndian => u64::from_be_bytes(n),
})
@@ -86,10 +100,10 @@ pub trait EndianReader: Read {
/// Reads an i64
#[inline(always)]
fn read_i64(&mut self) -> Result<i64, io::Error> {
pub fn read_i64(&mut self) -> Result<i64, io::Error> {
let mut n = [0u8; 8];
self.read_exact(&mut n)?;
Ok(match self.byte_order() {
self.reader.read_exact(&mut n)?;
Ok(match self.byte_order {
ByteOrder::LittleEndian => i64::from_le_bytes(n),
ByteOrder::BigEndian => i64::from_be_bytes(n),
})
@@ -97,10 +111,10 @@ pub trait EndianReader: Read {
/// Reads an f32
#[inline(always)]
fn read_f32(&mut self) -> Result<f32, io::Error> {
pub fn read_f32(&mut self) -> Result<f32, io::Error> {
let mut n = [0u8; 4];
self.read_exact(&mut n)?;
Ok(f32::from_bits(match self.byte_order() {
self.reader.read_exact(&mut n)?;
Ok(f32::from_bits(match self.byte_order {
ByteOrder::LittleEndian => u32::from_le_bytes(n),
ByteOrder::BigEndian => u32::from_be_bytes(n),
}))
@@ -108,10 +122,10 @@ pub trait EndianReader: Read {
/// Reads an f64
#[inline(always)]
fn read_f64(&mut self) -> Result<f64, io::Error> {
pub fn read_f64(&mut self) -> Result<f64, io::Error> {
let mut n = [0u8; 8];
self.read_exact(&mut n)?;
Ok(f64::from_bits(match self.byte_order() {
self.reader.read_exact(&mut n)?;
Ok(f64::from_bits(match self.byte_order {
ByteOrder::LittleEndian => u64::from_le_bytes(n),
ByteOrder::BigEndian => u64::from_be_bytes(n),
}))
@@ -126,6 +140,7 @@ pub trait EndianReader: Read {
/// ## Deflate Reader
///
#[cfg(feature = "deflate")]
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
///
@@ -133,11 +148,13 @@ pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
///
/// Reader that decompresses LZW streams
#[cfg(feature = "lzw")]
pub struct LZWReader<R: Read> {
reader: BufReader<Take<R>>,
decoder: weezl::decode::Decoder,
}
#[cfg(feature = "lzw")]
impl<R: Read> LZWReader<R> {
/// Wraps a reader
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
@@ -151,6 +168,7 @@ impl<R: Read> LZWReader<R> {
}
}
#[cfg(feature = "lzw")]
impl<R: Read> Read for LZWReader<R> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
loop {
@@ -183,156 +201,6 @@ impl<R: Read> Read for LZWReader<R> {
}
}
///
/// ## JPEG Reader (for "new-style" JPEG format (TIFF compression tag 7))
///
pub(crate) struct JpegReader {
jpeg_tables: Option<Arc<Vec<u8>>>,
buffer: io::Cursor<Vec<u8>>,
offset: usize,
}
impl JpegReader {
/// Constructs new JpegReader wrapping a SmartReader.
/// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one
/// central location, the constructor accepts this data as `jpeg_tables` here containing either
/// or both.
/// These `jpeg_tables` are simply prepended to the remaining jpeg image data.
/// Because these `jpeg_tables` start with a `SOI` (HEX: `0xFFD8`) or __start of image__ marker
/// which is also at the beginning of the remaining JPEG image data and would
/// confuse the JPEG renderer, one of these has to be taken off. In this case the first two
/// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
/// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
/// this has to be removed as well (last two bytes of `jpeg_tables`).
pub fn new<R: Read>(
mut reader: R,
length: u64,
jpeg_tables: Option<Arc<Vec<u8>>>,
) -> io::Result<JpegReader> {
// Read jpeg image data
let mut segment = vec![0; length as usize];
reader.read_exact(&mut segment[..])?;
match jpeg_tables {
Some(jpeg_tables) => {
assert!(
jpeg_tables.len() >= 2,
"jpeg_tables, if given, must be at least 2 bytes long. Got {:?}",
jpeg_tables
);
assert!(
length >= 2,
"if jpeg_tables is given, length must be at least 2 bytes long, got {}",
length
);
let mut buffer = io::Cursor::new(segment);
// Skip the first two bytes (marker bytes)
buffer.seek(SeekFrom::Start(2))?;
Ok(JpegReader {
buffer,
jpeg_tables: Some(jpeg_tables),
offset: 0,
})
}
None => Ok(JpegReader {
buffer: io::Cursor::new(segment),
jpeg_tables: None,
offset: 0,
}),
}
}
}
#[repr(u8)]
pub(crate) enum JpegTagApp14Transform {
// App14TransformYCCK = 2,
// App14TransformYCbCr = 1,
App14TransformUnknown = 0,
}
pub(crate) fn add_app14segment(jpeg_tables: &mut Vec<u8>, transform: JpegTagApp14Transform) {
// Add JPEG Tag APP14 Adobe segment to jpeg_tables.
// This segment stores image encoding information for DCT filters.
// When `transform` value is 0 which is defined as Unknown, jpeg-decoder interpret the
// color-space of image as RGB(3 channels) or CMYK(4).
let mut app14_offset = None;
let mut dht_offset = None;
for (offset, window) in jpeg_tables.windows(2).enumerate() {
if window == [0xff, 0xee] {
app14_offset = Some(offset);
break;
}
if window == [0xff, 0xc4] {
dht_offset = Some(offset);
}
}
match (app14_offset, dht_offset) {
(Some(i), _) => {
jpeg_tables[i + 16] = transform as u8;
}
(None, Some(sos_offset)) => {
let app14segment: [u8; 16] = [
0xff,
0xee,
0x00,
0x0e,
0x41,
0x64,
0x6f,
0x62,
0x65,
0x00,
0x64,
0x00,
0x00,
0x00,
0x00,
transform as u8,
];
jpeg_tables.splice(sos_offset..sos_offset, app14segment.iter().copied());
}
(None, None) => {}
}
}
impl Read for JpegReader {
// #[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut start = 0;
if let Some(jpeg_tables) = &self.jpeg_tables {
if jpeg_tables.len() - 2 > self.offset {
// Read (rest of) jpeg_tables to buf (without the last two bytes)
let size_remaining = jpeg_tables.len() - self.offset - 2;
let to_copy = size_remaining.min(buf.len());
buf[start..start + to_copy]
.copy_from_slice(&jpeg_tables[self.offset..self.offset + to_copy]);
self.offset += to_copy;
if to_copy == buf.len() {
return Ok(to_copy);
}
start += to_copy;
}
}
let read = self.buffer.read(&mut buf[start..])?;
self.offset += read;
Ok(read + start)
}
}
///
/// ## PackBits Reader
///
@@ -370,7 +238,7 @@ impl<R: Read> Read for PackBitsReader<R> {
let mut header: [u8; 1] = [0];
self.reader.read_exact(&mut header)?;
let h = header[0] as i8;
if h >= -127 && h <= -1 {
if (-127..=-1).contains(&h) {
let mut data: [u8; 1] = [0];
self.reader.read_exact(&mut data)?;
self.state = PackBitsReaderState::Repeat { value: data[0] };
@@ -384,79 +252,23 @@ impl<R: Read> Read for PackBitsReader<R> {
}
let length = buf.len().min(self.count);
match self.state {
PackBitsReaderState::Literal => {
self.reader.read(&mut buf[..length])?;
}
let actual = match self.state {
PackBitsReaderState::Literal => self.reader.read(&mut buf[..length])?,
PackBitsReaderState::Repeat { value } => {
for b in &mut buf[..length] {
*b = value;
}
length
}
PackBitsReaderState::Header => unreachable!(),
}
};
self.count -= length;
self.count -= actual;
if self.count == 0 {
self.state = PackBitsReaderState::Header;
}
return Ok(length);
}
}
///
/// ## SmartReader Reader
///
/// Reader that is aware of the byte order.
#[derive(Debug)]
pub struct SmartReader<R>
where
R: Read,
{
reader: R,
pub byte_order: ByteOrder,
}
impl<R> SmartReader<R>
where
R: Read,
{
/// Wraps a reader
pub fn wrap(reader: R, byte_order: ByteOrder) -> SmartReader<R> {
SmartReader { reader, byte_order }
}
pub fn into_inner(self) -> R {
self.reader
}
}
impl<R: Read + Seek> SmartReader<R> {
pub fn goto_offset(&mut self, offset: u64) -> io::Result<()> {
self.seek(io::SeekFrom::Start(offset)).map(|_| ())
}
}
impl<R> EndianReader for SmartReader<R>
where
R: Read,
{
#[inline(always)]
fn byte_order(&self) -> ByteOrder {
self.byte_order
}
}
impl<R: Read> Read for SmartReader<R> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.reader.read(buf)
}
}
impl<R: Read + Seek> Seek for SmartReader<R> {
#[inline]
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
self.reader.seek(pos)
Ok(actual)
}
}
+2 -3
View File
@@ -1,15 +1,14 @@
use std::convert::TryFrom;
use std::io::{Read, Seek};
use crate::tags::Tag;
use crate::{TiffError, TiffFormatError, TiffResult};
use super::ifd::{Directory, Value};
use super::stream::SmartReader;
use super::stream::EndianReader;
use super::Limits;
pub(crate) struct TagReader<'a, R: Read + Seek> {
pub reader: &'a mut SmartReader<R>,
pub reader: &'a mut EndianReader<R>,
pub ifd: &'a Directory,
pub limits: &'a Limits,
pub bigtiff: bool,
+116
View File
@@ -1,5 +1,31 @@
use crate::tags::{PhotometricInterpretation, SampleFormat};
macro_rules! integer_horizontal_predict {
() => {
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
let sample_size = Self::SAMPLE_FORMAT.len();
if row.len() < sample_size {
debug_assert!(false);
return;
}
let (start, rest) = row.split_at(sample_size);
result.extend_from_slice(start);
if result.capacity() - result.len() < rest.len() {
return;
}
result.extend(
row.into_iter()
.zip(rest)
.map(|(prev, current)| current.wrapping_sub(*prev)),
);
}
};
}
/// Trait for different colortypes that can be encoded.
pub trait ColorType {
/// The type of each sample of this colortype
@@ -10,6 +36,8 @@ pub trait ColorType {
const BITS_PER_SAMPLE: &'static [u16];
/// The value of the tiff tag `SampleFormat`
const SAMPLE_FORMAT: &'static [SampleFormat];
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
}
pub struct Gray8;
@@ -18,6 +46,8 @@ impl ColorType for Gray8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
}
pub struct GrayI8;
@@ -26,6 +56,8 @@ impl ColorType for GrayI8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
}
pub struct Gray16;
@@ -34,6 +66,8 @@ impl ColorType for Gray16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
}
pub struct GrayI16;
@@ -42,6 +76,8 @@ impl ColorType for GrayI16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
}
pub struct Gray32;
@@ -50,6 +86,8 @@ impl ColorType for Gray32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
}
pub struct GrayI32;
@@ -58,6 +96,8 @@ impl ColorType for GrayI32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
}
pub struct Gray32Float;
@@ -66,6 +106,10 @@ impl ColorType for Gray32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
}
pub struct Gray64;
@@ -74,6 +118,8 @@ impl ColorType for Gray64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint];
integer_horizontal_predict!();
}
pub struct GrayI64;
@@ -82,6 +128,8 @@ impl ColorType for GrayI64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Int];
integer_horizontal_predict!();
}
pub struct Gray64Float;
@@ -90,6 +138,10 @@ impl ColorType for Gray64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::BlackIsZero;
const BITS_PER_SAMPLE: &'static [u16] = &[64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
}
pub struct RGB8;
@@ -98,6 +150,8 @@ impl ColorType for RGB8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
}
pub struct RGB16;
@@ -106,6 +160,8 @@ impl ColorType for RGB16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
}
pub struct RGB32;
@@ -114,6 +170,8 @@ impl ColorType for RGB32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
}
pub struct RGB32Float;
@@ -122,6 +180,9 @@ impl ColorType for RGB32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
}
pub struct RGB64;
@@ -130,6 +191,8 @@ impl ColorType for RGB64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
}
pub struct RGB64Float;
@@ -138,6 +201,9 @@ impl ColorType for RGB64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
}
pub struct RGBA8;
@@ -146,6 +212,8 @@ impl ColorType for RGBA8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
}
pub struct RGBA16;
@@ -154,6 +222,8 @@ impl ColorType for RGBA16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
}
pub struct RGBA32;
@@ -162,6 +232,8 @@ impl ColorType for RGBA32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
}
pub struct RGBA32Float;
@@ -170,6 +242,9 @@ impl ColorType for RGBA32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
}
pub struct RGBA64;
@@ -178,6 +253,8 @@ impl ColorType for RGBA64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
}
pub struct RGBA64Float;
@@ -186,6 +263,9 @@ impl ColorType for RGBA64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
}
pub struct CMYK8;
@@ -194,6 +274,8 @@ impl ColorType for CMYK8 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
}
pub struct CMYK16;
@@ -202,6 +284,8 @@ impl ColorType for CMYK16 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[16, 16, 16, 16];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
}
pub struct CMYK32;
@@ -210,6 +294,8 @@ impl ColorType for CMYK32 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
}
pub struct CMYK32Float;
@@ -218,6 +304,10 @@ impl ColorType for CMYK32Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
}
pub struct CMYK64;
@@ -226,6 +316,8 @@ impl ColorType for CMYK64 {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 4];
integer_horizontal_predict!();
}
pub struct CMYK64Float;
@@ -234,4 +326,28 @@ impl ColorType for CMYK64Float {
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
unreachable!()
}
}
pub struct YCbCr8;
impl ColorType for YCbCr8 {
type Inner = u8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::YCbCr;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 3];
integer_horizontal_predict!();
}
pub struct CMYKA8;
impl ColorType for CMYKA8 {
type Inner = u8;
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::CMYK;
const BITS_PER_SAMPLE: &'static [u16] = &[8, 8, 8, 8, 8];
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::Uint; 5];
integer_horizontal_predict!();
}
+2 -3
View File
@@ -1,6 +1,5 @@
use crate::{encoder::compression::*, tags::CompressionMethod};
use crate::encoder::compression::*;
use flate2::{write::ZlibEncoder, Compression as FlateCompression};
use std::io::Write;
/// The Deflate algorithm used to compress image data in TIFF files.
#[derive(Debug, Clone, Copy)]
@@ -46,7 +45,7 @@ impl Compression for Deflate {
const COMPRESSION_METHOD: CompressionMethod = CompressionMethod::Deflate;
fn get_algorithm(&self) -> Compressor {
Compressor::Deflate(self.clone())
Compressor::Deflate(*self)
}
}
+2 -3
View File
@@ -1,5 +1,4 @@
use crate::{encoder::compression::*, tags::CompressionMethod};
use std::io::Write;
use crate::encoder::compression::*;
use weezl::encode::Encoder as LZWEncoder;
/// The LZW algorithm used to compress image data in TIFF files.
@@ -41,7 +40,7 @@ mod tests {
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Lzw::default().write_to(&mut writer, TEST_DATA).unwrap();
Lzw.write_to(&mut writer, TEST_DATA).unwrap();
assert_eq!(EXPECTED_COMPRESSED_DATA, compressed_data.as_slice());
}
}
+13 -3
View File
@@ -1,13 +1,19 @@
use crate::tags::CompressionMethod;
use std::io::{self, Write};
#[cfg(feature = "deflate")]
mod deflate;
#[cfg(feature = "lzw")]
mod lzw;
mod packbits;
mod uncompressed;
#[cfg(feature = "deflate")]
pub use self::deflate::{Deflate, DeflateLevel};
#[cfg(feature = "lzw")]
pub use self::lzw::Lzw;
pub use self::packbits::Packbits;
pub use self::uncompressed::Uncompressed;
@@ -28,9 +34,12 @@ pub trait Compression: CompressionAlgorithm {
}
/// An enum to store each compression algorithm.
#[non_exhaustive]
pub enum Compressor {
Uncompressed(Uncompressed),
#[cfg(feature = "lzw")]
Lzw(Lzw),
#[cfg(feature = "deflate")]
Deflate(Deflate),
Packbits(Packbits),
}
@@ -38,7 +47,7 @@ pub enum Compressor {
impl Default for Compressor {
/// The default compression strategy does not apply any compression.
fn default() -> Self {
Compressor::Uncompressed(Uncompressed::default())
Compressor::Uncompressed(Uncompressed)
}
}
@@ -46,7 +55,9 @@ impl CompressionAlgorithm for Compressor {
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
match self {
Compressor::Uncompressed(algorithm) => algorithm.write_to(writer, bytes),
#[cfg(feature = "lzw")]
Compressor::Lzw(algorithm) => algorithm.write_to(writer, bytes),
#[cfg(feature = "deflate")]
Compressor::Deflate(algorithm) => algorithm.write_to(writer, bytes),
Compressor::Packbits(algorithm) => algorithm.write_to(writer, bytes),
}
@@ -55,6 +66,5 @@ impl CompressionAlgorithm for Compressor {
#[cfg(test)]
mod tests {
pub const TEST_DATA: &'static [u8] =
b"This is a string for checking various compression algorithms.";
pub const TEST_DATA: &[u8] = b"This is a string for checking various compression algorithms.";
}
+32 -42
View File
@@ -1,5 +1,5 @@
use crate::{encoder::compression::*, tags::CompressionMethod};
use std::io::{BufWriter, Error, ErrorKind, Write};
use crate::encoder::compression::*;
use std::io::{BufWriter, Error, ErrorKind};
/// Compressor that uses the Packbits[^note] algorithm to compress bytes.
///
@@ -80,33 +80,31 @@ impl CompressionAlgorithm for Packbits {
run_index = 0;
in_run = false;
}
} else {
if bytes_pending > MAX_BYTES {
// We have as much differing data as we can output in one chunk.
// Output MAX_BYTES leaving one byte.
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(MAX_BYTES))?);
bufwriter.write(&bytes[pending_index..pending_index + MAX_BYTES as usize])?;
bytes_written += 1 + MAX_BYTES as u64;
} else if bytes_pending > MAX_BYTES {
// We have as much differing data as we can output in one chunk.
// Output MAX_BYTES leaving one byte.
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(MAX_BYTES))?);
bufwriter.write_all(&bytes[pending_index..pending_index + MAX_BYTES as usize])?;
bytes_written += 1 + MAX_BYTES as u64;
pending_index += MAX_BYTES as usize;
bytes_pending -= MAX_BYTES;
run_index = bytes_pending - 1; // A run could start here
} else if curr_byte == last_byte {
if (bytes_pending - run_index >= MIN_REPT) || (run_index == 0) {
// This is a worthwhile run
if run_index != 0 {
// Flush differing data out of input buffer
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(run_index))?);
bufwriter
.write(&bytes[pending_index..pending_index + run_index as usize])?;
bytes_written += 1 + run_index as u64;
}
bytes_pending -= run_index; // Length of run
in_run = true;
pending_index += MAX_BYTES as usize;
bytes_pending -= MAX_BYTES;
run_index = bytes_pending - 1; // A run could start here
} else if curr_byte == last_byte {
if (bytes_pending - run_index >= MIN_REPT) || (run_index == 0) {
// This is a worthwhile run
if run_index != 0 {
// Flush differing data out of input buffer
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(run_index))?);
bufwriter
.write_all(&bytes[pending_index..pending_index + run_index as usize])?;
bytes_written += 1 + run_index as u64;
}
} else {
run_index = bytes_pending - 1; // A run could start here
bytes_pending -= run_index; // Length of run
in_run = true;
}
} else {
run_index = bytes_pending - 1; // A run could start here
}
last_byte = curr_byte;
}
@@ -119,7 +117,7 @@ impl CompressionAlgorithm for Packbits {
} else {
bytes_written += 1 + bytes_pending as u64;
offset.get_or_insert(write_u8(&mut bufwriter, encode_diff(bytes_pending))?);
bufwriter.write(&bytes[pending_index..pending_index + bytes_pending as usize])?;
bufwriter.write_all(&bytes[pending_index..pending_index + bytes_pending as usize])?;
}
bufwriter.flush()?;
@@ -141,24 +139,20 @@ mod tests {
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits::default()
.write_to(&mut writer, &UNCOMPRESSED_DATA)
.unwrap();
Packbits.write_to(&mut writer, &UNCOMPRESSED_DATA).unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
#[test]
fn test_packbits_rept() {
// compress buffer with repetitive sequence
const UNCOMPRESSED_DATA: &'static [u8] =
const UNCOMPRESSED_DATA: &[u8] =
b"This strrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrring hangs.";
const EXPECTED_COMPRESSED_DATA: &'static [u8] = b"\x06This st\xD1r\x09ing hangs.";
const EXPECTED_COMPRESSED_DATA: &[u8] = b"\x06This st\xD1r\x09ing hangs.";
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits::default()
.write_to(&mut writer, UNCOMPRESSED_DATA)
.unwrap();
Packbits.write_to(&mut writer, UNCOMPRESSED_DATA).unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
@@ -192,23 +186,19 @@ mod tests {
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits::default()
.write_to(&mut writer, data.as_slice())
.unwrap();
Packbits.write_to(&mut writer, data.as_slice()).unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
#[test]
fn test_packbits() {
// compress teststring
const EXPECTED_COMPRESSED_DATA: &'static [u8] =
const EXPECTED_COMPRESSED_DATA: &[u8] =
b"\x3CThis is a string for checking various compression algorithms.";
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Packbits::default()
.write_to(&mut writer, TEST_DATA)
.unwrap();
Packbits.write_to(&mut writer, TEST_DATA).unwrap();
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
}
}
+2 -5
View File
@@ -1,5 +1,4 @@
use crate::{encoder::compression::*, tags::CompressionMethod};
use std::io::Write;
use crate::encoder::compression::*;
/// The default algorithm which does not compress at all.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@@ -29,9 +28,7 @@ mod tests {
fn test_no_compression() {
let mut compressed_data = Vec::<u8>::new();
let mut writer = Cursor::new(&mut compressed_data);
Uncompressed::default()
.write_to(&mut writer, TEST_DATA)
.unwrap();
Uncompressed.write_to(&mut writer, TEST_DATA).unwrap();
assert_eq!(TEST_DATA, compressed_data);
}
}
+126 -68
View File
@@ -3,7 +3,6 @@ pub use tiff_value::*;
use std::{
cmp,
collections::BTreeMap,
convert::{TryFrom, TryInto},
io::{self, Seek, Write},
marker::PhantomData,
mem,
@@ -11,8 +10,8 @@ use std::{
};
use crate::{
error::TiffResult,
tags::{CompressionMethod, ResolutionUnit, Tag},
error::{TiffResult, UsageError},
tags::{CompressionMethod, ResolutionUnit, SampleFormat, Tag},
TiffError, TiffFormatError,
};
@@ -22,9 +21,62 @@ mod tiff_value;
mod writer;
use self::colortype::*;
use self::compression::Compression as Comp;
use self::compression::*;
use self::writer::*;
/// Type of prediction to prepare the image with.
///
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
/// passes ran over the image data to prepare it for compression. This is mostly used for LZW
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
/// ratio over the unpredicted compression !
///
/// [Predictor::FloatingPoint] is currently not supported.
pub type Predictor = crate::tags::Predictor;
#[cfg(feature = "deflate")]
pub type DeflateLevel = compression::DeflateLevel;
#[derive(Clone, Copy, PartialEq)]
pub enum Compression {
Uncompressed,
#[cfg(feature = "lzw")]
Lzw,
#[cfg(feature = "deflate")]
Deflate(DeflateLevel),
Packbits,
}
impl Default for Compression {
fn default() -> Self {
Self::Uncompressed
}
}
impl Compression {
fn tag(&self) -> CompressionMethod {
match self {
Compression::Uncompressed => CompressionMethod::None,
#[cfg(feature = "lzw")]
Compression::Lzw => CompressionMethod::LZW,
#[cfg(feature = "deflate")]
Compression::Deflate(_) => CompressionMethod::Deflate,
Compression::Packbits => CompressionMethod::PackBits,
}
}
fn get_algorithm(&self) -> Compressor {
match self {
Compression::Uncompressed => compression::Uncompressed {}.get_algorithm(),
#[cfg(feature = "lzw")]
Compression::Lzw => compression::Lzw {}.get_algorithm(),
#[cfg(feature = "deflate")]
Compression::Deflate(level) => compression::Deflate::with_level(*level).get_algorithm(),
Compression::Packbits => compression::Packbits {}.get_algorithm(),
}
}
}
/// Encoder for Tiff and BigTiff files.
///
/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder`
@@ -53,6 +105,8 @@ use self::writer::*;
pub struct TiffEncoder<W, K: TiffKind = TiffKindStandard> {
writer: TiffWriter<W>,
kind: PhantomData<K>,
predictor: Predictor,
compression: Compression,
}
/// Constructor functions to create standard Tiff files.
@@ -84,6 +138,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
let mut encoder = TiffEncoder {
writer: TiffWriter::new(writer),
kind: PhantomData,
predictor: Predictor::None,
compression: Compression::Uncompressed,
};
K::write_header(&mut encoder.writer)?;
@@ -91,6 +147,23 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
Ok(encoder)
}
/// Set the predictor to use
///
/// A predictor is used to simplify the file before writing it. This is very
/// useful when writing a file compressed using LZW as it can improve efficiency
pub fn with_predictor(mut self, predictor: Predictor) -> Self {
self.predictor = predictor;
self
}
/// Set the compression method to use
pub fn with_compression(mut self, compression: Compression) -> Self {
self.compression = compression;
self
}
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
pub fn new_directory(&mut self) -> TiffResult<DirectoryEncoder<W, K>> {
DirectoryEncoder::new(&mut self.writer)
@@ -101,20 +174,9 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
&mut self,
width: u32,
height: u32,
) -> TiffResult<ImageEncoder<W, C, K, Uncompressed>> {
) -> TiffResult<ImageEncoder<W, C, K>> {
let encoder = DirectoryEncoder::new(&mut self.writer)?;
ImageEncoder::new(encoder, width, height)
}
/// Create an [`ImageEncoder`] to encode an image one slice at a time.
pub fn new_image_with_compression<C: ColorType, D: Compression>(
&mut self,
width: u32,
height: u32,
compression: D,
) -> TiffResult<ImageEncoder<W, C, K, D>> {
let encoder = DirectoryEncoder::new(&mut self.writer)?;
ImageEncoder::with_compression(encoder, width, height, compression)
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
}
/// Convenience function to write an entire image from memory.
@@ -128,24 +190,8 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
[C::Inner]: TiffValue,
{
let encoder = DirectoryEncoder::new(&mut self.writer)?;
let image: ImageEncoder<W, C, K> = ImageEncoder::new(encoder, width, height)?;
image.write_data(data)
}
/// Convenience function to write an entire image from memory with a given compression.
pub fn write_image_with_compression<C: ColorType, D: Compression>(
&mut self,
width: u32,
height: u32,
compression: D,
data: &[C::Inner],
) -> TiffResult<()>
where
[C::Inner]: TiffValue,
{
let encoder = DirectoryEncoder::new(&mut self.writer)?;
let image: ImageEncoder<W, C, K, D> =
ImageEncoder::with_compression(encoder, width, height, compression)?;
let image: ImageEncoder<W, C, K> =
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)?;
image.write_data(data)
}
}
@@ -219,19 +265,19 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
let offset = self.writer.offset();
K::write_entry_count(&mut self.writer, self.ifd.len())?;
K::write_entry_count(self.writer, self.ifd.len())?;
for (
tag,
&DirectoryEntry {
data_type: ref field_type,
ref count,
data: ref offset,
DirectoryEntry {
data_type: field_type,
count,
data: offset,
},
) in self.ifd.iter()
{
self.writer.write_u16(*tag)?;
self.writer.write_u16(*field_type)?;
(*count).write(&mut self.writer)?;
(*count).write(self.writer)?;
self.writer.write_bytes(offset)?;
}
@@ -243,7 +289,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
/// This could be used to write tiff strips.
pub fn write_data<T: TiffValue>(&mut self, value: T) -> TiffResult<u64> {
let offset = self.writer.offset();
value.write(&mut self.writer)?;
value.write(self.writer)?;
Ok(offset)
}
@@ -257,9 +303,9 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
let curr_pos = self.writer.offset();
self.writer.goto_offset(self.ifd_pointer_pos)?;
K::write_offset(&mut self.writer, ifd_pointer)?;
K::write_offset(self.writer, ifd_pointer)?;
self.writer.goto_offset(curr_pos)?;
K::write_offset(&mut self.writer, 0)?;
K::write_offset(self.writer, 0)?;
self.dropped = true;
@@ -313,13 +359,7 @@ impl<'a, W: Write + Seek, K: TiffKind> Drop for DirectoryEncoder<'a, W, K> {
/// # }
/// ```
/// You can also call write_data function wich will encode by strip and finish
pub struct ImageEncoder<
'a,
W: 'a + Write + Seek,
C: ColorType,
K: TiffKind,
D: Compression = Uncompressed,
> {
pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
encoder: DirectoryEncoder<'a, W, K>,
strip_idx: u64,
strip_count: u64,
@@ -330,25 +370,30 @@ pub struct ImageEncoder<
strip_offsets: Vec<K::OffsetType>,
strip_byte_count: Vec<K::OffsetType>,
dropped: bool,
compression: D,
compression: Compression,
predictor: Predictor,
_phantom: ::std::marker::PhantomData<C>,
}
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
ImageEncoder<'a, W, T, K, D>
{
fn new(encoder: DirectoryEncoder<'a, W, K>, width: u32, height: u32) -> TiffResult<Self>
where
D: Default,
{
Self::with_compression(encoder, width, height, D::default())
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
match (predictor, compression, T::SAMPLE_FORMAT[0]) {
(Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
}
(Predictor::FloatingPoint, _, _) => {
Err(TiffError::UsageError(UsageError::PredictorUnavailable))
}
_ => Ok(()),
}
}
fn with_compression(
fn new(
mut encoder: DirectoryEncoder<'a, W, K>,
width: u32,
height: u32,
compression: D,
compression: Compression,
predictor: Predictor,
) -> TiffResult<Self> {
if width == 0 || height == 0 {
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
@@ -356,13 +401,15 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
)));
}
Self::sanity_check(compression, predictor)?;
let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
// Limit the strip size to prevent potential memory and security issues.
// Also keep the multiple strip handling 'oiled'
let rows_per_strip = {
match D::COMPRESSION_METHOD {
match compression.tag() {
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
_ => (1_000_000 + row_bytes - 1) / row_bytes,
}
@@ -372,7 +419,8 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
encoder.write_tag(Tag::ImageWidth, width)?;
encoder.write_tag(Tag::ImageLength, height)?;
encoder.write_tag(Tag::Compression, D::COMPRESSION_METHOD.to_u16())?;
encoder.write_tag(Tag::Compression, compression.tag().to_u16())?;
encoder.write_tag(Tag::Predictor, predictor.to_u16())?;
encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
@@ -400,7 +448,8 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
strip_offsets: Vec::new(),
strip_byte_count: Vec::new(),
dropped: false,
compression: compression,
compression,
predictor,
_phantom: ::std::marker::PhantomData,
})
}
@@ -433,7 +482,18 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
}
// Write the (possible compressed) data to the encoder.
let offset = self.encoder.write_data(value)?;
let offset = match self.predictor {
Predictor::None => self.encoder.write_data(value)?,
Predictor::Horizontal => {
let mut row_result = Vec::with_capacity(value.len());
for row in value.chunks_exact(self.row_samples as usize) {
T::horizontal_predict(row, &mut row_result);
}
self.encoder.write_data(row_result.as_slice())?
}
_ => unimplemented!(),
};
let byte_count = self.encoder.last_written() as usize;
self.strip_offsets.push(K::convert_offset(offset)?);
@@ -553,9 +613,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
}
}
impl<'a, W: Write + Seek, C: ColorType, K: TiffKind, D: Compression> Drop
for ImageEncoder<'a, W, C, K, D>
{
impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W, C, K> {
fn drop(&mut self) {
if !self.dropped {
let _ = self.finish_internal();
+1 -1
View File
@@ -182,7 +182,7 @@ impl<W: Write> TiffWriter<W> {
impl<W: Seek> TiffWriter<W> {
pub fn goto_offset(&mut self, offset: u64) -> Result<(), io::Error> {
self.offset = offset;
self.writer.seek(SeekFrom::Start(offset as u64))?;
self.writer.seek(SeekFrom::Start(offset))?;
Ok(())
}
}
+176 -273
View File
@@ -1,275 +1,192 @@
use std::error::Error;
use std::fmt;
use std::io;
use std::str;
use std::string;
use crate::decoder::{ifd::Value, ChunkType};
use quick_error::quick_error;
use crate::decoder::ChunkType;
use crate::tags::{
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
};
use crate::ColorType;
use crate::weezl::LzwError;
quick_error! {
/// Tiff error kinds.
#[derive(Debug)]
pub enum TiffError {
/// The Image is not formatted properly.
FormatError(err: TiffFormatError) {
display("format error: {err}")
from()
source(err)
}
/// The Decoder does not support features required by the image.
UnsupportedError(err: TiffUnsupportedError) {
display("unsupported error: {err}")
from()
source(err)
}
/// An I/O Error occurred while decoding the image.
IoError(err: io::Error) {
display("{err}")
from()
source(err)
}
/// The Limits of the Decoder is exceeded.
LimitsExceeded {
display("decoder limits exceeded")
}
/// An integer conversion to or from a platform size failed.
IntSizeError {
display("platform or format size limits exceeded")
}
/// The image does not support the requested operation
UsageError(err: UsageError) {
display("usage error: {err}")
from()
source(err)
}
}
}
/// Tiff error kinds.
#[derive(Debug)]
pub enum TiffError {
/// The Image is not formatted properly.
FormatError(TiffFormatError),
quick_error! {
/// The image is not formatted properly.
///
/// This indicates that the encoder producing the image might behave incorrectly or that the
/// input file has been corrupted.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum TiffFormatError {
TiffSignatureNotFound {
display("TIFF signature not found")
}
TiffSignatureInvalid {
display("TIFF signature invalid")
}
ImageFileDirectoryNotFound {
display("image file directory not found")
}
InconsistentSizesEncountered {
display("inconsistent sizes encountered")
}
InvalidDimensions(width: u32, height: u32) {
display("invalid dimensions: {width}x{height}")
}
InvalidTag {
display("image contains invalid tag")
}
InvalidTagValueType(tag: Tag) {
display("tag `{tag:?}` did not have the expected value type")
}
RequiredTagNotFound(tag: Tag) {
display("required tag `{tag:?}` not found")
}
UnknownPredictor(pred: u16) {
display("unknown predictor “{pred}” encountered")
}
UnknownPlanarConfiguration(cfg: u16) {
display("unknown planar configuration “{cfg}”")
}
InvalidTypeForTag {
display("tag has invalid type")
}
StripTileTagConflict {
display("file should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found")
}
CycleInOffsets {
display("file contained a cycle in the list of IFDs")
}
SamplesPerPixelIsZero {
display("samples per pixel is zero")
}
CompressedDataCorrupt(message: String) {
display("compressed data is corrupt: {message}")
}
}
}
quick_error! {
/// The Decoder does not support features required by the image.
UnsupportedError(TiffUnsupportedError),
/// An I/O Error occurred while decoding the image.
IoError(io::Error),
/// The Limits of the Decoder is exceeded.
LimitsExceeded,
/// An integer conversion to or from a platform size failed, either due to
/// limits of the platform size or limits of the format.
IntSizeError,
/// The image does not support the requested operation
UsageError(UsageError),
}
/// The image is not formatted properly.
///
/// This indicates that the encoder producing the image might behave incorrectly or that the input
/// file has been corrupted.
///
/// The list of variants may grow to incorporate errors of future features. Matching against this
/// exhaustively is not covered by interface stability guarantees.
#[derive(Debug, Clone, PartialEq)]
pub enum TiffFormatError {
TiffSignatureNotFound,
TiffSignatureInvalid,
ImageFileDirectoryNotFound,
InconsistentSizesEncountered,
UnexpectedCompressedData {
actual_bytes: usize,
required_bytes: usize,
},
InconsistentStripSamples {
actual_samples: usize,
required_samples: usize,
},
InvalidDimensions(u32, u32),
InvalidTag,
InvalidTagValueType(Tag),
RequiredTagNotFound(Tag),
UnknownPredictor(u16),
ByteExpected(Value),
UnsignedIntegerExpected(Value),
SignedIntegerExpected(Value),
Format(String),
RequiredTagEmpty(Tag),
StripTileTagConflict,
CycleInOffsets,
#[doc(hidden)]
/// Do not match against this variant. It may get removed.
__NonExhaustive,
}
impl fmt::Display for TiffFormatError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::TiffFormatError::*;
match *self {
TiffSignatureNotFound => write!(fmt, "TIFF signature not found."),
TiffSignatureInvalid => write!(fmt, "TIFF signature invalid."),
ImageFileDirectoryNotFound => write!(fmt, "Image file directory not found."),
InconsistentSizesEncountered => write!(fmt, "Inconsistent sizes encountered."),
UnexpectedCompressedData {
actual_bytes,
required_bytes,
} => {
write!(
fmt,
"Decompression returned different amount of bytes than expected: got {}, expected {}.",
actual_bytes, required_bytes
)
}
InconsistentStripSamples {
actual_samples,
required_samples,
} => {
write!(
fmt,
"Inconsistent elements in strip: got {}, expected {}.",
actual_samples, required_samples
)
}
InvalidDimensions(width, height) => write!(fmt, "Invalid dimensions: {}x{}.", width, height),
InvalidTag => write!(fmt, "Image contains invalid tag."),
InvalidTagValueType(ref tag) => {
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
}
RequiredTagNotFound(ref tag) => write!(fmt, "Required tag `{:?}` not found.", tag),
UnknownPredictor(ref predictor) => {
write!(fmt, "Unknown predictor “{}” encountered", predictor)
}
ByteExpected(ref val) => write!(fmt, "Expected byte, {:?} found.", val),
UnsignedIntegerExpected(ref val) => {
write!(fmt, "Expected unsigned integer, {:?} found.", val)
}
SignedIntegerExpected(ref val) => {
write!(fmt, "Expected signed integer, {:?} found.", val)
}
Format(ref val) => write!(fmt, "Invalid format: {:?}.", val),
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", val),
StripTileTagConflict => write!(fmt, "File should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found."),
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
__NonExhaustive => unreachable!(),
///
/// 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.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TiffUnsupportedError {
FloatingPointPredictor(color_type: ColorType) {
display("floating point predictor for {color_type:?} is unsupported")
}
HorizontalPredictor(color_type: ColorType) {
display("horizontal predictor for {color_type:?} is unsupported")
}
InconsistentBitsPerSample(bits_per_sample: Vec<u8>) {
display("inconsistent bits per sample: {bits_per_sample:?}")
}
InterpretationWithBits(interpretation: PhotometricInterpretation, bits_per_sample: Vec<u8>) {
display("Photometric interpretation {interpretation:?} with bits per sample {bits_per_sample:?} is unsupported")
}
UnknownInterpretation {
display("unknown photometric interpretation")
}
UnknownCompressionMethod {
display("unknown compression method")
}
UnsupportedCompressionMethod(method: CompressionMethod) {
display("compression method {method:?} is unsupported")
}
UnsupportedSampleDepth(depth: u8) {
display("{depth} samples per pixel is unsupported")
}
UnsupportedSampleFormat(sample_format: Vec<SampleFormat>) {
display("sample format {sample_format:?} is unsupported")
}
UnsupportedColorType(color_type: ColorType) {
display("color type {color_type:?} is unsupported")
}
UnsupportedBitsPerChannel(bits_per_channel: u8) {
display("{bits_per_channel} bits per channel not supported")
}
UnsupportedPlanarConfig(planar: Option<PlanarConfiguration>) {
display("unsupported planar configuration “{planar:?}”")
}
UnsupportedDataType {
display("unsupported data type.")
}
UnsupportedInterpretation(interpretation: PhotometricInterpretation) {
display("unsupported photometric interpretation \"{interpretation:?}\"")
}
MisalignedTileBoundaries {
display("tile rows are not aligned to byte boundaries")
}
}
}
/// 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 {
FloatingPointPredictor(ColorType),
HorizontalPredictor(ColorType),
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
UnknownInterpretation,
UnknownCompressionMethod,
UnsupportedCompressionMethod(CompressionMethod),
UnsupportedSampleDepth(u8),
UnsupportedSampleFormat(Vec<SampleFormat>),
UnsupportedColorType(ColorType),
UnsupportedBitsPerChannel(u8),
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
UnsupportedDataType,
#[doc(hidden)]
/// Do not match against this variant. It may get removed.
__NonExhaustive,
}
impl fmt::Display for TiffUnsupportedError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::TiffUnsupportedError::*;
match *self {
FloatingPointPredictor(color_type) => write!(
fmt,
"Floating point predictor for {:?} is unsupported.",
color_type
),
HorizontalPredictor(color_type) => write!(
fmt,
"Horizontal predictor for {:?} is unsupported.",
color_type
),
InterpretationWithBits(ref photometric_interpretation, ref bits_per_sample) => write!(
fmt,
"{:?} with {:?} bits per sample is unsupported",
photometric_interpretation, bits_per_sample
),
UnknownInterpretation => write!(
fmt,
"The image is using an unknown photometric interpretation."
),
UnknownCompressionMethod => write!(fmt, "Unknown compression method."),
UnsupportedCompressionMethod(method) => {
write!(fmt, "Compression method {:?} is unsupported", method)
}
UnsupportedSampleDepth(samples) => {
write!(fmt, "{} samples per pixel is unsupported.", samples)
}
UnsupportedSampleFormat(ref formats) => {
write!(fmt, "Sample format {:?} is unsupported.", formats)
}
UnsupportedColorType(color_type) => {
write!(fmt, "Color type {:?} is unsupported", color_type)
}
UnsupportedBitsPerChannel(bits) => {
write!(fmt, "{} bits per channel not supported", bits)
}
UnsupportedPlanarConfig(config) => {
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
}
UnsupportedDataType => write!(fmt, "Unsupported data type."),
__NonExhaustive => unreachable!(),
quick_error! {
/// User attempted to use the Decoder in a way that is incompatible with a specific image.
///
/// For example: attempting to read a tile from a stripped image.
#[derive(Debug)]
#[non_exhaustive]
pub enum UsageError {
InvalidChunkType(expected: ChunkType, actual: ChunkType) {
display("requested operation is only valid for images with chunk encoding of type {expected:?} but got {actual:?}")
}
}
}
/// User attempted to use the Decoder in a way that is incompatible with a specific image.
///
/// For example: attempting to read a tile from a stripped image.
#[derive(Debug)]
pub enum UsageError {
InvalidChunkType(ChunkType, ChunkType),
InvalidChunkIndex(u32),
}
impl fmt::Display for UsageError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
use self::UsageError::*;
match *self {
InvalidChunkType(expected, actual) => {
write!(
fmt,
"Requested operation is only valid for images with chunk encoding of type: {:?}, got {:?}.",
expected, actual
)
}
InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({}) requested.", index),
InvalidChunkIndex(index: u32) {
display("invalid chunk index ({index}) requested")
}
}
}
impl fmt::Display for TiffError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
TiffError::FormatError(ref e) => write!(fmt, "Format error: {}", e),
TiffError::UnsupportedError(ref f) => write!(
fmt,
"The Decoder does not support the \
image format `{}`",
f
),
TiffError::IoError(ref e) => e.fmt(fmt),
TiffError::LimitsExceeded => write!(fmt, "The Decoder limits are exceeded"),
TiffError::IntSizeError => write!(fmt, "Platform or format size limits exceeded"),
TiffError::UsageError(ref e) => write!(fmt, "Usage error: {}", e),
PredictorCompressionMismatch {
display("requested predictor is not compatible with the requested compression")
}
}
}
impl Error for TiffError {
fn description(&self) -> &str {
match *self {
TiffError::FormatError(..) => "Format error",
TiffError::UnsupportedError(..) => "Unsupported error",
TiffError::IoError(..) => "IO error",
TiffError::LimitsExceeded => "Decoder limits exceeded",
TiffError::IntSizeError => "Platform or format size limits exceeded",
TiffError::UsageError(..) => "Invalid usage",
PredictorIncompatible {
display("the requested predictor is not compatible with the image's format")
}
}
fn cause(&self) -> Option<&dyn Error> {
match *self {
TiffError::IoError(ref e) => Some(e),
_ => None,
PredictorUnavailable {
display("the requested predictor is not available")
}
}
}
impl From<io::Error> for TiffError {
fn from(err: io::Error) -> TiffError {
TiffError::IoError(err)
}
}
impl From<str::Utf8Error> for TiffError {
fn from(_err: str::Utf8Error) -> TiffError {
TiffError::FormatError(TiffFormatError::InvalidTag)
@@ -282,37 +199,23 @@ impl From<string::FromUtf8Error> for TiffError {
}
}
impl From<TiffFormatError> for TiffError {
fn from(err: TiffFormatError) -> TiffError {
TiffError::FormatError(err)
}
}
impl From<TiffUnsupportedError> for TiffError {
fn from(err: TiffUnsupportedError) -> TiffError {
TiffError::UnsupportedError(err)
}
}
impl From<UsageError> for TiffError {
fn from(err: UsageError) -> TiffError {
TiffError::UsageError(err)
}
}
impl From<std::num::TryFromIntError> for TiffError {
fn from(_err: std::num::TryFromIntError) -> TiffError {
TiffError::IntSizeError
}
}
impl From<LzwError> for TiffError {
fn from(err: LzwError) -> TiffError {
match err {
LzwError::InvalidCode => TiffError::FormatError(TiffFormatError::Format(String::from(
"LZW compressed data corrupted",
))),
}
#[cfg(feature = "lzw")]
impl From<weezl::LzwError> for TiffError {
fn from(err: weezl::LzwError) -> TiffError {
TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
}
}
#[cfg(feature = "jpeg")]
impl From<zune_jpeg::errors::DecodeErrors> for TiffError {
fn from(err: zune_jpeg::errors::DecodeErrors) -> Self {
TiffError::FormatError(TiffFormatError::CompressedDataCorrupt(err.to_string()))
}
}
+25 -3
View File
@@ -6,9 +6,6 @@
//! # Related Links
//! * <https://web.archive.org/web/20210108073850/https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
extern crate jpeg;
extern crate weezl;
mod bytecast;
pub mod decoder;
pub mod encoder;
@@ -19,6 +16,7 @@ pub use self::error::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedErr
/// An enumeration over supported color types and their bit depths
#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
#[non_exhaustive]
pub enum ColorType {
/// Pixel is grayscale
Gray(u8),
@@ -37,4 +35,28 @@ pub enum ColorType {
/// Pixel is CMYK
CMYK(u8),
/// Pixel is CMYK with an alpha channel
CMYKA(u8),
/// Pixel is YCbCr
YCbCr(u8),
/// Pixel has multiple bands/channels
Multiband { bit_depth: u8, num_samples: u16 },
}
impl ColorType {
fn bit_depth(&self) -> u8 {
match *self {
ColorType::Gray(b)
| ColorType::RGB(b)
| ColorType::Palette(b)
| ColorType::GrayA(b)
| ColorType::RGBA(b)
| ColorType::CMYK(b)
| ColorType::CMYKA(b)
| ColorType::YCbCr(b)
| ColorType::Multiband { bit_depth: b, .. } => b,
}
}
}
+11 -5
View File
@@ -9,11 +9,9 @@ macro_rules! tags {
} => {
$( #[$enum_attr] )*
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
#[non_exhaustive]
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),
@@ -34,7 +32,6 @@ macro_rules! tags {
match *self {
$( $name::$tag => $val, )*
$( $name::Unknown(n) => { $unknown_doc; n }, )*
$name::__NonExhaustive => unreachable!(),
}
}
}
@@ -175,7 +172,7 @@ pub enum Type(u16) {
tags! {
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
/// for reference.
pub enum CompressionMethod(u16) {
pub enum CompressionMethod(u16) unknown("A custom compression method") {
None = 1,
Huffman = 2,
Fax3 = 3,
@@ -187,6 +184,9 @@ pub enum CompressionMethod(u16) {
Deflate = 8,
OldDeflate = 0x80B2,
PackBits = 0x8005,
// Self-assigned by libtiff
ZSTD = 0xC350,
}
}
@@ -212,8 +212,14 @@ pub enum PlanarConfiguration(u16) {
tags! {
pub enum Predictor(u16) {
/// No changes were made to the data
None = 1,
/// The images' rows were processed to contain the difference of each pixel from the previous one.
///
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
Horizontal = 2,
/// Not currently supported
FloatingPoint = 3,
}
}
+195
View File
@@ -0,0 +1,195 @@
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::ColorType;
use std::fs::File;
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images/";
/// Test a basic all white image
#[test]
fn test_white_ieee_fp16() {
let filenames = ["white-fp16.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for p in img {
assert!(p == half::f16::from_f32_const(1.0));
}
} else {
panic!("Wrong data type");
}
}
}
/// Test a single black pixel, to make sure scaling is ok
#[test]
fn test_one_black_pixel_ieee_fp16() {
let filenames = ["single-black-fp16.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for (i, p) in img.iter().enumerate() {
if i == 0 {
assert!(p < &half::f16::from_f32_const(0.001));
} else {
assert!(p == &half::f16::from_f32_const(1.0));
}
}
} else {
panic!("Wrong data type");
}
}
}
/// Test white with horizontal differencing predictor
#[test]
fn test_pattern_horizontal_differencing_ieee_fp16() {
let filenames = ["white-fp16-pred2.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
// 0, 2, 5, 8, 12, 16, 255 are black
let black = [0, 2, 5, 8, 12, 16, 255];
for (i, p) in img.iter().enumerate() {
if black.contains(&i) {
assert!(p < &half::f16::from_f32_const(0.001));
} else {
assert!(p == &half::f16::from_f32_const(1.0));
}
}
} else {
panic!("Wrong data type");
}
}
}
/// Test white with floating point predictor
#[test]
fn test_pattern_predictor_ieee_fp16() {
let filenames = ["white-fp16-pred3.tiff"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(256, 256)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
// 0, 2, 5, 8, 12, 16, 255 are black
let black = [0, 2, 5, 8, 12, 16, 255];
for (i, p) in img.iter().enumerate() {
if black.contains(&i) {
assert!(p < &half::f16::from_f32_const(0.001));
} else {
assert!(p == &half::f16::from_f32_const(1.0));
}
}
} else {
panic!("Wrong data type");
}
}
}
/// Test several random images
/// we'rell compare against a pnm file, that scales from 0 (for 0.0) to 65767 (for 1.0)
#[test]
fn test_predictor_ieee_fp16() {
// first parse pnm, skip the first 4 \n
let pnm_path = PathBuf::from(TEST_IMAGE_DIR).join("random-fp16.pgm");
let pnm_bytes = std::fs::read(pnm_path).expect("Failed to read expected PNM file");
// PGM looks like this:
// ---
// P5
// #Created with GIMP
// 16 16
// 65535
// ... <big-endian bytes>
// ---
// get index of 4th \n
let byte_start = pnm_bytes
.iter()
.enumerate()
.filter(|(_, &v)| v == b'\n')
.map(|(i, _)| i)
.nth(3)
.expect("Must be 4 \\n's");
let pnm_values: Vec<f32> = pnm_bytes[(byte_start + 1)..]
.chunks(2)
.map(|slice| {
let bts = [slice[0], slice[1]];
(u16::from_be_bytes(bts) as f32) / (u16::MAX as f32)
})
.collect();
assert!(pnm_values.len() == 256);
let filenames = [
"random-fp16-pred2.tiff",
"random-fp16-pred3.tiff",
"random-fp16.tiff",
];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(16, 16)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Gray(16)
);
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) {
assert!((exp - found.to_f32()).abs() < 0.0001);
}
} else {
panic!("Wrong data type");
}
}
}
+63
View File
@@ -0,0 +1,63 @@
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::tags::Tag;
use tiff::ColorType;
use std::fs::File;
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images";
#[test]
fn test_geo_tiff() {
let filenames = ["geo-5b.tif"];
for filename in filenames.iter() {
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
decoder = decoder.with_limits(tiff::decoder::Limits::unlimited());
assert_eq!(
decoder.dimensions().expect("Cannot get dimensions"),
(10, 10)
);
assert_eq!(
decoder.colortype().expect("Cannot get colortype"),
ColorType::Multiband {
bit_depth: 16,
num_samples: 5
}
);
assert_eq!(
decoder
.get_tag_u64(Tag::StripOffsets)
.expect("Cannot get StripOffsets"),
418
);
assert_eq!(
decoder
.get_tag_u64(Tag::RowsPerStrip)
.expect("Cannot get RowsPerStrip"),
10
);
assert_eq!(
decoder
.get_tag_u64(Tag::StripByteCounts)
.expect("Cannot get StripByteCounts"),
1000
);
assert_eq!(
decoder
.get_tag(Tag::ModelPixelScaleTag)
.expect("Cannot get pixel scale")
.into_f64_vec()
.expect("Cannot get pixel scale"),
vec![60.0, 60.0, 0.0]
);
let DecodingResult::I16(data) = decoder.read_image().unwrap() else {
panic!("Cannot read band data")
};
assert_eq!(data.len(), 500);
}
}
+57 -12
View File
@@ -216,6 +216,11 @@ fn issue_69() {
//assert!(img_res.is_ok());
//}
#[test]
fn test_tiled_gray_i1() {
test_image_sum_u8("tiled-gray-i1.tif", ColorType::Gray(1), 30531);
}
#[test]
fn test_tiled_rgb_u8() {
test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948);
@@ -285,6 +290,48 @@ fn test_tiled_incremental() {
}
}
#[test]
fn test_planar_rgb_u8() {
// gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
let file = "planar-rgb-u8.tif";
let expected_type = ColorType::RGB(8);
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let chunks = decoder.strip_count().unwrap();
assert_eq!(chunks as usize, 72);
// convert -quiet planar-rgb-u8.tif[0] -crop 1x1+0+0 txt:
// 0,0: (73,51,30) #49331E srgb(73,51,30)
// 1st band (red)
match decoder.read_chunk(0).unwrap() {
DecodingResult::U8(chunk) => {
assert_eq!(chunk[0], 73);
}
_ => panic!("Wrong bit depth"),
}
// 2nd band (green)
match decoder.read_chunk(chunks / 3).unwrap() {
DecodingResult::U8(chunk) => {
assert_eq!(chunk[0], 51);
}
_ => panic!("Wrong bit depth"),
}
// 3rd band (blue)
match decoder.read_chunk(chunks / 3 * 2).unwrap() {
DecodingResult::U8(chunk) => {
assert_eq!(chunk[0], 30);
}
_ => panic!("Wrong bit depth"),
}
test_image_sum_u8(file, ColorType::RGB(8), 15417630);
}
#[test]
fn test_div_zero() {
use tiff::{TiffError, TiffFormatError};
@@ -333,9 +380,7 @@ fn fuzzer_testcase5() {
178, 178, 178,
];
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
let _ = decoder.read_image().unwrap_err();
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
}
#[test]
@@ -352,7 +397,7 @@ fn fuzzer_testcase1() {
}
#[test]
fn test_stripped_image_overflow() {
fn fuzzer_testcase6() {
let image = [
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0,
0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0,
@@ -361,14 +406,7 @@ fn test_stripped_image_overflow() {
178, 178,
];
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
let err = decoder.read_image().unwrap_err();
match err {
tiff::TiffError::LimitsExceeded => {}
unexpected => panic!("Unexpected error {}", unexpected),
}
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
}
#[test]
@@ -477,3 +515,10 @@ fn test_predictor_3_rgb_f32() {
fn test_predictor_3_gray_f32() {
test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275);
}
#[test]
#[cfg(feature = "zstd")]
fn test_zstd_compression() {
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
}
+104
View File
@@ -36,6 +36,47 @@ fn encode_decode() {
let mut decoder = Decoder::new(&mut file).unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
let mut all_tags = decoder
.tag_iter()
.filter_map(Result::ok)
.collect::<Vec<_>>();
all_tags.sort_by_key(|(t, _)| t.to_u16());
assert_eq!(
all_tags,
vec![
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
(Tag::ImageLength, ifd::Value::Unsigned(100)),
(
Tag::BitsPerSample,
ifd::Value::List(vec![
ifd::Value::Short(8),
ifd::Value::Short(8),
ifd::Value::Short(8)
])
),
(Tag::Compression, ifd::Value::Short(1)),
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
(Tag::StripOffsets, ifd::Value::Unsigned(8)),
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
(Tag::XResolution, ifd::Value::Rational(1, 1)),
(Tag::YResolution, ifd::Value::Rational(1, 1)),
(Tag::ResolutionUnit, ifd::Value::Short(1)),
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
(Tag::Predictor, ifd::Value::Short(1)),
(
Tag::SampleFormat,
ifd::Value::List(vec![
ifd::Value::Short(1),
ifd::Value::Short(1),
ifd::Value::Short(1)
])
),
]
);
assert_eq!(
decoder.get_tag(Tag::Artist).unwrap(),
ifd::Value::Ascii("Image-tiff".into())
@@ -75,6 +116,47 @@ fn encode_decode_big() {
let mut decoder = Decoder::new(&mut file).unwrap();
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
let mut all_tags = decoder
.tag_iter()
.filter_map(Result::ok)
.collect::<Vec<_>>();
all_tags.sort_by_key(|(t, _)| t.to_u16());
assert_eq!(
all_tags,
vec![
(Tag::ImageWidth, ifd::Value::Unsigned(100)),
(Tag::ImageLength, ifd::Value::Unsigned(100)),
(
Tag::BitsPerSample,
ifd::Value::List(vec![
ifd::Value::Short(8),
ifd::Value::Short(8),
ifd::Value::Short(8)
])
),
(Tag::Compression, ifd::Value::Short(1)),
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
(Tag::StripOffsets, ifd::Value::UnsignedBig(16)),
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
(Tag::StripByteCounts, ifd::Value::UnsignedBig(30000)),
(Tag::XResolution, ifd::Value::Rational(1, 1)),
(Tag::YResolution, ifd::Value::Rational(1, 1)),
(Tag::ResolutionUnit, ifd::Value::Short(1)),
(Tag::Artist, ifd::Value::Ascii("Image-tiff".into())),
(Tag::Predictor, ifd::Value::Short(1)),
(
Tag::SampleFormat,
ifd::Value::List(vec![
ifd::Value::Short(1),
ifd::Value::Short(1),
ifd::Value::Short(1)
])
),
]
);
assert_eq!(
decoder.get_tag(Tag::Artist).unwrap(),
ifd::Value::Ascii("Image-tiff".into())
@@ -188,7 +270,9 @@ macro_rules! test_roundtrip {
}
test_roundtrip!(test_u8_roundtrip, U8, u8);
test_roundtrip!(test_i8_roundtrip, I8, i8);
test_roundtrip!(test_u16_roundtrip, U16, u16);
test_roundtrip!(test_i16_roundtrip, I16, i16);
test_roundtrip!(test_u32_roundtrip, U32, u32);
test_roundtrip!(test_u64_roundtrip, U64, u64);
test_roundtrip!(test_f32_roundtrip, F32, f32);
@@ -199,6 +283,11 @@ fn test_gray_u8_roundtrip() {
test_u8_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_gray_i8_roundtrip() {
test_i8_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_rgb_u8_roundtrip() {
test_u8_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
@@ -209,11 +298,21 @@ fn test_cmyk_u8_roundtrip() {
test_u8_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
}
#[test]
fn test_cmyka_u8_roundtrip() {
test_u8_roundtrip::<colortype::CMYKA8>("cmyk-4c-8b.tiff", ColorType::CMYKA(8));
}
#[test]
fn test_gray_u16_roundtrip() {
test_u16_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_gray_i16_roundtrip() {
test_i16_roundtrip::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_rgb_u16_roundtrip() {
test_u16_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
@@ -264,6 +363,11 @@ fn test_gray_f64_roundtrip() {
test_f64_roundtrip::<colortype::Gray64Float>("gradient-1c-64b-float.tiff", ColorType::Gray(64));
}
#[test]
fn test_ycbcr_u8_roundtrip() {
test_u8_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}
trait AssertDecode {
fn assert_tag_u32(&mut self, tag: u16) -> u32;
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
+18 -17
View File
@@ -6,7 +6,7 @@ use tiff::{
encoder::{
colortype::{self, ColorType},
compression::*,
TiffEncoder, TiffValue,
Compression, TiffEncoder, TiffValue,
},
};
@@ -18,15 +18,12 @@ trait TestImage<const NUM_CHANNELS: usize>: From<Vec<<Self::Color as ColorType>:
fn reference_data(&self) -> &[<Self::Color as ColorType>::Inner];
fn generate_pixel(x: u32, y: u32) -> [<Self::Color as ColorType>::Inner; NUM_CHANNELS];
fn compress<C: Compression, W: Write + Seek>(
&self,
encoder: &mut TiffEncoder<W>,
compression: C,
) where
fn compress<W: Write + Seek>(&self, encoder: &mut TiffEncoder<W>)
where
[<Self::Color as ColorType>::Inner]: TiffValue,
{
let image = encoder
.new_image_with_compression::<Self::Color, C>(Self::WIDTH, Self::HEIGHT, compression)
.new_image::<Self::Color>(Self::WIDTH, Self::HEIGHT)
.unwrap();
image.write_data(self.reference_data()).unwrap();
}
@@ -94,7 +91,7 @@ impl TestImage<1> for TestImageGrayscale {
}
}
fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
fn encode_decode_with_compression(compression: Compression) {
let mut data = Cursor::new(Vec::new());
let image_rgb = TestImageColor::generate();
@@ -103,9 +100,11 @@ fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
// Encode tiff with compression
{
// Create a multipage image with 2 images
let mut encoder = TiffEncoder::new(&mut data).unwrap();
image_rgb.compress(&mut encoder, compression.clone());
image_grayscale.compress(&mut encoder, compression);
let mut encoder = TiffEncoder::new(&mut data)
.unwrap()
.with_compression(compression);
image_rgb.compress(&mut encoder);
image_grayscale.compress(&mut encoder);
}
// Decode tiff
@@ -136,22 +135,24 @@ fn encode_decode_with_compression<C: Compression + Clone>(compression: C) {
#[test]
fn encode_decode_without_compression() {
encode_decode_with_compression(Uncompressed::default());
encode_decode_with_compression(Compression::Uncompressed);
}
#[test]
#[cfg(feature = "lzw")]
fn encode_decode_with_lzw() {
encode_decode_with_compression(Lzw::default());
encode_decode_with_compression(Compression::Lzw);
}
#[test]
#[cfg(feature = "deflate")]
fn encode_decode_with_deflate() {
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Fast));
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Balanced));
encode_decode_with_compression(Deflate::with_level(DeflateLevel::Best));
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
}
#[test]
fn encode_decode_with_packbits() {
encode_decode_with_compression(Packbits::default());
encode_decode_with_compression(Compression::Packbits);
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+231
View File
@@ -0,0 +1,231 @@
extern crate tiff;
use tiff::decoder::{Decoder, DecodingResult};
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
use tiff::ColorType;
use std::fs::File;
use std::io::{Cursor, Seek, SeekFrom};
use std::path::PathBuf;
const TEST_IMAGE_DIR: &str = "./tests/images/";
macro_rules! test_predict {
($name:ident, $buffer:ident, $buffer_ty:ty) => {
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
file: &str,
expected_type: ColorType,
) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(file).expect("Cannot create decoder!");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
DecodingResult::$buffer(res) => res,
_ => panic!("Wrong data type"),
};
let mut predicted = Vec::with_capacity(image_data.len());
C::horizontal_predict(&image_data, &mut predicted);
let sample_size = C::SAMPLE_FORMAT.len();
(0..sample_size).for_each(|i| {
assert_eq!(predicted[i], image_data[i]);
});
(sample_size..image_data.len()).for_each(|i| {
predicted[i] = predicted[i].wrapping_add(predicted[i - sample_size]);
assert_eq!(predicted[i], image_data[i]);
});
}
};
}
test_predict!(test_u8_predict, U8, u8);
test_predict!(test_i8_predict, I8, i8);
test_predict!(test_u16_predict, U16, u16);
test_predict!(test_i16_predict, I16, i16);
test_predict!(test_u32_predict, U32, u32);
test_predict!(test_u64_predict, U64, u64);
#[test]
fn test_gray_u8_predict() {
test_u8_predict::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_gray_i8_predict() {
test_i8_predict::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_rgb_u8_predict() {
test_u8_predict::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
}
#[test]
fn test_cmyk_u8_predict() {
test_u8_predict::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
}
#[test]
fn test_gray_u16_predict() {
test_u16_predict::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_gray_i16_predict() {
test_i16_predict::<colortype::GrayI16>("minisblack-1c-i16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_rgb_u16_predict() {
test_u16_predict::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
}
#[test]
fn test_cmyk_u16_predict() {
test_u16_predict::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
}
#[test]
fn test_gray_u32_predict() {
test_u32_predict::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
}
#[test]
fn test_rgb_u32_predict() {
test_u32_predict::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
}
#[test]
fn test_gray_u64_predict() {
test_u64_predict::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
}
#[test]
fn test_rgb_u64_predict() {
test_u64_predict::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
}
#[test]
fn test_ycbcr_u8_predict() {
test_u8_predict::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}
macro_rules! test_predict_roundtrip {
($name:ident, $buffer:ident, $buffer_ty:ty) => {
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
file: &str,
expected_type: ColorType,
) {
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
let img_file = File::open(path).expect("Cannot find test image!");
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
assert_eq!(decoder.colortype().unwrap(), expected_type);
let image_data = match decoder.read_image().unwrap() {
DecodingResult::$buffer(res) => res,
_ => panic!("Wrong data type"),
};
let mut file = Cursor::new(Vec::new());
{
let mut tiff = TiffEncoder::new(&mut file)
.unwrap()
.with_predictor(Predictor::Horizontal);
let (width, height) = decoder.dimensions().unwrap();
tiff.write_image::<C>(width, height, &image_data).unwrap();
}
file.seek(SeekFrom::Start(0)).unwrap();
{
let mut decoder = Decoder::new(&mut file).unwrap();
if let DecodingResult::$buffer(img_res) =
decoder.read_image().expect("Decoding image failed")
{
assert_eq!(image_data, img_res);
} else {
panic!("Wrong data type");
}
}
}
};
}
test_predict_roundtrip!(test_u8_predict_roundtrip, U8, u8);
test_predict_roundtrip!(test_i8_predict_roundtrip, I8, i8);
test_predict_roundtrip!(test_u16_predict_roundtrip, U16, u16);
test_predict_roundtrip!(test_i16_predict_roundtrip, I16, i16);
test_predict_roundtrip!(test_u32_predict_roundtrip, U32, u32);
test_predict_roundtrip!(test_u64_predict_roundtrip, U64, u64);
#[test]
fn test_gray_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::Gray8>("minisblack-1c-8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_gray_i8_predict_roundtrip() {
test_i8_predict_roundtrip::<colortype::GrayI8>("minisblack-1c-i8b.tiff", ColorType::Gray(8));
}
#[test]
fn test_rgb_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::RGB8>("rgb-3c-8b.tiff", ColorType::RGB(8));
}
#[test]
fn test_cmyk_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::CMYK8>("cmyk-3c-8b.tiff", ColorType::CMYK(8));
}
#[test]
fn test_gray_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::Gray16>("minisblack-1c-16b.tiff", ColorType::Gray(16));
}
#[test]
fn test_gray_i16_predict_roundtrip() {
test_i16_predict_roundtrip::<colortype::GrayI16>(
"minisblack-1c-i16b.tiff",
ColorType::Gray(16),
);
}
#[test]
fn test_rgb_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::RGB16>("rgb-3c-16b.tiff", ColorType::RGB(16));
}
#[test]
fn test_cmyk_u16_predict_roundtrip() {
test_u16_predict_roundtrip::<colortype::CMYK16>("cmyk-3c-16b.tiff", ColorType::CMYK(16));
}
#[test]
fn test_gray_u32_predict_roundtrip() {
test_u32_predict_roundtrip::<colortype::Gray32>("gradient-1c-32b.tiff", ColorType::Gray(32));
}
#[test]
fn test_rgb_u32_predict_roundtrip() {
test_u32_predict_roundtrip::<colortype::RGB32>("gradient-3c-32b.tiff", ColorType::RGB(32));
}
#[test]
fn test_gray_u64_predict_roundtrip() {
test_u64_predict_roundtrip::<colortype::Gray64>("gradient-1c-64b.tiff", ColorType::Gray(64));
}
#[test]
fn test_rgb_u64_predict_roundtrip() {
test_u64_predict_roundtrip::<colortype::RGB64>("gradient-3c-64b.tiff", ColorType::RGB(64));
}
#[test]
fn test_ycbcr_u8_predict_roundtrip() {
test_u8_predict_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
}