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
|
- 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
@@ -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
@@ -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"]
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ pub struct Directory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Directory {
|
impl Directory {
|
||||||
|
/// Create a directory in an initial state without entries. Note that an empty directory can
|
||||||
|
/// not be encoded in a file, it must contain at least one entry.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Directory {
|
Directory {
|
||||||
entries: BTreeMap::new(),
|
entries: BTreeMap::new(),
|
||||||
@@ -72,6 +74,12 @@ impl Directory {
|
|||||||
self.entries.len()
|
self.entries.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if there are any entries in this directory. Note that an empty directory can not be
|
||||||
|
/// encoded in the file, it must contain at least one entry.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.entries.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the pointer to the next IFD, if it was defined.
|
/// Get the pointer to the next IFD, if it was defined.
|
||||||
pub fn next(&self) -> Option<IfdPointer> {
|
pub fn next(&self) -> Option<IfdPointer> {
|
||||||
self.next_ifd.map(|n| IfdPointer(n.get()))
|
self.next_ifd.map(|n| IfdPointer(n.get()))
|
||||||
|
|||||||
@@ -11,21 +11,17 @@ pub struct Deflate {
|
|||||||
/// It allows trading compression ratio for compression speed.
|
/// It allows trading compression ratio for compression speed.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[derive(Default)]
|
||||||
pub enum DeflateLevel {
|
pub enum DeflateLevel {
|
||||||
/// The fastest possible compression mode.
|
/// The fastest possible compression mode.
|
||||||
Fast = 1,
|
Fast = 1,
|
||||||
/// The conserative choice between speed and ratio.
|
/// The conserative choice between speed and ratio.
|
||||||
|
#[default]
|
||||||
Balanced = 6,
|
Balanced = 6,
|
||||||
/// The best compression available with Deflate.
|
/// The best compression available with Deflate.
|
||||||
Best = 9,
|
Best = 9,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DeflateLevel {
|
|
||||||
fn default() -> Self {
|
|
||||||
DeflateLevel::Balanced
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deflate {
|
impl Deflate {
|
||||||
/// Create a new deflate compressor with a specific level of compression.
|
/// Create a new deflate compressor with a specific level of compression.
|
||||||
pub fn with_level(level: DeflateLevel) -> Self {
|
pub fn with_level(level: DeflateLevel) -> Self {
|
||||||
|
|||||||
+7
-7
@@ -198,7 +198,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> TiffResult<ImageEncoder<W, C, K>> {
|
) -> TiffResult<ImageEncoder<'_, W, C, K>> {
|
||||||
let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?;
|
let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?;
|
||||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let entry = Self::write_value(
|
let entry = Self::write_value(
|
||||||
&mut self.writer,
|
self.writer,
|
||||||
&DirectoryEntry {
|
&DirectoryEntry {
|
||||||
data_type: <T>::FIELD_TYPE,
|
data_type: <T>::FIELD_TYPE,
|
||||||
count: value.count().try_into()?,
|
count: value.count().try_into()?,
|
||||||
@@ -370,14 +370,14 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
|
|
||||||
if bytes.len() > in_entry_bytes {
|
if bytes.len() > in_entry_bytes {
|
||||||
let offset = writer.offset();
|
let offset = writer.offset();
|
||||||
writer.write_bytes(&bytes)?;
|
writer.write_bytes(bytes)?;
|
||||||
|
|
||||||
let offset = K::convert_offset(offset)?;
|
let offset = K::convert_offset(offset)?;
|
||||||
offset_bytes[..offset.bytes()].copy_from_slice(&offset.data());
|
offset_bytes[..offset.bytes()].copy_from_slice(&offset.data());
|
||||||
} else {
|
} else {
|
||||||
// Note: we have indicated our native byte order in the header, hence this
|
// Note: we have indicated our native byte order in the header, hence this
|
||||||
// corresponds to our byte order no matter the value type.
|
// corresponds to our byte order no matter the value type.
|
||||||
offset_bytes[..bytes.len()].copy_from_slice(&bytes);
|
offset_bytes[..bytes.len()].copy_from_slice(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undoing some hidden type. Offset is either u32 or u64. Due to the trait API being public
|
// Undoing some hidden type. Offset is either u32 or u64. Due to the trait API being public
|
||||||
@@ -532,11 +532,11 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
let rows_per_strip = {
|
let rows_per_strip = {
|
||||||
match compression.tag() {
|
match compression.tag() {
|
||||||
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
|
CompressionMethod::PackBits => 1, // Each row must be packed separately. Do not compress across row boundaries
|
||||||
_ => (1_000_000 + row_bytes - 1) / row_bytes,
|
_ => 1_000_000_u64.div_ceil(row_bytes),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let strip_count = (u64::from(height) + rows_per_strip - 1) / rows_per_strip;
|
let strip_count = u64::from(height).div_ceil(rows_per_strip);
|
||||||
|
|
||||||
encoder.write_tag(Tag::ImageWidth, width)?;
|
encoder.write_tag(Tag::ImageWidth, width)?;
|
||||||
encoder.write_tag(Tag::ImageLength, height)?;
|
encoder.write_tag(Tag::ImageLength, height)?;
|
||||||
@@ -705,7 +705,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
self.encoder.write_tag(Tag::RowsPerStrip, value)?;
|
self.encoder.write_tag(Tag::RowsPerStrip, value)?;
|
||||||
|
|
||||||
let value: u64 = value as u64;
|
let value: u64 = value as u64;
|
||||||
self.strip_count = (self.height as u64 + value - 1) / value;
|
self.strip_count = (self.height as u64).div_ceil(value);
|
||||||
self.rows_per_strip = value;
|
self.rows_per_strip = value;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+29
-29
@@ -15,7 +15,7 @@ pub trait TiffValue {
|
|||||||
|
|
||||||
/// Access this value as an contiguous sequence of bytes.
|
/// Access this value as an contiguous sequence of bytes.
|
||||||
/// If their is no trivial representation, allocate it on the heap.
|
/// If their is no trivial representation, allocate it on the heap.
|
||||||
fn data(&self) -> Cow<[u8]>;
|
fn data(&self) -> Cow<'_, [u8]>;
|
||||||
|
|
||||||
/// Write this value to a TiffWriter.
|
/// Write this value to a TiffWriter.
|
||||||
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
||||||
@@ -34,7 +34,7 @@ impl TiffValue for [u8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(self)
|
Cow::Borrowed(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ impl TiffValue for [i8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ impl TiffValue for [u16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ impl TiffValue for [i16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ impl TiffValue for [u32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ impl TiffValue for [i32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ impl TiffValue for [u64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ impl TiffValue for [i64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ impl TiffValue for [f32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
// We write using native endian so this should be safe
|
// We write using native endian so this should be safe
|
||||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ impl TiffValue for [f64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
// We write using native endian so this should be safe
|
// We write using native endian so this should be safe
|
||||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ impl TiffValue for u8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(from_ref(self))
|
Cow::Borrowed(from_ref(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@ impl TiffValue for i8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ impl TiffValue for u16 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ impl TiffValue for i16 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ impl TiffValue for u32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ impl TiffValue for i32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,7 +279,7 @@ impl TiffValue for u64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,7 +297,7 @@ impl TiffValue for i64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,7 +315,7 @@ impl TiffValue for f32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,7 +333,7 @@ impl TiffValue for f64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,7 +351,7 @@ impl TiffValue for Ifd {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,7 +369,7 @@ impl TiffValue for Ifd8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +388,7 @@ impl TiffValue for Rational {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
||||||
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
||||||
@@ -411,7 +411,7 @@ impl TiffValue for SRational {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
||||||
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
||||||
@@ -438,7 +438,7 @@ impl TiffValue for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||||
let bytes: &[u8] = self.as_bytes();
|
let bytes: &[u8] = self.as_bytes();
|
||||||
@@ -450,7 +450,7 @@ impl TiffValue for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
impl<T: TiffValue + ?Sized> TiffValue for &'_ T {
|
||||||
const BYTE_LEN: u8 = T::BYTE_LEN;
|
const BYTE_LEN: u8 = T::BYTE_LEN;
|
||||||
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
||||||
|
|
||||||
@@ -462,7 +462,7 @@ impl<'a, T: TiffValue + ?Sized> TiffValue for &'a T {
|
|||||||
(*self).write(writer)
|
(*self).write(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
T::data(self)
|
T::data(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,7 +484,7 @@ macro_rules! impl_tiff_value_for_contiguous_sequence {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<[u8]> {
|
fn data(&self) -> Cow<'_, [u8]> {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
||||||
for x in self {
|
for x in self {
|
||||||
buf.extend_from_slice(&x.data());
|
buf.extend_from_slice(&x.data());
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ quick_error! {
|
|||||||
PredictorUnavailable {
|
PredictorUnavailable {
|
||||||
display("the requested predictor is not available")
|
display("the requested predictor is not available")
|
||||||
}
|
}
|
||||||
|
InsufficientOutputBufferSize { needed: usize, provided: usize } {
|
||||||
|
display("the borrowed output buffer is not large enough for the decoded data, needed {needed} but have {provided}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-10
@@ -2,7 +2,7 @@ macro_rules! tags {
|
|||||||
{
|
{
|
||||||
// Permit arbitrary meta items, which include documentation.
|
// Permit arbitrary meta items, which include documentation.
|
||||||
$( #[$enum_attr:meta] )*
|
$( #[$enum_attr:meta] )*
|
||||||
$vis:vis enum $name:ident($ty:tt) $(unknown($unknown_doc:literal))* {
|
$vis:vis enum $name:ident($ty:tt) $(unknown(#[$unknown_meta:meta] $unknown_doc:ident))* {
|
||||||
// Each of the `Name = Val,` permitting documentation.
|
// Each of the `Name = Val,` permitting documentation.
|
||||||
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
|
$($(#[$ident_attr:meta])* $tag:ident = $val:expr,)*
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ macro_rules! tags {
|
|||||||
pub enum $name {
|
pub enum $name {
|
||||||
$($(#[$ident_attr])* $tag,)*
|
$($(#[$ident_attr])* $tag,)*
|
||||||
$(
|
$(
|
||||||
#[doc = $unknown_doc]
|
#[$unknown_meta]
|
||||||
Unknown($ty),
|
Unknown($ty),
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ macro_rules! tags {
|
|||||||
fn __to_inner_type(&self) -> $ty {
|
fn __to_inner_type(&self) -> $ty {
|
||||||
match *self {
|
match *self {
|
||||||
$( $name::$tag => $val, )*
|
$( $name::$tag => $val, )*
|
||||||
$( $name::Unknown(n) => { $unknown_doc; n }, )*
|
$( $name::Unknown($unknown_doc) => { $unknown_doc }, )*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ macro_rules! tags {
|
|||||||
tags!($name, $ty, $($unknown_doc)*);
|
tags!($name, $ty, $($unknown_doc)*);
|
||||||
};
|
};
|
||||||
// For u16 tags, provide direct inherent primitive conversion methods.
|
// For u16 tags, provide direct inherent primitive conversion methods.
|
||||||
($name:tt, u16, $($unknown_doc:literal)*) => {
|
($name:tt, u16, $($unknown_doc:ident)*) => {
|
||||||
impl $name {
|
impl $name {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn from_u16(val: u16) -> Option<Self> {
|
pub fn from_u16(val: u16) -> Option<Self> {
|
||||||
@@ -48,9 +48,8 @@ macro_rules! tags {
|
|||||||
|
|
||||||
$(
|
$(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn from_u16_exhaustive(val: u16) -> Self {
|
pub fn from_u16_exhaustive($unknown_doc: u16) -> Self {
|
||||||
$unknown_doc;
|
Self::__from_inner_type($unknown_doc).unwrap_or_else(|_| $name::Unknown($unknown_doc))
|
||||||
Self::__from_inner_type(val).unwrap_or_else(|_| $name::Unknown(val))
|
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
@@ -68,7 +67,10 @@ macro_rules! tags {
|
|||||||
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
// Note: These tags appear in the order they are mentioned in the TIFF reference
|
||||||
tags! {
|
tags! {
|
||||||
/// TIFF tags
|
/// TIFF tags
|
||||||
pub enum Tag(u16) unknown("A private or extension tag") {
|
pub enum Tag(u16) unknown(
|
||||||
|
/// A private or extension tag
|
||||||
|
unknown
|
||||||
|
) {
|
||||||
// Baseline tags:
|
// Baseline tags:
|
||||||
Artist = 315,
|
Artist = 315,
|
||||||
// grayscale images PhotometricInterpretation 1 or 3
|
// grayscale images PhotometricInterpretation 1 or 3
|
||||||
@@ -131,6 +133,8 @@ pub enum Tag(u16) unknown("A private or extension tag") {
|
|||||||
ExifDirectory = 0x8769,
|
ExifDirectory = 0x8769,
|
||||||
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
// <https://web.archive.org/web/20131111073619/http://www.exif.org/Exif2-1.PDF>
|
||||||
GpsDirectory = 0x8825,
|
GpsDirectory = 0x8825,
|
||||||
|
// <https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf>
|
||||||
|
IccProfile = 34675,
|
||||||
GeoKeyDirectoryTag = 34735, // (SPOT)
|
GeoKeyDirectoryTag = 34735, // (SPOT)
|
||||||
GeoDoubleParamsTag = 34736, // (SPOT)
|
GeoDoubleParamsTag = 34736, // (SPOT)
|
||||||
GeoAsciiParamsTag = 34737, // (SPOT)
|
GeoAsciiParamsTag = 34737, // (SPOT)
|
||||||
@@ -192,7 +196,10 @@ pub enum Type(u16) {
|
|||||||
tags! {
|
tags! {
|
||||||
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||||
/// for reference.
|
/// for reference.
|
||||||
pub enum CompressionMethod(u16) unknown("A custom compression method") {
|
pub enum CompressionMethod(u16) unknown(
|
||||||
|
/// A custom compression method
|
||||||
|
unknown
|
||||||
|
) {
|
||||||
None = 1,
|
None = 1,
|
||||||
Huffman = 2,
|
Huffman = 2,
|
||||||
Fax3 = 3,
|
Fax3 = 3,
|
||||||
@@ -254,7 +261,10 @@ pub enum ResolutionUnit(u16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
pub enum SampleFormat(u16) unknown("An unknown extension sample format") {
|
pub enum SampleFormat(u16) unknown(
|
||||||
|
/// An unknown extension sample format
|
||||||
|
unknown
|
||||||
|
) {
|
||||||
Uint = 1,
|
Uint = 1,
|
||||||
Int = 2,
|
Int = 2,
|
||||||
IEEEFP = 3,
|
IEEEFP = 3,
|
||||||
|
|||||||
+160
-10
@@ -4,6 +4,7 @@ use tiff::decoder::{ifd, Decoder, DecodingResult};
|
|||||||
use tiff::ColorType;
|
use tiff::ColorType;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Seek};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
||||||
@@ -45,12 +46,12 @@ fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
|
|||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
assert!(match decoder.read_image() {
|
assert!(matches!(
|
||||||
|
decoder.read_image(),
|
||||||
Err(tiff::TiffError::UnsupportedError(
|
Err(tiff::TiffError::UnsupportedError(
|
||||||
tiff::TiffUnsupportedError::UnsupportedColorType(_),
|
tiff::TiffUnsupportedError::UnsupportedColorType(_),
|
||||||
)) => true,
|
))
|
||||||
_ => false,
|
));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -172,7 +173,7 @@ fn test_string_tags() {
|
|||||||
&s,
|
&s,
|
||||||
"GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/"
|
"GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/"
|
||||||
),
|
),
|
||||||
_ => assert!(false),
|
other => unreachable!("Expected Ascii tag from file, got {other:?}"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,6 +232,54 @@ fn test_tiled_rect_rgb_u8() {
|
|||||||
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
|
test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inner_access() {
|
||||||
|
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
|
||||||
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
|
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||||
|
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||||
|
|
||||||
|
let c = decoder
|
||||||
|
.get_tag(tiff::tags::Tag::Compression)
|
||||||
|
.unwrap()
|
||||||
|
.into_u16()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(c, tiff::tags::CompressionMethod::None.to_u16());
|
||||||
|
|
||||||
|
// Because the image is uncompressed, reading the first tile directly with the inner reader
|
||||||
|
// should yield the same result as reading it with the decoder's read_chunk method.
|
||||||
|
let first_offset = decoder
|
||||||
|
.get_tag_u32_vec(tiff::tags::Tag::TileOffsets)
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
let first_byte_count = decoder
|
||||||
|
.get_tag_u32_vec(tiff::tags::Tag::TileByteCounts)
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
decoder
|
||||||
|
.inner()
|
||||||
|
.seek(std::io::SeekFrom::Start(first_offset as u64))
|
||||||
|
.expect("Cannot seek to tile offset");
|
||||||
|
let mut buf = vec![0; first_byte_count as usize];
|
||||||
|
decoder
|
||||||
|
.inner()
|
||||||
|
.read_exact(&mut buf)
|
||||||
|
.expect("Cannot read tile data");
|
||||||
|
let raw_sum: u64 = buf.into_iter().map(<u64>::from).sum();
|
||||||
|
|
||||||
|
match decoder.read_chunk(0).unwrap() {
|
||||||
|
DecodingResult::U8(chunk) => {
|
||||||
|
let sum: u64 = chunk.into_iter().map(<u64>::from).sum();
|
||||||
|
assert_eq!(sum, raw_sum);
|
||||||
|
}
|
||||||
|
_ => panic!("Wrong bit depth"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* #[test]
|
/* #[test]
|
||||||
fn test_tiled_jpeg_rgb_u8() {
|
fn test_tiled_jpeg_rgb_u8() {
|
||||||
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
|
test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606);
|
||||||
@@ -292,6 +341,11 @@ fn test_tiled_incremental() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_planar_rgb_u8() {
|
fn test_planar_rgb_u8() {
|
||||||
|
test_image_sum_u8("planar-rgb-u8.tif", ColorType::RGB(8), 15417630);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_read_planar_bands() {
|
||||||
// gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
|
// gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE
|
||||||
let file = "planar-rgb-u8.tif";
|
let file = "planar-rgb-u8.tif";
|
||||||
let expected_type = ColorType::RGB(8);
|
let expected_type = ColorType::RGB(8);
|
||||||
@@ -328,8 +382,6 @@ fn test_planar_rgb_u8() {
|
|||||||
}
|
}
|
||||||
_ => panic!("Wrong bit depth"),
|
_ => panic!("Wrong bit depth"),
|
||||||
}
|
}
|
||||||
|
|
||||||
test_image_sum_u8(file, ColorType::RGB(8), 15417630);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -348,7 +400,7 @@ fn test_div_zero() {
|
|||||||
|
|
||||||
match err {
|
match err {
|
||||||
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
unexpected => panic!("Unexpected error {unexpected}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +418,7 @@ fn test_too_many_value_bytes() {
|
|||||||
|
|
||||||
match error {
|
match error {
|
||||||
tiff::TiffError::LimitsExceeded => {}
|
tiff::TiffError::LimitsExceeded => {}
|
||||||
unexpected => panic!("Unexpected error {}", unexpected),
|
unexpected => panic!("Unexpected error {unexpected}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,7 +549,7 @@ fn timeout() {
|
|||||||
|
|
||||||
match error {
|
match error {
|
||||||
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
||||||
e => panic!("Unexpected error {:?}", e),
|
e => panic!("Unexpected error {e:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,3 +574,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,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -621,7 +621,7 @@ fn test_rows_per_strip() {
|
|||||||
let img2 = [i; 2 * 100];
|
let img2 = [i; 2 * 100];
|
||||||
match decoder.read_chunk(i as u32).unwrap() {
|
match decoder.read_chunk(i as u32).unwrap() {
|
||||||
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
|
DecodingResult::U8(data) => assert_eq!(&img2[..], &data[..]),
|
||||||
other => panic!("Incorrect strip type {:?}", other),
|
other => panic!("Incorrect strip type {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -652,8 +652,6 @@ fn test_auxiliary_directory() {
|
|||||||
encoder.write_tag(Tag::ExifDirectory, exif.offset).unwrap();
|
encoder.write_tag(Tag::ExifDirectory, exif.offset).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(tiff);
|
|
||||||
|
|
||||||
data.set_position(0);
|
data.set_position(0);
|
||||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ fn encode_decode_with_compression(compression: Compression) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
match decoder.read_image() {
|
match decoder.read_image() {
|
||||||
Ok(DecodingResult::U16(image_data)) => image_data,
|
Ok(DecodingResult::U16(image_data)) => image_data,
|
||||||
unexpected => panic!("Descoding RGB failed: {:?}", unexpected),
|
unexpected => panic!("Descoding RGB failed: {unexpected:?}"),
|
||||||
},
|
},
|
||||||
image_rgb.reference_data()
|
image_rgb.reference_data()
|
||||||
);
|
);
|
||||||
@@ -126,7 +126,7 @@ fn encode_decode_with_compression(compression: Compression) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
match decoder.read_image() {
|
match decoder.read_image() {
|
||||||
Ok(DecodingResult::U8(image_data)) => image_data,
|
Ok(DecodingResult::U8(image_data)) => image_data,
|
||||||
unexpected => panic!("Decoding grayscale failed: {:?}", unexpected),
|
unexpected => panic!("Decoding grayscale failed: {unexpected:?}"),
|
||||||
},
|
},
|
||||||
image_grayscale.reference_data()
|
image_grayscale.reference_data()
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
extern crate tiff;
|
extern crate tiff;
|
||||||
|
|
||||||
use tiff::decoder::{Decoder, DecodingResult};
|
use tiff::decoder::{Decoder, DecodingResult};
|
||||||
use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder};
|
use tiff::encoder::{colortype, Predictor, TiffEncoder};
|
||||||
use tiff::ColorType;
|
use tiff::ColorType;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|||||||
Reference in new issue
Block a user