Compare commits
100
Commits
v0.7.2
...
tile-padding
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0da9770916 | ||
|
|
c30ea911a2 | ||
|
|
da07c0f4f5 | ||
|
|
289fab2fcd | ||
|
|
21252d9d89 | ||
|
|
ffaccf2b52 | ||
|
|
4dea7c4c81 | ||
|
|
aa42fcb1bb | ||
|
|
1f7b25ef55 | ||
|
|
6c65e66440 | ||
|
|
a733ff13d9 | ||
|
|
b1cd459330 | ||
|
|
4f1c88e35f | ||
|
|
7307e96782 | ||
|
|
a08604b015 | ||
|
|
9333d99c92 | ||
|
|
65bc24d5d9 | ||
|
|
7fc75f194a | ||
|
|
7679be19c1 | ||
|
|
0e9f8e3b5b | ||
|
|
c900129f2c | ||
|
|
61234c0005 | ||
|
|
1416af1018 | ||
|
|
87d652ae5e | ||
|
|
b5a78fbec4 | ||
|
|
30640d50ff | ||
|
|
b87770225f | ||
|
|
d38b65eeb2 | ||
|
|
ee27553cf6 | ||
|
|
5a809a728e | ||
|
|
b0b99fc536 | ||
|
|
3bce92781c | ||
|
|
907bbee576 | ||
|
|
fc0dafcde2 | ||
|
|
945a02d1e1 | ||
|
|
ef3294d1fe | ||
|
|
0311402999 | ||
|
|
361578df00 | ||
|
|
bd23428f52 | ||
|
|
d32adf8ef4 | ||
|
|
2ec1ab7489 | ||
|
|
80e7c46151 | ||
|
|
5003803c97 | ||
|
|
7c4a6fae08 | ||
|
|
723ef6431a | ||
|
|
802949f6e1 | ||
|
|
5cadbffebf | ||
|
|
e1f4161e0b | ||
|
|
8c0c13f022 | ||
|
|
d5e3f4aa8a | ||
|
|
3c42a6e1f4 | ||
|
|
5e7702a874 | ||
|
|
8e5c5b44c5 | ||
|
|
8a6e93f66e | ||
|
|
6e398ce7e3 | ||
|
|
8bd02ac389 | ||
|
|
0447d81b07 | ||
|
|
54664d553d | ||
|
|
6bec36ab20 | ||
|
|
e57ca1a926 | ||
|
|
ca4da33dd7 | ||
|
|
6d4c4a49a1 | ||
|
|
d8520bde1f | ||
|
|
56c20c2c20 | ||
|
|
6efa89c249 | ||
|
|
68d677d1b3 | ||
|
|
72ca636625 | ||
|
|
1f501b5acb | ||
|
|
697d2877a1 | ||
|
|
805a1e5cef | ||
|
|
dc8311fb99 | ||
|
|
f25530adec | ||
|
|
dd8c9e4f2b | ||
|
|
8f34748d01 | ||
|
|
4ce3c2f41f | ||
|
|
f809d7d1bb | ||
|
|
902f945375 | ||
|
|
d437c2708b | ||
|
|
a133056287 | ||
|
|
ce00bb5a28 | ||
|
|
623a3d957d | ||
|
|
9e6f9771d3 | ||
|
|
b196d19238 | ||
|
|
436cfaf69f | ||
|
|
8fcf49deac | ||
|
|
2cf33d634d | ||
|
|
eeb79ee07a | ||
|
|
592405268e | ||
|
|
9f2508e37e | ||
|
|
4f46bacfda | ||
|
|
b286457337 | ||
|
|
7881291032 | ||
|
|
167088b33d | ||
|
|
ff33343372 | ||
|
|
4097bf37b1 | ||
|
|
c910ef8534 | ||
|
|
1063d6dd27 | ||
|
|
e4eac36c1c | ||
|
|
9394f6d043 | ||
|
|
3399f93620 |
No files matched your search
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust: ["1.48", stable, beta, nightly]
|
rust: ["1.61", stable, beta, nightly]
|
||||||
command: [build, test]
|
command: [build, test]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
- name: test
|
- name: test
|
||||||
run: >
|
run: >
|
||||||
cargo test --tests --benches --no-default-features --features "$FEATURES"
|
cargo test --tests --benches --no-default-features --features "$FEATURES"
|
||||||
if: ${{ matrix.rust != '1.48' }}
|
if: ${{ matrix.rust != '1.61' }}
|
||||||
env:
|
env:
|
||||||
FEATURES: ${{ matrix.features }}
|
FEATURES: ${{ matrix.features }}
|
||||||
rustfmt:
|
rustfmt:
|
||||||
|
|||||||
+53
@@ -1,3 +1,56 @@
|
|||||||
|
# Version 0.8.1
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
* The jpeg decoder gained to ability to utilize the Photometric Interpretation
|
||||||
|
directly instead of relying on a custom APP segment.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* A spurious error within the PackBits decoder lead to the incorrect results
|
||||||
|
(wrong bits or errors), based on the maximum size of reads from the
|
||||||
|
underlying reader.
|
||||||
|
* Removed a panic path in jpeg decoding, when a feature such as photometric
|
||||||
|
interpretation is not supported. An error is returned instead.
|
||||||
|
|
||||||
|
# Version 0.8.0
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
* The minimum supported rust version is now indicated in `Cargo.toml`.
|
||||||
|
* The enums `TiffFormatError` and `TiffUnsupportedError` are now
|
||||||
|
marked with the `#[non_exhaustive]` attribute.
|
||||||
|
* Additionally, tag related enums `Value`, `Tags`, `Type`, `CompressionMethod`,
|
||||||
|
`PhotometricInterpretation`, `PlanarConfiguration`, `Predictor`,
|
||||||
|
`ResolutionUnit`, `SampleFormat` are also changed.
|
||||||
|
|
||||||
|
Removals:
|
||||||
|
* Removed deprecated methods of `Decoder`: `init`, `read_jpeg`,
|
||||||
|
`read_strip_to_buffer`, `read_strip`, `read_tile`. The implicit chunk (row or
|
||||||
|
tile) index order could not be easily tracked by the caller. New separate
|
||||||
|
utility interfaces may be introduced at a later point, for now callers are
|
||||||
|
obligated to choose their own.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Update to `jpeg_decoder = 0.3`.
|
||||||
|
|
||||||
|
# Version 0.7.4
|
||||||
|
|
||||||
|
New features:
|
||||||
|
* Creating an encoder for invalid, zero-sized images is now rejected.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Fix panic, in a case where decoding jpeg encoded images did not expect the
|
||||||
|
jpeg decoder to return an error.
|
||||||
|
* Fix panic by validating `rows_per_strip` better, fixing a division-by-zero.
|
||||||
|
|
||||||
|
# Version 0.7.3
|
||||||
|
|
||||||
|
New features:
|
||||||
|
* Allow decoder to access specific tiles by index.
|
||||||
|
* Add support for floating point predictor.
|
||||||
|
* Tiled jpeg file support.
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
* Various refactoring and performance improvements.
|
||||||
|
|
||||||
# Version 0.7.2
|
# Version 0.7.2
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|||||||
+12
-13
@@ -1,25 +1,24 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.7.2"
|
version = "0.8.1"
|
||||||
authors = [
|
edition = "2018"
|
||||||
"ccgn",
|
resolver = "2"
|
||||||
"bvssvni <bvssvni@gmail.com>",
|
|
||||||
"nwin",
|
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
||||||
"TyOverby <ty@pre-alpha.com>",
|
rust-version = "1.61.0"
|
||||||
"HeroicKatora",
|
|
||||||
"Calum",
|
|
||||||
"CensoredUsername <cens.username@gmail.com>",
|
|
||||||
"Robzz",
|
|
||||||
"birktj"]
|
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "TIFF decoding and encoding library in pure Rust"
|
description = "TIFF decoding and encoding library in pure Rust"
|
||||||
|
authors = ["The image-rs Developers"]
|
||||||
|
|
||||||
repository = "https://github.com/image-rs/image-tiff"
|
repository = "https://github.com/image-rs/image-tiff"
|
||||||
|
categories = ["multimedia::images", "multimedia::encoding"]
|
||||||
|
|
||||||
exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
weezl = "0.1.0"
|
weezl = "0.1.0"
|
||||||
jpeg = { package = "jpeg-decoder", version = "0.2.4", default-features = false }
|
jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false }
|
||||||
flate2 = "1.0.20"
|
flate2 = "1.0.20"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
+3
-9
@@ -17,6 +17,7 @@ use self::Value::{
|
|||||||
|
|
||||||
#[allow(unused_qualifications)]
|
#[allow(unused_qualifications)]
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
Byte(u8),
|
Byte(u8),
|
||||||
Short(u16),
|
Short(u16),
|
||||||
@@ -34,8 +35,6 @@ pub enum Value {
|
|||||||
Ascii(String),
|
Ascii(String),
|
||||||
Ifd(u32),
|
Ifd(u32),
|
||||||
IfdBig(u64),
|
IfdBig(u64),
|
||||||
#[doc(hidden)] // Do not match against this.
|
|
||||||
__NonExhaustive,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
@@ -361,7 +360,6 @@ impl Entry {
|
|||||||
| Type::RATIONAL
|
| Type::RATIONAL
|
||||||
| Type::SRATIONAL
|
| Type::SRATIONAL
|
||||||
| Type::IFD8 => 8,
|
| Type::IFD8 => 8,
|
||||||
Type::__NonExhaustive => unreachable!(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let value_bytes = match self.count.checked_mul(tag_size) {
|
let value_bytes = match self.count.checked_mul(tag_size) {
|
||||||
@@ -398,7 +396,6 @@ impl Entry {
|
|||||||
| Type::SLONG
|
| Type::SLONG
|
||||||
| Type::FLOAT
|
| Type::FLOAT
|
||||||
| Type::IFD => unreachable!(),
|
| Type::IFD => unreachable!(),
|
||||||
Type::__NonExhaustive => unreachable!(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,15 +441,14 @@ impl Entry {
|
|||||||
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
IfdBig(reader.read_u64()?)
|
IfdBig(reader.read_u64()?)
|
||||||
}
|
}
|
||||||
Type::__NonExhaustive => unreachable!(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 3: There is more than one value, but it fits in the offset field.
|
// Case 3: There is more than one value, but it fits in the offset field.
|
||||||
if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
|
if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
|
||||||
match self.type_ {
|
match self.type_ {
|
||||||
Type::BYTE => return Ok(offset_to_bytes(self.count as usize, self)?),
|
Type::BYTE => return offset_to_bytes(self.count as usize, self),
|
||||||
Type::SBYTE => return Ok(offset_to_sbytes(self.count as usize, self)?),
|
Type::SBYTE => return offset_to_sbytes(self.count as usize, self),
|
||||||
Type::ASCII => {
|
Type::ASCII => {
|
||||||
let mut buf = vec![0; self.count as usize];
|
let mut buf = vec![0; self.count as usize];
|
||||||
self.r(bo).read_exact(&mut buf)?;
|
self.r(bo).read_exact(&mut buf)?;
|
||||||
@@ -528,7 +524,6 @@ impl Entry {
|
|||||||
| Type::IFD8 => {
|
| Type::IFD8 => {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
Type::__NonExhaustive => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,7 +606,6 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
Ok(Ascii(String::from_utf8(out)?))
|
Ok(Ascii(String::from_utf8(out)?))
|
||||||
}
|
}
|
||||||
Type::__NonExhaustive => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+340
-29
@@ -1,11 +1,13 @@
|
|||||||
use super::ifd::{Directory, Value};
|
use super::ifd::{Directory, Value};
|
||||||
|
use super::stream::{ByteOrder, DeflateReader, JpegReader, LZWReader, PackBitsReader};
|
||||||
use super::tag_reader::TagReader;
|
use super::tag_reader::TagReader;
|
||||||
use super::Limits;
|
use super::{fp_predict_f32, fp_predict_f64, DecodingBuffer, Limits};
|
||||||
use super::{stream::SmartReader, ChunkType};
|
use super::{stream::SmartReader, ChunkType};
|
||||||
use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag};
|
use crate::tags::{CompressionMethod, PhotometricInterpretation, Predictor, SampleFormat, Tag};
|
||||||
use crate::{TiffError, TiffFormatError, TiffResult, TiffUnsupportedError};
|
use crate::{ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::io::{Read, Seek};
|
use std::io::{self, Cursor, Read, Seek};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct StripDecodeState {
|
pub(crate) struct StripDecodeState {
|
||||||
@@ -17,7 +19,6 @@ pub(crate) struct StripDecodeState {
|
|||||||
pub(crate) struct TileAttributes {
|
pub(crate) struct TileAttributes {
|
||||||
pub image_width: usize,
|
pub image_width: usize,
|
||||||
pub image_height: usize,
|
pub image_height: usize,
|
||||||
pub samples_per_pixel: usize,
|
|
||||||
|
|
||||||
pub tile_width: usize,
|
pub tile_width: usize,
|
||||||
pub tile_length: usize,
|
pub tile_length: usize,
|
||||||
@@ -31,30 +32,11 @@ impl TileAttributes {
|
|||||||
(self.image_height + self.tile_length - 1) / self.tile_length
|
(self.image_height + self.tile_length - 1) / self.tile_length
|
||||||
}
|
}
|
||||||
fn padding_right(&self) -> usize {
|
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 {
|
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 row_samples(&self) -> usize {
|
|
||||||
self.tile_width * self.samples_per_pixel
|
|
||||||
}
|
|
||||||
pub fn tile_samples(&self) -> usize {
|
|
||||||
self.tile_length * self.tile_width * self.samples_per_pixel
|
|
||||||
}
|
|
||||||
fn tile_strip_samples(&self) -> usize {
|
|
||||||
(self.tile_samples() * self.tiles_across())
|
|
||||||
- (self.padding_right() * self.tile_length * self.samples_per_pixel)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the tile offset in the result buffer, counted in samples
|
|
||||||
pub fn get_offset(&self, tile: usize) -> usize {
|
|
||||||
let row = tile / self.tiles_across();
|
|
||||||
let column = tile % self.tiles_across();
|
|
||||||
|
|
||||||
(row * self.tile_strip_samples()) + (column * self.row_samples())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_padding(&self, tile: usize) -> (usize, usize) {
|
pub fn get_padding(&self, tile: usize) -> (usize, usize) {
|
||||||
let row = tile / self.tiles_across();
|
let row = tile / self.tiles_across();
|
||||||
let column = tile % self.tiles_across();
|
let column = tile % self.tiles_across();
|
||||||
@@ -87,7 +69,7 @@ pub(crate) struct Image {
|
|||||||
pub photometric_interpretation: PhotometricInterpretation,
|
pub photometric_interpretation: PhotometricInterpretation,
|
||||||
pub compression_method: CompressionMethod,
|
pub compression_method: CompressionMethod,
|
||||||
pub predictor: Predictor,
|
pub predictor: Predictor,
|
||||||
pub jpeg_tables: Option<Vec<u8>>,
|
pub jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||||
pub chunk_type: ChunkType,
|
pub chunk_type: ChunkType,
|
||||||
pub strip_decoder: Option<StripDecodeState>,
|
pub strip_decoder: Option<StripDecodeState>,
|
||||||
pub tile_attributes: Option<TileAttributes>,
|
pub tile_attributes: Option<TileAttributes>,
|
||||||
@@ -111,6 +93,11 @@ impl Image {
|
|||||||
|
|
||||||
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
||||||
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
|
||||||
|
width, height,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
let photometric_interpretation = tag_reader
|
let photometric_interpretation = tag_reader
|
||||||
.find_tag(Tag::PhotometricInterpretation)?
|
.find_tag(Tag::PhotometricInterpretation)?
|
||||||
@@ -139,7 +126,8 @@ impl Image {
|
|||||||
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
TiffFormatError::InvalidTagValueType(Tag::JPEGTables),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Some(vec)
|
|
||||||
|
Some(Arc::new(vec))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -216,7 +204,11 @@ impl Image {
|
|||||||
strip_decoder = Some(StripDecodeState { rows_per_strip });
|
strip_decoder = Some(StripDecodeState { rows_per_strip });
|
||||||
tile_attributes = None;
|
tile_attributes = None;
|
||||||
|
|
||||||
if chunk_offsets.len() != chunk_bytes.len() {
|
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
|
||||||
|
{
|
||||||
return Err(TiffError::FormatError(
|
return Err(TiffError::FormatError(
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
));
|
));
|
||||||
@@ -240,7 +232,6 @@ impl Image {
|
|||||||
tile_attributes = Some(TileAttributes {
|
tile_attributes = Some(TileAttributes {
|
||||||
image_width: usize::try_from(width)?,
|
image_width: usize::try_from(width)?,
|
||||||
image_height: usize::try_from(height)?,
|
image_height: usize::try_from(height)?,
|
||||||
samples_per_pixel: bits_per_sample.len(),
|
|
||||||
tile_width,
|
tile_width,
|
||||||
tile_length,
|
tile_length,
|
||||||
});
|
});
|
||||||
@@ -287,4 +278,324 @@ impl Image {
|
|||||||
chunk_bytes,
|
chunk_bytes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)),
|
||||||
|
// FIXME: We should _ignore_ other components. In particular:
|
||||||
|
// > Beware of extra components. Some TIFF files may have more components per pixel
|
||||||
|
// than you think. A Baseline TIFF reader must skip over them gracefully,using the
|
||||||
|
// values of the SamplesPerPixel and BitsPerSample fields.
|
||||||
|
// > -- TIFF 6.0 Specification, Section 7, Additional Baseline requirements.
|
||||||
|
_ => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
self.bits_per_sample.clone(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
PhotometricInterpretation::CMYK => match self.bits_per_sample[..] {
|
||||||
|
[c, m, y, k] if [c, c, c] == [m, y, k] => Ok(ColorType::CMYK(c)),
|
||||||
|
_ => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
self.bits_per_sample.clone(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
PhotometricInterpretation::YCbCr => match self.bits_per_sample[..] {
|
||||||
|
[y, cb, cr] if [y, y] == [cb, cr] => Ok(ColorType::YCbCr(y)),
|
||||||
|
_ => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
self.bits_per_sample.clone(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero
|
||||||
|
if self.bits_per_sample.len() == 1 =>
|
||||||
|
{
|
||||||
|
Ok(ColorType::Gray(self.bits_per_sample[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this is bad we should not fail at this point
|
||||||
|
_ => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
self.bits_per_sample.clone(),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_reader<'r, R: 'r + Read>(
|
||||||
|
reader: R,
|
||||||
|
photometric_interpretation: PhotometricInterpretation,
|
||||||
|
compression_method: CompressionMethod,
|
||||||
|
compressed_length: u64,
|
||||||
|
jpeg_tables: Option<Arc<Vec<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)?))
|
||||||
|
}
|
||||||
|
CompressionMethod::PackBits => Box::new(PackBitsReader::new(reader, compressed_length)),
|
||||||
|
CompressionMethod::Deflate | CompressionMethod::OldDeflate => {
|
||||||
|
Box::new(DeflateReader::new(reader))
|
||||||
|
}
|
||||||
|
CompressionMethod::ModernJPEG => {
|
||||||
|
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);
|
||||||
|
|
||||||
|
match photometric_interpretation {
|
||||||
|
PhotometricInterpretation::RGB => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::RGB)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::WhiteIsZero => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::BlackIsZero => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::TransparencyMask => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::None)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::CMYK => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::CMYK)
|
||||||
|
}
|
||||||
|
PhotometricInterpretation::YCbCr => {
|
||||||
|
decoder.set_color_transform(jpeg::ColorTransform::YCbCr)
|
||||||
|
}
|
||||||
|
photometric_interpretation => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedInterpretation(
|
||||||
|
photometric_interpretation,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = decoder.decode()?;
|
||||||
|
|
||||||
|
Box::new(Cursor::new(data))
|
||||||
|
}
|
||||||
|
method => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn chunk_file_range(&self, chunk: u32) -> TiffResult<(u64, u64)> {
|
||||||
|
let file_offset = self
|
||||||
|
.chunk_offsets
|
||||||
|
.get(chunk as usize)
|
||||||
|
.ok_or(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let compressed_bytes =
|
||||||
|
self.chunk_bytes
|
||||||
|
.get(chunk as usize)
|
||||||
|
.ok_or(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
))?;
|
||||||
|
|
||||||
|
Ok((*file_offset, *compressed_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn chunk_dimensions(&self) -> TiffResult<(u32, u32)> {
|
||||||
|
match self.chunk_type {
|
||||||
|
ChunkType::Strip => {
|
||||||
|
let strip_attrs = self.strip_decoder.as_ref().unwrap();
|
||||||
|
Ok((self.width, strip_attrs.rows_per_strip))
|
||||||
|
}
|
||||||
|
ChunkType::Tile => {
|
||||||
|
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
||||||
|
Ok((
|
||||||
|
u32::try_from(tile_attrs.tile_width)?,
|
||||||
|
u32::try_from(tile_attrs.tile_length)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
|
||||||
|
let dims = self.chunk_dimensions()?;
|
||||||
|
|
||||||
|
match self.chunk_type {
|
||||||
|
ChunkType::Strip => {
|
||||||
|
let strip_height_without_padding = chunk_index
|
||||||
|
.checked_mul(dims.1)
|
||||||
|
.and_then(|x| self.height.checked_sub(x))
|
||||||
|
.ok_or(TiffError::UsageError(UsageError::InvalidChunkIndex(
|
||||||
|
chunk_index,
|
||||||
|
)))?;
|
||||||
|
|
||||||
|
// Ignore potential vertical padding on the bottommost strip
|
||||||
|
let strip_height = dims.1.min(strip_height_without_padding);
|
||||||
|
|
||||||
|
Ok((dims.0, strip_height))
|
||||||
|
}
|
||||||
|
ChunkType::Tile => {
|
||||||
|
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
||||||
|
let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize);
|
||||||
|
|
||||||
|
let tile_width = tile_attrs.tile_width - padding_right;
|
||||||
|
let tile_length = tile_attrs.tile_length - padding_down;
|
||||||
|
|
||||||
|
Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn expand_chunk(
|
||||||
|
&self,
|
||||||
|
reader: impl Read,
|
||||||
|
mut buffer: DecodingBuffer,
|
||||||
|
output_width: usize,
|
||||||
|
byte_order: ByteOrder,
|
||||||
|
chunk_index: u32,
|
||||||
|
) -> TiffResult<()> {
|
||||||
|
// Validate that the provided buffer is of the expected type.
|
||||||
|
let color_type = self.colortype()?;
|
||||||
|
match (color_type, &buffer) {
|
||||||
|
(ColorType::RGB(n), _)
|
||||||
|
| (ColorType::RGBA(n), _)
|
||||||
|
| (ColorType::CMYK(n), _)
|
||||||
|
| (ColorType::YCbCr(n), _)
|
||||||
|
| (ColorType::Gray(n), _)
|
||||||
|
if usize::from(n) == buffer.byte_len() * 8 => {}
|
||||||
|
(ColorType::Gray(n), DecodingBuffer::U8(_)) if n < 8 => match self.predictor {
|
||||||
|
Predictor::None => {}
|
||||||
|
Predictor::Horizontal => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::HorizontalPredictor(color_type),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Predictor::FloatingPoint => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::FloatingPointPredictor(color_type),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(type_, _) => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedColorType(type_),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that the predictor is supported for the sample type.
|
||||||
|
match (self.predictor, &buffer) {
|
||||||
|
(Predictor::Horizontal, DecodingBuffer::F32(_))
|
||||||
|
| (Predictor::Horizontal, DecodingBuffer::F64(_)) => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::HorizontalPredictor(color_type),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
(Predictor::FloatingPoint, DecodingBuffer::F32(_))
|
||||||
|
| (Predictor::FloatingPoint, DecodingBuffer::F64(_)) => {}
|
||||||
|
(Predictor::FloatingPoint, _) => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::FloatingPointPredictor(color_type),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let compressed_bytes =
|
||||||
|
self.chunk_bytes
|
||||||
|
.get(chunk_index as usize)
|
||||||
|
.ok_or(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
))?;
|
||||||
|
|
||||||
|
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 chunk_dims = self.chunk_dimensions()?;
|
||||||
|
let data_dims = self.chunk_data_dimensions(chunk_index)?;
|
||||||
|
|
||||||
|
let padding_right = chunk_dims.0 - data_dims.0;
|
||||||
|
|
||||||
|
let jpeg_tables = self.jpeg_tables.clone();
|
||||||
|
let mut reader = Self::create_reader(
|
||||||
|
reader,
|
||||||
|
photometric_interpretation,
|
||||||
|
compression_method,
|
||||||
|
*compressed_bytes,
|
||||||
|
jpeg_tables,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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];
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
|
super::invert_colors(&mut buffer.subrange(0..total_samples), color_type);
|
||||||
|
}
|
||||||
|
} else if padding_right > 0 && 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;
|
||||||
|
|
||||||
|
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),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
|
super::invert_colors(&mut buffer.subrange(row_start..row_end), color_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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)];
|
||||||
|
reader.read_exact(row)?;
|
||||||
|
|
||||||
|
// Skip horizontal padding
|
||||||
|
if padding_right > 0 {
|
||||||
|
let len = u64::try_from(padding_right as usize * samples * byte_len)?;
|
||||||
|
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);
|
||||||
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
|
super::invert_colors(&mut row, color_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+281
-647
File diff suppressed because it is too large.
Load diff
+135
-100
@@ -1,6 +1,8 @@
|
|||||||
//! All IO functionality needed for TIFF decoding
|
//! All IO functionality needed for TIFF decoding
|
||||||
|
|
||||||
use std::io::{self, Read, Seek};
|
use std::convert::TryFrom;
|
||||||
|
use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Take};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Byte order of the TIFF file.
|
/// Byte order of the TIFF file.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
@@ -124,70 +126,60 @@ pub trait EndianReader: Read {
|
|||||||
/// ## Deflate Reader
|
/// ## Deflate Reader
|
||||||
///
|
///
|
||||||
|
|
||||||
pub type DeflateReader<'r, R> = flate2::read::ZlibDecoder<&'r mut SmartReader<R>>;
|
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// ## LZW Reader
|
/// ## LZW Reader
|
||||||
///
|
///
|
||||||
|
|
||||||
/// Reader that decompresses LZW streams
|
/// Reader that decompresses LZW streams
|
||||||
pub struct LZWReader {
|
pub struct LZWReader<R: Read> {
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
reader: BufReader<Take<R>>,
|
||||||
|
decoder: weezl::decode::Decoder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LZWReader {
|
impl<R: Read> LZWReader<R> {
|
||||||
/// Wraps a reader
|
/// Wraps a reader
|
||||||
pub fn new<R>(
|
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
|
||||||
reader: &mut SmartReader<R>,
|
Self {
|
||||||
compressed_length: usize,
|
reader: BufReader::with_capacity(
|
||||||
max_uncompressed_length: usize,
|
(32 * 1024).min(compressed_length),
|
||||||
) -> io::Result<(usize, LZWReader)>
|
reader.take(u64::try_from(compressed_length).unwrap()),
|
||||||
where
|
),
|
||||||
R: Read + Seek,
|
decoder: weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8),
|
||||||
{
|
}
|
||||||
let mut compressed = vec![0; compressed_length as usize];
|
}
|
||||||
reader.read_exact(&mut compressed[..])?;
|
}
|
||||||
let mut uncompressed = vec![0; max_uncompressed_length];
|
|
||||||
let mut decoder = weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
|
|
||||||
|
|
||||||
let mut bytes_read = 0;
|
impl<R: Read> Read for LZWReader<R> {
|
||||||
let mut bytes_written = 0;
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
loop {
|
||||||
while bytes_written < max_uncompressed_length {
|
let result = self.decoder.decode_bytes(self.reader.fill_buf()?, buf);
|
||||||
let result = decoder.decode_bytes(
|
self.reader.consume(result.consumed_in);
|
||||||
&compressed[bytes_read..],
|
|
||||||
&mut uncompressed[bytes_written..],
|
|
||||||
);
|
|
||||||
bytes_read += result.consumed_in;
|
|
||||||
bytes_written += result.consumed_out;
|
|
||||||
|
|
||||||
match result.status {
|
match result.status {
|
||||||
Ok(weezl::LzwStatus::Ok) => {}
|
Ok(weezl::LzwStatus::Ok) => {
|
||||||
Ok(weezl::LzwStatus::Done) => break,
|
if result.consumed_out == 0 {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return Ok(result.consumed_out);
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(weezl::LzwStatus::NoProgress) => {
|
Ok(weezl::LzwStatus::NoProgress) => {
|
||||||
|
assert_eq!(result.consumed_in, 0);
|
||||||
|
assert_eq!(result.consumed_out, 0);
|
||||||
|
assert!(self.reader.buffer().is_empty());
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::UnexpectedEof,
|
io::ErrorKind::UnexpectedEof,
|
||||||
"no lzw end code found",
|
"no lzw end code found",
|
||||||
))
|
));
|
||||||
|
}
|
||||||
|
Ok(weezl::LzwStatus::Done) => {
|
||||||
|
return Ok(result.consumed_out);
|
||||||
}
|
}
|
||||||
Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)),
|
Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uncompressed.resize(bytes_written, 0);
|
|
||||||
Ok((
|
|
||||||
uncompressed.len(),
|
|
||||||
LZWReader {
|
|
||||||
buffer: io::Cursor::new(uncompressed),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for LZWReader {
|
|
||||||
#[inline]
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.buffer.read(buf)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,8 +188,13 @@ impl Read for LZWReader {
|
|||||||
///
|
///
|
||||||
|
|
||||||
pub(crate) struct JpegReader {
|
pub(crate) struct JpegReader {
|
||||||
|
jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||||
|
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
buffer: io::Cursor<Vec<u8>>,
|
||||||
|
|
||||||
|
offset: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JpegReader {
|
impl JpegReader {
|
||||||
/// Constructs new JpegReader wrapping a SmartReader.
|
/// Constructs new JpegReader wrapping a SmartReader.
|
||||||
/// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one
|
/// Because JPEG compression in TIFF allows to save quantization and/or huffman tables in one
|
||||||
@@ -210,25 +207,22 @@ impl JpegReader {
|
|||||||
/// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
|
/// bytes of the remaining JPEG data is removed because it follows `jpeg_tables`.
|
||||||
/// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
|
/// Similary, `jpeg_tables` ends with a `EOI` (HEX: `0xFFD9`) or __end of image__ marker,
|
||||||
/// this has to be removed as well (last two bytes of `jpeg_tables`).
|
/// this has to be removed as well (last two bytes of `jpeg_tables`).
|
||||||
|
pub fn new<R: Read>(
|
||||||
pub fn new<R>(
|
mut reader: R,
|
||||||
reader: &mut SmartReader<R>,
|
length: u64,
|
||||||
length: u32,
|
jpeg_tables: Option<Arc<Vec<u8>>>,
|
||||||
jpeg_tables: &Option<Vec<u8>>,
|
) -> io::Result<JpegReader> {
|
||||||
) -> io::Result<JpegReader>
|
|
||||||
where
|
|
||||||
R: Read + Seek,
|
|
||||||
{
|
|
||||||
// Read jpeg image data
|
// Read jpeg image data
|
||||||
let mut segment = vec![0; length as usize];
|
let mut segment = vec![0; length as usize];
|
||||||
|
|
||||||
reader.read_exact(&mut segment[..])?;
|
reader.read_exact(&mut segment[..])?;
|
||||||
|
|
||||||
match jpeg_tables {
|
match jpeg_tables {
|
||||||
Some(tables) => {
|
Some(jpeg_tables) => {
|
||||||
assert!(
|
assert!(
|
||||||
tables.len() >= 2,
|
jpeg_tables.len() >= 2,
|
||||||
"jpeg_tables, if given, must be at least 2 bytes long. Got {:?}",
|
"jpeg_tables, if given, must be at least 2 bytes long. Got {:?}",
|
||||||
tables
|
jpeg_tables
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@@ -237,17 +231,20 @@ impl JpegReader {
|
|||||||
length
|
length
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut jpeg_data = tables.clone();
|
let mut buffer = io::Cursor::new(segment);
|
||||||
let truncated_length = jpeg_data.len() - 2;
|
// Skip the first two bytes (marker bytes)
|
||||||
jpeg_data.truncate(truncated_length);
|
buffer.seek(SeekFrom::Start(2))?;
|
||||||
jpeg_data.extend_from_slice(&segment[2..]);
|
|
||||||
|
|
||||||
Ok(JpegReader {
|
Ok(JpegReader {
|
||||||
buffer: io::Cursor::new(jpeg_data),
|
buffer,
|
||||||
|
jpeg_tables: Some(jpeg_tables),
|
||||||
|
offset: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
None => Ok(JpegReader {
|
None => Ok(JpegReader {
|
||||||
buffer: io::Cursor::new(segment),
|
buffer: io::Cursor::new(segment),
|
||||||
|
jpeg_tables: None,
|
||||||
|
offset: 0,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +253,31 @@ impl JpegReader {
|
|||||||
impl Read for JpegReader {
|
impl Read for JpegReader {
|
||||||
// #[inline]
|
// #[inline]
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
self.buffer.read(buf)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,56 +285,70 @@ impl Read for JpegReader {
|
|||||||
/// ## PackBits Reader
|
/// ## PackBits Reader
|
||||||
///
|
///
|
||||||
|
|
||||||
/// Reader that unpacks Apple's `PackBits` format
|
enum PackBitsReaderState {
|
||||||
pub struct PackBitsReader {
|
Header,
|
||||||
buffer: io::Cursor<Vec<u8>>,
|
Literal,
|
||||||
|
Repeat { value: u8 },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackBitsReader {
|
/// Reader that unpacks Apple's `PackBits` format
|
||||||
|
pub struct PackBitsReader<R: Read> {
|
||||||
|
reader: Take<R>,
|
||||||
|
state: PackBitsReaderState,
|
||||||
|
count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> PackBitsReader<R> {
|
||||||
/// Wraps a reader
|
/// Wraps a reader
|
||||||
pub fn new<R: Read + Seek>(
|
pub fn new(reader: R, length: u64) -> Self {
|
||||||
mut reader: R,
|
Self {
|
||||||
length: usize,
|
reader: reader.take(length),
|
||||||
) -> io::Result<(usize, PackBitsReader)> {
|
state: PackBitsReaderState::Header,
|
||||||
let mut buffer = Vec::new();
|
count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read> Read for PackBitsReader<R> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
while let PackBitsReaderState::Header = self.state {
|
||||||
|
if self.reader.limit() == 0 {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
let mut header: [u8; 1] = [0];
|
let mut header: [u8; 1] = [0];
|
||||||
let mut data: [u8; 1] = [0];
|
self.reader.read_exact(&mut header)?;
|
||||||
|
|
||||||
let mut bytes_read = 0;
|
|
||||||
while bytes_read < length {
|
|
||||||
reader.read_exact(&mut header)?;
|
|
||||||
bytes_read += 1;
|
|
||||||
|
|
||||||
let h = header[0] as i8;
|
let h = header[0] as i8;
|
||||||
if h >= -127 && h <= -1 {
|
if h >= -127 && h <= -1 {
|
||||||
let new_len = buffer.len() + (1 - h as isize) as usize;
|
let mut data: [u8; 1] = [0];
|
||||||
reader.read_exact(&mut data)?;
|
self.reader.read_exact(&mut data)?;
|
||||||
buffer.resize(new_len, data[0]);
|
self.state = PackBitsReaderState::Repeat { value: data[0] };
|
||||||
bytes_read += 1;
|
self.count = (1 - h as isize) as usize;
|
||||||
} else if h >= 0 {
|
} else if h >= 0 {
|
||||||
let num_vals = h as usize + 1;
|
self.state = PackBitsReaderState::Literal;
|
||||||
let start = buffer.len();
|
self.count = h as usize + 1;
|
||||||
buffer.resize(start + num_vals, 0);
|
|
||||||
reader.read_exact(&mut buffer[start..])?;
|
|
||||||
bytes_read += num_vals
|
|
||||||
} else {
|
} else {
|
||||||
// h = -128 is a no-op.
|
// h = -128 is a no-op.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
let length = buf.len().min(self.count);
|
||||||
buffer.len(),
|
let actual = match self.state {
|
||||||
PackBitsReader {
|
PackBitsReaderState::Literal => self.reader.read(&mut buf[..length])?,
|
||||||
buffer: io::Cursor::new(buffer),
|
PackBitsReaderState::Repeat { value } => {
|
||||||
},
|
for b in &mut buf[..length] {
|
||||||
))
|
*b = value;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Read for PackBitsReader {
|
length
|
||||||
#[inline]
|
}
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
PackBitsReaderState::Header => unreachable!(),
|
||||||
self.buffer.read(buf)
|
};
|
||||||
|
|
||||||
|
self.count -= actual;
|
||||||
|
if self.count == 0 {
|
||||||
|
self.state = PackBitsReaderState::Header;
|
||||||
|
}
|
||||||
|
return Ok(actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +421,7 @@ mod test {
|
|||||||
let encoded_len = encoded.len();
|
let encoded_len = encoded.len();
|
||||||
|
|
||||||
let buff = io::Cursor::new(encoded);
|
let buff = io::Cursor::new(encoded);
|
||||||
let (_, mut decoder) = PackBitsReader::new(buff, encoded_len).unwrap();
|
let mut decoder = PackBitsReader::new(buff, encoded_len as u64);
|
||||||
|
|
||||||
let mut decoded = Vec::new();
|
let mut decoded = Vec::new();
|
||||||
decoder.read_to_end(&mut decoded).unwrap();
|
decoder.read_to_end(&mut decoded).unwrap();
|
||||||
|
|||||||
@@ -235,3 +235,11 @@ impl ColorType for CMYK64Float {
|
|||||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ impl Compression for Deflate {
|
|||||||
impl CompressionAlgorithm for Deflate {
|
impl CompressionAlgorithm for Deflate {
|
||||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
let mut encoder = ZlibEncoder::new(writer, self.level);
|
let mut encoder = ZlibEncoder::new(writer, self.level);
|
||||||
encoder.write_all(&bytes)?;
|
encoder.write_all(bytes)?;
|
||||||
encoder.try_finish()?;
|
encoder.try_finish()?;
|
||||||
Ok(encoder.total_out())
|
Ok(encoder.total_out())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ mod tests {
|
|||||||
let mut compressed_data = Vec::<u8>::new();
|
let mut compressed_data = Vec::<u8>::new();
|
||||||
let mut writer = Cursor::new(&mut compressed_data);
|
let mut writer = Cursor::new(&mut compressed_data);
|
||||||
Packbits::default()
|
Packbits::default()
|
||||||
.write_to(&mut writer, &UNCOMPRESSED_DATA)
|
.write_to(&mut writer, UNCOMPRESSED_DATA)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ mod tests {
|
|||||||
let mut compressed_data = Vec::<u8>::new();
|
let mut compressed_data = Vec::<u8>::new();
|
||||||
let mut writer = Cursor::new(&mut compressed_data);
|
let mut writer = Cursor::new(&mut compressed_data);
|
||||||
Packbits::default()
|
Packbits::default()
|
||||||
.write_to(&mut writer, &TEST_DATA)
|
.write_to(&mut writer, TEST_DATA)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
assert_eq!(compressed_data, EXPECTED_COMPRESSED_DATA);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::TiffResult,
|
error::TiffResult,
|
||||||
tags::{CompressionMethod, ResolutionUnit, Tag},
|
tags::{CompressionMethod, ResolutionUnit, Tag},
|
||||||
|
TiffError, TiffFormatError,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod colortype;
|
pub mod colortype;
|
||||||
@@ -349,6 +350,12 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind, D: Compression>
|
|||||||
height: u32,
|
height: u32,
|
||||||
compression: D,
|
compression: D,
|
||||||
) -> TiffResult<Self> {
|
) -> TiffResult<Self> {
|
||||||
|
if width == 0 || height == 0 {
|
||||||
|
return Err(TiffError::FormatError(TiffFormatError::InvalidDimensions(
|
||||||
|
width, height,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
|
let row_samples = u64::from(width) * u64::try_from(<T>::BITS_PER_SAMPLE.len())?;
|
||||||
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
|
let row_bytes = row_samples * u64::from(<T::Inner>::BYTE_LEN);
|
||||||
|
|
||||||
|
|||||||
+73
-8
@@ -1,8 +1,12 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::string;
|
use std::string;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use jpeg::UnsupportedFeature;
|
||||||
|
|
||||||
use crate::decoder::{ifd::Value, ChunkType};
|
use crate::decoder::{ifd::Value, ChunkType};
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
@@ -43,6 +47,7 @@ pub enum TiffError {
|
|||||||
/// The list of variants may grow to incorporate errors of future features. Matching against this
|
/// The list of variants may grow to incorporate errors of future features. Matching against this
|
||||||
/// exhaustively is not covered by interface stability guarantees.
|
/// exhaustively is not covered by interface stability guarantees.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum TiffFormatError {
|
pub enum TiffFormatError {
|
||||||
TiffSignatureNotFound,
|
TiffSignatureNotFound,
|
||||||
TiffSignatureInvalid,
|
TiffSignatureInvalid,
|
||||||
@@ -56,6 +61,7 @@ pub enum TiffFormatError {
|
|||||||
actual_samples: usize,
|
actual_samples: usize,
|
||||||
required_samples: usize,
|
required_samples: usize,
|
||||||
},
|
},
|
||||||
|
InvalidDimensions(u32, u32),
|
||||||
InvalidTag,
|
InvalidTag,
|
||||||
InvalidTagValueType(Tag),
|
InvalidTagValueType(Tag),
|
||||||
RequiredTagNotFound(Tag),
|
RequiredTagNotFound(Tag),
|
||||||
@@ -67,9 +73,7 @@ pub enum TiffFormatError {
|
|||||||
RequiredTagEmpty(Tag),
|
RequiredTagEmpty(Tag),
|
||||||
StripTileTagConflict,
|
StripTileTagConflict,
|
||||||
CycleInOffsets,
|
CycleInOffsets,
|
||||||
#[doc(hidden)]
|
JpegDecoder(JpegDecoderError),
|
||||||
/// Do not match against this variant. It may get removed.
|
|
||||||
__NonExhaustive,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TiffFormatError {
|
impl fmt::Display for TiffFormatError {
|
||||||
@@ -100,6 +104,7 @@ impl fmt::Display for TiffFormatError {
|
|||||||
actual_samples, required_samples
|
actual_samples, required_samples
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
InvalidDimensions(width, height) => write!(fmt, "Invalid dimensions: {}x{}.", width, height),
|
||||||
InvalidTag => write!(fmt, "Image contains invalid tag."),
|
InvalidTag => write!(fmt, "Image contains invalid tag."),
|
||||||
InvalidTagValueType(ref tag) => {
|
InvalidTagValueType(ref tag) => {
|
||||||
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
|
write!(fmt, "Tag `{:?}` did not have the expected value type.", tag)
|
||||||
@@ -119,7 +124,7 @@ impl fmt::Display for TiffFormatError {
|
|||||||
RequiredTagEmpty(ref val) => write!(fmt, "Required tag {:?} was empty.", 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."),
|
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"),
|
CycleInOffsets => write!(fmt, "File contained a cycle in the list of IFDs"),
|
||||||
__NonExhaustive => unreachable!(),
|
JpegDecoder(ref error) => write!(fmt, "{}", error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +138,9 @@ impl fmt::Display for TiffFormatError {
|
|||||||
/// The list of variants may grow. Matching against this exhaustively is not covered by interface
|
/// The list of variants may grow. Matching against this exhaustively is not covered by interface
|
||||||
/// stability guarantees.
|
/// stability guarantees.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum TiffUnsupportedError {
|
pub enum TiffUnsupportedError {
|
||||||
|
FloatingPointPredictor(ColorType),
|
||||||
HorizontalPredictor(ColorType),
|
HorizontalPredictor(ColorType),
|
||||||
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
|
InterpretationWithBits(PhotometricInterpretation, Vec<u8>),
|
||||||
UnknownInterpretation,
|
UnknownInterpretation,
|
||||||
@@ -145,15 +152,19 @@ pub enum TiffUnsupportedError {
|
|||||||
UnsupportedBitsPerChannel(u8),
|
UnsupportedBitsPerChannel(u8),
|
||||||
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
|
UnsupportedPlanarConfig(Option<PlanarConfiguration>),
|
||||||
UnsupportedDataType,
|
UnsupportedDataType,
|
||||||
#[doc(hidden)]
|
UnsupportedInterpretation(PhotometricInterpretation),
|
||||||
/// Do not match against this variant. It may get removed.
|
UnsupportedJpegFeature(UnsupportedFeature),
|
||||||
__NonExhaustive,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for TiffUnsupportedError {
|
impl fmt::Display for TiffUnsupportedError {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
use self::TiffUnsupportedError::*;
|
use self::TiffUnsupportedError::*;
|
||||||
match *self {
|
match *self {
|
||||||
|
FloatingPointPredictor(color_type) => write!(
|
||||||
|
fmt,
|
||||||
|
"Floating point predictor for {:?} is unsupported.",
|
||||||
|
color_type
|
||||||
|
),
|
||||||
HorizontalPredictor(color_type) => write!(
|
HorizontalPredictor(color_type) => write!(
|
||||||
fmt,
|
fmt,
|
||||||
"Horizontal predictor for {:?} is unsupported.",
|
"Horizontal predictor for {:?} is unsupported.",
|
||||||
@@ -188,7 +199,16 @@ impl fmt::Display for TiffUnsupportedError {
|
|||||||
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
write!(fmt, "Unsupported planar configuration “{:?}”.", config)
|
||||||
}
|
}
|
||||||
UnsupportedDataType => write!(fmt, "Unsupported data type."),
|
UnsupportedDataType => write!(fmt, "Unsupported data type."),
|
||||||
__NonExhaustive => unreachable!(),
|
UnsupportedInterpretation(interpretation) => {
|
||||||
|
write!(
|
||||||
|
fmt,
|
||||||
|
"Unsupported photometric interpretation \"{:?}\".",
|
||||||
|
interpretation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
UnsupportedJpegFeature(ref unsupported_feature) => {
|
||||||
|
write!(fmt, "Unsupported JPEG feature {:?}", unsupported_feature)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,6 +219,7 @@ impl fmt::Display for TiffUnsupportedError {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum UsageError {
|
pub enum UsageError {
|
||||||
InvalidChunkType(ChunkType, ChunkType),
|
InvalidChunkType(ChunkType, ChunkType),
|
||||||
|
InvalidChunkIndex(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for UsageError {
|
impl fmt::Display for UsageError {
|
||||||
@@ -212,6 +233,7 @@ impl fmt::Display for UsageError {
|
|||||||
expected, actual
|
expected, actual
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
InvalidChunkIndex(index) => write!(fmt, "Image chunk index ({}) requested.", index),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,6 +306,12 @@ impl From<TiffUnsupportedError> for TiffError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<UsageError> for TiffError {
|
||||||
|
fn from(err: UsageError) -> TiffError {
|
||||||
|
TiffError::UsageError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<std::num::TryFromIntError> for TiffError {
|
impl From<std::num::TryFromIntError> for TiffError {
|
||||||
fn from(_err: std::num::TryFromIntError) -> TiffError {
|
fn from(_err: std::num::TryFromIntError) -> TiffError {
|
||||||
TiffError::IntSizeError
|
TiffError::IntSizeError
|
||||||
@@ -300,5 +328,42 @@ impl From<LzwError> for TiffError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JpegDecoderError {
|
||||||
|
inner: Arc<jpeg::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JpegDecoderError {
|
||||||
|
fn new(error: jpeg::Error) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for JpegDecoderError {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
Arc::ptr_eq(&self.inner, &other.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for JpegDecoderError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.inner.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JpegDecoderError> for TiffError {
|
||||||
|
fn from(error: JpegDecoderError) -> Self {
|
||||||
|
TiffError::FormatError(TiffFormatError::JpegDecoder(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<jpeg::Error> for TiffError {
|
||||||
|
fn from(error: jpeg::Error) -> Self {
|
||||||
|
JpegDecoderError::new(error).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Result of an image decoding/encoding process
|
/// Result of an image decoding/encoding process
|
||||||
pub type TiffResult<T> = Result<T, TiffError>;
|
pub type TiffResult<T> = Result<T, TiffError>;
|
||||||
+4
-1
@@ -4,7 +4,7 @@
|
|||||||
//! lossless and lossy compression.
|
//! lossless and lossy compression.
|
||||||
//!
|
//!
|
||||||
//! # Related Links
|
//! # Related Links
|
||||||
//! * <https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
//! * <https://web.archive.org/web/20210108073850/https://www.adobe.io/open/standards/TIFF.html> - The TIFF specification
|
||||||
|
|
||||||
extern crate jpeg;
|
extern crate jpeg;
|
||||||
extern crate weezl;
|
extern crate weezl;
|
||||||
@@ -37,4 +37,7 @@ pub enum ColorType {
|
|||||||
|
|
||||||
/// Pixel is CMYK
|
/// Pixel is CMYK
|
||||||
CMYK(u8),
|
CMYK(u8),
|
||||||
|
|
||||||
|
/// Pixel is YCbCr
|
||||||
|
YCbCr(u8),
|
||||||
}
|
}
|
||||||
+2
-4
@@ -9,11 +9,9 @@ macro_rules! tags {
|
|||||||
} => {
|
} => {
|
||||||
$( #[$enum_attr] )*
|
$( #[$enum_attr] )*
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub enum $name {
|
pub enum $name {
|
||||||
$($(#[$ident_attr])* $tag,)*
|
$($(#[$ident_attr])* $tag,)*
|
||||||
// FIXME: switch to non_exhaustive once stabilized and compiler requirement new enough
|
|
||||||
#[doc(hidden)]
|
|
||||||
__NonExhaustive,
|
|
||||||
$(
|
$(
|
||||||
#[doc = $unknown_doc]
|
#[doc = $unknown_doc]
|
||||||
Unknown($ty),
|
Unknown($ty),
|
||||||
@@ -34,7 +32,6 @@ macro_rules! tags {
|
|||||||
match *self {
|
match *self {
|
||||||
$( $name::$tag => $val, )*
|
$( $name::$tag => $val, )*
|
||||||
$( $name::Unknown(n) => { $unknown_doc; n }, )*
|
$( $name::Unknown(n) => { $unknown_doc; n }, )*
|
||||||
$name::__NonExhaustive => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,6 +211,7 @@ tags! {
|
|||||||
pub enum Predictor(u16) {
|
pub enum Predictor(u16) {
|
||||||
None = 1,
|
None = 1,
|
||||||
Horizontal = 2,
|
Horizontal = 2,
|
||||||
|
FloatingPoint = 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-36
@@ -275,7 +275,7 @@ fn test_tiled_incremental() {
|
|||||||
assert_eq!(tiles as usize, sums.len());
|
assert_eq!(tiles as usize, sums.len());
|
||||||
|
|
||||||
for tile in 0..tiles {
|
for tile in 0..tiles {
|
||||||
match decoder.read_tile().unwrap() {
|
match decoder.read_chunk(tile).unwrap() {
|
||||||
DecodingResult::U8(res) => {
|
DecodingResult::U8(res) => {
|
||||||
let sum: u64 = res.into_iter().map(<u64>::from).sum();
|
let sum: u64 = res.into_iter().map(<u64>::from).sum();
|
||||||
assert_eq!(sum, sums[tile as usize]);
|
assert_eq!(sum, sums[tile as usize]);
|
||||||
@@ -324,7 +324,7 @@ fn test_too_many_value_bytes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bad_strip_count() {
|
fn fuzzer_testcase5() {
|
||||||
let image = [
|
let image = [
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0,
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0,
|
||||||
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
@@ -335,12 +335,7 @@ fn test_bad_strip_count() {
|
|||||||
|
|
||||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
||||||
|
|
||||||
let err = decoder.read_image().unwrap_err();
|
let _ = decoder.read_image().unwrap_err();
|
||||||
|
|
||||||
match err {
|
|
||||||
tiff::TiffError::IntSizeError => {}
|
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -385,18 +380,11 @@ fn oom() {
|
|||||||
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178,
|
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
let err = decoder.read_image().unwrap_err();
|
|
||||||
|
|
||||||
match err {
|
|
||||||
tiff::TiffError::LimitsExceeded => {}
|
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn oom_2() {
|
fn fuzzer_testcase4() {
|
||||||
let image = [
|
let image = [
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0,
|
||||||
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
@@ -404,14 +392,7 @@ fn oom_2() {
|
|||||||
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13,
|
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
let err = decoder.read_image().unwrap_err();
|
|
||||||
|
|
||||||
match err {
|
|
||||||
tiff::TiffError::LimitsExceeded => {}
|
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -432,9 +413,6 @@ fn fuzzer_testcase2() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_jpeg_tag_2() {
|
fn invalid_jpeg_tag_2() {
|
||||||
use tiff::tags;
|
|
||||||
use tiff::{TiffError, TiffFormatError};
|
|
||||||
|
|
||||||
let image = [
|
let image = [
|
||||||
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 242, 0, 1, 4, 0, 1, 0,
|
73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 242, 0, 1, 4, 0, 1, 0,
|
||||||
0, 0, 0, 129, 16, 0, 1, 1, 4, 0, 1, 0, 0, 0, 214, 0, 0, 248, 253, 1, 3, 0, 1, 0, 0, 0, 64,
|
0, 0, 0, 129, 16, 0, 1, 1, 4, 0, 1, 0, 0, 0, 214, 0, 0, 248, 253, 1, 3, 0, 1, 0, 0, 0, 64,
|
||||||
@@ -446,14 +424,7 @@ fn invalid_jpeg_tag_2() {
|
|||||||
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0,
|
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut decoder = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap();
|
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||||
|
|
||||||
let err = decoder.read_image().unwrap_err();
|
|
||||||
|
|
||||||
match err {
|
|
||||||
TiffError::FormatError(TiffFormatError::InvalidTagValueType(tags::Tag::JPEGTables)) => {}
|
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -496,3 +467,13 @@ fn timeout() {
|
|||||||
fn test_no_rows_per_strip() {
|
fn test_no_rows_per_strip() {
|
||||||
test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840);
|
test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_predictor_3_rgb_f32() {
|
||||||
|
test_image_sum_f32("predictor-3-rgb-f32.tif", ColorType::RGB(32), 54004.33);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_predictor_3_gray_f32() {
|
||||||
|
test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275);
|
||||||
|
}
|
||||||
@@ -94,6 +94,7 @@ fn test_encode_ifd() {
|
|||||||
{
|
{
|
||||||
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
||||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
||||||
|
image_encoder.write_strip(&[1]).unwrap();
|
||||||
let encoder = image_encoder.encoder();
|
let encoder = image_encoder.encoder();
|
||||||
|
|
||||||
// Use the "reusable" tags section as per the TIFF6 spec
|
// Use the "reusable" tags section as per the TIFF6 spec
|
||||||
@@ -263,6 +264,11 @@ fn test_gray_f64_roundtrip() {
|
|||||||
test_f64_roundtrip::<colortype::Gray64Float>("gradient-1c-64b-float.tiff", ColorType::Gray(64));
|
test_f64_roundtrip::<colortype::Gray64Float>("gradient-1c-64b-float.tiff", ColorType::Gray(64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ycbcr_u8_roundtrip() {
|
||||||
|
test_u8_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
||||||
|
}
|
||||||
|
|
||||||
trait AssertDecode {
|
trait AssertDecode {
|
||||||
fn assert_tag_u32(&mut self, tag: u16) -> u32;
|
fn assert_tag_u32(&mut self, tag: u16) -> u32;
|
||||||
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
|
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
|
||||||
@@ -320,6 +326,7 @@ fn test_multiple_byte() {
|
|||||||
{
|
{
|
||||||
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
||||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
||||||
|
image_encoder.write_strip(&[1]).unwrap();
|
||||||
let encoder = image_encoder.encoder();
|
let encoder = image_encoder.encoder();
|
||||||
|
|
||||||
encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]).unwrap();
|
encoder.write_tag(Tag::Unknown(65000), &[1_u8][..]).unwrap();
|
||||||
@@ -360,6 +367,7 @@ fn test_signed() {
|
|||||||
{
|
{
|
||||||
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
let mut tiff = TiffEncoder::new(&mut data).unwrap();
|
||||||
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
let mut image_encoder = tiff.new_image::<colortype::Gray8>(1, 1).unwrap();
|
||||||
|
image_encoder.write_strip(&[1]).unwrap();
|
||||||
let encoder = image_encoder.encoder();
|
let encoder = image_encoder.encoder();
|
||||||
|
|
||||||
//Use the "reusable" tags section as per the TIFF6 spec
|
//Use the "reusable" tags section as per the TIFF6 spec
|
||||||
@@ -512,7 +520,7 @@ fn test_rows_per_strip() {
|
|||||||
|
|
||||||
for i in 0..50 {
|
for i in 0..50 {
|
||||||
let img2 = [i; 2 * 100];
|
let img2 = [i; 2 * 100];
|
||||||
match decoder.read_strip().unwrap() {
|
match decoder.read_chunk(i as u32).unwrap() {
|
||||||
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
|
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
|
||||||
other => panic!("Incorrect strip type {:?}", other),
|
other => panic!("Incorrect strip type {:?}", other),
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user