Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6e8ed772d | ||
|
|
9b452e93b7 | ||
|
|
96d37bb087 | ||
|
|
12f16dbfdd | ||
|
|
a8abdd2484 | ||
|
|
934fde185a | ||
|
|
3302936b08 | ||
|
|
a5b9a57e09 |
No files matched your search
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
/target/
|
/target/
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
tests/benches/corpus/
|
tests/benches/corpus/
|
||||||
|
.idea
|
||||||
|
.agentbridge
|
||||||
+5
-3
@@ -1,8 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "ndbioimage-tiff"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
resolver = "2"
|
|
||||||
|
|
||||||
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
||||||
rust-version = "1.88.0"
|
rust-version = "1.88.0"
|
||||||
@@ -16,6 +15,9 @@ categories = ["multimedia::images", "multimedia::encoding"]
|
|||||||
|
|
||||||
exclude = ["tests/images/*", "tests/fuzz_images/*", "tests/**/*.tif"]
|
exclude = ["tests/images/*", "tests/fuzz_images/*", "tests/**/*.tif"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "tiff"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
half = { version = "2.4.1" }
|
half = { version = "2.4.1" }
|
||||||
quick-error = "2.0.1"
|
quick-error = "2.0.1"
|
||||||
@@ -24,7 +26,7 @@ quick-error = "2.0.1"
|
|||||||
fax34 = { package = "fax", version = "0.2.6", optional = true }
|
fax34 = { package = "fax", version = "0.2.6", optional = true }
|
||||||
flate2 = { version = "1.0.20", optional = true }
|
flate2 = { version = "1.0.20", optional = true }
|
||||||
weezl = { version = "0.2.1", optional = true }
|
weezl = { version = "0.2.1", optional = true }
|
||||||
zstd = { version = "0.13", optional = true }
|
zstd = { version = "0.14", optional = true }
|
||||||
zrip-decode = { version = "0.8.2", features = ["paranoid"], optional = true }
|
zrip-decode = { version = "0.8.2", features = ["paranoid"], optional = true }
|
||||||
zune-jpeg = { version = "0.5.1", optional = true }
|
zune-jpeg = { version = "0.5.1", optional = true }
|
||||||
image-webp = {version = "0.2.4", optional = true }
|
image-webp = {version = "0.2.4", optional = true }
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
TIFF decoding and encoding library in pure Rust
|
TIFF decoding and encoding library in pure Rust
|
||||||
|
|
||||||
|
Forked from https://github.com/image-rs/image-tiff. Changes: read images containing RGBPalette frames, ignoring colormaps.
|
||||||
|
|
||||||
## Supported
|
## Supported
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|||||||
+42
-22
@@ -599,8 +599,10 @@ impl Entry {
|
|||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(
|
v.extend(
|
||||||
bytes
|
bytes
|
||||||
.chunks_exact(2)
|
.as_chunks::<2>()
|
||||||
.map(|ch| Short(u16::from_ne_bytes(ch.try_into().unwrap()))),
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&ch| Short(u16::from_ne_bytes(ch))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -609,8 +611,10 @@ impl Entry {
|
|||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(
|
v.extend(
|
||||||
bytes
|
bytes
|
||||||
.chunks_exact(2)
|
.as_chunks::<2>()
|
||||||
.map(|ch| SignedShort(i16::from_ne_bytes(ch.try_into().unwrap()))),
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&ch| SignedShort(i16::from_ne_bytes(ch))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -619,8 +623,10 @@ impl Entry {
|
|||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(
|
v.extend(
|
||||||
bytes
|
bytes
|
||||||
.chunks_exact(4)
|
.as_chunks::<4>()
|
||||||
.map(|ch| Unsigned(u32::from_ne_bytes(ch.try_into().unwrap()))),
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&ch| Unsigned(u32::from_ne_bytes(ch))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -629,8 +635,10 @@ impl Entry {
|
|||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(
|
v.extend(
|
||||||
bytes
|
bytes
|
||||||
.chunks_exact(4)
|
.as_chunks::<4>()
|
||||||
.map(|ch| Signed(i32::from_ne_bytes(ch.try_into().unwrap()))),
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&ch| Signed(i32::from_ne_bytes(ch))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -639,8 +647,10 @@ impl Entry {
|
|||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(
|
v.extend(
|
||||||
bytes
|
bytes
|
||||||
.chunks_exact(4)
|
.as_chunks::<4>()
|
||||||
.map(|ch| Float(f32::from_ne_bytes(ch.try_into().unwrap()))),
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&ch| Float(f32::from_ne_bytes(ch))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -649,15 +659,17 @@ impl Entry {
|
|||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(
|
v.extend(
|
||||||
bytes
|
bytes
|
||||||
.chunks_exact(8)
|
.as_chunks::<8>()
|
||||||
.map(|ch| Double(f64::from_ne_bytes(ch.try_into().unwrap()))),
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&ch| Double(f64::from_ne_bytes(ch))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Type::RATIONAL => {
|
Type::RATIONAL => {
|
||||||
v = Self::vec_with_capacity(self.count, self.type_, limits)?;
|
v = Self::vec_with_capacity(self.count, self.type_, limits)?;
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(bytes.chunks_exact(8).map(|ch| {
|
v.extend(bytes.as_chunks::<8>().0.iter().map(|ch| {
|
||||||
Rational(
|
Rational(
|
||||||
u32::from_ne_bytes(ch[..4].try_into().unwrap()),
|
u32::from_ne_bytes(ch[..4].try_into().unwrap()),
|
||||||
u32::from_ne_bytes(ch[4..].try_into().unwrap()),
|
u32::from_ne_bytes(ch[4..].try_into().unwrap()),
|
||||||
@@ -668,7 +680,7 @@ impl Entry {
|
|||||||
Type::SRATIONAL => {
|
Type::SRATIONAL => {
|
||||||
v = Self::vec_with_capacity(self.count, self.type_, limits)?;
|
v = Self::vec_with_capacity(self.count, self.type_, limits)?;
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(bytes.chunks_exact(8).map(|ch| {
|
v.extend(bytes.as_chunks::<8>().0.iter().map(|ch| {
|
||||||
SRational(
|
SRational(
|
||||||
i32::from_ne_bytes(ch[..4].try_into().unwrap()),
|
i32::from_ne_bytes(ch[..4].try_into().unwrap()),
|
||||||
i32::from_ne_bytes(ch[4..].try_into().unwrap()),
|
i32::from_ne_bytes(ch[4..].try_into().unwrap()),
|
||||||
@@ -681,8 +693,10 @@ impl Entry {
|
|||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(
|
v.extend(
|
||||||
bytes
|
bytes
|
||||||
.chunks_exact(8)
|
.as_chunks::<8>()
|
||||||
.map(|ch| UnsignedBig(u64::from_ne_bytes(ch.try_into().unwrap()))),
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&ch| UnsignedBig(u64::from_ne_bytes(ch))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -691,8 +705,10 @@ impl Entry {
|
|||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(
|
v.extend(
|
||||||
bytes
|
bytes
|
||||||
.chunks_exact(8)
|
.as_chunks::<8>()
|
||||||
.map(|ch| SignedBig(i64::from_ne_bytes(ch.try_into().unwrap()))),
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&ch| SignedBig(i64::from_ne_bytes(ch))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -701,8 +717,10 @@ impl Entry {
|
|||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(
|
v.extend(
|
||||||
bytes
|
bytes
|
||||||
.chunks_exact(4)
|
.as_chunks::<4>()
|
||||||
.map(|ch| Ifd(u32::from_ne_bytes(ch.try_into().unwrap()))),
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&ch| Ifd(u32::from_ne_bytes(ch))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -711,8 +729,10 @@ impl Entry {
|
|||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
self.decode_values(self.count, self.type_, reader, |bytes| {
|
||||||
v.extend(
|
v.extend(
|
||||||
bytes
|
bytes
|
||||||
.chunks_exact(8)
|
.as_chunks::<8>()
|
||||||
.map(|ch| IfdBig(u64::from_ne_bytes(ch.try_into().unwrap()))),
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|&ch| IfdBig(u64::from_ne_bytes(ch))),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-21
@@ -1,9 +1,8 @@
|
|||||||
use super::ifd::Value;
|
use super::ifd::Value;
|
||||||
use super::stream::{PackBitsReader, ReadSamples};
|
use super::stream::PackBitsReader;
|
||||||
use super::tag_reader::TagReader;
|
use super::tag_reader::TagReader;
|
||||||
use super::ChunkType;
|
use super::ChunkType;
|
||||||
use super::{predict_f16, predict_f32, predict_f64, ValueReader};
|
use super::{predict_f16, predict_f32, predict_f64, ValueReader};
|
||||||
use crate::decoder::stream::CompressionEngines;
|
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
ByteOrder, CompressionMethod, ExtraSamples, PhotometricInterpretation, PlanarConfiguration,
|
ByteOrder, CompressionMethod, ExtraSamples, PhotometricInterpretation, PlanarConfiguration,
|
||||||
Predictor, SampleFormat, Tag,
|
Predictor, SampleFormat, Tag,
|
||||||
@@ -12,7 +11,7 @@ use crate::{
|
|||||||
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::io::{Cursor, Read, Seek};
|
use std::io::{self, Cursor, Read, Seek};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -37,6 +36,9 @@ impl TileAttributes {
|
|||||||
pub fn tiles_down(&self) -> usize {
|
pub fn tiles_down(&self) -> usize {
|
||||||
self.image_height.div_ceil(self.tile_length)
|
self.image_height.div_ceil(self.tile_length)
|
||||||
}
|
}
|
||||||
|
pub fn tiles_per_plane(&self) -> usize {
|
||||||
|
self.tiles_across() * self.tiles_down()
|
||||||
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -177,7 +179,7 @@ impl Image {
|
|||||||
.map(Value::into_u16)
|
.map(Value::into_u16)
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.and_then(PhotometricInterpretation::from_u16)
|
.and_then(PhotometricInterpretation::from_u16)
|
||||||
.ok_or(TiffUnsupportedError::UnknownInterpretation)?;
|
.unwrap_or(PhotometricInterpretation::BlackIsZero);
|
||||||
|
|
||||||
// Try to parse both the compression method and the number, format, and bits of the included samples.
|
// Try to parse both the compression method and the number, format, and bits of the included samples.
|
||||||
// If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
|
// If they are not explicitly specified, those tags are reset to their default values and not carried from previous images.
|
||||||
@@ -468,11 +470,11 @@ impl Image {
|
|||||||
}
|
}
|
||||||
Some(map)
|
Some(map)
|
||||||
}
|
}
|
||||||
None => {
|
// A missing palette is not an error. The pixel values of an indexed image are just
|
||||||
return Err(TiffError::FormatError(
|
// indices into the palette, and the palette itself is only display information, so
|
||||||
TiffFormatError::RequiredTagNotFound(Tag::ColorMap),
|
// the image still decodes. Callers observe the absence as `None` from
|
||||||
));
|
// `Decoder::color_map`.
|
||||||
}
|
None => None,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -699,8 +701,7 @@ impl Image {
|
|||||||
dimensions: (u32, u32),
|
dimensions: (u32, u32),
|
||||||
samples: u16,
|
samples: u16,
|
||||||
#[cfg_attr(not(feature = "fax"), allow(unused_variables))] fill_order: u16,
|
#[cfg_attr(not(feature = "fax"), allow(unused_variables))] fill_order: u16,
|
||||||
engines: &mut CompressionEngines,
|
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||||
) -> TiffResult<Box<dyn ReadSamples + 'r>> {
|
|
||||||
Ok(match compression_method {
|
Ok(match compression_method {
|
||||||
CompressionMethod::None => Box::new(reader),
|
CompressionMethod::None => Box::new(reader),
|
||||||
CompressionMethod::SgiLog => match samples {
|
CompressionMethod::SgiLog => match samples {
|
||||||
@@ -731,7 +732,6 @@ impl Image {
|
|||||||
CompressionMethod::LZW => Box::new(super::stream::LZWReader::new(
|
CompressionMethod::LZW => Box::new(super::stream::LZWReader::new(
|
||||||
reader,
|
reader,
|
||||||
usize::try_from(compressed_length)?,
|
usize::try_from(compressed_length)?,
|
||||||
engines,
|
|
||||||
)),
|
)),
|
||||||
#[cfg(feature = "zstd")]
|
#[cfg(feature = "zstd")]
|
||||||
CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?),
|
CompressionMethod::ZSTD => Box::new(zstd::Decoder::new(reader)?),
|
||||||
@@ -1019,7 +1019,15 @@ impl Image {
|
|||||||
}
|
}
|
||||||
ChunkType::Tile => {
|
ChunkType::Tile => {
|
||||||
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
let tile_attrs = self.tile_attributes.as_ref().unwrap();
|
||||||
let (padding_right, padding_down) = tile_attrs.get_padding(chunk_index as usize);
|
// The chunk index counts the tiles of all planes, but the tile grid repeats per
|
||||||
|
// plane, so only the position within the plane decides the padding.
|
||||||
|
let tiles_per_plane = tile_attrs.tiles_per_plane();
|
||||||
|
let tile = if tiles_per_plane == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
chunk_index as usize % tiles_per_plane
|
||||||
|
};
|
||||||
|
let (padding_right, padding_down) = tile_attrs.get_padding(tile);
|
||||||
|
|
||||||
let tile_width = tile_attrs.tile_width - padding_right;
|
let tile_width = tile_attrs.tile_width - padding_right;
|
||||||
let tile_length = tile_attrs.tile_length - padding_down;
|
let tile_length = tile_attrs.tile_length - padding_down;
|
||||||
@@ -1036,7 +1044,6 @@ impl Image {
|
|||||||
layout: &ReadoutLayout,
|
layout: &ReadoutLayout,
|
||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
scratch: &mut Vec<u8>,
|
scratch: &mut Vec<u8>,
|
||||||
engines: &mut CompressionEngines,
|
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let ValueReader {
|
let ValueReader {
|
||||||
reader,
|
reader,
|
||||||
@@ -1183,7 +1190,6 @@ impl Image {
|
|||||||
chunk_dims,
|
chunk_dims,
|
||||||
self.samples,
|
self.samples,
|
||||||
self.fill_order,
|
self.fill_order,
|
||||||
engines,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Extended bit depths (9-15, 17-31): packed N-bit samples must be unpacked to
|
// Extended bit depths (9-15, 17-31): packed N-bit samples must be unpacked to
|
||||||
@@ -1313,12 +1319,11 @@ impl Image {
|
|||||||
// Two ways how we get here: we have more bytes in our chunk data than in the image
|
// Two ways how we get here: we have more bytes in our chunk data than in the image
|
||||||
// we are to read. Then we need to skip the rest of the data. Or we have a bigger
|
// we are to read. Then we need to skip the rest of the data. Or we have a bigger
|
||||||
// row stride than the chunk contains data, then we need to fill only the front.
|
// row stride than the chunk contains data, then we need to fill only the front.
|
||||||
if chunk_row_bytes > data_row_bytes {
|
|
||||||
// Skip horizontal padding
|
|
||||||
let skip = u64::try_from(chunk_row_bytes - data_row_bytes)?;
|
|
||||||
reader.read_padded_samples(&mut row[..used], skip)?;
|
|
||||||
} else {
|
|
||||||
reader.read_exact(&mut row[..used])?;
|
reader.read_exact(&mut row[..used])?;
|
||||||
|
// Skip horizontal padding
|
||||||
|
if chunk_row_bytes > data_row_bytes {
|
||||||
|
let len = u64::try_from(chunk_row_bytes - data_row_bytes)?;
|
||||||
|
io::copy(&mut reader.by_ref().take(len), &mut io::sink())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
super::fix_endianness_and_predict(
|
super::fix_endianness_and_predict(
|
||||||
@@ -1408,7 +1413,6 @@ impl Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.cache_or_destroy(engines);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,14 +83,6 @@ impl<R: Read> Read for LogLuv24<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> super::stream::ReadSamples for LogLuv24<R> {
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> std::io::Result<()> {
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
std::io::copy(&mut self.by_ref().take(discard), &mut std::io::sink())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn packbits_decode<R: Read, const N: usize>(
|
fn packbits_decode<R: Read, const N: usize>(
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
buf: &mut [[u8; N]],
|
buf: &mut [[u8; N]],
|
||||||
@@ -220,14 +212,6 @@ impl<R: Read> Read for LogLuv32<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> super::stream::ReadSamples for LogLuv32<R> {
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> std::io::Result<()> {
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
std::io::copy(&mut self.by_ref().take(discard), &mut std::io::sink())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pack-Bits like encoding for a single 16-bit log-L sample.
|
/// Pack-Bits like encoding for a single 16-bit log-L sample.
|
||||||
pub struct LogLuv16<R> {
|
pub struct LogLuv16<R> {
|
||||||
reader: Take<R>,
|
reader: Take<R>,
|
||||||
@@ -294,14 +278,6 @@ impl<R: Read> Read for LogLuv16<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> super::stream::ReadSamples for LogLuv16<R> {
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> std::io::Result<()> {
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
std::io::copy(&mut self.by_ref().take(discard), &mut std::io::sink())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table information from: `libtiff`, 1997 by Greg Ward Larson, SGI
|
// Table information from: `libtiff`, 1997 by Greg Ward Larson, SGI
|
||||||
#[doc(alias = "UV_SQSIZ")]
|
#[doc(alias = "UV_SQSIZ")]
|
||||||
const UV_QUANTIZATION_WIDTH: f32 = 0.003500;
|
const UV_QUANTIZATION_WIDTH: f32 = 0.003500;
|
||||||
|
|||||||
+9
-21
@@ -259,7 +259,6 @@ pub struct Decoder<R> {
|
|||||||
scratch: Vec<u8>,
|
scratch: Vec<u8>,
|
||||||
/// Leniency flag that allows libtiff-like TIFF color interpretation behavior
|
/// Leniency flag that allows libtiff-like TIFF color interpretation behavior
|
||||||
lenient: bool,
|
lenient: bool,
|
||||||
compression: stream::CompressionEngines,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All the information needed to read and interpret byte slices from the underlying file, i.e. to
|
/// All the information needed to read and interpret byte slices from the underlying file, i.e. to
|
||||||
@@ -295,8 +294,8 @@ fn one_byte_predict<const N: usize>(buf: &mut [u8]) {
|
|||||||
acc.copy_from_slice(&buf[..N]);
|
acc.copy_from_slice(&buf[..N]);
|
||||||
|
|
||||||
// Process N bytes per iteration. Each channel's prefix sum is independent.
|
// Process N bytes per iteration. Each channel's prefix sum is independent.
|
||||||
let mut chunks = buf[N..].chunks_exact_mut(N);
|
let chunks = buf[N..].as_chunks_mut::<N>();
|
||||||
for chunk in &mut chunks {
|
for chunk in &mut chunks.0.iter_mut() {
|
||||||
for c in 0..N {
|
for c in 0..N {
|
||||||
acc[c] = chunk[c].wrapping_add(acc[c]);
|
acc[c] = chunk[c].wrapping_add(acc[c]);
|
||||||
chunk[c] = acc[c];
|
chunk[c] = acc[c];
|
||||||
@@ -304,7 +303,7 @@ fn one_byte_predict<const N: usize>(buf: &mut [u8]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle trailing bytes (when buf.len() - N is not a multiple of N).
|
// Handle trailing bytes (when buf.len() - N is not a multiple of N).
|
||||||
for (c, byte) in chunks.into_remainder().iter_mut().enumerate() {
|
for (c, byte) in chunks.1.iter_mut().enumerate() {
|
||||||
*byte = byte.wrapping_add(acc[c]);
|
*byte = byte.wrapping_add(acc[c]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -854,7 +853,6 @@ impl TiffHeader {
|
|||||||
image: None,
|
image: None,
|
||||||
scratch: Vec::new(),
|
scratch: Vec::new(),
|
||||||
lenient: false,
|
lenient: false,
|
||||||
compression: stream::CompressionEngines::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -951,18 +949,12 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
#[expect(clippy::unnecessary_unwrap)] // Lifetimes would overlap due to return.
|
#[expect(clippy::unnecessary_unwrap)] // Lifetimes would overlap due to return.
|
||||||
fn ensure_image_and_reader(
|
fn ensure_image_and_reader(
|
||||||
&mut self,
|
&mut self,
|
||||||
) -> TiffResult<(
|
) -> TiffResult<(&mut Image, &mut ValueReader<R>, &mut Vec<u8>)> {
|
||||||
&mut Image,
|
|
||||||
&mut ValueReader<R>,
|
|
||||||
&mut Vec<u8>,
|
|
||||||
&mut stream::CompressionEngines,
|
|
||||||
)> {
|
|
||||||
if self.image.is_some() {
|
if self.image.is_some() {
|
||||||
return Ok((
|
return Ok((
|
||||||
self.image.as_mut().unwrap(),
|
self.image.as_mut().unwrap(),
|
||||||
&mut self.value_reader,
|
&mut self.value_reader,
|
||||||
&mut self.scratch,
|
&mut self.scratch,
|
||||||
&mut self.compression,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -971,7 +963,6 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self.image.insert(image),
|
self.image.insert(image),
|
||||||
&mut self.value_reader,
|
&mut self.value_reader,
|
||||||
&mut self.scratch,
|
&mut self.scratch,
|
||||||
&mut self.compression,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1104,7 +1095,6 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
image: core::mem::take(&mut self.image),
|
image: core::mem::take(&mut self.image),
|
||||||
scratch: core::mem::take(&mut self.scratch),
|
scratch: core::mem::take(&mut self.scratch),
|
||||||
lenient: self.lenient,
|
lenient: self.lenient,
|
||||||
compression: core::mem::take(&mut self.compression),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,7 +1288,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// Utility to create a reader like [`Self::read_entry`], from the current IFD.
|
/// Utility to create a reader like [`Self::read_entry`], from the current IFD.
|
||||||
///
|
///
|
||||||
/// Returns an `Ok(None)` if the tag is not found in the current directory. See
|
/// Returns an `Ok(None)` if the tag is not found in the current directory. See
|
||||||
/// [`Self::read_entry´] of which this is a simple wrapper.
|
/// [`Self::read_entry`] of which this is a simple wrapper.
|
||||||
pub fn read_tag(&mut self, tag: Tag) -> TiffResult<Option<EntryBytesReader<'_, R>>> {
|
pub fn read_tag(&mut self, tag: Tag) -> TiffResult<Option<EntryBytesReader<'_, R>>> {
|
||||||
match self.current_ifd().find_entry(tag) {
|
match self.current_ifd().find_entry(tag) {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@@ -1588,10 +1578,10 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
layout: &image::ReadoutLayout,
|
layout: &image::ReadoutLayout,
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let (image, value_reader, scratch, engines) = self.ensure_image_and_reader()?;
|
let (image, value_reader, scratch) = self.ensure_image_and_reader()?;
|
||||||
let offset = image.chunk_file_range(chunk_index)?.0;
|
let offset = image.chunk_file_range(chunk_index)?.0;
|
||||||
value_reader.reader.goto_offset(offset)?;
|
value_reader.reader.goto_offset(offset)?;
|
||||||
image.expand_chunk(value_reader, buffer, layout, chunk_index, scratch, engines)?;
|
image.expand_chunk(value_reader, buffer, layout, chunk_index, scratch)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1743,7 +1733,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
slice: TiffCodingUnit,
|
slice: TiffCodingUnit,
|
||||||
buffer: &mut [u8],
|
buffer: &mut [u8],
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let (image, value_reader, scratch, engines) = self.ensure_image_and_reader()?;
|
let (image, value_reader, scratch) = self.ensure_image_and_reader()?;
|
||||||
|
|
||||||
let (width, height) = image.chunk_data_dimensions(slice.0)?;
|
let (width, height) = image.chunk_data_dimensions(slice.0)?;
|
||||||
let readout = image.readout_for_size(width, height)?;
|
let readout = image.readout_for_size(width, height)?;
|
||||||
@@ -1776,7 +1766,6 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
readout,
|
readout,
|
||||||
chunk,
|
chunk,
|
||||||
scratch,
|
scratch,
|
||||||
engines,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1948,7 +1937,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// Returns an error if the buffer fits less than one plane. In particular, for non-planar
|
/// Returns an error if the buffer fits less than one plane. In particular, for non-planar
|
||||||
/// images returns an error if the buffer does not fit the required size.
|
/// images returns an error if the buffer does not fit the required size.
|
||||||
pub fn read_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
|
pub fn read_image_bytes(&mut self, buffer: &mut [u8]) -> TiffResult<()> {
|
||||||
let (image, value_reader, scratch, engines) = self.ensure_image_and_reader()?;
|
let (image, value_reader, scratch) = self.ensure_image_and_reader()?;
|
||||||
let readout = image.readout_for_image()?;
|
let readout = image.readout_for_image()?;
|
||||||
|
|
||||||
let ref layout @ image::PlaneLayout {
|
let ref layout @ image::PlaneLayout {
|
||||||
@@ -1982,7 +1971,6 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
readout,
|
readout,
|
||||||
chunk,
|
chunk,
|
||||||
scratch,
|
scratch,
|
||||||
engines,
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-188
@@ -122,86 +122,9 @@ impl<R: Read> EndianReader<R> {
|
|||||||
// # READERS
|
// # READERS
|
||||||
//
|
//
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub(crate) struct CompressionEngines {
|
|
||||||
#[cfg(feature = "lzw")]
|
|
||||||
lzw: Option<weezl::decode::Decoder>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::fmt::Debug for CompressionEngines {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let mut dbg = f.debug_struct("CompressionEngines");
|
|
||||||
#[cfg(feature = "lzw")]
|
|
||||||
dbg.field("lzw", &self.lzw.is_some().then_some("<weezl>"));
|
|
||||||
dbg.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A variant of `io::Read` that takes one block to the end.
|
|
||||||
///
|
|
||||||
/// This is derived from `Read` so that any special optimization done by `std` (or any nightly crate
|
|
||||||
/// for that matter) surely remains usable.
|
|
||||||
///
|
|
||||||
/// We want our decompression engines to be much more specialized than the standard library trait.
|
|
||||||
/// Firstly there is more information available to communicate (the tiff Type, known decompressed
|
|
||||||
/// size, …) and secondly they can potentially *reset*. This could be solved with an extension trait
|
|
||||||
/// but since we want to avoid `read` in favor of `read_exact` (or an equivalent if we want to pass
|
|
||||||
/// in a strided block buffer) a separate trait that's auto derived is favored.
|
|
||||||
///
|
|
||||||
/// FIXME: Also consider that we will want to read subsets of samples. Via IO this is done by
|
|
||||||
/// discarding data *after* it is put into the output buffer. Yet, for some decompression that write
|
|
||||||
/// alone is considerable work where it could simply skip bits more efficiently. So the reader would
|
|
||||||
/// likely want to know about a sample mask.
|
|
||||||
pub trait ReadSamples: Read {
|
|
||||||
/// Read a row of samples and throw away some additional padding samples at the end.
|
|
||||||
///
|
|
||||||
/// This is automatically implemented for `Self: Read` as a copy into `io::Sink`.
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> io::Result<()>;
|
|
||||||
|
|
||||||
/// Put this back into our cache of compression engines.
|
|
||||||
fn cache_or_destroy(self: Box<Self>, _: &mut CompressionEngines) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Note: the `Seek` bound ensures this does not overlap actual decompression.. This is an odd
|
|
||||||
/// design problem born out of wanting both the underlying `Read` for uncompressed streams due to
|
|
||||||
/// performance as well as the override to `cache_or_destroy` for all compressed streams.
|
|
||||||
impl<R: Read + Seek> ReadSamples for R {
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> io::Result<()> {
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
if discard > 0 {
|
|
||||||
std::io::copy(&mut self.by_ref().take(discard), &mut std::io::sink())?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type alias for the deflate Reader
|
/// Type alias for the deflate Reader
|
||||||
#[cfg(feature = "deflate")]
|
#[cfg(feature = "deflate")]
|
||||||
pub struct DeflateReader<R> {
|
pub type DeflateReader<R> = flate2::read::ZlibDecoder<R>;
|
||||||
inner: flate2::read::ZlibDecoder<R>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> DeflateReader<R> {
|
|
||||||
pub fn new(inner: R) -> Self {
|
|
||||||
DeflateReader {
|
|
||||||
inner: flate2::read::ZlibDecoder::new(inner),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> Read for DeflateReader<R> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
self.inner.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Read> ReadSamples for DeflateReader<R> {
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> io::Result<()> {
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
std::io::copy(&mut self.by_ref().take(discard), &mut std::io::sink())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// ## LZW Reader
|
// ## LZW Reader
|
||||||
@@ -212,17 +135,12 @@ impl<R: Read> ReadSamples for DeflateReader<R> {
|
|||||||
pub struct LZWReader<R: Read> {
|
pub struct LZWReader<R: Read> {
|
||||||
reader: BufReader<Take<R>>,
|
reader: BufReader<Take<R>>,
|
||||||
decoder: weezl::decode::Decoder,
|
decoder: weezl::decode::Decoder,
|
||||||
is_old_style: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "lzw")]
|
#[cfg(feature = "lzw")]
|
||||||
impl<R: Read> LZWReader<R> {
|
impl<R: Read> LZWReader<R> {
|
||||||
/// Wraps a reader
|
/// Wraps a reader
|
||||||
pub fn new(
|
pub fn new(reader: R, compressed_length: usize) -> LZWReader<R> {
|
||||||
reader: R,
|
|
||||||
compressed_length: usize,
|
|
||||||
engine: &mut CompressionEngines,
|
|
||||||
) -> LZWReader<R> {
|
|
||||||
let mut buffered = BufReader::with_capacity(
|
let mut buffered = BufReader::with_capacity(
|
||||||
(32 * 1024).min(compressed_length),
|
(32 * 1024).min(compressed_length),
|
||||||
reader.take(u64::try_from(compressed_length).unwrap()),
|
reader.take(u64::try_from(compressed_length).unwrap()),
|
||||||
@@ -240,75 +158,26 @@ impl<R: Read> LZWReader<R> {
|
|||||||
Ok(head) if head.len() >= 2 && head[0] == 0x00 && head[1] & 0x01 != 0
|
Ok(head) if head.len() >= 2 && head[0] == 0x00 && head[1] & 0x01 != 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let decoder = if old_style {
|
let configuration = if old_style {
|
||||||
weezl::decode::Configuration::new(weezl::BitOrder::Lsb, 8)
|
weezl::decode::Configuration::new(weezl::BitOrder::Lsb, 8)
|
||||||
.with_yield_on_full_buffer(true)
|
|
||||||
.build()
|
|
||||||
} else {
|
|
||||||
// All standard LZW decoders have the same configuration. We can thus reuse any that we
|
|
||||||
// have previously constructed, skipping internal buffer allocation. We only need to
|
|
||||||
// reset them to a new stream.
|
|
||||||
if let Some(mut lzw) = engine.lzw.take() {
|
|
||||||
lzw.reset();
|
|
||||||
lzw
|
|
||||||
} else {
|
} else {
|
||||||
weezl::decode::Configuration::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
|
weezl::decode::Configuration::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
|
||||||
.with_yield_on_full_buffer(true)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
};
|
.with_yield_on_full_buffer(true);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
reader: buffered,
|
reader: buffered,
|
||||||
decoder: decoder,
|
decoder: configuration.build(),
|
||||||
is_old_style: old_style,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn discard_n(&mut self, mut n: u64) -> io::Result<()> {
|
|
||||||
let mut buf = [0u8; 2048];
|
|
||||||
|
|
||||||
while n > 0 {
|
|
||||||
let limit = n.min(2048) as usize;
|
|
||||||
let result = self
|
|
||||||
.decoder
|
|
||||||
.decode_bytes(self.reader.fill_buf()?, &mut buf[..limit]);
|
|
||||||
|
|
||||||
self.reader.consume(result.consumed_in);
|
|
||||||
n -= result.consumed_out as u64;
|
|
||||||
|
|
||||||
match result.status {
|
|
||||||
Ok(weezl::LzwStatus::Ok) => {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Ok(weezl::LzwStatus::NoProgress) => {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::UnexpectedEof,
|
|
||||||
"no lzw end code found",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(weezl::LzwStatus::Done) => {
|
|
||||||
if n != 0 {
|
|
||||||
// stream is too short.
|
|
||||||
return Err(std::io::ErrorKind::UnexpectedEof)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "lzw")]
|
#[cfg(feature = "lzw")]
|
||||||
impl<R: Read> Read for LZWReader<R> {
|
impl<R: Read> Read for LZWReader<R> {
|
||||||
fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
loop {
|
loop {
|
||||||
let result = self.decoder.decode_bytes(self.reader.fill_buf()?, buf);
|
let result = self.decoder.decode_bytes(self.reader.fill_buf()?, buf);
|
||||||
|
|
||||||
self.reader.consume(result.consumed_in);
|
self.reader.consume(result.consumed_in);
|
||||||
let _ = buf.split_off_mut(..result.consumed_out);
|
|
||||||
|
|
||||||
match result.status {
|
match result.status {
|
||||||
Ok(weezl::LzwStatus::Ok) => {
|
Ok(weezl::LzwStatus::Ok) => {
|
||||||
@@ -333,24 +202,6 @@ impl<R: Read> Read for LZWReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> ReadSamples for LZWReader<R> {
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> io::Result<()> {
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
|
|
||||||
if discard > 0 {
|
|
||||||
self.discard_n(discard)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cache_or_destroy(self: Box<Self>, engines: &mut CompressionEngines) {
|
|
||||||
if !self.is_old_style && engines.lzw.is_none() {
|
|
||||||
engines.lzw = Some(self.decoder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// ## PackBits Reader
|
// ## PackBits Reader
|
||||||
//
|
//
|
||||||
@@ -423,14 +274,6 @@ impl<R: Read> Read for PackBitsReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> ReadSamples for PackBitsReader<R> {
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> io::Result<()> {
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
std::io::copy(&mut self.by_ref().take(discard), &mut std::io::sink())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterator adapter that reverses bits in each byte for FillOrder=2 (LSB-to-MSB).
|
/// Iterator adapter that reverses bits in each byte for FillOrder=2 (LSB-to-MSB).
|
||||||
#[cfg(feature = "fax")]
|
#[cfg(feature = "fax")]
|
||||||
type FaxBytes<R> =
|
type FaxBytes<R> =
|
||||||
@@ -547,14 +390,6 @@ impl<R: Read> Read for Group4Reader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read> ReadSamples for Group4Reader<R> {
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> io::Result<()> {
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
std::io::copy(&mut self.by_ref().take(discard), &mut std::io::sink())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Owning byte reader for Group 3 decoding: the compressed data is consumed
|
/// Owning byte reader for Group 3 decoding: the compressed data is consumed
|
||||||
/// into an iterator so the reader doesn't borrow the buffer.
|
/// into an iterator so the reader doesn't borrow the buffer.
|
||||||
#[cfg(feature = "fax")]
|
#[cfg(feature = "fax")]
|
||||||
@@ -693,14 +528,6 @@ impl Read for Group3Reader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReadSamples for Group3Reader {
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> io::Result<()> {
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
std::io::copy(&mut self.by_ref().take(discard), &mut std::io::sink())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "webp")]
|
#[cfg(feature = "webp")]
|
||||||
pub struct WebPReader {
|
pub struct WebPReader {
|
||||||
inner: Cursor<Vec<u8>>,
|
inner: Cursor<Vec<u8>>,
|
||||||
@@ -756,15 +583,6 @@ impl Read for WebPReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "webp")]
|
|
||||||
impl ReadSamples for WebPReader {
|
|
||||||
fn read_padded_samples(&mut self, buf: &mut [u8], discard: u64) -> io::Result<()> {
|
|
||||||
self.read_exact(buf)?;
|
|
||||||
std::io::copy(&mut self.by_ref().take(discard), &mut std::io::sink())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
+1
-1
@@ -171,7 +171,7 @@ mod tests {
|
|||||||
dir.get(Tag::Unknown(1))
|
dir.get(Tag::Unknown(1))
|
||||||
.expect("tag 1 should be present after this chain")
|
.expect("tag 1 should be present after this chain")
|
||||||
.count(),
|
.count(),
|
||||||
u16::MAX.into()
|
u64::from(u16::MAX)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-7
@@ -617,26 +617,22 @@ impl ByteOrder {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: at MSRV 1.89 or higher use `slice::as_chunks_mut`.
|
|
||||||
match cls {
|
match cls {
|
||||||
EndianBytes::One => {
|
EndianBytes::One => {
|
||||||
// No change needed
|
// No change needed
|
||||||
}
|
}
|
||||||
EndianBytes::Two => {
|
EndianBytes::Two => {
|
||||||
for chunk in buffer.chunks_exact_mut(2) {
|
for chunk in buffer.as_chunks_mut::<2>().0 {
|
||||||
let chunk: &mut [u8; 2] = chunk.try_into().unwrap();
|
|
||||||
*chunk = u16::from_be_bytes(*chunk).to_le_bytes();
|
*chunk = u16::from_be_bytes(*chunk).to_le_bytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EndianBytes::Four => {
|
EndianBytes::Four => {
|
||||||
for chunk in buffer.chunks_exact_mut(4) {
|
for chunk in buffer.as_chunks_mut::<4>().0 {
|
||||||
let chunk: &mut [u8; 4] = chunk.try_into().unwrap();
|
|
||||||
*chunk = u32::from_be_bytes(*chunk).to_le_bytes();
|
*chunk = u32::from_be_bytes(*chunk).to_le_bytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EndianBytes::Eight => {
|
EndianBytes::Eight => {
|
||||||
for chunk in buffer.chunks_exact_mut(8) {
|
for chunk in buffer.as_chunks_mut::<8>().0 {
|
||||||
let chunk: &mut [u8; 8] = chunk.try_into().unwrap();
|
|
||||||
*chunk = u64::from_be_bytes(*chunk).to_le_bytes();
|
*chunk = u64::from_be_bytes(*chunk).to_le_bytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-6
@@ -506,6 +506,47 @@ fn test_tiled_extra_samples_border_u8() {
|
|||||||
assert_eq!(data, expected);
|
assert_eq!(data, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_tiled_planar_border_u16() {
|
||||||
|
// A planar (separate) tiled image whose dimensions are not multiples of the tile size.
|
||||||
|
// The tile grid repeats for every plane, but the chunk (tile) index counts all planes, so
|
||||||
|
// the border padding of the second plane used to be looked up with the wrong tile index.
|
||||||
|
// The bottom-right tile of plane 1 then claimed a full tile height, and the decoder ran
|
||||||
|
// past the end of the output buffer.
|
||||||
|
//
|
||||||
|
// Note that `read_image` only decodes the first plane, so this has to go through
|
||||||
|
// `read_image_to_buffer` to reach the tiles that were mis-sized.
|
||||||
|
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-planar-2c-16b.tif");
|
||||||
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
|
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||||
|
decoder.next_image().expect("Cannot read image IFD");
|
||||||
|
assert_eq!(decoder.dimensions().unwrap(), (30, 20));
|
||||||
|
assert_eq!(
|
||||||
|
decoder.colortype().unwrap(),
|
||||||
|
ColorType::Multiband {
|
||||||
|
bit_depth: 16,
|
||||||
|
num_samples: 2
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut buffer = DecodingSampleBuffer::U16(Vec::new());
|
||||||
|
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
|
let DecodingSampleBuffer::U16(data) = buffer else {
|
||||||
|
panic!("Wrong bit depth")
|
||||||
|
};
|
||||||
|
assert_eq!(data.len(), 2 * 20 * 30);
|
||||||
|
|
||||||
|
let mut expected = Vec::with_capacity(2 * 20 * 30);
|
||||||
|
for plane in 0..2u16 {
|
||||||
|
for y in 0..20u16 {
|
||||||
|
for x in 0..30u16 {
|
||||||
|
expected.push(1000 * plane + 10 * y + x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(data, expected);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_inner_access() {
|
fn test_inner_access() {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
|
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
|
||||||
@@ -898,22 +939,22 @@ fn test_image_bytes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn byte_order_u16(bytes: &mut [u8]) {
|
fn byte_order_u16(bytes: &mut [u8]) {
|
||||||
for chunk in bytes.chunks_exact_mut(2) {
|
for chunk in bytes.as_chunks_mut::<2>().0 {
|
||||||
let n = u16::from_ne_bytes(chunk.try_into().unwrap());
|
let n = u16::from_ne_bytes(*chunk);
|
||||||
chunk.copy_from_slice(&n.to_le_bytes());
|
chunk.copy_from_slice(&n.to_le_bytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn byte_order_u32(bytes: &mut [u8]) {
|
fn byte_order_u32(bytes: &mut [u8]) {
|
||||||
for chunk in bytes.chunks_exact_mut(4) {
|
for chunk in bytes.as_chunks_mut::<4>().0 {
|
||||||
let n = u32::from_ne_bytes(chunk.try_into().unwrap());
|
let n = u32::from_ne_bytes(*chunk);
|
||||||
chunk.copy_from_slice(&n.to_le_bytes());
|
chunk.copy_from_slice(&n.to_le_bytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn byte_order_u64(bytes: &mut [u8]) {
|
fn byte_order_u64(bytes: &mut [u8]) {
|
||||||
for chunk in bytes.chunks_exact_mut(8) {
|
for chunk in bytes.as_chunks_mut::<8>().0 {
|
||||||
let n = u64::from_ne_bytes(chunk.try_into().unwrap());
|
let n = u64::from_ne_bytes(*chunk);
|
||||||
chunk.copy_from_slice(&n.to_le_bytes());
|
chunk.copy_from_slice(&n.to_le_bytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Reference in new issue
Block a user