Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
756ea3e813 | ||
|
|
0eead083f6 | ||
|
|
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
|
||||
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:
|
||||
# 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
|
||||
|
||||
+21
@@ -1,3 +1,24 @@
|
||||
# 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
|
||||
|
||||
New features:
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tiff"
|
||||
version = "0.10.0"
|
||||
version = "0.10.2"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
@@ -27,6 +27,8 @@ zune-jpeg = { version = "0.4.17", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.1"
|
||||
# We may already depend on this via flate2
|
||||
crc32fast = "1.5"
|
||||
|
||||
[features]
|
||||
default = ["deflate", "jpeg", "lzw"]
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
//! the unsafe code guidelines).
|
||||
//!
|
||||
//! 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 half::f16;
|
||||
|
||||
+1
-1
@@ -445,7 +445,7 @@ impl Entry {
|
||||
// 2b: the value is at most 4 bytes or doesn't fit in the offset field.
|
||||
return Ok(match self.type_ {
|
||||
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::SHORT => Short(self.r(bo).read_u16()?),
|
||||
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
||||
|
||||
+24
-11
@@ -10,6 +10,7 @@ use crate::{
|
||||
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||
};
|
||||
use std::io::{self, Cursor, Read, Seek};
|
||||
use std::num::NonZeroUsize;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -29,10 +30,10 @@ pub(crate) struct TileAttributes {
|
||||
|
||||
impl TileAttributes {
|
||||
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 {
|
||||
(self.image_height + self.tile_length - 1) / self.tile_length
|
||||
self.image_height.div_ceil(self.tile_length)
|
||||
}
|
||||
fn padding_right(&self) -> usize {
|
||||
(self.tile_width - self.image_width % self.tile_width) % self.tile_width
|
||||
@@ -363,6 +364,22 @@ 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>(
|
||||
reader: R,
|
||||
compression_method: CompressionMethod,
|
||||
@@ -625,12 +642,12 @@ impl Image {
|
||||
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let chunk_row_bytes: usize = ((chunk_row_bits + 7) / 8).try_into()?;
|
||||
let 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))
|
||||
.checked_mul(samples as u64)
|
||||
.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?
|
||||
assert!(output_row_stride >= data_row_bytes);
|
||||
@@ -643,11 +660,11 @@ impl Image {
|
||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||
)?;
|
||||
|
||||
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];
|
||||
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(
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
@@ -678,11 +695,7 @@ impl Image {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (_i, row) in buf
|
||||
.chunks_mut(output_row_stride)
|
||||
.take(data_dims.1 as usize)
|
||||
.enumerate()
|
||||
{
|
||||
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
|
||||
let row = &mut row[..data_row_bytes];
|
||||
reader.read_exact(row)?;
|
||||
|
||||
|
||||
+291
-54
@@ -1,5 +1,7 @@
|
||||
use std::alloc::{Layout, LayoutError};
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{self, Read, Seek};
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use crate::tags::{
|
||||
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 {
|
||||
DecodingResult::U8(ref mut buf) => DecodingBuffer::U8(&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)]
|
||||
/// Chunk type of the internal representation
|
||||
pub enum ChunkType {
|
||||
@@ -206,6 +290,7 @@ pub enum ChunkType {
|
||||
|
||||
/// Decoding limits
|
||||
#[derive(Clone, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct Limits {
|
||||
/// The maximum size of any `DecodingResult` in bytes, the default is
|
||||
/// 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
|
||||
/// segment even if the entire image is decoded at once.
|
||||
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 {
|
||||
@@ -234,10 +315,9 @@ impl Limits {
|
||||
/// naturally, the machine running the program does not have infinite memory.
|
||||
pub fn unlimited() -> Limits {
|
||||
Limits {
|
||||
decoding_buffer_size: usize::max_value(),
|
||||
ifd_value_size: usize::max_value(),
|
||||
intermediate_buffer_size: usize::max_value(),
|
||||
_non_exhaustive: (),
|
||||
decoding_buffer_size: usize::MAX,
|
||||
ifd_value_size: usize::MAX,
|
||||
intermediate_buffer_size: usize::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,7 +328,6 @@ impl Default for Limits {
|
||||
decoding_buffer_size: 256 * 1024 * 1024,
|
||||
intermediate_buffer_size: 128 * 1024 * 1024,
|
||||
ifd_value_size: 1024 * 1024,
|
||||
_non_exhaustive: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -665,6 +744,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
|
||||
#[inline]
|
||||
pub fn read_byte(&mut self) -> Result<u8, io::Error> {
|
||||
@@ -837,17 +921,26 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
chunk_index: u32,
|
||||
output_width: usize,
|
||||
) -> TiffResult<()> {
|
||||
let offset = self.image.chunk_file_range(chunk_index)?.0;
|
||||
self.goto_offset_u64(offset)?;
|
||||
|
||||
let output_row_stride = (output_width as u64)
|
||||
.saturating_mul(self.image.samples_per_pixel() as u64)
|
||||
.saturating_mul(self.image.bits_per_sample as u64)
|
||||
/ 8;
|
||||
.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(
|
||||
&mut self.value_reader,
|
||||
buffer.as_bytes_mut(),
|
||||
buffer,
|
||||
output_row_stride.try_into()?,
|
||||
chunk_index,
|
||||
)?;
|
||||
@@ -856,56 +949,109 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
}
|
||||
|
||||
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 {
|
||||
width
|
||||
} else {
|
||||
((((width as u64) * bits_per_sample as u64) + 7) / 8)
|
||||
((width as u64) * bits_per_sample as u64)
|
||||
.div_ceil(8)
|
||||
.try_into()
|
||||
.map_err(|_| TiffError::LimitsExceeded)?
|
||||
};
|
||||
|
||||
let buffer_size = row_samples
|
||||
.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)?;
|
||||
|
||||
let max_sample_bits = self.image().bits_per_sample;
|
||||
let limits = &self.value_reader.limits;
|
||||
Ok(match self.image().sample_format {
|
||||
SampleFormat::Uint => match bits_per_sample {
|
||||
n if n <= 8 => DecodingExtent::U8(buffer_size),
|
||||
n if n <= 16 => DecodingExtent::U16(buffer_size),
|
||||
n if n <= 32 => DecodingExtent::U32(buffer_size),
|
||||
n if n <= 64 => DecodingExtent::U64(buffer_size),
|
||||
n => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
||||
))
|
||||
}
|
||||
},
|
||||
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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
match self.image().sample_format {
|
||||
SampleFormat::Uint => match max_sample_bits {
|
||||
n if n <= 8 => DecodingResult::new_u8(buffer_size, limits),
|
||||
n if n <= 16 => DecodingResult::new_u16(buffer_size, limits),
|
||||
n if n <= 32 => DecodingResult::new_u32(buffer_size, limits),
|
||||
n if n <= 64 => DecodingResult::new_u64(buffer_size, limits),
|
||||
n => Err(TiffError::UnsupportedError(
|
||||
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()),
|
||||
}
|
||||
/// 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.
|
||||
///
|
||||
/// # 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> {
|
||||
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
||||
|
||||
@@ -916,6 +1062,32 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
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
|
||||
/// the value returned here. For the size of the data (chunk minus padding), use `chunk_data_dimensions`.
|
||||
pub fn chunk_dimensions(&self) -> (u32, u32) {
|
||||
@@ -930,13 +1102,69 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
.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
|
||||
///
|
||||
/// # 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> {
|
||||
let width = self.image().width;
|
||||
let height = self.image().height;
|
||||
|
||||
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 {
|
||||
return Ok(result);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let chunk_dimensions = self.image().chunk_dimensions()?;
|
||||
@@ -960,12 +1188,12 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
let output_row_bits = (width as u64 * self.image.bits_per_sample as u64)
|
||||
.checked_mul(samples as u64)
|
||||
.ok_or(TiffError::LimitsExceeded)?;
|
||||
let output_row_stride: usize = ((output_row_bits + 7) / 8).try_into()?;
|
||||
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)
|
||||
.checked_mul(samples as u64)
|
||||
.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;
|
||||
|
||||
@@ -989,13 +1217,13 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
y * output_row_stride * chunk_dimensions.1 as usize + x * chunk_row_bytes;
|
||||
self.image.expand_chunk(
|
||||
&mut self.value_reader,
|
||||
&mut result.as_buffer(0).as_bytes_mut()[buffer_offset..],
|
||||
&mut buffer[buffer_offset..],
|
||||
output_row_stride,
|
||||
chunk as u32,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the IFD decoder for our current image IFD.
|
||||
@@ -1053,6 +1281,15 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
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
|
||||
/// 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> {
|
||||
|
||||
+11
-13
@@ -135,20 +135,17 @@ impl<R: Read> EndianReader<R> {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// # READERS
|
||||
///
|
||||
|
||||
///
|
||||
/// ## Deflate Reader
|
||||
///
|
||||
//
|
||||
// # READERS
|
||||
//
|
||||
|
||||
/// Type alias for the deflate Reader
|
||||
#[cfg(feature = "deflate")]
|
||||
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
||||
|
||||
///
|
||||
/// ## LZW Reader
|
||||
///
|
||||
//
|
||||
// ## LZW Reader
|
||||
//
|
||||
|
||||
/// Reader that decompresses LZW streams
|
||||
#[cfg(feature = "lzw")]
|
||||
@@ -207,10 +204,11 @@ impl<R: Read> Read for LZWReader<R> {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// ## PackBits Reader
|
||||
///
|
||||
//
|
||||
// ## PackBits Reader
|
||||
//
|
||||
|
||||
/// Internal state machine for the PackBitsReader.
|
||||
enum PackBitsReaderState {
|
||||
Header,
|
||||
Literal,
|
||||
|
||||
@@ -27,6 +27,8 @@ pub struct 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 {
|
||||
Directory {
|
||||
entries: BTreeMap::new(),
|
||||
@@ -72,6 +74,12 @@ impl Directory {
|
||||
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.
|
||||
pub fn next(&self) -> Option<IfdPointer> {
|
||||
self.next_ifd.map(|n| IfdPointer(n.get()))
|
||||
|
||||
@@ -11,21 +11,17 @@ pub struct Deflate {
|
||||
/// It allows trading compression ratio for compression speed.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Default)]
|
||||
pub enum DeflateLevel {
|
||||
/// The fastest possible compression mode.
|
||||
Fast = 1,
|
||||
/// The conserative choice between speed and ratio.
|
||||
#[default]
|
||||
Balanced = 6,
|
||||
/// The best compression available with Deflate.
|
||||
Best = 9,
|
||||
}
|
||||
|
||||
impl Default for DeflateLevel {
|
||||
fn default() -> Self {
|
||||
DeflateLevel::Balanced
|
||||
}
|
||||
}
|
||||
|
||||
impl Deflate {
|
||||
/// Create a new deflate compressor with a specific level of compression.
|
||||
pub fn with_level(level: DeflateLevel) -> Self {
|
||||
|
||||
+7
-7
@@ -198,7 +198,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
||||
&mut self,
|
||||
width: 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)?;
|
||||
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(
|
||||
&mut self.writer,
|
||||
self.writer,
|
||||
&DirectoryEntry {
|
||||
data_type: <T>::FIELD_TYPE,
|
||||
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 {
|
||||
let offset = writer.offset();
|
||||
writer.write_bytes(&bytes)?;
|
||||
writer.write_bytes(bytes)?;
|
||||
|
||||
let offset = K::convert_offset(offset)?;
|
||||
offset_bytes[..offset.bytes()].copy_from_slice(&offset.data());
|
||||
} else {
|
||||
// Note: we have indicated our native byte order in the header, hence this
|
||||
// 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
|
||||
@@ -532,11 +532,11 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
let rows_per_strip = {
|
||||
match compression.tag() {
|
||||
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
|
||||
_ => (1_000_000 + row_bytes - 1) / row_bytes,
|
||||
_ => 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::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)?;
|
||||
|
||||
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;
|
||||
|
||||
Ok(())
|
||||
|
||||
+29
-29
@@ -15,7 +15,7 @@ pub trait TiffValue {
|
||||
|
||||
/// Access this value as an contiguous sequence of bytes.
|
||||
/// If their is no trivial representation, allocate it on the heap.
|
||||
fn data(&self) -> Cow<[u8]>;
|
||||
fn data(&self) -> Cow<'_, [u8]>;
|
||||
|
||||
/// Write this value to a TiffWriter.
|
||||
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
||||
@@ -34,7 +34,7 @@ impl TiffValue for [u8] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(self)
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ impl TiffValue for [i8] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ impl TiffValue for [u16] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ impl TiffValue for [i16] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ impl TiffValue for [u32] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ impl TiffValue for [i32] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ impl TiffValue for [u64] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ impl TiffValue for [i64] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ impl TiffValue for [f32] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
// We write using native endian so this should be safe
|
||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||
}
|
||||
@@ -152,7 +152,7 @@ impl TiffValue for [f64] {
|
||||
self.len()
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
// We write using native endian so this should be safe
|
||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
||||
}
|
||||
@@ -171,7 +171,7 @@ impl TiffValue for u8 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(from_ref(self))
|
||||
}
|
||||
}
|
||||
@@ -189,7 +189,7 @@ impl TiffValue for i8 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -207,7 +207,7 @@ impl TiffValue for u16 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -225,7 +225,7 @@ impl TiffValue for i16 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -243,7 +243,7 @@ impl TiffValue for u32 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -261,7 +261,7 @@ impl TiffValue for i32 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -279,7 +279,7 @@ impl TiffValue for u64 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -297,7 +297,7 @@ impl TiffValue for i64 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -315,7 +315,7 @@ impl TiffValue for f32 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -333,7 +333,7 @@ impl TiffValue for f64 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||
}
|
||||
}
|
||||
@@ -351,7 +351,7 @@ impl TiffValue for Ifd {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
||||
}
|
||||
}
|
||||
@@ -369,7 +369,7 @@ impl TiffValue for Ifd8 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
||||
}
|
||||
}
|
||||
@@ -388,7 +388,7 @@ impl TiffValue for Rational {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Owned({
|
||||
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
||||
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
||||
@@ -411,7 +411,7 @@ impl TiffValue for SRational {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Owned({
|
||||
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
||||
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
||||
@@ -438,7 +438,7 @@ impl TiffValue for str {
|
||||
}
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
Cow::Owned({
|
||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||
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 FIELD_TYPE: Type = T::FIELD_TYPE;
|
||||
|
||||
@@ -462,7 +462,7 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
||||
(*self).write(writer)
|
||||
}
|
||||
|
||||
fn data(&self) -> Cow<[u8]> {
|
||||
fn data(&self) -> Cow<'_, [u8]> {
|
||||
T::data(self)
|
||||
}
|
||||
}
|
||||
@@ -484,7 +484,7 @@ macro_rules! impl_tiff_value_for_contiguous_sequence {
|
||||
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());
|
||||
for x in self {
|
||||
buf.extend_from_slice(&x.data());
|
||||
|
||||
@@ -184,6 +184,9 @@ quick_error! {
|
||||
PredictorUnavailable {
|
||||
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.
|
||||
$( #[$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.
|
||||
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
|
||||
}
|
||||
@@ -13,7 +13,7 @@ macro_rules! tags {
|
||||
pub enum $name {
|
||||
$($(#[$ident_attr])* $tag,)*
|
||||
$(
|
||||
#[doc = $unknown_doc]
|
||||
#[$unknown_meta]
|
||||
Unknown($ty),
|
||||
)*
|
||||
}
|
||||
@@ -31,7 +31,7 @@ macro_rules! tags {
|
||||
fn __to_inner_type(&self) -> $ty {
|
||||
match *self {
|
||||
$( $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)*);
|
||||
};
|
||||
// For u16 tags, provide direct inherent primitive conversion methods.
|
||||
($name:tt, u16, $($unknown_doc:literal)*) => {
|
||||
($name:tt, u16, $($unknown_doc:ident)*) => {
|
||||
impl $name {
|
||||
#[inline(always)]
|
||||
pub fn from_u16(val: u16) -> Option<Self> {
|
||||
@@ -48,9 +48,8 @@ macro_rules! tags {
|
||||
|
||||
$(
|
||||
#[inline(always)]
|
||||
pub fn from_u16_exhaustive(val: u16) -> Self {
|
||||
$unknown_doc;
|
||||
Self::__from_inner_type(val).unwrap_or_else(|_| $name::Unknown(val))
|
||||
pub fn from_u16_exhaustive($unknown_doc: u16) -> Self {
|
||||
Self::__from_inner_type($unknown_doc).unwrap_or_else(|_| $name::Unknown($unknown_doc))
|
||||
}
|
||||
)*
|
||||
|
||||
@@ -68,7 +67,10 @@ macro_rules! tags {
|
||||
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
||||
tags! {
|
||||
/// TIFF tags
|
||||
pub enum Tag(u16) unknown("A private or extension tag") {
|
||||
pub enum Tag(u16) unknown(
|
||||
/// A private or extension tag
|
||||
unknown
|
||||
) {
|
||||
// Baseline tags:
|
||||
Artist = 315,
|
||||
// grayscale images PhotometricInterpretation 1 or 3
|
||||
@@ -131,6 +133,8 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
||||
ExifDirectory = 0x8769,
|
||||
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
||||
GpsDirectory = 0x8825,
|
||||
// <https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf>
|
||||
IccProfile = 34675,
|
||||
GeoKeyDirectoryTag = 34735, // (SPOT)
|
||||
GeoDoubleParamsTag = 34736, // (SPOT)
|
||||
GeoAsciiParamsTag = 34737, // (SPOT)
|
||||
@@ -192,7 +196,10 @@ pub enum Type(u16) {
|
||||
tags! {
|
||||
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||
/// for reference.
|
||||
pub enum CompressionMethod(u16) unknown("A custom compression method") {
|
||||
pub enum CompressionMethod(u16) unknown(
|
||||
/// A custom compression method
|
||||
unknown
|
||||
) {
|
||||
None = 1,
|
||||
Huffman = 2,
|
||||
Fax3 = 3,
|
||||
@@ -254,7 +261,10 @@ pub enum ResolutionUnit(u16) {
|
||||
}
|
||||
|
||||
tags! {
|
||||
pub enum SampleFormat(u16) unknown("An unknown extension sample format") {
|
||||
pub enum SampleFormat(u16) unknown(
|
||||
/// An unknown extension sample format
|
||||
unknown
|
||||
) {
|
||||
Uint = 1,
|
||||
Int = 2,
|
||||
IEEEFP = 3,
|
||||
|
||||
+160
-10
@@ -4,6 +4,7 @@ use tiff::decoder::{ifd, Decoder, DecodingResult};
|
||||
use tiff::ColorType;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek};
|
||||
use std::path::PathBuf;
|
||||
|
||||
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 mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
assert!(match decoder.read_image() {
|
||||
assert!(matches!(
|
||||
decoder.read_image(),
|
||||
Err(tiff::TiffError::UnsupportedError(
|
||||
tiff::TiffUnsupportedError::UnsupportedColorType(_),
|
||||
)) => true,
|
||||
_ => false,
|
||||
});
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -172,7 +173,7 @@ fn test_string_tags() {
|
||||
&s,
|
||||
"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]
|
||||
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]
|
||||
fn test_tiled_jpeg_rgb_u8() {
|
||||
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
|
||||
@@ -292,6 +341,11 @@ fn test_tiled_incremental() {
|
||||
|
||||
#[test]
|
||||
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
|
||||
let file = "planar-rgb-u8.tif";
|
||||
let expected_type = ColorType::RGB(8);
|
||||
@@ -328,8 +382,6 @@ fn test_planar_rgb_u8() {
|
||||
}
|
||||
_ => panic!("Wrong bit depth"),
|
||||
}
|
||||
|
||||
test_image_sum_u8(file, ColorType::RGB(8), 15417630);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -348,7 +400,7 @@ fn test_div_zero() {
|
||||
|
||||
match err {
|
||||
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 {
|
||||
tiff::TiffError::LimitsExceeded => {}
|
||||
unexpected => panic!("Unexpected error {}", unexpected),
|
||||
unexpected => panic!("Unexpected error {unexpected}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +549,7 @@ fn timeout() {
|
||||
|
||||
match error {
|
||||
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
||||
e => panic!("Unexpected error {:?}", e),
|
||||
e => panic!("Unexpected error {e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,3 +574,101 @@ fn test_zstd_compression() {
|
||||
// gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif
|
||||
test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396);
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
@@ -621,7 +621,7 @@ fn test_rows_per_strip() {
|
||||
let img2 = [i; 2 * 100];
|
||||
match decoder.read_chunk(i as u32).unwrap() {
|
||||
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();
|
||||
}
|
||||
|
||||
drop(tiff);
|
||||
|
||||
data.set_position(0);
|
||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ fn encode_decode_with_compression(compression: Compression) {
|
||||
assert_eq!(
|
||||
match decoder.read_image() {
|
||||
Ok(DecodingResult::U16(image_data)) => image_data,
|
||||
unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
|
||||
unexpected => panic!("Descoding RGB failed: {unexpected:?}"),
|
||||
},
|
||||
image_rgb.reference_data()
|
||||
);
|
||||
@@ -126,7 +126,7 @@ fn encode_decode_with_compression(compression: Compression) {
|
||||
assert_eq!(
|
||||
match decoder.read_image() {
|
||||
Ok(DecodingResult::U8(image_data)) => image_data,
|
||||
unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
|
||||
unexpected => panic!("Decoding grayscale failed: {unexpected:?}"),
|
||||
},
|
||||
image_grayscale.reference_data()
|
||||
);
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
extern crate tiff;
|
||||
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
|
||||
use tiff::encoder::{colortype, Predictor, TiffEncoder};
|
||||
use tiff::ColorType;
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
Reference in new issue
Block a user