Compare commits

..
Author SHA1 Message Date
Aurelia Molzer 756ea3e813 Add metadata for 0.10.2 2025-08-26 11:24:04 +02:00
Aurelia Molzer 0eead083f6 Fix missing method on Decoder 2025-08-26 11:19:35 +02:00
Aurelia Molzer cbcf6b97d4 Bump version number 0.10.1 2025-08-25 23:21:34 +02:00
Sergey "Shnatsel" Davidoff 19c811e86d populate changelog for 0.10.1 2025-08-25 15:20:04 +01:00
Aurelia Molzer 50be15e356 Merge pull request #290 from jakubadamw/nightly-compilation-warnings
Address compilation warnings in the Nightly channel of Rust
2025-08-14 16:50:22 +00:00
Jacob Adam 718f2cb455 Address compilation warnings in the Nightly channel of Rust. 2025-08-14 17:45:21 +01:00
Aurelia Molzer 36a16db659 Merge pull request #285 from image-rs/read-into-buffer
Read into byte buffer
2025-07-25 19:16:09 +00:00
A. Molzer d70c31dcbf Add a Bug note about planar configuration 2025-07-25 21:13:41 +02:00
Aurelia Molzer b589d10a27 Merge pull request #289 from aotimme/decoder-inner
Add inner() method to decoder to provide access to raw reader
2025-07-22 20:18:56 +02:00
Alden Timme f4378eeb80 Also split test_planar_rgb_u8 test 2025-07-22 08:48:57 -07:00
Alden Timme 49015c6e93 Move into separate test 2025-07-22 08:42:39 -07:00
Alden Timme 29402875cb Add inner() method to decoder to provide access to raw reader 2025-07-21 20:47:47 -07:00
Aurelia Molzer 33453289b6 Merge pull request #287 from image-rs/icc-tag
Add ICC (color profile) to the set of known tags
2025-07-19 18:17:56 +02:00
A. Molzer ce0ca5c234 Add ICC (color profile) to the set of known tags 2025-07-19 18:12:54 +02:00
A. Molzer f671293bd8 Fix byte-crc test for big endian platforms 2025-07-19 18:08:02 +02:00
A. Molzer 48d52bed9b Return buffer layout as a struct
Prepares extensibility for the eventual case of supporting multi-planar
image layouts that are to be written into a multi-planar buffer as
indicated by the caller in some way (possibly not via read_*_bytes but a
method taking a wrapper around the output buffer instead). Through the
struct we can later add an indicator that can be used by the caller to
decide between methods of fetching the pixel data as appropriate.
2025-07-19 17:46:37 +02:00
Aurelia Molzer 7707060982 Merge pull request #286 from image-rs/clippy
Resolve all the clippy lints and add it to CI
2025-07-19 17:39:26 +02:00
A. Molzer c370ac4b92 Take row stride input in chunk bytes 2025-07-19 00:12:43 +02:00
A. Molzer b2bcbbaf23 Enable clippy for the merge workflow 2025-07-14 19:56:06 +02:00
A. Molzer d07da5a6cb Resolve clippy lints in tests 2025-07-14 19:56:06 +02:00
A. Molzer a3c81e9830 Add Directory::is_empty for completeness 2025-07-14 19:56:06 +02:00
A. Molzer 46e51cc67e Make documentation adhere clippy, more natural 2025-07-14 19:56:06 +02:00
A. Molzer f6a762b72c Upgrade usage of format! and Default 2025-07-14 19:56:06 +02:00
A. Molzer 23672b2490 Switch to div_ceil for rounding implementation 2025-07-14 19:25:12 +02:00
A. Molzer ca74c03520 Fix straightforward cast/borrow lints 2025-07-14 19:25:12 +02:00
A. Molzer 2942f9de97 Mark f16 bytecast as expected unused 2025-07-14 19:25:12 +02:00
A. Molzer 29add10ca7 Fix manual non_exhaustive pattern 2025-07-14 18:24:01 +02:00
A. Molzer 59d32a66a7 Add test coverage for to byte decoding 2025-07-14 18:23:30 +02:00
A. Molzer 5a6b2ad8f3 Complementary methods reading into borrowed buffer 2025-07-14 18:23:30 +02:00
A. Molzer c746653269 Add Decoder::read_image_bytes, borrowed output 2025-07-14 18:23:30 +02:00
Aurelia Molzer cc75d051af Merge pull request #283 from image-rs/write-directory
Write extra directories
2025-07-14 00:01:39 +02:00
Aurelia Molzer 2e51ee4b15 Merge pull request #282 from image-rs/write-directory
Refactor encoding of directories
2025-07-13 22:28:05 +02:00
18 changed files with 591 additions and 148 deletions

