Compare commits
56
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4acdfea783 | ||
|
|
64286c2110 | ||
|
|
12c98535f6 | ||
|
|
1556713d6e | ||
|
|
682ef0c375 | ||
|
|
bc71086a96 | ||
|
|
e67bac763f | ||
|
|
839655b975 | ||
|
|
85635c3cd2 | ||
|
|
481a2ab54a | ||
|
|
7c6c327817 | ||
|
|
9f1de91880 | ||
|
|
4a68feab5a | ||
|
|
b5afb8cca4 | ||
|
|
c8d43262e9 | ||
|
|
80ce77190e | ||
|
|
c75aa98ce2 | ||
|
|
c1087f6f08 | ||
|
|
8cc6bc657d | ||
|
|
9fcc2a3b65 | ||
|
|
666f871d30 | ||
|
|
a919b8febf | ||
|
|
85b5df7a62 | ||
|
|
756ea3e813 | ||
|
|
0eead083f6 | ||
|
|
34232d8141 | ||
|
|
cbcf6b97d4 | ||
|
|
19c811e86d | ||
|
|
50be15e356 | ||
|
|
718f2cb455 | ||
|
|
36a16db659 | ||
|
|
d70c31dcbf | ||
|
|
b589d10a27 | ||
|
|
f4378eeb80 | ||
|
|
49015c6e93 | ||
|
|
29402875cb | ||
|
|
33453289b6 | ||
|
|
ce0ca5c234 | ||
|
|
f671293bd8 | ||
|
|
48d52bed9b | ||
|
|
7707060982 | ||
|
|
c370ac4b92 | ||
|
|
b2bcbbaf23 | ||
|
|
d07da5a6cb | ||
|
|
a3c81e9830 | ||
|
|
46e51cc67e | ||
|
|
f6a762b72c | ||
|
|
23672b2490 | ||
|
|
ca74c03520 | ||
|
|
2942f9de97 | ||
|
|
29add10ca7 | ||
|
|
59d32a66a7 | ||
|
|
5a6b2ad8f3 | ||
|
|
c746653269 | ||
|
|
cc75d051af | ||
|
|
2e51ee4b15 |
No files matched your search
@@ -45,6 +45,10 @@ jobs:
|
|||||||
- name: Cargo check
|
- name: Cargo check
|
||||||
run: cargo check --features "${{ matrix.feature }}" --no-default-features
|
run: cargo check --features "${{ matrix.feature }}" --no-default-features
|
||||||
|
|
||||||
|
- name: Cargo clippy
|
||||||
|
if: ${{ matrix.feature != '' }}
|
||||||
|
run: cargo clippy --no-deps --all-features --all-targets -- -D warnings
|
||||||
|
|
||||||
other_archs:
|
other_archs:
|
||||||
# github actions does not support 32-bit or big endian systems directly, but
|
# github actions does not support 32-bit or big endian systems directly, but
|
||||||
# it does support QEMU. so we install qemu, then build and run the tests on
|
# it does support QEMU. so we install qemu, then build and run the tests on
|
||||||
|
|||||||
+37
@@ -1,3 +1,40 @@
|
|||||||
|
# Version 0.10.3
|
||||||
|
|
||||||
|
New features:
|
||||||
|
- Group 4 `fax` is now recognized as a compression type and decoded.
|
||||||
|
- A crate feature `fax` controls related dependencies, on by default.
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- The presence of PhotometricInterpretation::WhiteIsZero now returns an error
|
||||||
|
if the color type can not be inverted by the library where they were
|
||||||
|
previously silently ignored. Please report any missing color types.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- The PhotometricInterpretation::WhiteIsZero is now also applied to 1-bit
|
||||||
|
images, as well as 2-bit and 4-bit sample types where previously the presence
|
||||||
|
of this tag was ignored.
|
||||||
|
|
||||||
|
# Version 0.10.2
|
||||||
|
|
||||||
|
Republished 0.10.1, fixing an accidentally removed method (`Decoder::find_tag_unsigned_vec`).
|
||||||
|
|
||||||
|
# Version 0.10.1
|
||||||
|
|
||||||
|
New features:
|
||||||
|
- Added a dedicated `Directory` type (#277)
|
||||||
|
- Expose methods related to `Directory` publicly (#278)
|
||||||
|
- Implement writing directories (#281, #283)
|
||||||
|
- Add ICC (color profile) to the set of known tags (#287)
|
||||||
|
- Added `Decoder.inner()` to access the underlying reader (#289)
|
||||||
|
- Added methods for reading into a pre-allocated byte buffer (#285)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- No longer perform color conversion when decoding embedded JPEG. This fixes decoding for JPEG images with alpha channel. (#274)
|
||||||
|
- More lenient LZW decoding to match libtiff (#231)
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Fixed seeking multiple times in a row (#279)
|
||||||
|
|
||||||
# Version 0.10.0
|
# Version 0.10.0
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|||||||
+7
-2
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.10.0"
|
version = "0.10.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
@@ -20,6 +20,8 @@ exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
|||||||
half = { version = "2.4.1" }
|
half = { version = "2.4.1" }
|
||||||
quick-error = "2.0.1"
|
quick-error = "2.0.1"
|
||||||
|
|
||||||
|
# Rename to avoid confusion with the feature name.
|
||||||
|
fax34 = { package = "fax", version = "0.2.6", optional = true }
|
||||||
flate2 = { version = "1.0.20", optional = true }
|
flate2 = { version = "1.0.20", optional = true }
|
||||||
weezl = { version = "0.1.10", optional = true }
|
weezl = { version = "0.1.10", optional = true }
|
||||||
zstd = { version = "0.13", optional = true }
|
zstd = { version = "0.13", optional = true }
|
||||||
@@ -27,12 +29,15 @@ zune-jpeg = { version = "0.4.17", optional = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3.1"
|
criterion = "0.3.1"
|
||||||
|
# We may already depend on this via flate2
|
||||||
|
crc32fast = "1.5"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["deflate", "jpeg", "lzw"]
|
default = ["deflate", "fax", "jpeg", "lzw"]
|
||||||
|
|
||||||
# Compression algorithms
|
# Compression algorithms
|
||||||
deflate = ["dep:flate2"]
|
deflate = ["dep:flate2"]
|
||||||
|
fax = ["dep:fax34"]
|
||||||
jpeg = ["dep:zune-jpeg"]
|
jpeg = ["dep:zune-jpeg"]
|
||||||
lzw = ["dep:weezl"]
|
lzw = ["dep:weezl"]
|
||||||
zstd = ["dep:zstd"]
|
zstd = ["dep:zstd"]
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ This table lists photometric interpretations and sample formats which are suppor
|
|||||||
| LZW | ✓ | ✓ |
|
| LZW | ✓ | ✓ |
|
||||||
| Deflate | ✓ | ✓ |
|
| Deflate | ✓ | ✓ |
|
||||||
| PackBits | ✓ | ✓ |
|
| PackBits | ✓ | ✓ |
|
||||||
|
| Fax4 | ✓ | not yet |
|
||||||
| JPEG | ✓ | not yet |
|
| JPEG | ✓ | not yet |
|
||||||
| ZSTD | ✓ | not yet |
|
| ZSTD | ✓ | not yet |
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
//! the unsafe code guidelines).
|
//! the unsafe code guidelines).
|
||||||
//!
|
//!
|
||||||
//! TODO: Would like to use std-lib here.
|
//! TODO: Would like to use std-lib here.
|
||||||
|
// Until we implement predictors for f16, we don't need f16_as_ne_bytes. (Due to the macro we do
|
||||||
|
// not apply this directly to the functions). And rust-version is not new enough for `expect`.
|
||||||
|
#![allow(dead_code)]
|
||||||
use std::{mem, slice};
|
use std::{mem, slice};
|
||||||
|
|
||||||
use half::f16;
|
use half::f16;
|
||||||
|
|||||||
+3
-3
@@ -445,7 +445,7 @@ impl Entry {
|
|||||||
// 2b: the value is at most 4 bytes or doesn't fit in the offset field.
|
// 2b: the value is at most 4 bytes or doesn't fit in the offset field.
|
||||||
return Ok(match self.type_ {
|
return Ok(match self.type_ {
|
||||||
Type::BYTE => Byte(self.offset[0]),
|
Type::BYTE => Byte(self.offset[0]),
|
||||||
Type::SBYTE => SignedByte(i8::from(self.offset[0] as i8)),
|
Type::SBYTE => SignedByte(self.offset[0] as i8),
|
||||||
Type::UNDEFINED => Byte(self.offset[0]),
|
Type::UNDEFINED => Byte(self.offset[0]),
|
||||||
Type::SHORT => Short(self.r(bo).read_u16()?),
|
Type::SHORT => Short(self.r(bo).read_u16()?),
|
||||||
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
||||||
@@ -577,10 +577,10 @@ impl Entry {
|
|||||||
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
let mut buf = [0; 1];
|
let mut buf = [0; 1];
|
||||||
reader.inner().read_exact(&mut buf)?;
|
reader.inner().read_exact(&mut buf)?;
|
||||||
Ok(UnsignedBig(u64::from(buf[0])))
|
Ok(Byte(buf[0]))
|
||||||
}),
|
}),
|
||||||
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
Ok(SignedBig(i64::from(reader.read_i8()?)))
|
Ok(SignedByte(reader.read_i8()?))
|
||||||
}),
|
}),
|
||||||
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
Ok(Short(reader.read_u16()?))
|
Ok(Short(reader.read_u16()?))
|
||||||
|
|||||||
+41
-15
@@ -9,7 +9,9 @@ use crate::tags::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::io::{self, Cursor, Read, Seek};
|
use std::io::{self, Cursor, Read, Seek};
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -29,10 +31,10 @@ pub(crate) struct TileAttributes {
|
|||||||
|
|
||||||
impl TileAttributes {
|
impl TileAttributes {
|
||||||
pub fn tiles_across(&self) -> usize {
|
pub fn tiles_across(&self) -> usize {
|
||||||
(self.image_width + self.tile_width - 1) / self.tile_width
|
self.image_width.div_ceil(self.tile_width)
|
||||||
}
|
}
|
||||||
pub fn tiles_down(&self) -> usize {
|
pub fn tiles_down(&self) -> usize {
|
||||||
(self.image_height + self.tile_length - 1) / self.tile_length
|
self.image_height.div_ceil(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.tile_width - self.image_width % self.tile_width) % self.tile_width
|
||||||
@@ -342,6 +344,9 @@ impl Image {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
// TODO: treatment of WhiteIsZero is not quite consistent with `invert_colors` that is
|
||||||
|
// later called when that interpretation is read. That function does not support
|
||||||
|
// Multiband as a color type and will error. It's unclear how to resolve that exactly.
|
||||||
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
|
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
|
||||||
match self.samples {
|
match self.samples {
|
||||||
1 => Ok(ColorType::Gray(self.bits_per_sample)),
|
1 => Ok(ColorType::Gray(self.bits_per_sample)),
|
||||||
@@ -363,11 +368,29 @@ impl Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn minimum_row_stride(&self, dims: (u32, u32)) -> Option<NonZeroUsize> {
|
||||||
|
let (width, height) = dims;
|
||||||
|
|
||||||
|
let row_stride = u64::from(width)
|
||||||
|
.saturating_mul(self.samples_per_pixel() as u64)
|
||||||
|
.saturating_mul(self.bits_per_sample as u64)
|
||||||
|
.div_ceil(8);
|
||||||
|
|
||||||
|
// Note: row stride should be smaller than the len if we have an actual buffer. If there
|
||||||
|
// are no pixels in the buffer (height _or_ width is 0) then the stride is not well defined
|
||||||
|
// and we return `None`.
|
||||||
|
(height > 0)
|
||||||
|
.then_some(row_stride as usize)
|
||||||
|
.and_then(NonZeroUsize::new)
|
||||||
|
}
|
||||||
|
|
||||||
fn create_reader<'r, R: 'r + Read>(
|
fn create_reader<'r, R: 'r + Read>(
|
||||||
reader: R,
|
reader: R,
|
||||||
compression_method: CompressionMethod,
|
compression_method: CompressionMethod,
|
||||||
compressed_length: u64,
|
compressed_length: u64,
|
||||||
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
|
// FIXME: these should be `expect` attributes or we choose another way of passing them.
|
||||||
|
#[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>,
|
||||||
|
#[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32),
|
||||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||||
Ok(match compression_method {
|
Ok(match compression_method {
|
||||||
CompressionMethod::None => Box::new(reader),
|
CompressionMethod::None => Box::new(reader),
|
||||||
@@ -435,6 +458,12 @@ impl Image {
|
|||||||
|
|
||||||
Box::new(Cursor::new(data))
|
Box::new(Cursor::new(data))
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new(
|
||||||
|
dimensions,
|
||||||
|
reader,
|
||||||
|
compressed_length,
|
||||||
|
)?),
|
||||||
method => {
|
method => {
|
||||||
return Err(TiffError::UnsupportedError(
|
return Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
||||||
@@ -625,12 +654,12 @@ impl Image {
|
|||||||
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
|
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
|
||||||
.checked_mul(samples as u64)
|
.checked_mul(samples as u64)
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
|
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
|
||||||
|
|
||||||
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
|
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
|
||||||
.checked_mul(samples as u64)
|
.checked_mul(samples as u64)
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
let data_row_bytes: usize = ((data_row_bits + 7) / 8).try_into()?;
|
let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?;
|
||||||
|
|
||||||
// TODO: Should these return errors instead?
|
// TODO: Should these return errors instead?
|
||||||
assert!(output_row_stride >= data_row_bytes);
|
assert!(output_row_stride >= data_row_bytes);
|
||||||
@@ -641,13 +670,14 @@ impl Image {
|
|||||||
compression_method,
|
compression_method,
|
||||||
*compressed_bytes,
|
*compressed_bytes,
|
||||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||||
|
chunk_dims,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if output_row_stride == chunk_row_bytes as usize {
|
if output_row_stride == chunk_row_bytes {
|
||||||
let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
|
let tile = &mut buf[..chunk_row_bytes * data_dims.1 as usize];
|
||||||
reader.read_exact(tile)?;
|
reader.read_exact(tile)?;
|
||||||
|
|
||||||
for row in tile.chunks_mut(chunk_row_bytes as usize) {
|
for row in tile.chunks_mut(chunk_row_bytes) {
|
||||||
super::fix_endianness_and_predict(
|
super::fix_endianness_and_predict(
|
||||||
row,
|
row,
|
||||||
color_type.bit_depth(),
|
color_type.bit_depth(),
|
||||||
@@ -657,7 +687,7 @@ impl Image {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
super::invert_colors(tile, color_type, self.sample_format);
|
super::invert_colors(tile, color_type, self.sample_format)?;
|
||||||
}
|
}
|
||||||
} else if chunk_row_bytes > data_row_bytes && 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
|
// The floating point predictor shuffles the padding bytes into the encoded output, so
|
||||||
@@ -674,15 +704,11 @@ impl Image {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
super::invert_colors(row, color_type, self.sample_format);
|
super::invert_colors(row, color_type, self.sample_format)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (_i, row) in buf
|
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
|
||||||
.chunks_mut(output_row_stride)
|
|
||||||
.take(data_dims.1 as usize)
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
let row = &mut row[..data_row_bytes];
|
let row = &mut row[..data_row_bytes];
|
||||||
reader.read_exact(row)?;
|
reader.read_exact(row)?;
|
||||||
|
|
||||||
@@ -700,7 +726,7 @@ impl Image {
|
|||||||
predictor,
|
predictor,
|
||||||
);
|
);
|
||||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
super::invert_colors(row, color_type, self.sample_format);
|
super::invert_colors(row, color_type, self.sample_format)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+311
-58
@@ -1,5 +1,7 @@
|
|||||||
|
use std::alloc::{Layout, LayoutError};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::io::{self, Read, Seek};
|
use std::io::{self, Read, Seek};
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor,
|
CompressionMethod, IfdPointer, PhotometricInterpretation, PlanarConfiguration, Predictor,
|
||||||
@@ -136,7 +138,7 @@ impl DecodingResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_buffer(&mut self, start: usize) -> DecodingBuffer {
|
pub fn as_buffer(&mut self, start: usize) -> DecodingBuffer<'_> {
|
||||||
match *self {
|
match *self {
|
||||||
DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]),
|
DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&mut buf[start..]),
|
||||||
DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]),
|
DecodingResult::U16(ref mut buf) => DecodingBuffer::U16(&mut buf[start..]),
|
||||||
@@ -197,6 +199,88 @@ impl<'a> DecodingBuffer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Information on the byte buffer that should be supplied to the decoder.
|
||||||
|
///
|
||||||
|
/// This is relevant for [`Decoder::read_image_bytes`] and [`Decoder::read_chunk_bytes`] where the
|
||||||
|
/// caller provided buffer must fit the expectations of the decoder to be filled with data from the
|
||||||
|
/// current image.
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct BufferLayoutPreference {
|
||||||
|
/// Minimum byte size of the buffer to read image data.
|
||||||
|
pub len: usize,
|
||||||
|
/// Minimum number of bytes to represent a row of image data of the requested content.
|
||||||
|
pub row_stride: Option<NonZeroUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The count and matching discriminant for a `DecodingBuffer`.
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum DecodingExtent {
|
||||||
|
U8(usize),
|
||||||
|
U16(usize),
|
||||||
|
U32(usize),
|
||||||
|
U64(usize),
|
||||||
|
F16(usize),
|
||||||
|
F32(usize),
|
||||||
|
F64(usize),
|
||||||
|
I8(usize),
|
||||||
|
I16(usize),
|
||||||
|
I32(usize),
|
||||||
|
I64(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodingExtent {
|
||||||
|
fn to_result_buffer(&self, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
|
match *self {
|
||||||
|
DecodingExtent::U8(count) => DecodingResult::new_u8(count, limits),
|
||||||
|
DecodingExtent::U16(count) => DecodingResult::new_u16(count, limits),
|
||||||
|
DecodingExtent::U32(count) => DecodingResult::new_u32(count, limits),
|
||||||
|
DecodingExtent::U64(count) => DecodingResult::new_u64(count, limits),
|
||||||
|
DecodingExtent::F16(count) => DecodingResult::new_f16(count, limits),
|
||||||
|
DecodingExtent::F32(count) => DecodingResult::new_f32(count, limits),
|
||||||
|
DecodingExtent::F64(count) => DecodingResult::new_f64(count, limits),
|
||||||
|
DecodingExtent::I8(count) => DecodingResult::new_i8(count, limits),
|
||||||
|
DecodingExtent::I16(count) => DecodingResult::new_i16(count, limits),
|
||||||
|
DecodingExtent::I32(count) => DecodingResult::new_i32(count, limits),
|
||||||
|
DecodingExtent::I64(count) => DecodingResult::new_i64(count, limits),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn preferred_layout(self) -> TiffResult<Layout> {
|
||||||
|
fn overflow(_: LayoutError) -> TiffError {
|
||||||
|
TiffError::LimitsExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
match self {
|
||||||
|
DecodingExtent::U8(count) => Layout::array::<u8>(count),
|
||||||
|
DecodingExtent::U16(count) => Layout::array::<u16>(count),
|
||||||
|
DecodingExtent::U32(count) => Layout::array::<u32>(count),
|
||||||
|
DecodingExtent::U64(count) => Layout::array::<u64>(count),
|
||||||
|
DecodingExtent::F16(count) => Layout::array::<f16>(count),
|
||||||
|
DecodingExtent::F32(count) => Layout::array::<f32>(count),
|
||||||
|
DecodingExtent::F64(count) => Layout::array::<f64>(count),
|
||||||
|
DecodingExtent::I8(count) => Layout::array::<i8>(count),
|
||||||
|
DecodingExtent::I16(count) => Layout::array::<i16>(count),
|
||||||
|
DecodingExtent::I32(count) => Layout::array::<i32>(count),
|
||||||
|
DecodingExtent::I64(count) => Layout::array::<i64>(count),
|
||||||
|
}
|
||||||
|
.map_err(overflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_layout(self, buffer: usize) -> TiffResult<()> {
|
||||||
|
let needed_bytes = self.preferred_layout()?.size();
|
||||||
|
if buffer < needed_bytes {
|
||||||
|
Err(TiffError::UsageError(
|
||||||
|
UsageError::InsufficientOutputBufferSize {
|
||||||
|
needed: needed_bytes,
|
||||||
|
provided: buffer,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
/// Chunk type of the internal representation
|
/// Chunk type of the internal representation
|
||||||
pub enum ChunkType {
|
pub enum ChunkType {
|
||||||
@@ -206,6 +290,7 @@ pub enum ChunkType {
|
|||||||
|
|
||||||
/// Decoding limits
|
/// Decoding limits
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
pub struct Limits {
|
pub struct Limits {
|
||||||
/// The maximum size of any `DecodingResult` in bytes, the default is
|
/// The maximum size of any `DecodingResult` in bytes, the default is
|
||||||
/// 256MiB. If the entire image is decoded at once, then this will
|
/// 256MiB. If the entire image is decoded at once, then this will
|
||||||
@@ -218,10 +303,6 @@ pub struct Limits {
|
|||||||
/// Maximum size for intermediate buffer which may be used to limit the amount of data read per
|
/// Maximum size for intermediate buffer which may be used to limit the amount of data read per
|
||||||
/// segment even if the entire image is decoded at once.
|
/// segment even if the entire image is decoded at once.
|
||||||
pub intermediate_buffer_size: usize,
|
pub intermediate_buffer_size: usize,
|
||||||
/// The purpose of this is to prevent all the fields of the struct from
|
|
||||||
/// being public, as this would make adding new fields a major version
|
|
||||||
/// bump.
|
|
||||||
_non_exhaustive: (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Limits {
|
impl Limits {
|
||||||
@@ -234,10 +315,9 @@ impl Limits {
|
|||||||
/// naturally, the machine running the program does not have infinite memory.
|
/// naturally, the machine running the program does not have infinite memory.
|
||||||
pub fn unlimited() -> Limits {
|
pub fn unlimited() -> Limits {
|
||||||
Limits {
|
Limits {
|
||||||
decoding_buffer_size: usize::max_value(),
|
decoding_buffer_size: usize::MAX,
|
||||||
ifd_value_size: usize::max_value(),
|
ifd_value_size: usize::MAX,
|
||||||
intermediate_buffer_size: usize::max_value(),
|
intermediate_buffer_size: usize::MAX,
|
||||||
_non_exhaustive: (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,7 +328,6 @@ impl Default for Limits {
|
|||||||
decoding_buffer_size: 256 * 1024 * 1024,
|
decoding_buffer_size: 256 * 1024 * 1024,
|
||||||
intermediate_buffer_size: 128 * 1024 * 1024,
|
intermediate_buffer_size: 128 * 1024 * 1024,
|
||||||
ifd_value_size: 1024 * 1024,
|
ifd_value_size: 1024 * 1024,
|
||||||
_non_exhaustive: (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,11 +475,22 @@ fn fix_endianness_and_predict(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invert_colors(buf: &mut [u8], color_type: ColorType, sample_format: SampleFormat) {
|
fn invert_colors(
|
||||||
|
buf: &mut [u8],
|
||||||
|
color_type: ColorType,
|
||||||
|
sample_format: SampleFormat,
|
||||||
|
) -> TiffResult<()> {
|
||||||
match (color_type, sample_format) {
|
match (color_type, sample_format) {
|
||||||
(ColorType::Gray(8), SampleFormat::Uint) => {
|
// Where pixels do not cross a byte boundary
|
||||||
|
(ColorType::Gray(1 | 2 | 4 | 8), SampleFormat::Uint) => {
|
||||||
for x in buf {
|
for x in buf {
|
||||||
*x = 0xff - *x;
|
// Equivalent to both of the following:
|
||||||
|
//
|
||||||
|
// *x = 0xff - *x
|
||||||
|
// *x = !*x
|
||||||
|
//
|
||||||
|
// since -x = !x+1
|
||||||
|
*x = !*x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(ColorType::Gray(16), SampleFormat::Uint) => {
|
(ColorType::Gray(16), SampleFormat::Uint) => {
|
||||||
@@ -433,8 +523,14 @@ fn invert_colors(buf: &mut [u8], color_type: ColorType, sample_format: SampleFor
|
|||||||
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
|
x.copy_from_slice(&(1.0 - v).to_ne_bytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnknownInterpretation,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fix endianness. If `byte_order` matches the host, then conversion is a no-op.
|
/// Fix endianness. If `byte_order` matches the host, then conversion is a no-op.
|
||||||
@@ -468,7 +564,6 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read + Seek> Decoder<R> {
|
impl<R: Read + Seek> Decoder<R> {
|
||||||
/// Create a new decoder that decodes from the stream ```r```
|
|
||||||
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
|
pub fn new(mut r: R) -> TiffResult<Decoder<R>> {
|
||||||
let mut endianess = Vec::with_capacity(2);
|
let mut endianess = Vec::with_capacity(2);
|
||||||
(&mut r).take(2).read_to_end(&mut endianess)?;
|
(&mut r).take(2).read_to_end(&mut endianess)?;
|
||||||
@@ -665,6 +760,11 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a mutable reference to the stream being decoded.
|
||||||
|
pub fn inner(&mut self) -> &mut R {
|
||||||
|
self.value_reader.reader.inner()
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads a TIFF byte value
|
/// Reads a TIFF byte value
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn read_byte(&mut self) -> Result<u8, io::Error> {
|
pub fn read_byte(&mut self) -> Result<u8, io::Error> {
|
||||||
@@ -837,17 +937,26 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
output_width: usize,
|
output_width: usize,
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let offset = self.image.chunk_file_range(chunk_index)?.0;
|
|
||||||
self.goto_offset_u64(offset)?;
|
|
||||||
|
|
||||||
let output_row_stride = (output_width as u64)
|
let output_row_stride = (output_width as u64)
|
||||||
.saturating_mul(self.image.samples_per_pixel() as u64)
|
.saturating_mul(self.image.samples_per_pixel() as u64)
|
||||||
.saturating_mul(self.image.bits_per_sample as u64)
|
.saturating_mul(self.image.bits_per_sample as u64)
|
||||||
/ 8;
|
.div_ceil(8);
|
||||||
|
|
||||||
|
self.read_chunk_to_bytes(buffer.as_bytes_mut(), chunk_index, output_row_stride)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_chunk_to_bytes(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut [u8],
|
||||||
|
chunk_index: u32,
|
||||||
|
output_row_stride: u64,
|
||||||
|
) -> TiffResult<()> {
|
||||||
|
let offset = self.image.chunk_file_range(chunk_index)?.0;
|
||||||
|
self.goto_offset_u64(offset)?;
|
||||||
|
|
||||||
self.image.expand_chunk(
|
self.image.expand_chunk(
|
||||||
&mut self.value_reader,
|
&mut self.value_reader,
|
||||||
buffer.as_bytes_mut(),
|
buffer,
|
||||||
output_row_stride.try_into()?,
|
output_row_stride.try_into()?,
|
||||||
chunk_index,
|
chunk_index,
|
||||||
)?;
|
)?;
|
||||||
@@ -856,56 +965,109 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
|
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
|
||||||
let bits_per_sample = self.image().bits_per_sample;
|
self.result_extent(width, height)?
|
||||||
|
.to_result_buffer(&self.value_reader.limits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: it's unusual for this method to take a `usize` dimension parameter since those would
|
||||||
|
// typically come from the image data. The preferred consistent representation should be `u32`
|
||||||
|
// and that would avoid some unusual casts `usize -> u64` with a `u64::from` instead.
|
||||||
|
fn result_extent(&self, width: usize, height: usize) -> TiffResult<DecodingExtent> {
|
||||||
|
let bits_per_sample = self.image.bits_per_sample;
|
||||||
|
|
||||||
|
// If samples take up more than one byte, we expand it to a full number (integer or float)
|
||||||
|
// and the number of samples in that integer type just counts the number in the underlying
|
||||||
|
// image itself. Otherwise, a row is represented as the byte slice of bitfields without
|
||||||
|
// expansion.
|
||||||
let row_samples = if bits_per_sample >= 8 {
|
let row_samples = if bits_per_sample >= 8 {
|
||||||
width
|
width
|
||||||
} else {
|
} else {
|
||||||
((((width as u64) * bits_per_sample as u64) + 7) / 8)
|
((width as u64) * bits_per_sample as u64)
|
||||||
|
.div_ceil(8)
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| TiffError::LimitsExceeded)?
|
.map_err(|_| TiffError::LimitsExceeded)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let buffer_size = row_samples
|
let buffer_size = row_samples
|
||||||
.checked_mul(height)
|
.checked_mul(height)
|
||||||
.and_then(|x| x.checked_mul(self.image().samples_per_pixel()))
|
.and_then(|x| x.checked_mul(self.image.samples_per_pixel()))
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
|
|
||||||
let max_sample_bits = self.image().bits_per_sample;
|
Ok(match self.image().sample_format {
|
||||||
let limits = &self.value_reader.limits;
|
SampleFormat::Uint => match bits_per_sample {
|
||||||
|
n if n <= 8 => DecodingExtent::U8(buffer_size),
|
||||||
match self.image().sample_format {
|
n if n <= 16 => DecodingExtent::U16(buffer_size),
|
||||||
SampleFormat::Uint => match max_sample_bits {
|
n if n <= 32 => DecodingExtent::U32(buffer_size),
|
||||||
n if n <= 8 => DecodingResult::new_u8(buffer_size, limits),
|
n if n <= 64 => DecodingExtent::U64(buffer_size),
|
||||||
n if n <= 16 => DecodingResult::new_u16(buffer_size, limits),
|
n => {
|
||||||
n if n <= 32 => DecodingResult::new_u32(buffer_size, limits),
|
return Err(TiffError::UnsupportedError(
|
||||||
n if n <= 64 => DecodingResult::new_u64(buffer_size, limits),
|
|
||||||
n => Err(TiffError::UnsupportedError(
|
|
||||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||||
)),
|
))
|
||||||
},
|
|
||||||
SampleFormat::IEEEFP => match max_sample_bits {
|
|
||||||
16 => DecodingResult::new_f16(buffer_size, limits),
|
|
||||||
32 => DecodingResult::new_f32(buffer_size, limits),
|
|
||||||
64 => DecodingResult::new_f64(buffer_size, limits),
|
|
||||||
n => Err(TiffError::UnsupportedError(
|
|
||||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
SampleFormat::Int => match max_sample_bits {
|
|
||||||
n if n <= 8 => DecodingResult::new_i8(buffer_size, limits),
|
|
||||||
n if n <= 16 => DecodingResult::new_i16(buffer_size, limits),
|
|
||||||
n if n <= 32 => DecodingResult::new_i32(buffer_size, limits),
|
|
||||||
n if n <= 64 => DecodingResult::new_i64(buffer_size, limits),
|
|
||||||
n => Err(TiffError::UnsupportedError(
|
|
||||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
format => Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format]).into()),
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
SampleFormat::IEEEFP => match bits_per_sample {
|
||||||
|
16 => DecodingExtent::F16(buffer_size),
|
||||||
|
32 => DecodingExtent::F32(buffer_size),
|
||||||
|
64 => DecodingExtent::F64(buffer_size),
|
||||||
|
n => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SampleFormat::Int => match bits_per_sample {
|
||||||
|
n if n <= 8 => DecodingExtent::I8(buffer_size),
|
||||||
|
n if n <= 16 => DecodingExtent::I16(buffer_size),
|
||||||
|
n if n <= 32 => DecodingExtent::I32(buffer_size),
|
||||||
|
n if n <= 64 => DecodingExtent::I64(buffer_size),
|
||||||
|
n => {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
format => {
|
||||||
|
return Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format]).into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the layout preferred to read the specified chunk with [`Self::read_chunk_bytes`].
|
||||||
|
///
|
||||||
|
/// Returns the layout without being specific as to the underlying type for forward
|
||||||
|
/// compatibility. Note that, in general, a TIFF may contain an almost arbitrary number of
|
||||||
|
/// channels of individual *bit* length and format each.
|
||||||
|
///
|
||||||
|
/// See [`Self::colortype`] to describe the sample types.
|
||||||
|
///
|
||||||
|
/// # Bugs
|
||||||
|
///
|
||||||
|
/// When the image is stored as a planar configuration, this method will currently only
|
||||||
|
/// indicate the layout needed to read the first data plane. This will be fixed in a future
|
||||||
|
/// major version of `tiff`.
|
||||||
|
pub fn image_chunk_buffer_layout(
|
||||||
|
&mut self,
|
||||||
|
chunk_index: u32,
|
||||||
|
) -> TiffResult<BufferLayoutPreference> {
|
||||||
|
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
||||||
|
let layout = self
|
||||||
|
.result_extent(data_dims.0 as usize, data_dims.1 as usize)?
|
||||||
|
.preferred_layout()?;
|
||||||
|
|
||||||
|
let row_stride = self.image.minimum_row_stride(data_dims);
|
||||||
|
|
||||||
|
Ok(BufferLayoutPreference {
|
||||||
|
len: layout.size(),
|
||||||
|
row_stride,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector.
|
/// Read the specified chunk (at index `chunk_index`) and return the binary data as a Vector.
|
||||||
|
///
|
||||||
|
/// # Bugs
|
||||||
|
///
|
||||||
|
/// When the image is stored as a planar configuration, this method will currently only read
|
||||||
|
/// the first sample's plane. This will be fixed in a future major version of `tiff`.
|
||||||
pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<DecodingResult> {
|
pub fn read_chunk(&mut self, chunk_index: u32) -> TiffResult<DecodingResult> {
|
||||||
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
||||||
|
|
||||||
@@ -916,6 +1078,32 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read the specified chunk (at index `chunk_index`) into an allocated buffer.
|
||||||
|
///
|
||||||
|
/// Returns a [`TiffError::UsageError`] if the chunk is smaller than the size indicated with a
|
||||||
|
/// call to [`Self::image_chunk_buffer_layout`]. Note that the alignment may be arbitrary, but
|
||||||
|
/// an alignment smaller than the preferred alignment may perform worse.
|
||||||
|
///
|
||||||
|
/// # Bugs
|
||||||
|
///
|
||||||
|
/// When the image is stored as a planar configuration, this method will currently only read
|
||||||
|
/// the first sample's plane. This will be fixed in a future major version of `tiff`.
|
||||||
|
pub fn read_chunk_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> {
|
||||||
|
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
||||||
|
|
||||||
|
let extent = self.result_extent(data_dims.0 as usize, data_dims.1 as usize)?;
|
||||||
|
extent.assert_layout(buffer.len())?;
|
||||||
|
|
||||||
|
let output_row_stride = u64::from(data_dims.0)
|
||||||
|
.saturating_mul(self.image.samples_per_pixel() as u64)
|
||||||
|
.saturating_mul(self.image.bits_per_sample as u64)
|
||||||
|
.div_ceil(8);
|
||||||
|
|
||||||
|
self.read_chunk_to_bytes(buffer, chunk_index, output_row_stride)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the default chunk size for the current image. Any given chunk in the image is at most as large as
|
/// Returns the default chunk size for the current image. Any given chunk in the image is at most as large as
|
||||||
/// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`.
|
/// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`.
|
||||||
pub fn chunk_dimensions(&self) -> (u32, u32) {
|
pub fn chunk_dimensions(&self) -> (u32, u32) {
|
||||||
@@ -930,13 +1118,69 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
.expect("invalid chunk_index")
|
.expect("invalid chunk_index")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the preferred buffer required to read the whole image with [`Self::read_image_bytes`].
|
||||||
|
///
|
||||||
|
/// Returns the layout without being specific as to the underlying type for forward
|
||||||
|
/// compatibility. Note that, in general, a TIFF may contain an almost arbitrary number of
|
||||||
|
/// channels of individual *bit* length and format each.
|
||||||
|
///
|
||||||
|
/// See [`Self::colortype`] to describe the sample types.
|
||||||
|
///
|
||||||
|
/// # Bugs
|
||||||
|
///
|
||||||
|
/// When the image is stored as a planar configuration, this method will currently only
|
||||||
|
/// indicate the layout needed to read the first data plane. This will be fixed in a future
|
||||||
|
/// major version of `tiff`.
|
||||||
|
pub fn image_buffer_layout(&mut self) -> TiffResult<BufferLayoutPreference> {
|
||||||
|
let data_dims @ (width, height) = (self.image.width, self.image.height);
|
||||||
|
|
||||||
|
let layout = self
|
||||||
|
.result_extent(width as usize, height as usize)?
|
||||||
|
.preferred_layout()?;
|
||||||
|
|
||||||
|
let row_stride = self.image.minimum_row_stride(data_dims);
|
||||||
|
|
||||||
|
Ok(BufferLayoutPreference {
|
||||||
|
len: layout.size(),
|
||||||
|
row_stride,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Decodes the entire image and return it as a Vector
|
/// Decodes the entire image and return it as a Vector
|
||||||
|
///
|
||||||
|
/// # Bugs
|
||||||
|
///
|
||||||
|
/// When the image is stored as a planar configuration, this method will currently only read
|
||||||
|
/// the first sample's plane. This will be fixed in a future major version of `tiff`.
|
||||||
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
||||||
let width = self.image().width;
|
let width = self.image().width;
|
||||||
let height = self.image().height;
|
let height = self.image().height;
|
||||||
|
|
||||||
let mut result = self.result_buffer(width as usize, height as usize)?;
|
let mut result = self.result_buffer(width as usize, height as usize)?;
|
||||||
|
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes the entire image into a provided buffer.
|
||||||
|
///
|
||||||
|
/// Returns a [`TiffError::UsageError`] if the chunk is smaller than the size indicated with a
|
||||||
|
/// call to [`Self::image_buffer_layout`]. Note that the alignment may be arbitrary, but an
|
||||||
|
/// alignment smaller than the preferred alignment may perform worse.
|
||||||
|
///
|
||||||
|
/// # Bugs
|
||||||
|
///
|
||||||
|
/// When the image is stored as a planar configuration, this method will currently only read
|
||||||
|
/// the first sample's plane. This will be fixed in a future major version of `tiff`.
|
||||||
|
pub fn read_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
|
||||||
|
let width = self.image().width;
|
||||||
|
let height = self.image().height;
|
||||||
|
|
||||||
|
let extent = self.result_extent(width as usize, height as usize)?;
|
||||||
|
extent.assert_layout(buffer.len())?;
|
||||||
|
|
||||||
if width == 0 || height == 0 {
|
if width == 0 || height == 0 {
|
||||||
return Ok(result);
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let chunk_dimensions = self.image().chunk_dimensions()?;
|
let chunk_dimensions = self.image().chunk_dimensions()?;
|
||||||
@@ -960,12 +1204,12 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
let output_row_bits = (width as u64 * self.image.bits_per_sample as u64)
|
let output_row_bits = (width as u64 * self.image.bits_per_sample as u64)
|
||||||
.checked_mul(samples as u64)
|
.checked_mul(samples as u64)
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
let output_row_stride: usize = ((output_row_bits + 7) / 8).try_into()?;
|
let output_row_stride: usize = output_row_bits.div_ceil(8).try_into()?;
|
||||||
|
|
||||||
let chunk_row_bits = (chunk_dimensions.0 as u64 * self.image.bits_per_sample as u64)
|
let chunk_row_bits = (chunk_dimensions.0 as u64 * self.image.bits_per_sample as u64)
|
||||||
.checked_mul(samples as u64)
|
.checked_mul(samples as u64)
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
|
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
|
||||||
|
|
||||||
let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize;
|
let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize;
|
||||||
|
|
||||||
@@ -989,13 +1233,13 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
y * output_row_stride * chunk_dimensions.1 as usize + x * chunk_row_bytes;
|
y * output_row_stride * chunk_dimensions.1 as usize + x * chunk_row_bytes;
|
||||||
self.image.expand_chunk(
|
self.image.expand_chunk(
|
||||||
&mut self.value_reader,
|
&mut self.value_reader,
|
||||||
&mut result.as_buffer(0).as_bytes_mut()[buffer_offset..],
|
&mut buffer[buffer_offset..],
|
||||||
output_row_stride,
|
output_row_stride,
|
||||||
chunk as u32,
|
chunk as u32,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the IFD decoder for our current image IFD.
|
/// Get the IFD decoder for our current image IFD.
|
||||||
@@ -1053,6 +1297,15 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.image_ifd().find_tag_unsigned(tag)
|
self.image_ifd().find_tag_unsigned(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tries to retrieve a vector of all a tag's values and convert them to the desired unsigned
|
||||||
|
/// type.
|
||||||
|
pub fn find_tag_unsigned_vec<T: TryFrom<u64>>(
|
||||||
|
&mut self,
|
||||||
|
tag: Tag,
|
||||||
|
) -> TiffResult<Option<Vec<T>>> {
|
||||||
|
self.image_ifd().find_tag_unsigned_vec(tag)
|
||||||
|
}
|
||||||
|
|
||||||
/// Tries to retrieve a tag from the current image directory and convert it to the desired
|
/// Tries to retrieve a tag from the current image directory and convert it to the desired
|
||||||
/// unsigned type. Returns an error if the tag is not present.
|
/// unsigned type. Returns an error if the tag is not present.
|
||||||
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
|
pub fn get_tag_unsigned<T: TryFrom<u64>>(&mut self, tag: Tag) -> TiffResult<T> {
|
||||||
|
|||||||
+111
-14
@@ -1,5 +1,4 @@
|
|||||||
//! All IO functionality needed for TIFF decoding
|
//! All IO functionality needed for TIFF decoding
|
||||||
|
|
||||||
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
||||||
|
|
||||||
/// Byte order of the TIFF file.
|
/// Byte order of the TIFF file.
|
||||||
@@ -135,20 +134,17 @@ impl<R: Read> EndianReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
//
|
||||||
/// # READERS
|
// # READERS
|
||||||
///
|
//
|
||||||
|
|
||||||
///
|
|
||||||
/// ## Deflate Reader
|
|
||||||
///
|
|
||||||
|
|
||||||
|
/// Type alias for the deflate Reader
|
||||||
#[cfg(feature = "deflate")]
|
#[cfg(feature = "deflate")]
|
||||||
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
||||||
|
|
||||||
///
|
//
|
||||||
/// ## LZW Reader
|
// ## LZW Reader
|
||||||
///
|
//
|
||||||
|
|
||||||
/// Reader that decompresses LZW streams
|
/// Reader that decompresses LZW streams
|
||||||
#[cfg(feature = "lzw")]
|
#[cfg(feature = "lzw")]
|
||||||
@@ -207,10 +203,11 @@ impl<R: Read> Read for LZWReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
//
|
||||||
/// ## PackBits Reader
|
// ## PackBits Reader
|
||||||
///
|
//
|
||||||
|
|
||||||
|
/// Internal state machine for the PackBitsReader.
|
||||||
enum PackBitsReaderState {
|
enum PackBitsReaderState {
|
||||||
Header,
|
Header,
|
||||||
Literal,
|
Literal,
|
||||||
@@ -278,6 +275,106 @@ impl<R: Read> Read for PackBitsReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
pub struct Group4Reader<R: Read> {
|
||||||
|
decoder: fax34::decoder::Group4Decoder<io::Bytes<io::BufReader<io::Take<R>>>>,
|
||||||
|
line_buf: io::Cursor<Vec<u8>>,
|
||||||
|
height: u16,
|
||||||
|
width: u16,
|
||||||
|
y: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
impl<R: Read> Group4Reader<R> {
|
||||||
|
pub fn new(
|
||||||
|
dimensions: (u32, u32),
|
||||||
|
reader: R,
|
||||||
|
compressed_length: u64,
|
||||||
|
) -> crate::TiffResult<Self> {
|
||||||
|
let width = u16::try_from(dimensions.0)?;
|
||||||
|
let height = u16::try_from(dimensions.1)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
decoder: fax34::decoder::Group4Decoder::new(
|
||||||
|
io::BufReader::new(reader.take(compressed_length)).bytes(),
|
||||||
|
width,
|
||||||
|
)?,
|
||||||
|
line_buf: io::Cursor::new(Vec::with_capacity(width.into())),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
impl<R: Read> Read for Group4Reader<R> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
// Either we have not read any line or we are at the end of a line.
|
||||||
|
if self.line_buf.position() as usize == self.line_buf.get_ref().len()
|
||||||
|
&& self.y < self.height
|
||||||
|
{
|
||||||
|
let next = self.decoder.advance().map_err(std::io::Error::other)?;
|
||||||
|
|
||||||
|
match next {
|
||||||
|
fax34::decoder::DecodeStatus::End => (),
|
||||||
|
fax34::decoder::DecodeStatus::Incomplete => {
|
||||||
|
self.y += 1;
|
||||||
|
|
||||||
|
// We known `transitions` yields exactly `self.width` items (per doc).
|
||||||
|
// FIXME: performance. We do not need an individual pixel iterator, filling
|
||||||
|
// memory especially for long runs can be much quicker. The `transitions` are
|
||||||
|
// the positions at which each run-length ends, i.e. the prefix sum of run
|
||||||
|
// lengths. Runs in fax4 start with white.
|
||||||
|
let transitions = fax34::decoder::pels(self.decoder.transition(), self.width);
|
||||||
|
|
||||||
|
let buffer = self.line_buf.get_mut();
|
||||||
|
buffer.resize(usize::from(self.width).div_ceil(8), 0u8);
|
||||||
|
|
||||||
|
let target = &mut buffer[..];
|
||||||
|
|
||||||
|
// Note: it may seem strange to treat black as 0b1 and white as 0b0 despite all
|
||||||
|
// our streams by default decoding as-if PhotometricInterpretation::BlackIsMin.
|
||||||
|
// This is however consistent with libtiff. It seems that fax4's "White"
|
||||||
|
// differs from what libtiff thinks of as "White". For content, a line of data
|
||||||
|
// is in runlength encoding of white-black-white-black always starting with
|
||||||
|
// white. In libtiff, the loop always does both colors in one go and the
|
||||||
|
// structure is:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// for (; runs < erun; runs += 2)
|
||||||
|
// // white run
|
||||||
|
// do { *lp++ = 0L; } while (…)
|
||||||
|
// // black run
|
||||||
|
// do { *lp++ = -1L; } while (…)
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// So indeed the Fax4::White run is implemented by filling with zeros.
|
||||||
|
let mut bits = transitions.map(|c| match c {
|
||||||
|
fax34::Color::Black => true,
|
||||||
|
fax34::Color::White => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assemble bits in MSB as per our library representation for buffer.
|
||||||
|
for byte in target {
|
||||||
|
let mut val = 0;
|
||||||
|
|
||||||
|
for (idx, bit) in bits.by_ref().take(8).enumerate() {
|
||||||
|
val |= u8::from(bit) << (7 - idx % 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
*byte = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.line_buf.set_position(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.line_buf.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ pub struct Directory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Directory {
|
impl Directory {
|
||||||
|
/// Create a directory in an initial state without entries. Note that an empty directory can
|
||||||
|
/// not be encoded in a file, it must contain at least one entry.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Directory {
|
Directory {
|
||||||
entries: BTreeMap::new(),
|
entries: BTreeMap::new(),
|
||||||
@@ -72,6 +74,12 @@ impl Directory {
|
|||||||
self.entries.len()
|
self.entries.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if there are any entries in this directory. Note that an empty directory can not be
|
||||||
|
/// encoded in the file, it must contain at least one entry.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.entries.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the pointer to the next IFD, if it was defined.
|
/// Get the pointer to the next IFD, if it was defined.
|
||||||
pub fn next(&self) -> Option<IfdPointer> {
|
pub fn next(&self) -> Option<IfdPointer> {
|
||||||
self.next_ifd.map(|n| IfdPointer(n.get()))
|
self.next_ifd.map(|n| IfdPointer(n.get()))
|
||||||
|
|||||||
@@ -11,21 +11,17 @@ pub struct Deflate {
|
|||||||
/// It allows trading compression ratio for compression speed.
|
/// It allows trading compression ratio for compression speed.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[derive(Default)]
|
||||||
pub enum DeflateLevel {
|
pub enum DeflateLevel {
|
||||||
/// The fastest possible compression mode.
|
/// The fastest possible compression mode.
|
||||||
Fast = 1,
|
Fast = 1,
|
||||||
/// The conserative choice between speed and ratio.
|
/// The conserative choice between speed and ratio.
|
||||||
|
#[default]
|
||||||
Balanced = 6,
|
Balanced = 6,
|
||||||
/// The best compression available with Deflate.
|
/// The best compression available with Deflate.
|
||||||
Best = 9,
|
Best = 9,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DeflateLevel {
|
|
||||||
fn default() -> Self {
|
|
||||||
DeflateLevel::Balanced
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deflate {
|
impl Deflate {
|
||||||
/// Create a new deflate compressor with a specific level of compression.
|
/// Create a new deflate compressor with a specific level of compression.
|
||||||
pub fn with_level(level: DeflateLevel) -> Self {
|
pub fn with_level(level: DeflateLevel) -> Self {
|
||||||
|
|||||||
+7
-7
@@ -198,7 +198,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> TiffResult<ImageEncoder<W, C, K>> {
|
) -> TiffResult<ImageEncoder<'_, W, C, K>> {
|
||||||
let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?;
|
let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?;
|
||||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let entry = Self::write_value(
|
let entry = Self::write_value(
|
||||||
&mut self.writer,
|
self.writer,
|
||||||
&DirectoryEntry {
|
&DirectoryEntry {
|
||||||
data_type: <T>::FIELD_TYPE,
|
data_type: <T>::FIELD_TYPE,
|
||||||
count: value.count().try_into()?,
|
count: value.count().try_into()?,
|
||||||
@@ -370,14 +370,14 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
|
|
||||||
if bytes.len() > in_entry_bytes {
|
if bytes.len() > in_entry_bytes {
|
||||||
let offset = writer.offset();
|
let offset = writer.offset();
|
||||||
writer.write_bytes(&bytes)?;
|
writer.write_bytes(bytes)?;
|
||||||
|
|
||||||
let offset = K::convert_offset(offset)?;
|
let offset = K::convert_offset(offset)?;
|
||||||
offset_bytes[..offset.bytes()].copy_from_slice(&offset.data());
|
offset_bytes[..offset.bytes()].copy_from_slice(&offset.data());
|
||||||
} else {
|
} else {
|
||||||
// Note: we have indicated our native byte order in the header, hence this
|
// Note: we have indicated our native byte order in the header, hence this
|
||||||
// corresponds to our byte order no matter the value type.
|
// corresponds to our byte order no matter the value type.
|
||||||
offset_bytes[..bytes.len()].copy_from_slice(&bytes);
|
offset_bytes[..bytes.len()].copy_from_slice(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undoing some hidden type. Offset is either u32 or u64. Due to the trait API being public
|
// Undoing some hidden type. Offset is either u32 or u64. Due to the trait API being public
|
||||||
@@ -532,11 +532,11 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
let rows_per_strip = {
|
let rows_per_strip = {
|
||||||
match compression.tag() {
|
match compression.tag() {
|
||||||
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
|
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
|
||||||
_ => (1_000_000 + row_bytes - 1) / row_bytes,
|
_ => 1_000_000_u64.div_ceil(row_bytes),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
|
let strip_count = u64::from(height).div_ceil(rows_per_strip);
|
||||||
|
|
||||||
encoder.write_tag(Tag::ImageWidth, width)?;
|
encoder.write_tag(Tag::ImageWidth, width)?;
|
||||||
encoder.write_tag(Tag::ImageLength, height)?;
|
encoder.write_tag(Tag::ImageLength, height)?;
|
||||||
@@ -705,7 +705,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
self.encoder.write_tag(Tag::RowsPerStrip, value)?;
|
self.encoder.write_tag(Tag::RowsPerStrip, value)?;
|
||||||
|
|
||||||
let value: u64 = value as u64;
|
let value: u64 = value as u64;
|
||||||
self.strip_count = (self.height as u64 + value - 1) / value;
|
self.strip_count = (self.height as u64).div_ceil(value);
|
||||||
self.rows_per_strip = value;
|
self.rows_per_strip = value;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+29
-29
@@ -15,7 +15,7 @@ pub trait TiffValue {
|
|||||||
|
|
||||||
/// Access this value as an contiguous sequence of bytes.
|
/// Access this value as an contiguous sequence of bytes.
|
||||||
/// If their is no trivial representation, allocate it on the heap.
|
/// If their is no trivial representation, allocate it on the heap.
|
||||||
fn data(&self) -> Cow<[u8]>;
|
fn data(&self) -> Cow<'_, [u8]>;
|
||||||
|
|
||||||
/// Write this value to a TiffWriter.
|
/// Write this value to a TiffWriter.
|
||||||
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
||||||
@@ -34,7 +34,7 @@ impl TiffValue for [u8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(self)
|
Cow::Borrowed(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ impl TiffValue for [i8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ impl TiffValue for [u16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ impl TiffValue for [i16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ impl TiffValue for [u32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ impl TiffValue for [i32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ impl TiffValue for [u64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ impl TiffValue for [i64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ impl TiffValue for [f32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
// We write using native endian so this should be safe
|
// We write using native endian so this should be safe
|
||||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ impl TiffValue for [f64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
// We write using native endian so this should be safe
|
// We write using native endian so this should be safe
|
||||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ impl TiffValue for u8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(from_ref(self))
|
Cow::Borrowed(from_ref(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@ impl TiffValue for i8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ impl TiffValue for u16 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ impl TiffValue for i16 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ impl TiffValue for u32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ impl TiffValue for i32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,7 +279,7 @@ impl TiffValue for u64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,7 +297,7 @@ impl TiffValue for i64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,7 +315,7 @@ impl TiffValue for f32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,7 +333,7 @@ impl TiffValue for f64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,7 +351,7 @@ impl TiffValue for Ifd {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,7 +369,7 @@ impl TiffValue for Ifd8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +388,7 @@ impl TiffValue for Rational {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
||||||
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
||||||
@@ -411,7 +411,7 @@ impl TiffValue for SRational {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
||||||
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
||||||
@@ -438,7 +438,7 @@ impl TiffValue for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||||
let bytes: &[u8] = self.as_bytes();
|
let bytes: &[u8] = self.as_bytes();
|
||||||
@@ -450,7 +450,7 @@ impl TiffValue for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
impl<T: TiffValue + ?Sized> TiffValue for &'_ T {
|
||||||
const BYTE_LEN: u8 = T::BYTE_LEN;
|
const BYTE_LEN: u8 = T::BYTE_LEN;
|
||||||
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
||||||
|
|
||||||
@@ -462,7 +462,7 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
|||||||
(*self).write(writer)
|
(*self).write(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
T::data(self)
|
T::data(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,7 +484,7 @@ macro_rules! impl_tiff_value_for_contiguous_sequence {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
||||||
for x in self {
|
for x in self {
|
||||||
buf.extend_from_slice(&x.data());
|
buf.extend_from_slice(&x.data());
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ quick_error! {
|
|||||||
PredictorUnavailable {
|
PredictorUnavailable {
|
||||||
display("the requested predictor is not available")
|
display("the requested predictor is not available")
|
||||||
}
|
}
|
||||||
|
InsufficientOutputBufferSize { needed: usize, provided: usize } {
|
||||||
|
display("the borrowed output buffer is not large enough for the decoded data, needed {needed} but have {provided}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-10
@@ -2,7 +2,7 @@ macro_rules! tags {
|
|||||||
{
|
{
|
||||||
// Permit arbitrary meta items, which include documentation.
|
// Permit arbitrary meta items, which include documentation.
|
||||||
$( #[$enum_attr:meta] )*
|
$( #[$enum_attr:meta] )*
|
||||||
$vis:vis enum $name:ident($ty:tt) $(unknown($unknown_doc:literal))* {
|
$vis:vis enum $name:ident($ty:tt) $(unknown(#[$unknown_meta:meta] $unknown_doc:ident))* {
|
||||||
// Each of the `Name = Val,` permitting documentation.
|
// Each of the `Name = Val,` permitting documentation.
|
||||||
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
|
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ macro_rules! tags {
|
|||||||
pub enum $name {
|
pub enum $name {
|
||||||
$($(#[$ident_attr])* $tag,)*
|
$($(#[$ident_attr])* $tag,)*
|
||||||
$(
|
$(
|
||||||
#[doc = $unknown_doc]
|
#[$unknown_meta]
|
||||||
Unknown($ty),
|
Unknown($ty),
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ macro_rules! tags {
|
|||||||
fn __to_inner_type(&self) -> $ty {
|
fn __to_inner_type(&self) -> $ty {
|
||||||
match *self {
|
match *self {
|
||||||
$( $name::$tag => $val, )*
|
$( $name::$tag => $val, )*
|
||||||
$( $name::Unknown(n) => { $unknown_doc; n }, )*
|
$( $name::Unknown($unknown_doc) => { $unknown_doc }, )*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ macro_rules! tags {
|
|||||||
tags!($name, $ty, $($unknown_doc)*);
|
tags!($name, $ty, $($unknown_doc)*);
|
||||||
};
|
};
|
||||||
// For u16 tags, provide direct inherent primitive conversion methods.
|
// For u16 tags, provide direct inherent primitive conversion methods.
|
||||||
($name:tt, u16, $($unknown_doc:literal)*) => {
|
($name:tt, u16, $($unknown_doc:ident)*) => {
|
||||||
impl $name {
|
impl $name {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn from_u16(val: u16) -> Option<Self> {
|
pub fn from_u16(val: u16) -> Option<Self> {
|
||||||
@@ -48,9 +48,8 @@ macro_rules! tags {
|
|||||||
|
|
||||||
$(
|
$(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn from_u16_exhaustive(val: u16) -> Self {
|
pub fn from_u16_exhaustive($unknown_doc: u16) -> Self {
|
||||||
$unknown_doc;
|
Self::__from_inner_type($unknown_doc).unwrap_or_else(|_| $name::Unknown($unknown_doc))
|
||||||
Self::__from_inner_type(val).unwrap_or_else(|_| $name::Unknown(val))
|
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
@@ -68,7 +67,10 @@ macro_rules! tags {
|
|||||||
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
||||||
tags! {
|
tags! {
|
||||||
/// TIFF tags
|
/// TIFF tags
|
||||||
pub enum Tag(u16) unknown("A private or extension tag") {
|
pub enum Tag(u16) unknown(
|
||||||
|
/// A private or extension tag
|
||||||
|
unknown
|
||||||
|
) {
|
||||||
// Baseline tags:
|
// Baseline tags:
|
||||||
Artist = 315,
|
Artist = 315,
|
||||||
// grayscale images PhotometricInterpretation 1 or 3
|
// grayscale images PhotometricInterpretation 1 or 3
|
||||||
@@ -131,6 +133,8 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
|||||||
ExifDirectory = 0x8769,
|
ExifDirectory = 0x8769,
|
||||||
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
||||||
GpsDirectory = 0x8825,
|
GpsDirectory = 0x8825,
|
||||||
|
// <https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf>
|
||||||
|
IccProfile = 34675,
|
||||||
GeoKeyDirectoryTag = 34735, // (SPOT)
|
GeoKeyDirectoryTag = 34735, // (SPOT)
|
||||||
GeoDoubleParamsTag = 34736, // (SPOT)
|
GeoDoubleParamsTag = 34736, // (SPOT)
|
||||||
GeoAsciiParamsTag = 34737, // (SPOT)
|
GeoAsciiParamsTag = 34737, // (SPOT)
|
||||||
@@ -192,7 +196,10 @@ pub enum Type(u16) {
|
|||||||
tags! {
|
tags! {
|
||||||
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||||
/// for reference.
|
/// for reference.
|
||||||
pub enum CompressionMethod(u16) unknown("A custom compression method") {
|
pub enum CompressionMethod(u16) unknown(
|
||||||
|
/// A custom compression method
|
||||||
|
unknown
|
||||||
|
) {
|
||||||
None = 1,
|
None = 1,
|
||||||
Huffman = 2,
|
Huffman = 2,
|
||||||
Fax3 = 3,
|
Fax3 = 3,
|
||||||
@@ -254,7 +261,10 @@ pub enum ResolutionUnit(u16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
pub enum SampleFormat(u16) unknown("An unknown extension sample format") {
|
pub enum SampleFormat(u16) unknown(
|
||||||
|
/// An unknown extension sample format
|
||||||
|
unknown
|
||||||
|
) {
|
||||||
Uint = 1,
|
Uint = 1,
|
||||||
Int = 2,
|
Int = 2,
|
||||||
IEEEFP = 3,
|
IEEEFP = 3,
|
||||||
|
|||||||
+172
-10
@@ -4,6 +4,7 @@ use tiff::decoder::{ifd, Decoder, DecodingResult};
|
|||||||
use tiff::ColorType;
|
use tiff::ColorType;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Seek};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
||||||
@@ -45,12 +46,12 @@ fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
|
|||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
assert!(match decoder.read_image() {
|
assert!(matches!(
|
||||||
|
decoder.read_image(),
|
||||||
Err(tiff::TiffError::UnsupportedError(
|
Err(tiff::TiffError::UnsupportedError(
|
||||||
tiff::TiffUnsupportedError::UnsupportedColorType(_),
|
tiff::TiffUnsupportedError::UnsupportedColorType(_),
|
||||||
)) => true,
|
))
|
||||||
_ => false,
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -172,7 +173,7 @@ fn test_string_tags() {
|
|||||||
&s,
|
&s,
|
||||||
"GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/"
|
"GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/"
|
||||||
),
|
),
|
||||||
_ => assert!(false),
|
other => unreachable!("Expected Ascii tag from file, got {other:?}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,6 +232,54 @@ fn test_tiled_rect_rgb_u8() {
|
|||||||
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
|
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inner_access() {
|
||||||
|
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
|
||||||
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||||
|
|
||||||
|
let c = decoder
|
||||||
|
.get_tag(tiff::tags::Tag::Compression)
|
||||||
|
.unwrap()
|
||||||
|
.into_u16()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(c, tiff::tags::CompressionMethod::None.to_u16());
|
||||||
|
|
||||||
|
// Because the image is uncompressed, reading the first tile directly with the inner reader
|
||||||
|
// should yield the same result as reading it with the decoder's read_chunk method.
|
||||||
|
let first_offset = decoder
|
||||||
|
.get_tag_u32_vec(tiff::tags::Tag::TileOffsets)
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
let first_byte_count = decoder
|
||||||
|
.get_tag_u32_vec(tiff::tags::Tag::TileByteCounts)
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
decoder
|
||||||
|
.inner()
|
||||||
|
.seek(std::io::SeekFrom::Start(first_offset as u64))
|
||||||
|
.expect("Cannot seek to tile offset");
|
||||||
|
let mut buf = vec![0; first_byte_count as usize];
|
||||||
|
decoder
|
||||||
|
.inner()
|
||||||
|
.read_exact(&mut buf)
|
||||||
|
.expect("Cannot read tile data");
|
||||||
|
let raw_sum: u64 = buf.into_iter().map(<u64>::from).sum();
|
||||||
|
|
||||||
|
match decoder.read_chunk(0).unwrap() {
|
||||||
|
DecodingResult::U8(chunk) => {
|
||||||
|
let sum: u64 = chunk.into_iter().map(<u64>::from).sum();
|
||||||
|
assert_eq!(sum, raw_sum);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong bit depth"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* #[test]
|
/* #[test]
|
||||||
fn test_tiled_jpeg_rgb_u8() {
|
fn test_tiled_jpeg_rgb_u8() {
|
||||||
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
|
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
|
||||||
@@ -292,6 +341,11 @@ fn test_tiled_incremental() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_planar_rgb_u8() {
|
fn test_planar_rgb_u8() {
|
||||||
|
test_image_sum_u8("planar-rgb-u8.tif", ColorType::RGB(8), 15417630);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_planar_bands() {
|
||||||
// gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
|
// 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 file = "planar-rgb-u8.tif";
|
||||||
let expected_type = ColorType::RGB(8);
|
let expected_type = ColorType::RGB(8);
|
||||||
@@ -328,8 +382,6 @@ fn test_planar_rgb_u8() {
|
|||||||
}
|
}
|
||||||
_ => panic!("Wrong bit depth"),
|
_ => panic!("Wrong bit depth"),
|
||||||
}
|
}
|
||||||
|
|
||||||
test_image_sum_u8(file, ColorType::RGB(8), 15417630);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -348,7 +400,7 @@ fn test_div_zero() {
|
|||||||
|
|
||||||
match err {
|
match err {
|
||||||
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
unexpected => panic!("Unexpected error {unexpected}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +418,7 @@ fn test_too_many_value_bytes() {
|
|||||||
|
|
||||||
match error {
|
match error {
|
||||||
tiff::TiffError::LimitsExceeded => {}
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
unexpected => panic!("Unexpected error {unexpected}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,7 +549,7 @@ fn timeout() {
|
|||||||
|
|
||||||
match error {
|
match error {
|
||||||
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
||||||
e => panic!("Unexpected error {:?}", e),
|
e => panic!("Unexpected error {e:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,3 +574,113 @@ fn test_zstd_compression() {
|
|||||||
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
|
// 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);
|
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_image_bytes(
|
||||||
|
file: &str,
|
||||||
|
expected: &[(ColorType, u32)],
|
||||||
|
// Represent all samples as little endian, ensuring check sums on all platforms.
|
||||||
|
normalize_byte_order: fn(&mut [u8]),
|
||||||
|
) {
|
||||||
|
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");
|
||||||
|
|
||||||
|
let mut buffer = vec![0u8; 0];
|
||||||
|
for (idx, &(expected_type, expected_crc)) in expected.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
decoder
|
||||||
|
.next_image()
|
||||||
|
.expect("mismatched number of directories");
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
|
let layout = decoder
|
||||||
|
.image_buffer_layout()
|
||||||
|
.expect("Cannot describe buffer layout");
|
||||||
|
|
||||||
|
buffer.resize(layout.len, 0u8);
|
||||||
|
decoder.read_image_bytes(&mut buffer).unwrap();
|
||||||
|
normalize_byte_order(&mut buffer);
|
||||||
|
|
||||||
|
// Well almost a crc..
|
||||||
|
assert_eq!(crc32fast::hash(&buffer), expected_crc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_order_u16(bytes: &mut [u8]) {
|
||||||
|
for chunk in bytes.chunks_exact_mut(2) {
|
||||||
|
let n = u16::from_ne_bytes(chunk.try_into().unwrap());
|
||||||
|
chunk.copy_from_slice(&n.to_le_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_order_u32(bytes: &mut [u8]) {
|
||||||
|
for chunk in bytes.chunks_exact_mut(4) {
|
||||||
|
let n = u32::from_ne_bytes(chunk.try_into().unwrap());
|
||||||
|
chunk.copy_from_slice(&n.to_le_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_order_u64(bytes: &mut [u8]) {
|
||||||
|
for chunk in bytes.chunks_exact_mut(8) {
|
||||||
|
let n = u64::from_ne_bytes(chunk.try_into().unwrap());
|
||||||
|
chunk.copy_from_slice(&n.to_le_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_cmyk_3c_8b() {
|
||||||
|
test_image_bytes(
|
||||||
|
"cmyk-3c-8b.tiff",
|
||||||
|
&[(ColorType::CMYK(8), 2935032230)],
|
||||||
|
|_| (),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_gray_16b() {
|
||||||
|
test_image_bytes(
|
||||||
|
"minisblack-1c-16b.tiff",
|
||||||
|
&[(ColorType::Gray(16), 3524597615)],
|
||||||
|
byte_order_u16,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_gray_32b() {
|
||||||
|
test_image_bytes(
|
||||||
|
"gradient-1c-32b.tiff",
|
||||||
|
&[(ColorType::Gray(32), 2257036530)],
|
||||||
|
byte_order_u32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_gray_u64() {
|
||||||
|
test_image_bytes(
|
||||||
|
"gradient-1c-64b.tiff",
|
||||||
|
&[(ColorType::Gray(64), 2156547113)],
|
||||||
|
byte_order_u64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bytes_gray_f32() {
|
||||||
|
test_image_bytes(
|
||||||
|
"gradient-1c-32b-float.tiff",
|
||||||
|
&[(ColorType::Gray(32), 3232356730)],
|
||||||
|
byte_order_u32,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
fn test_fax4() {
|
||||||
|
test_image_sum_u8("fax4.tiff", ColorType::Gray(1), 7802706);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "fax")]
|
||||||
|
fn test_fax4_white_is_min() {
|
||||||
|
test_image_sum_u8("imagemagick_group4.tiff", ColorType::Gray(1), 3742820);
|
||||||
|
}
|
||||||
@@ -621,7 +621,7 @@ fn test_rows_per_strip() {
|
|||||||
let img2 = [i; 2 * 100];
|
let img2 = [i; 2 * 100];
|
||||||
match decoder.read_chunk(i as u32).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:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -652,8 +652,6 @@ fn test_auxiliary_directory() {
|
|||||||
encoder.write_tag(Tag::ExifDirectory, exif.offset).unwrap();
|
encoder.write_tag(Tag::ExifDirectory, exif.offset).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(tiff);
|
|
||||||
|
|
||||||
data.set_position(0);
|
data.set_position(0);
|
||||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ fn encode_decode_with_compression(compression: Compression) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
match decoder.read_image() {
|
match decoder.read_image() {
|
||||||
Ok(DecodingResult::U16(image_data)) => image_data,
|
Ok(DecodingResult::U16(image_data)) => image_data,
|
||||||
unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
|
unexpected => panic!("Descoding RGB failed: {unexpected:?}"),
|
||||||
},
|
},
|
||||||
image_rgb.reference_data()
|
image_rgb.reference_data()
|
||||||
);
|
);
|
||||||
@@ -126,7 +126,7 @@ fn encode_decode_with_compression(compression: Compression) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
match decoder.read_image() {
|
match decoder.read_image() {
|
||||||
Ok(DecodingResult::U8(image_data)) => image_data,
|
Ok(DecodingResult::U8(image_data)) => image_data,
|
||||||
unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
|
unexpected => panic!("Decoding grayscale failed: {unexpected:?}"),
|
||||||
},
|
},
|
||||||
image_grayscale.reference_data()
|
image_grayscale.reference_data()
|
||||||
);
|
);
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
extern crate tiff;
|
extern crate tiff;
|
||||||
|
|
||||||
use tiff::decoder::{Decoder, DecodingResult};
|
use tiff::decoder::{Decoder, DecodingResult};
|
||||||
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
|
use tiff::encoder::{colortype, Predictor, TiffEncoder};
|
||||||
use tiff::ColorType;
|
use tiff::ColorType;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|||||||
Reference in new issue
Block a user