No files matched your search

+4
View File
@@ -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
+21
View File
@@ -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 # Version 0.10.0
New features: New features:
+3 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tiff" name = "tiff"
version = "0.10.0" version = "0.10.2"
edition = "2021" edition = "2021"
resolver = "2" resolver = "2"
@@ -27,6 +27,8 @@ 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", "jpeg", "lzw"]
+3
View File
@@ -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;
+1 -1
View File
@@ -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()?),
+24 -11
View File
@@ -10,6 +10,7 @@ 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 +30,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
@@ -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>( fn create_reader<'r, R: 'r + Read>(
reader: R, reader: R,
compression_method: CompressionMethod, compression_method: CompressionMethod,
@@ -625,12 +642,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);
@@ -643,11 +660,11 @@ impl Image {
self.jpeg_tables.as_deref().map(|a| &**a), 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]; 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(),
@@ -678,11 +695,7 @@ impl Image {
} }
} }
} 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)?;
+291 -54
View File
@@ -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: (),
} }
} }
} }
@@ -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 /// 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 +921,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 +949,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),
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 { /// Returns the layout preferred to read the specified chunk with [`Self::read_chunk_bytes`].
SampleFormat::Uint => match max_sample_bits { ///
n if n <= 8 => DecodingResult::new_u8(buffer_size, limits), /// Returns the layout without being specific as to the underlying type for forward
n if n <= 16 => DecodingResult::new_u16(buffer_size, limits), /// compatibility. Note that, in general, a TIFF may contain an almost arbitrary number of
n if n <= 32 => DecodingResult::new_u32(buffer_size, limits), /// channels of individual *bit* length and format each.
n if n <= 64 => DecodingResult::new_u64(buffer_size, limits), ///
n => Err(TiffError::UnsupportedError( /// See [`Self::colortype`] to describe the sample types.
TiffUnsupportedError::UnsupportedBitsPerChannel(n), ///
)), /// # Bugs
}, ///
SampleFormat::IEEEFP => match max_sample_bits { /// When the image is stored as a planar configuration, this method will currently only
16 => DecodingResult::new_f16(buffer_size, limits), /// indicate the layout needed to read the first data plane. This will be fixed in a future
32 => DecodingResult::new_f32(buffer_size, limits), /// major version of `tiff`.
64 => DecodingResult::new_f64(buffer_size, limits), pub fn image_chunk_buffer_layout(
n => Err(TiffError::UnsupportedError( &mut self,
TiffUnsupportedError::UnsupportedBitsPerChannel(n), chunk_index: u32,
)), ) -> TiffResult<BufferLayoutPreference> {
}, let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
SampleFormat::Int => match max_sample_bits { let layout = self
n if n <= 8 => DecodingResult::new_i8(buffer_size, limits), .result_extent(data_dims.0 as usize, data_dims.1 as usize)?
n if n <= 16 => DecodingResult::new_i16(buffer_size, limits), .preferred_layout()?;
n if n <= 32 => DecodingResult::new_i32(buffer_size, limits),
n if n <= 64 => DecodingResult::new_i64(buffer_size, limits), let row_stride = self.image.minimum_row_stride(data_dims);
n => Err(TiffError::UnsupportedError(
TiffUnsupportedError::UnsupportedBitsPerChannel(n), Ok(BufferLayoutPreference {
)), len: layout.size(),
}, row_stride,
format => Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format]).into()), })
}
} }
/// 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 +1062,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 +1102,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 +1188,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 +1217,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 +1281,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> {
+11 -13
View File
@@ -135,20 +135,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 +204,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,
+8
View File
@@ -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()))
+2 -6
View File
@@ -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
View File
@@ -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
View File
@@ -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());
+3
View File
@@ -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
View File
@@ -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,
+160 -10
View File
@@ -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,101 @@ 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,
);
}
+1 -3
View File
@@ -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();
+2 -2
View File
@@ -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()
); );
+1 -1
View File
@@ -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;