Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b21bcc75c | ||
|
|
fe305ec24a | ||
|
|
a3ab252168 | ||
|
|
bd64e438a4 | ||
|
|
fa225e820b | ||
|
|
ea373bae00 | ||
|
|
23d1867f75 | ||
|
|
234b2ba677 | ||
|
|
a63a01319c | ||
|
|
efd2a6dbd8 | ||
|
|
d38fba4268 | ||
|
|
1732481b01 | ||
|
|
355ecc8ad9 | ||
|
|
2d90179602 | ||
|
|
5a3b11314e | ||
|
|
5150e91bbb | ||
|
|
6d56d014a1 | ||
|
|
af8a8d660f | ||
|
|
b1c74495ce | ||
|
|
1f02ff8ca1 | ||
|
|
5b9dc30919 | ||
|
|
c3882b252c | ||
|
|
6fe21da979 | ||
|
|
0de1261948 | ||
|
|
6439880e3d | ||
|
|
dce1c40bd5 | ||
|
|
5d3a5242ea | ||
|
|
58543962f3 | ||
|
|
95f2d475fa | ||
|
|
f9eaf9632a | ||
|
|
61b3027f38 | ||
|
|
65662c4834 | ||
|
|
985f3822c8 | ||
|
|
2eef9ac088 | ||
|
|
f6d95249dd | ||
|
|
8517bb81db |
No files matched your search
+49
-1
@@ -1,7 +1,55 @@
|
|||||||
# Version 0.10.4 (unreleased)
|
# Version 0.11.0
|
||||||
|
|
||||||
- `Directory` now implements `FromIterator<(Tag, Value)>`.
|
- `Directory` now implements `FromIterator<(Tag, Value)>`.
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- The decoder now interprets the `ExtraSamples` tag. The sample count must now
|
||||||
|
more strict match the expected value with alpha channels only allowing for
|
||||||
|
explicitly denoted unassociated or associated alpha. This effects the
|
||||||
|
indicated color type when decoding images with additional samples indicated
|
||||||
|
as unspecified relation. Previously, these may have been interpreted as
|
||||||
|
alpha by the total sample count (e.g. RgbA if 4 samples under a photometric
|
||||||
|
interpretation of RGB).
|
||||||
|
- The decoder handles planar data, current limited to non-subsampled channels.
|
||||||
|
The `Decoder::read_image` method return planes one-after-another depending on
|
||||||
|
the size of the buffer that was passed.
|
||||||
|
- `Decoder::read_image_to_buffer` now takes `&mut DecodingResult` and resizes
|
||||||
|
it according to the required layout. Previously, a borrowed `DecodingBuffer`
|
||||||
|
was passed which can be replaced by calling `as_bytes_mut` and
|
||||||
|
`read_image_bytes`.
|
||||||
|
- Several methods of tags are now `const`. Note that does not guarantee any
|
||||||
|
particular value when calling these methods.
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
- Fix a bug in the uncompressed encoder that could lead to short writes, i.e.
|
||||||
|
data silently dropped when the underlying writer did not accept all data in a
|
||||||
|
single write call.
|
||||||
|
- Encoding YCbCr data now writes the `ChromaSubsampling` tag as `(1, 1)` to
|
||||||
|
indicate no subsampling, instead of leaving it at its default of `(2, 2)`.
|
||||||
|
- The decoder will reject subsampled YCbCr data as there is no upsampling
|
||||||
|
routine, except for JPEG compressed images where the JPEG decoders handles
|
||||||
|
this. Since the buffer in that case indicates a full-sized plane for all
|
||||||
|
color samples any future support for the tag will upsample all planes (at
|
||||||
|
least within this major version).
|
||||||
|
|
||||||
|
Additions:
|
||||||
|
- Added support for the `CieLab` color type.
|
||||||
|
- Added `DecodingResult::resize_to` to create a buffer with a matching sample
|
||||||
|
type and dimensions.
|
||||||
|
- Added `ByteOrder::native` to access the platform's native endianness.
|
||||||
|
- Added `ByteOrder::convert` to change the byte-order of values in a byte
|
||||||
|
buffer, depending on their `Type` as described at runtime.
|
||||||
|
- Added `DirectoryOffset::new` to encode a directory whose offset is known to
|
||||||
|
the caller but that has not been written through the encoder itself.
|
||||||
|
- Added `Encoder::extra_samples` to encode images with more samples than their
|
||||||
|
color's trait implementation would otherwise suggest.
|
||||||
|
- Added `DirectoryEncoder::extend_from` to encode multiple tag entries from a
|
||||||
|
directory whose values were written to the file by means outside the
|
||||||
|
encoder's control.
|
||||||
|
- Added `Decoder::read_coding_unit_bytes` to retrieve data of corresponding
|
||||||
|
coordinates from potentially planar data, which is encoded in multiple chunks
|
||||||
|
of the file.
|
||||||
|
|
||||||
# Version 0.10.3
|
# Version 0.10.3
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.10.3"
|
version = "0.11.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
use tiff::decoder::{BufferLayoutPreference, Decoder, DecodingBuffer, DecodingResult};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let Some(image) = std::env::args_os().nth(1) else {
|
||||||
|
eprintln!("Usage: decode FILE");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = std::fs::File::open(image)?;
|
||||||
|
let io = std::io::BufReader::new(file);
|
||||||
|
let mut reader = Decoder::new(io)?;
|
||||||
|
|
||||||
|
let mut data = DecodingResult::I8(vec![]);
|
||||||
|
|
||||||
|
for i in 0u32.. {
|
||||||
|
let colortype = reader.colortype()?;
|
||||||
|
let dimensions = reader.dimensions()?;
|
||||||
|
let layout = reader.read_image_to_buffer(&mut data)?;
|
||||||
|
|
||||||
|
debug_planes(i, &mut data, &layout, &colortype, dimensions);
|
||||||
|
|
||||||
|
if !reader.more_images() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.next_image()?
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_planes(
|
||||||
|
index: u32,
|
||||||
|
data: &mut DecodingResult,
|
||||||
|
layout: &BufferLayoutPreference,
|
||||||
|
colortype: &tiff::ColorType,
|
||||||
|
(width, height): (u32, u32),
|
||||||
|
) {
|
||||||
|
let (depth, mut tupltype) = match colortype {
|
||||||
|
tiff::ColorType::Gray(_) => (1, "GRAYSCALE"),
|
||||||
|
tiff::ColorType::RGB(_) => (3, "RGB"),
|
||||||
|
tiff::ColorType::RGBA(_) => (4, "RGB_ALPHA"),
|
||||||
|
tiff::ColorType::Palette(_) => (1, "PALETTE"),
|
||||||
|
tiff::ColorType::CMYK(_) => (4, "CMYK"),
|
||||||
|
tiff::ColorType::Multiband { num_samples: 2, .. } => (2, "GRAYSCALE_ALPHA"),
|
||||||
|
_ => {
|
||||||
|
eprintln!("Unsupported color type for PAM output: {:?}", colortype);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let maxval = match data.as_buffer(0) {
|
||||||
|
DecodingBuffer::U8(_) => u64::from(u8::MAX),
|
||||||
|
DecodingBuffer::U16(_) => u64::from(u16::MAX),
|
||||||
|
DecodingBuffer::U32(_) => u64::from(u32::MAX),
|
||||||
|
DecodingBuffer::U64(_) => u64::MAX,
|
||||||
|
// Eh, this is a good effort I guess.
|
||||||
|
DecodingBuffer::I8(items) => {
|
||||||
|
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
||||||
|
u64::from(i8::MAX as u8)
|
||||||
|
}
|
||||||
|
DecodingBuffer::I16(items) => {
|
||||||
|
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
||||||
|
u64::from(i16::MAX as u16)
|
||||||
|
}
|
||||||
|
DecodingBuffer::I32(items) => {
|
||||||
|
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
||||||
|
u64::from(i32::MAX as u32)
|
||||||
|
}
|
||||||
|
DecodingBuffer::I64(items) => {
|
||||||
|
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
||||||
|
i64::MAX as u64
|
||||||
|
}
|
||||||
|
// Not supported by PAM.
|
||||||
|
DecodingBuffer::F16(_) | DecodingBuffer::F32(_) | DecodingBuffer::F64(_) => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let byte_len = if layout.planes > 1 {
|
||||||
|
tupltype = "GRAYSCALE";
|
||||||
|
layout.plane_stride.unwrap().get()
|
||||||
|
} else {
|
||||||
|
data.as_buffer(0).byte_len()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write out this image's planes to PAM files.
|
||||||
|
let header = format!("P7\nWIDTH {width}\nHEIGHT {height}\nDEPTH {depth}\nMAXVAL {maxval}\nTUPLTYPE {tupltype}\nENDHDR\n");
|
||||||
|
|
||||||
|
for (plane, plane_data) in data
|
||||||
|
.as_buffer(0)
|
||||||
|
.as_bytes_mut()
|
||||||
|
.chunks_exact(byte_len)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let filename = format!("image_{index:03}_plane_{plane:03}.pam");
|
||||||
|
let mut file = std::fs::File::create(filename).expect("Failed to create output file");
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
file.write_all(header.as_bytes())
|
||||||
|
.expect("Failed to write PAM header");
|
||||||
|
file.write_all(plane_data)
|
||||||
|
.expect("Failed to write PAM data");
|
||||||
|
}
|
||||||
|
}
|
||||||
+484
-62
@@ -4,14 +4,14 @@ 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::tags::{
|
use crate::tags::{
|
||||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
|
CompressionMethod, ExtraSamples, PhotometricInterpretation, PlanarConfiguration, Predictor,
|
||||||
|
SampleFormat, Tag,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::io::{self, Cursor, Read, Seek};
|
use std::io::{self, Cursor, Read, Seek};
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -69,6 +69,11 @@ pub(crate) struct Image {
|
|||||||
pub height: u32,
|
pub height: u32,
|
||||||
pub bits_per_sample: u8,
|
pub bits_per_sample: u8,
|
||||||
pub samples: u16,
|
pub samples: u16,
|
||||||
|
/// The `ExtraSamples`, defaulting to empty if not given.
|
||||||
|
pub extra_samples: Vec<ExtraSamples>,
|
||||||
|
/// Number of samples that belong to the photometric interpretation, samples except
|
||||||
|
/// `ExtraSamples` (338, 0x0152) tag.
|
||||||
|
pub photometric_samples: u16,
|
||||||
pub sample_format: SampleFormat,
|
pub sample_format: SampleFormat,
|
||||||
pub photometric_interpretation: PhotometricInterpretation,
|
pub photometric_interpretation: PhotometricInterpretation,
|
||||||
pub compression_method: CompressionMethod,
|
pub compression_method: CompressionMethod,
|
||||||
@@ -80,6 +85,58 @@ pub(crate) struct Image {
|
|||||||
pub tile_attributes: Option<TileAttributes>,
|
pub tile_attributes: Option<TileAttributes>,
|
||||||
pub chunk_offsets: Vec<u64>,
|
pub chunk_offsets: Vec<u64>,
|
||||||
pub chunk_bytes: Vec<u64>,
|
pub chunk_bytes: Vec<u64>,
|
||||||
|
pub chroma_subsampling: (u16, u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes how to read a tile-aligned portion of the image.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct ReadoutLayout {
|
||||||
|
/// The planar configuration, which applies to both the underlying image and the output buffer.
|
||||||
|
/// This may be relaxed if we find a clean enough way to provide it.
|
||||||
|
pub planar_config: PlanarConfiguration,
|
||||||
|
|
||||||
|
/// The sample interpretation (interpret with planar_config).
|
||||||
|
///
|
||||||
|
/// FIXME: we should not require this here. The ability to turn out the raw bytes from the
|
||||||
|
/// sample arrays is very different from turning out interpretable color. Firstly we can always
|
||||||
|
/// readout `Multiband` but currently only use that ColorType in special circumstances (it must
|
||||||
|
/// not overlap cases where actually want to use a ColorType).
|
||||||
|
///
|
||||||
|
/// And then we have CIE Lab, which uses a tuple of `(u8, i8, i8)`, that is still filterable
|
||||||
|
/// but still not represented by any of our `DecoderResult` variants. Other color variants
|
||||||
|
/// depend on extra tags (YCbCrCoefficients/0x0211) and we don't have a good side channel to
|
||||||
|
/// tag the output with all that TIFF specific information, so arguably we should process and
|
||||||
|
/// apply those to the data so it becomes a self-contained representation.
|
||||||
|
///
|
||||||
|
/// This should be computed at a higher level, in `Decoder`, instead.
|
||||||
|
pub color: ColorType,
|
||||||
|
/// The number of bytes from one row to another.
|
||||||
|
pub minimum_row_stride: usize,
|
||||||
|
/// The format of samples (assumed uniform for now, same with depth of `ColorType`).
|
||||||
|
pub sample_format: SampleFormat,
|
||||||
|
|
||||||
|
/// Number of bytes to advance in output per row.
|
||||||
|
pub row_stride: usize,
|
||||||
|
/// Number of bytes to advance in output per chunk in width.
|
||||||
|
pub chunk_row_stride: usize,
|
||||||
|
/// Number of bytes to advance in output per chunk in height.
|
||||||
|
pub chunk_col_stride: usize,
|
||||||
|
/// Number of bytes in output from one plane to another.
|
||||||
|
pub plane_stride: usize,
|
||||||
|
|
||||||
|
/// Bits per sample in the encoded data.
|
||||||
|
pub tiff_bits_per_sample: u8,
|
||||||
|
/// Number of samples in the encoded data.
|
||||||
|
pub tiff_samples: u16,
|
||||||
|
/// Dimensions of the underlying rectangular chunks (tile or strips).
|
||||||
|
pub tiff_chunk_dimensions: (u32, u32),
|
||||||
|
/// Number of bytes in the underlying data with all samples per row of chunks.
|
||||||
|
pub tiff_row_bytes: usize,
|
||||||
|
|
||||||
|
/// Chunks until wrapping to the next row of chunks.
|
||||||
|
pub chunks_across: u32,
|
||||||
|
/// Chunks to advance to get to the next plane of chunks.
|
||||||
|
pub chunks_per_plane: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
@@ -134,10 +191,30 @@ impl Image {
|
|||||||
.map(Value::into_u16)
|
.map(Value::into_u16)
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(1);
|
.unwrap_or(1);
|
||||||
|
|
||||||
if samples == 0 {
|
if samples == 0 {
|
||||||
return Err(TiffFormatError::SamplesPerPixelIsZero.into());
|
return Err(TiffFormatError::SamplesPerPixelIsZero.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let extra_samples = match tag_reader.find_tag(Tag::ExtraSamples)? {
|
||||||
|
Some(n) => n.into_u16_vec()?,
|
||||||
|
None => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let extra_samples = extra_samples
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| ExtraSamples::from_u16(x).unwrap_or(ExtraSamples::Unspecified))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let photometric_samples = match usize::from(samples).checked_sub(extra_samples.len()) {
|
||||||
|
None => {
|
||||||
|
return Err(TiffError::FormatError(
|
||||||
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some(n) => n as u16,
|
||||||
|
};
|
||||||
|
|
||||||
let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
|
let sample_format = match tag_reader.find_tag_uint_vec(Tag::SampleFormat)? {
|
||||||
Some(vals) => {
|
Some(vals) => {
|
||||||
let sample_format: Vec<_> = vals
|
let sample_format: Vec<_> = vals
|
||||||
@@ -164,7 +241,7 @@ impl Image {
|
|||||||
|
|
||||||
// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows
|
// Technically bits_per_sample.len() should be *equal* to samples, but libtiff also allows
|
||||||
// it to be a single value that applies to all samples.
|
// it to be a single value that applies to all samples.
|
||||||
if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 {
|
if bits_per_sample.len() != usize::from(samples) && bits_per_sample.len() != 1 {
|
||||||
return Err(TiffError::FormatError(
|
return Err(TiffError::FormatError(
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
));
|
));
|
||||||
@@ -199,6 +276,25 @@ impl Image {
|
|||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(PlanarConfiguration::Chunky);
|
.unwrap_or(PlanarConfiguration::Chunky);
|
||||||
|
|
||||||
|
let ycbcr_subsampling = tag_reader.find_tag_uint_vec::<u16>(Tag::ChromaSubsampling)?;
|
||||||
|
|
||||||
|
let chroma_subsampling = if let Some(subsamples) = &ycbcr_subsampling {
|
||||||
|
let [a, b] = subsamples.as_slice() else {
|
||||||
|
return Err(TiffError::FormatError(TiffFormatError::InvalidCountForTag(
|
||||||
|
Tag::ChromaSubsampling,
|
||||||
|
subsamples.len(),
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
// ImageWidth and ImageLength are constrained to be integer multiples of
|
||||||
|
// YCbCrSubsampleHoriz and YCbCrSubsampleVert respectively. TileWidth and TileLength
|
||||||
|
// have the same constraints. RowsPerStrip must be an integer multiple of
|
||||||
|
// YCbCrSubsampleVert.
|
||||||
|
(*a, *b)
|
||||||
|
} else {
|
||||||
|
(2, 2)
|
||||||
|
};
|
||||||
|
|
||||||
let planes = match planar_config {
|
let planes = match planar_config {
|
||||||
PlanarConfiguration::Chunky => 1,
|
PlanarConfiguration::Chunky => 1,
|
||||||
PlanarConfiguration::Planar => samples,
|
PlanarConfiguration::Planar => samples,
|
||||||
@@ -297,6 +393,8 @@ impl Image {
|
|||||||
height,
|
height,
|
||||||
bits_per_sample: bits_per_sample[0],
|
bits_per_sample: bits_per_sample[0],
|
||||||
samples,
|
samples,
|
||||||
|
extra_samples,
|
||||||
|
photometric_samples,
|
||||||
sample_format,
|
sample_format,
|
||||||
photometric_interpretation,
|
photometric_interpretation,
|
||||||
compression_method,
|
compression_method,
|
||||||
@@ -308,19 +406,24 @@ impl Image {
|
|||||||
tile_attributes,
|
tile_attributes,
|
||||||
chunk_offsets,
|
chunk_offsets,
|
||||||
chunk_bytes,
|
chunk_bytes,
|
||||||
|
chroma_subsampling,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
|
pub(crate) fn colortype(&self) -> TiffResult<ColorType> {
|
||||||
|
let is_alpha_extra_samples = matches!(
|
||||||
|
self.extra_samples.as_slice(),
|
||||||
|
[ExtraSamples::AssociatedAlpha, ..] | [ExtraSamples::UnassociatedAlpha, ..]
|
||||||
|
);
|
||||||
|
|
||||||
match self.photometric_interpretation {
|
match self.photometric_interpretation {
|
||||||
PhotometricInterpretation::RGB => match self.samples {
|
PhotometricInterpretation::RGB => match self.photometric_samples {
|
||||||
3 => Ok(ColorType::RGB(self.bits_per_sample)),
|
3 => Ok(if is_alpha_extra_samples {
|
||||||
|
ColorType::RGBA(self.bits_per_sample)
|
||||||
|
} else {
|
||||||
|
ColorType::RGB(self.bits_per_sample)
|
||||||
|
}),
|
||||||
4 => Ok(ColorType::RGBA(self.bits_per_sample)),
|
4 => Ok(ColorType::RGBA(self.bits_per_sample)),
|
||||||
// FIXME: We should _ignore_ other components. In particular:
|
|
||||||
// > Beware of extra components. Some TIFF files may have more components per pixel
|
|
||||||
// than you think. A Baseline TIFF reader must skip over them gracefully,using the
|
|
||||||
// values of the SamplesPerPixel and BitsPerSample fields.
|
|
||||||
// > -- TIFF 6.0 Specification, Section 7, Additional Baseline requirements.
|
|
||||||
_ => Err(TiffError::UnsupportedError(
|
_ => Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::InterpretationWithBits(
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
self.photometric_interpretation,
|
self.photometric_interpretation,
|
||||||
@@ -328,8 +431,12 @@ impl Image {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
PhotometricInterpretation::CMYK => match self.samples {
|
PhotometricInterpretation::CMYK => match self.photometric_samples {
|
||||||
4 => Ok(ColorType::CMYK(self.bits_per_sample)),
|
4 => Ok(if is_alpha_extra_samples {
|
||||||
|
ColorType::CMYKA(self.bits_per_sample)
|
||||||
|
} else {
|
||||||
|
ColorType::CMYK(self.bits_per_sample)
|
||||||
|
}),
|
||||||
5 => Ok(ColorType::CMYKA(self.bits_per_sample)),
|
5 => Ok(ColorType::CMYKA(self.bits_per_sample)),
|
||||||
_ => Err(TiffError::UnsupportedError(
|
_ => Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::InterpretationWithBits(
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
@@ -338,7 +445,7 @@ impl Image {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
PhotometricInterpretation::YCbCr => match self.samples {
|
PhotometricInterpretation::YCbCr => match self.photometric_samples {
|
||||||
3 => Ok(ColorType::YCbCr(self.bits_per_sample)),
|
3 => Ok(ColorType::YCbCr(self.bits_per_sample)),
|
||||||
_ => Err(TiffError::UnsupportedError(
|
_ => Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::InterpretationWithBits(
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
@@ -351,6 +458,9 @@ impl Image {
|
|||||||
// later called when that interpretation is read. That function does not support
|
// later called when that interpretation is read. That function does not support
|
||||||
// Multiband as a color type and will error. It's unclear how to resolve that exactly.
|
// Multiband as a color type and will error. It's unclear how to resolve that exactly.
|
||||||
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
|
PhotometricInterpretation::BlackIsZero | PhotometricInterpretation::WhiteIsZero => {
|
||||||
|
// Note: compatibility with previous implementation requires us to return extra
|
||||||
|
// samples as `Multiband`. For gray images however the better choice would be
|
||||||
|
// returning a `Gray` color, i.e. matching on `photometric_samples` instead.
|
||||||
match self.samples {
|
match self.samples {
|
||||||
1 => Ok(ColorType::Gray(self.bits_per_sample)),
|
1 => Ok(ColorType::Gray(self.bits_per_sample)),
|
||||||
_ => Ok(ColorType::Multiband {
|
_ => Ok(ColorType::Multiband {
|
||||||
@@ -359,34 +469,63 @@ impl Image {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: this is bad we should not fail at this point
|
// ```
|
||||||
PhotometricInterpretation::RGBPalette
|
// struct IccLab /* Interpretation 9* {
|
||||||
| PhotometricInterpretation::TransparencyMask
|
// pub L: u8, // SampleFormat::Uint
|
||||||
| PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError(
|
// pub a: u8, // SampleFormat::Uint, defined as TiffLab::a + 128
|
||||||
|
// pub b: u8, // SampleFormat::Uint, defined as TiffLab::b + 128
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
PhotometricInterpretation::IccLab => match self.photometric_samples {
|
||||||
|
3 if matches!(self.sample_format, SampleFormat::Uint) => {
|
||||||
|
Ok(ColorType::Lab(self.bits_per_sample))
|
||||||
|
}
|
||||||
|
_ => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
vec![self.bits_per_sample; self.samples as usize],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
// Unsupported due to inherently heterogeneous sample types. This is represented as:
|
||||||
|
// ```
|
||||||
|
// struct TiffLab /* Interpretation 8* {
|
||||||
|
// pub L: u8, // SampleFormat::Uint
|
||||||
|
// pub a: i8, // SampleFormat::Int
|
||||||
|
// pub b: i8, // SampleFormat::Int
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::InterpretationWithBits(
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
self.photometric_interpretation,
|
PhotometricInterpretation::CIELab,
|
||||||
vec![self.bits_per_sample; self.samples as usize],
|
vec![self.bits_per_sample; self.samples as usize],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
// Unsupported due to extra unfiltering and conversion steps. We need to find the
|
||||||
|
// Decode tag (SRATIONAL; 2 * SamplesPerPixel) and apply the following conversion:
|
||||||
|
//
|
||||||
|
// L* = Decode[0] + Lsample x (Decode[1] - Decode[0]) / (2^n -1)
|
||||||
|
// …
|
||||||
|
//
|
||||||
|
// So we'll have a larger depth in the output and either worry about reducing fractions
|
||||||
|
// or turn everything into floats. That's a lot of decisions.
|
||||||
|
PhotometricInterpretation::ItuLab => Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
PhotometricInterpretation::CIELab,
|
||||||
|
vec![self.bits_per_sample; self.samples as usize],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
PhotometricInterpretation::RGBPalette | PhotometricInterpretation::TransparencyMask => {
|
||||||
|
Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
vec![self.bits_per_sample; self.samples as usize],
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
@@ -483,18 +622,25 @@ impl Image {
|
|||||||
/// Example with `bits_per_sample = [8, 8, 8]` and `PhotometricInterpretation::RGB`:
|
/// Example with `bits_per_sample = [8, 8, 8]` and `PhotometricInterpretation::RGB`:
|
||||||
/// * `PlanarConfiguration::Chunky` -> 3 (RGBRGBRGB...)
|
/// * `PlanarConfiguration::Chunky` -> 3 (RGBRGBRGB...)
|
||||||
/// * `PlanarConfiguration::Planar` -> 1 (RRR...) (GGG...) (BBB...)
|
/// * `PlanarConfiguration::Planar` -> 1 (RRR...) (GGG...) (BBB...)
|
||||||
pub(crate) fn samples_per_pixel(&self) -> usize {
|
pub(crate) fn samples_per_pixel(&self) -> u16 {
|
||||||
match self.planar_config {
|
match self.planar_config {
|
||||||
PlanarConfiguration::Chunky => self.samples.into(),
|
PlanarConfiguration::Chunky => self.samples,
|
||||||
|
PlanarConfiguration::Planar => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn samples_per_out_texel(&self, color: ColorType) -> u16 {
|
||||||
|
match self.planar_config {
|
||||||
|
PlanarConfiguration::Chunky => color.num_samples(),
|
||||||
PlanarConfiguration::Planar => 1,
|
PlanarConfiguration::Planar => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Number of strips per pixel.
|
/// Number of strips per pixel.
|
||||||
pub(crate) fn strips_per_pixel(&self) -> usize {
|
pub(crate) fn strips_per_pixel(&self) -> u16 {
|
||||||
match self.planar_config {
|
match self.planar_config {
|
||||||
PlanarConfiguration::Chunky => 1,
|
PlanarConfiguration::Chunky => 1,
|
||||||
PlanarConfiguration::Planar => self.samples.into(),
|
PlanarConfiguration::Planar => self.samples,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,14 +678,107 @@ impl Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn readout_for_image(&self) -> TiffResult<ReadoutLayout> {
|
||||||
|
let Image { width, height, .. } = *self;
|
||||||
|
self.readout_for_size(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the layout for reading out a tile-aligned portion of the image.
|
||||||
|
///
|
||||||
|
/// The provided width and height should be less than or equal to the image dimensions.
|
||||||
|
pub(crate) fn readout_for_size(&self, width: u32, height: u32) -> TiffResult<ReadoutLayout> {
|
||||||
|
let color = self.colortype()?;
|
||||||
|
|
||||||
|
let tiff_samples = self.samples_per_pixel();
|
||||||
|
let tiff_bits_per_sample = self.bits_per_sample;
|
||||||
|
let data_samples = self.samples_per_out_texel(color);
|
||||||
|
let tiff_chunk_dimensions = self.chunk_dimensions()?;
|
||||||
|
let strips_per_pixel = self.strips_per_pixel();
|
||||||
|
|
||||||
|
let data_dimensions = (width, height);
|
||||||
|
|
||||||
|
let tiff_row_bits = (u64::from(tiff_chunk_dimensions.0) * u64::from(tiff_bits_per_sample))
|
||||||
|
.checked_mul(u64::from(tiff_samples))
|
||||||
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
|
let tiff_row_bytes: usize = tiff_row_bits.div_ceil(8).try_into()?;
|
||||||
|
|
||||||
|
let chunk_row_bits = (u64::from(tiff_chunk_dimensions.0) * u64::from(tiff_bits_per_sample))
|
||||||
|
.checked_mul(u64::from(data_samples))
|
||||||
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
|
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
|
||||||
|
|
||||||
|
let data_row_bits = (u64::from(data_dimensions.0) * u64::from(tiff_bits_per_sample))
|
||||||
|
.checked_mul(u64::from(data_samples))
|
||||||
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
|
let data_row_bytes: usize = data_row_bits.div_ceil(8).try_into()?;
|
||||||
|
|
||||||
|
let chunk_col_stride: usize = data_row_bits
|
||||||
|
.div_ceil(8)
|
||||||
|
.checked_mul(u64::from(tiff_chunk_dimensions.1))
|
||||||
|
.ok_or(TiffError::LimitsExceeded)?
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
|
let plane_stride: usize = data_row_bits
|
||||||
|
.div_ceil(8)
|
||||||
|
.checked_mul(u64::from(data_dimensions.1))
|
||||||
|
.ok_or(TiffError::LimitsExceeded)?
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
|
let minimum_row_stride = data_row_bytes;
|
||||||
|
|
||||||
|
let chunks_across: u32 = data_dimensions.0.div_ceil(tiff_chunk_dimensions.0);
|
||||||
|
let chunks_per_plane = (self.chunk_offsets.len() as u32) / u32::from(strips_per_pixel);
|
||||||
|
|
||||||
|
// We would not get an offset in byte units, sorry, no bit interleaving in the output.
|
||||||
|
if chunks_across > 1 && chunk_row_bits % 8 != 0 {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::MisalignedTileBoundaries,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only this color type interprets the tag, which is defined with a default of (2, 2)
|
||||||
|
if matches!(color, ColorType::YCbCr(_)) && self.chroma_subsampling != (1, 1) {
|
||||||
|
// The JPEG library does upsampling for us and defines its buffers correctly
|
||||||
|
// (presumably). All other compression schemes are not supported..
|
||||||
|
//
|
||||||
|
// NOTE: as explained in <fa225e820b96bef35f01bf4685654beeb4a8df0c> we may be better
|
||||||
|
// off supporting this tag by consistently upsampling, not by adjusting the buffer
|
||||||
|
// size. At least as a default this makes more sense and is much more permissive in
|
||||||
|
// case the compression stream disagrees with the tags (we would not have enough / or
|
||||||
|
// the wrong buffer layout if we only asked for subsampled planes in a planar layout).
|
||||||
|
if !matches!(self.compression_method, CompressionMethod::ModernJPEG) {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::ChromaSubsampling,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ReadoutLayout {
|
||||||
|
planar_config: self.planar_config,
|
||||||
|
color,
|
||||||
|
minimum_row_stride,
|
||||||
|
sample_format: self.sample_format,
|
||||||
|
row_stride: data_row_bytes,
|
||||||
|
chunk_row_stride: chunk_row_bytes,
|
||||||
|
chunk_col_stride,
|
||||||
|
plane_stride,
|
||||||
|
tiff_bits_per_sample,
|
||||||
|
tiff_samples,
|
||||||
|
tiff_chunk_dimensions,
|
||||||
|
tiff_row_bytes,
|
||||||
|
chunks_across,
|
||||||
|
chunks_per_plane,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
|
pub(crate) fn chunk_data_dimensions(&self, chunk_index: u32) -> TiffResult<(u32, u32)> {
|
||||||
let dims = self.chunk_dimensions()?;
|
let dims = self.chunk_dimensions()?;
|
||||||
|
|
||||||
match self.chunk_type {
|
match self.chunk_type {
|
||||||
ChunkType::Strip => {
|
ChunkType::Strip => {
|
||||||
let strip_attrs = self.strip_decoder.as_ref().unwrap();
|
let rows_per_strip = dims.1;
|
||||||
let strips_per_band =
|
let strips_per_band = self.height.div_ceil(rows_per_strip);
|
||||||
self.height.saturating_sub(1) / strip_attrs.rows_per_strip + 1;
|
|
||||||
let strip_height_without_padding = (chunk_index % strips_per_band)
|
let strip_height_without_padding = (chunk_index % strips_per_band)
|
||||||
.checked_mul(dims.1)
|
.checked_mul(dims.1)
|
||||||
.and_then(|x| self.height.checked_sub(x))
|
.and_then(|x| self.height.checked_sub(x))
|
||||||
@@ -568,7 +807,7 @@ impl Image {
|
|||||||
&self,
|
&self,
|
||||||
reader: &mut ValueReader<impl Read>,
|
reader: &mut ValueReader<impl Read>,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
output_row_stride: usize,
|
layout: &ReadoutLayout,
|
||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let ValueReader {
|
let ValueReader {
|
||||||
@@ -578,10 +817,10 @@ impl Image {
|
|||||||
} = reader;
|
} = reader;
|
||||||
|
|
||||||
let byte_order = reader.byte_order;
|
let byte_order = reader.byte_order;
|
||||||
let reader = reader.inner();
|
|
||||||
|
|
||||||
// Validate that the color type is supported.
|
// Validate that the color type is supported.
|
||||||
let color_type = self.colortype()?;
|
let color_type = layout.color;
|
||||||
|
|
||||||
match color_type {
|
match color_type {
|
||||||
ColorType::RGB(n)
|
ColorType::RGB(n)
|
||||||
| ColorType::RGBA(n)
|
| ColorType::RGBA(n)
|
||||||
@@ -643,6 +882,7 @@ impl Image {
|
|||||||
.ok_or(TiffError::FormatError(
|
.ok_or(TiffError::FormatError(
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
if *compressed_bytes > limits.intermediate_buffer_size as u64 {
|
if *compressed_bytes > limits.intermediate_buffer_size as u64 {
|
||||||
return Err(TiffError::LimitsExceeded);
|
return Err(TiffError::LimitsExceeded);
|
||||||
}
|
}
|
||||||
@@ -650,34 +890,39 @@ impl Image {
|
|||||||
let compression_method = self.compression_method;
|
let compression_method = self.compression_method;
|
||||||
let photometric_interpretation = self.photometric_interpretation;
|
let photometric_interpretation = self.photometric_interpretation;
|
||||||
let predictor = self.predictor;
|
let predictor = self.predictor;
|
||||||
let samples = self.samples_per_pixel();
|
|
||||||
|
|
||||||
|
let samples = layout.tiff_samples;
|
||||||
|
let data_samples = layout.samples_per_out_texel();
|
||||||
|
|
||||||
|
// We have two dimensions: the 2d rectangle of encoded data and the 2d rectangle this
|
||||||
|
// takes up in the output. Each has an associated count of bits per pixel. The first
|
||||||
|
// dimension, i.e. a ''row'', is the number of pixels that are encoded with bit packing
|
||||||
|
// while the second is the byte-padded array of each so encoded slices.
|
||||||
|
//
|
||||||
|
// During decoding we map the relevant bits from one to the other.
|
||||||
let chunk_dims = self.chunk_dimensions()?;
|
let chunk_dims = self.chunk_dimensions()?;
|
||||||
let data_dims = self.chunk_data_dimensions(chunk_index)?;
|
let data_dims = self.chunk_data_dimensions(chunk_index)?;
|
||||||
|
|
||||||
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
|
let chunk_row_bytes: usize = layout.tiff_row_bytes;
|
||||||
.checked_mul(samples as u64)
|
let data_row_bytes: usize = layout.chunk_row_bytes(data_dims.0)?;
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
|
||||||
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
|
|
||||||
|
|
||||||
let data_row_bits = (u64::from(data_dims.0) * u64::from(self.bits_per_sample))
|
|
||||||
.checked_mul(samples as u64)
|
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
|
||||||
let data_row_bytes: usize = data_row_bits.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!(layout.minimum_row_stride >= data_row_bytes);
|
||||||
assert!(buf.len() >= output_row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
|
assert!(buf.len() >= layout.row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
|
||||||
|
|
||||||
|
let is_all_bits = samples == data_samples;
|
||||||
|
let is_output_chunk_rows = layout.row_stride == chunk_row_bytes;
|
||||||
|
|
||||||
let mut reader = Self::create_reader(
|
let mut reader = Self::create_reader(
|
||||||
reader,
|
reader.inner(),
|
||||||
compression_method,
|
compression_method,
|
||||||
*compressed_bytes,
|
*compressed_bytes,
|
||||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||||
chunk_dims,
|
chunk_dims,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if output_row_stride == chunk_row_bytes {
|
if is_output_chunk_rows && is_all_bits {
|
||||||
|
// Here we can read directly into the output buffer itself.
|
||||||
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)?;
|
||||||
|
|
||||||
@@ -690,6 +935,7 @@ impl Image {
|
|||||||
predictor,
|
predictor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
super::invert_colors(tile, color_type, self.sample_format)?;
|
super::invert_colors(tile, color_type, self.sample_format)?;
|
||||||
}
|
}
|
||||||
@@ -697,7 +943,7 @@ impl Image {
|
|||||||
// The floating point predictor shuffles the padding bytes into the encoded output, so
|
// The floating point predictor shuffles the padding bytes into the encoded output, so
|
||||||
// this case is handled specially when needed.
|
// this case is handled specially when needed.
|
||||||
let mut encoded = vec![0u8; chunk_row_bytes];
|
let mut encoded = vec![0u8; chunk_row_bytes];
|
||||||
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
|
for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) {
|
||||||
reader.read_exact(&mut encoded)?;
|
reader.read_exact(&mut encoded)?;
|
||||||
|
|
||||||
let row = &mut row[..data_row_bytes];
|
let row = &mut row[..data_row_bytes];
|
||||||
@@ -711,11 +957,16 @@ impl Image {
|
|||||||
super::invert_colors(row, color_type, self.sample_format)?;
|
super::invert_colors(row, color_type, self.sample_format)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if is_all_bits {
|
||||||
for row in buf.chunks_mut(output_row_stride).take(data_dims.1 as usize) {
|
// We read row-by-row but each row fits in its output buffer.
|
||||||
|
for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) {
|
||||||
let row = &mut row[..data_row_bytes];
|
let row = &mut row[..data_row_bytes];
|
||||||
reader.read_exact(row)?;
|
let used = data_row_bytes.min(chunk_row_bytes);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// row stride than the chunk contains data, then we need to fill only the front.
|
||||||
|
reader.read_exact(&mut row[..used])?;
|
||||||
// Skip horizontal padding
|
// Skip horizontal padding
|
||||||
if chunk_row_bytes > data_row_bytes {
|
if chunk_row_bytes > data_row_bytes {
|
||||||
let len = u64::try_from(chunk_row_bytes - data_row_bytes)?;
|
let len = u64::try_from(chunk_row_bytes - data_row_bytes)?;
|
||||||
@@ -729,6 +980,48 @@ impl Image {
|
|||||||
byte_order,
|
byte_order,
|
||||||
predictor,
|
predictor,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
|
super::invert_colors(row, color_type, self.sample_format)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The encoded data potentially takes up more space than the output data so we must be
|
||||||
|
// prepared to discard some of it. That decision is bit-by-bit.
|
||||||
|
let bits_per_pixel = u32::from(self.bits_per_sample) * u32::from(self.samples);
|
||||||
|
// Assumes the photometric samples are always the start.. This is slightly problematic.
|
||||||
|
// To expand spport we should instead have different methods of transforming the read
|
||||||
|
// buffer data, not only the `compact_photometric_bytes` method below and then choose
|
||||||
|
// from the right one with supplied parameters. Then we can also bit-for-bit copy with
|
||||||
|
// a selection for better performance.
|
||||||
|
let photometric_bit_end = u32::from(self.bits_per_sample) * data_samples as u32;
|
||||||
|
|
||||||
|
debug_assert!(bits_per_pixel >= photometric_bit_end);
|
||||||
|
|
||||||
|
if bits_per_pixel % 8 != 0 || photometric_bit_end % 8 != 0 {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::InterpretationWithBits(
|
||||||
|
self.photometric_interpretation,
|
||||||
|
vec![self.bits_per_sample; self.samples as usize],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let photo_range = photometric_bit_end / 8..bits_per_pixel / 8;
|
||||||
|
let mut encoded = vec![0u8; chunk_row_bytes];
|
||||||
|
for row in buf.chunks_mut(layout.row_stride).take(data_dims.1 as usize) {
|
||||||
|
reader.read_exact(&mut encoded)?;
|
||||||
|
|
||||||
|
Self::compact_photometric_bytes(&mut encoded, row, &photo_range);
|
||||||
|
|
||||||
|
super::fix_endianness_and_predict(
|
||||||
|
row,
|
||||||
|
color_type.bit_depth(),
|
||||||
|
samples,
|
||||||
|
byte_order,
|
||||||
|
predictor,
|
||||||
|
);
|
||||||
|
|
||||||
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
if photometric_interpretation == PhotometricInterpretation::WhiteIsZero {
|
||||||
super::invert_colors(row, color_type, self.sample_format)?;
|
super::invert_colors(row, color_type, self.sample_format)?;
|
||||||
}
|
}
|
||||||
@@ -737,4 +1030,133 @@ impl Image {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turn a contiguous buffer of a whole number of raw sample arrays into a whole number of
|
||||||
|
/// photometric sample arrays by removing the extra samples in-between.
|
||||||
|
fn compact_photometric_bytes(
|
||||||
|
raw: &mut [u8],
|
||||||
|
row: &mut [u8],
|
||||||
|
photo_range: &std::ops::Range<u32>,
|
||||||
|
) {
|
||||||
|
raw.chunks_exact_mut(photo_range.end as usize)
|
||||||
|
.zip(row.chunks_exact_mut(photo_range.start as usize))
|
||||||
|
.for_each(|(src, dst)| {
|
||||||
|
dst.copy_from_slice(&src[..photo_range.start as usize]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadoutLayout {
|
||||||
|
pub(crate) fn samples_per_out_texel(&self) -> u16 {
|
||||||
|
match self.planar_config {
|
||||||
|
PlanarConfiguration::Chunky => self.color.num_samples(),
|
||||||
|
PlanarConfiguration::Planar => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For a concrete chunk, which may be a partial border chunk, the byte length of one row of its
|
||||||
|
// pixel data.
|
||||||
|
pub(crate) fn chunk_row_bytes(&self, width: u32) -> TiffResult<usize> {
|
||||||
|
let data_samples = self.samples_per_out_texel();
|
||||||
|
let data_row_bits = (u64::from(width) * u64::from(self.tiff_bits_per_sample))
|
||||||
|
.checked_mul(u64::from(data_samples))
|
||||||
|
.ok_or(TiffError::LimitsExceeded)?;
|
||||||
|
Ok(data_row_bits.div_ceil(8).try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_row_stride(&mut self, row_stride: usize) -> Result<(), TiffError> {
|
||||||
|
if row_stride < self.minimum_row_stride {
|
||||||
|
return Err(TiffError::UsageError(
|
||||||
|
UsageError::InsufficientOutputRowStride {
|
||||||
|
needed: self.minimum_row_stride,
|
||||||
|
requested: row_stride,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data_row_bytes = u64::try_from(row_stride)?;
|
||||||
|
|
||||||
|
let chunk_col_stride = data_row_bytes
|
||||||
|
.checked_mul(u64::from(self.tiff_chunk_dimensions.1))
|
||||||
|
.ok_or(TiffError::LimitsExceeded)?
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
|
let height = self.plane_stride.checked_div(self.row_stride);
|
||||||
|
|
||||||
|
let plane_stride = height
|
||||||
|
.and_then(|h| data_row_bytes.checked_mul(h as u64))
|
||||||
|
// If height was zero, or the previous stride was zero, there are no bytes in a plane
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
|
self.row_stride = row_stride;
|
||||||
|
self.chunk_col_stride = chunk_col_stride;
|
||||||
|
self.plane_stride = plane_stride;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reduce this down to the layout of the output planes.
|
||||||
|
pub(crate) fn to_plane_layout(&self) -> Result<PlaneLayout, TiffError> {
|
||||||
|
let num_planes = self.color.num_samples() / self.samples_per_out_texel();
|
||||||
|
|
||||||
|
// Using the standard range iterator as checked_add on steroids.
|
||||||
|
//
|
||||||
|
// Note: for supporting subsampling, adjust as required.
|
||||||
|
let mut offset = (0..=usize::MAX).step_by(self.plane_stride);
|
||||||
|
|
||||||
|
let plane_offsets = offset.by_ref().take(usize::from(num_planes)).collect();
|
||||||
|
|
||||||
|
// Get the past-the-end of the last plane.
|
||||||
|
//
|
||||||
|
// This also verifies the `take` above was not short.
|
||||||
|
let Some(total_bytes) = offset.next() else {
|
||||||
|
return Err(TiffError::LimitsExceeded);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(PlaneLayout {
|
||||||
|
plane_offsets,
|
||||||
|
total_bytes,
|
||||||
|
readout: self.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `ReadoutLayout` with pre-calculated plane information.
|
||||||
|
pub(crate) struct PlaneLayout {
|
||||||
|
/// The underlying readout layout.
|
||||||
|
pub readout: ReadoutLayout,
|
||||||
|
/// Buffer offset from one plane of output to the next.
|
||||||
|
pub plane_offsets: Vec<usize>,
|
||||||
|
/// Total number of bytes for all planes in given order.
|
||||||
|
pub total_bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlaneLayout {
|
||||||
|
/// Return the number of planes to extract into the provided buffer.
|
||||||
|
pub(crate) fn used_planes(&self, buffer: &[impl Sized]) -> TiffResult<u16> {
|
||||||
|
self.readout.assert_min_layout(buffer)?;
|
||||||
|
let buffer_len = core::mem::size_of_val(buffer);
|
||||||
|
|
||||||
|
// Note: with differently sized planes this is dependent on the plane.
|
||||||
|
let last_plane_start = buffer_len.checked_sub(self.readout.plane_stride);
|
||||||
|
|
||||||
|
// Find how many planes fit into the output buffer.
|
||||||
|
let used_plane_offsets = self
|
||||||
|
.plane_offsets
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
// Find the first plane that would not fit completely at its offset.
|
||||||
|
.skip_while(|(_, &offset)| last_plane_start >= Some(offset))
|
||||||
|
.nth(0)
|
||||||
|
// If all planes fit, use all of them.
|
||||||
|
.map_or(self.plane_offsets.len(), |(idx, _)| idx);
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
used_plane_offsets <= usize::from(u16::MAX),
|
||||||
|
"Planes limited by number of samples, which is encoded as u16"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(used_plane_offsets as u16)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+479
-262
@@ -22,6 +22,10 @@ mod image;
|
|||||||
mod stream;
|
mod stream;
|
||||||
mod tag_reader;
|
mod tag_reader;
|
||||||
|
|
||||||
|
/// An index referring to a (rectangular) region of an image.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TiffCodingUnit(pub u32);
|
||||||
|
|
||||||
/// Result of a decoding process
|
/// Result of a decoding process
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DecodingResult {
|
pub enum DecodingResult {
|
||||||
@@ -50,94 +54,87 @@ pub enum DecodingResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DecodingResult {
|
impl DecodingResult {
|
||||||
fn new_u8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
/// Reallocate the buffer to decode all planes of the indicated layout.
|
||||||
if size > limits.decoding_buffer_size {
|
pub fn resize_to(
|
||||||
|
&mut self,
|
||||||
|
buffer: &BufferLayoutPreference,
|
||||||
|
limits: &Limits,
|
||||||
|
) -> Result<(), TiffError> {
|
||||||
|
let sample_type = buffer.sample_type.ok_or(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnknownInterpretation,
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let extent = sample_type.extent_for_bytes(buffer.complete_len);
|
||||||
|
self.resize_to_extent(extent, limits)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize_to_extent(
|
||||||
|
&mut self,
|
||||||
|
extent: DecodingExtent,
|
||||||
|
limits: &Limits,
|
||||||
|
) -> Result<(), TiffError> {
|
||||||
|
// FIXME: we *can* reuse the allocation sometimes.
|
||||||
|
*self = extent.to_result_buffer(limits)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new<T: Default + Copy>(
|
||||||
|
size: usize,
|
||||||
|
limits: &Limits,
|
||||||
|
from_fn: fn(Vec<T>) -> Self,
|
||||||
|
) -> TiffResult<DecodingResult> {
|
||||||
|
if size > limits.decoding_buffer_size / core::mem::size_of::<T>() {
|
||||||
Err(TiffError::LimitsExceeded)
|
Err(TiffError::LimitsExceeded)
|
||||||
} else {
|
} else {
|
||||||
Ok(DecodingResult::U8(vec![0; size]))
|
Ok(from_fn(vec![T::default(); size]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_u8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
|
Self::new(size, limits, DecodingResult::U8)
|
||||||
|
}
|
||||||
|
|
||||||
fn new_u16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
fn new_u16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
if size > limits.decoding_buffer_size / 2 {
|
Self::new(size, limits, DecodingResult::U16)
|
||||||
Err(TiffError::LimitsExceeded)
|
|
||||||
} else {
|
|
||||||
Ok(DecodingResult::U16(vec![0; size]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_u32(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
fn new_u32(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
if size > limits.decoding_buffer_size / 4 {
|
Self::new(size, limits, DecodingResult::U32)
|
||||||
Err(TiffError::LimitsExceeded)
|
|
||||||
} else {
|
|
||||||
Ok(DecodingResult::U32(vec![0; size]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_u64(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
fn new_u64(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
if size > limits.decoding_buffer_size / 8 {
|
Self::new(size, limits, DecodingResult::U64)
|
||||||
Err(TiffError::LimitsExceeded)
|
|
||||||
} else {
|
|
||||||
Ok(DecodingResult::U64(vec![0; size]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_f32(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
fn new_f32(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
if size > limits.decoding_buffer_size / std::mem::size_of::<f32>() {
|
Self::new(size, limits, DecodingResult::F32)
|
||||||
Err(TiffError::LimitsExceeded)
|
|
||||||
} else {
|
|
||||||
Ok(DecodingResult::F32(vec![0.0; size]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_f64(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
fn new_f64(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
if size > limits.decoding_buffer_size / std::mem::size_of::<f64>() {
|
Self::new(size, limits, DecodingResult::F64)
|
||||||
Err(TiffError::LimitsExceeded)
|
|
||||||
} else {
|
|
||||||
Ok(DecodingResult::F64(vec![0.0; size]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_f16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
fn new_f16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
if size > limits.decoding_buffer_size / std::mem::size_of::<u16>() {
|
Self::new(size, limits, DecodingResult::F16)
|
||||||
Err(TiffError::LimitsExceeded)
|
|
||||||
} else {
|
|
||||||
Ok(DecodingResult::F16(vec![f16::ZERO; size]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_i8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
fn new_i8(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
if size > limits.decoding_buffer_size / std::mem::size_of::<i8>() {
|
Self::new(size, limits, DecodingResult::I8)
|
||||||
Err(TiffError::LimitsExceeded)
|
|
||||||
} else {
|
|
||||||
Ok(DecodingResult::I8(vec![0; size]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_i16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
fn new_i16(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
if size > limits.decoding_buffer_size / 2 {
|
Self::new(size, limits, DecodingResult::I16)
|
||||||
Err(TiffError::LimitsExceeded)
|
|
||||||
} else {
|
|
||||||
Ok(DecodingResult::I16(vec![0; size]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_i32(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
fn new_i32(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
if size > limits.decoding_buffer_size / 4 {
|
Self::new(size, limits, DecodingResult::I32)
|
||||||
Err(TiffError::LimitsExceeded)
|
|
||||||
} else {
|
|
||||||
Ok(DecodingResult::I32(vec![0; size]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_i64(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
fn new_i64(size: usize, limits: &Limits) -> TiffResult<DecodingResult> {
|
||||||
if size > limits.decoding_buffer_size / 8 {
|
Self::new(size, limits, DecodingResult::I64)
|
||||||
Err(TiffError::LimitsExceeded)
|
|
||||||
} else {
|
|
||||||
Ok(DecodingResult::I64(vec![0; size]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a view of this buffer starting from the nth _sample_ of the current type.
|
||||||
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..]),
|
||||||
@@ -182,9 +179,25 @@ pub enum DecodingBuffer<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DecodingBuffer<'a> {
|
impl<'a> DecodingBuffer<'a> {
|
||||||
fn as_bytes_mut(&mut self) -> &mut [u8] {
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
match self {
|
match self {
|
||||||
DecodingBuffer::U8(ref mut buf) => buf,
|
DecodingBuffer::U8(buf) => buf,
|
||||||
|
DecodingBuffer::I8(buf) => bytecast::i8_as_ne_bytes(buf),
|
||||||
|
DecodingBuffer::U16(buf) => bytecast::u16_as_ne_bytes(buf),
|
||||||
|
DecodingBuffer::I16(buf) => bytecast::i16_as_ne_bytes(buf),
|
||||||
|
DecodingBuffer::U32(buf) => bytecast::u32_as_ne_bytes(buf),
|
||||||
|
DecodingBuffer::I32(buf) => bytecast::i32_as_ne_bytes(buf),
|
||||||
|
DecodingBuffer::U64(buf) => bytecast::u64_as_ne_bytes(buf),
|
||||||
|
DecodingBuffer::I64(buf) => bytecast::i64_as_ne_bytes(buf),
|
||||||
|
DecodingBuffer::F16(buf) => bytecast::f16_as_ne_bytes(buf),
|
||||||
|
DecodingBuffer::F32(buf) => bytecast::f32_as_ne_bytes(buf),
|
||||||
|
DecodingBuffer::F64(buf) => bytecast::f64_as_ne_bytes(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
|
||||||
|
match self {
|
||||||
|
DecodingBuffer::U8(buf) => buf,
|
||||||
DecodingBuffer::I8(buf) => bytecast::i8_as_ne_mut_bytes(buf),
|
DecodingBuffer::I8(buf) => bytecast::i8_as_ne_mut_bytes(buf),
|
||||||
DecodingBuffer::U16(buf) => bytecast::u16_as_ne_mut_bytes(buf),
|
DecodingBuffer::U16(buf) => bytecast::u16_as_ne_mut_bytes(buf),
|
||||||
DecodingBuffer::I16(buf) => bytecast::i16_as_ne_mut_bytes(buf),
|
DecodingBuffer::I16(buf) => bytecast::i16_as_ne_mut_bytes(buf),
|
||||||
@@ -197,6 +210,43 @@ impl<'a> DecodingBuffer<'a> {
|
|||||||
DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf),
|
DecodingBuffer::F64(buf) => bytecast::f64_as_ne_mut_bytes(buf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn byte_len(&self) -> usize {
|
||||||
|
self.as_bytes().len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum DecodingSampleType {
|
||||||
|
U8,
|
||||||
|
U16,
|
||||||
|
U32,
|
||||||
|
U64,
|
||||||
|
F16,
|
||||||
|
F32,
|
||||||
|
F64,
|
||||||
|
I8,
|
||||||
|
I16,
|
||||||
|
I32,
|
||||||
|
I64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DecodingSampleType {
|
||||||
|
fn extent_for_bytes(self, bytes: usize) -> DecodingExtent {
|
||||||
|
match self {
|
||||||
|
DecodingSampleType::U8 => DecodingExtent::U8(bytes),
|
||||||
|
DecodingSampleType::U16 => DecodingExtent::U16(bytes.div_ceil(2)),
|
||||||
|
DecodingSampleType::U32 => DecodingExtent::U32(bytes.div_ceil(4)),
|
||||||
|
DecodingSampleType::U64 => DecodingExtent::U64(bytes.div_ceil(8)),
|
||||||
|
DecodingSampleType::I8 => DecodingExtent::I8(bytes),
|
||||||
|
DecodingSampleType::I16 => DecodingExtent::I16(bytes.div_ceil(2)),
|
||||||
|
DecodingSampleType::I32 => DecodingExtent::I32(bytes.div_ceil(4)),
|
||||||
|
DecodingSampleType::I64 => DecodingExtent::I64(bytes.div_ceil(8)),
|
||||||
|
DecodingSampleType::F16 => DecodingExtent::F16(bytes.div_ceil(2)),
|
||||||
|
DecodingSampleType::F32 => DecodingExtent::F32(bytes.div_ceil(4)),
|
||||||
|
DecodingSampleType::F64 => DecodingExtent::F64(bytes.div_ceil(8)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information on the byte buffer that should be supplied to the decoder.
|
/// Information on the byte buffer that should be supplied to the decoder.
|
||||||
@@ -205,11 +255,131 @@ impl<'a> DecodingBuffer<'a> {
|
|||||||
/// caller provided buffer must fit the expectations of the decoder to be filled with data from the
|
/// caller provided buffer must fit the expectations of the decoder to be filled with data from the
|
||||||
/// current image.
|
/// current image.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct BufferLayoutPreference {
|
pub struct BufferLayoutPreference {
|
||||||
/// Minimum byte size of the buffer to read image data.
|
/// Minimum byte size of the buffer to read image data.
|
||||||
pub len: usize,
|
pub len: usize,
|
||||||
|
/// The interpretation of each sample in the image.
|
||||||
|
///
|
||||||
|
/// We only support a uniform sample layout. Detailed information for mixed colors may be added
|
||||||
|
/// in the future and will become available by explicit query. The same goes for the bit-depth
|
||||||
|
/// of samples that must also be uniform.
|
||||||
|
pub sample_format: SampleFormat,
|
||||||
|
/// The type representation for each sample. Only available for depths and formats which the
|
||||||
|
/// library can describe.
|
||||||
|
pub sample_type: Option<DecodingSampleType>,
|
||||||
/// Minimum number of bytes to represent a row of image data of the requested content.
|
/// Minimum number of bytes to represent a row of image data of the requested content.
|
||||||
pub row_stride: Option<NonZeroUsize>,
|
pub row_stride: Option<NonZeroUsize>,
|
||||||
|
/// Number of planes in the image.
|
||||||
|
pub planes: usize,
|
||||||
|
/// Number of bytes used to represent one plane.
|
||||||
|
pub plane_stride: Option<NonZeroUsize>,
|
||||||
|
/// Number of bytes of data when reading all planes.
|
||||||
|
pub complete_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferLayoutPreference {
|
||||||
|
fn from_planes(layout: &image::PlaneLayout) -> Self {
|
||||||
|
BufferLayoutPreference {
|
||||||
|
len: layout.readout.plane_stride,
|
||||||
|
row_stride: core::num::NonZeroUsize::new(layout.readout.row_stride),
|
||||||
|
planes: layout.plane_offsets.len(),
|
||||||
|
plane_stride: core::num::NonZeroUsize::new(layout.readout.plane_stride),
|
||||||
|
complete_len: layout.total_bytes,
|
||||||
|
sample_format: layout.readout.sample_format,
|
||||||
|
sample_type: Self::sample_type(layout.readout.sample_format, layout.readout.color),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_type(sample_format: SampleFormat, color: ColorType) -> Option<DecodingSampleType> {
|
||||||
|
Some(match sample_format {
|
||||||
|
SampleFormat::Uint => match color.bit_depth() {
|
||||||
|
n if n <= 8 => DecodingSampleType::U8,
|
||||||
|
n if n <= 16 => DecodingSampleType::U16,
|
||||||
|
n if n <= 32 => DecodingSampleType::U32,
|
||||||
|
n if n <= 64 => DecodingSampleType::U64,
|
||||||
|
_ => return None,
|
||||||
|
},
|
||||||
|
SampleFormat::IEEEFP => match color.bit_depth() {
|
||||||
|
16 => DecodingSampleType::F16,
|
||||||
|
32 => DecodingSampleType::F32,
|
||||||
|
64 => DecodingSampleType::F64,
|
||||||
|
_ => return None,
|
||||||
|
},
|
||||||
|
SampleFormat::Int => match color.bit_depth() {
|
||||||
|
n if n <= 8 => DecodingSampleType::I8,
|
||||||
|
n if n <= 16 => DecodingSampleType::I16,
|
||||||
|
n if n <= 32 => DecodingSampleType::I32,
|
||||||
|
n if n <= 64 => DecodingSampleType::I64,
|
||||||
|
_ => return None,
|
||||||
|
},
|
||||||
|
_other => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl image::ReadoutLayout {
|
||||||
|
// FIXME: when planes are not homogenous (i.e. subsampled or differing depths) then the
|
||||||
|
// `readout_for_size` or `to_plane_layout` needs a parameter to determine the planes being
|
||||||
|
// read instead of assuming a constant repeated size for them.
|
||||||
|
fn result_extent_for_planes(
|
||||||
|
self: &image::ReadoutLayout,
|
||||||
|
planes: core::ops::Range<u16>,
|
||||||
|
) -> TiffResult<DecodingExtent> {
|
||||||
|
let buffer = self.to_plane_layout()?;
|
||||||
|
|
||||||
|
// The layout is for all planes. So restrict ourselves to the planes that were requested.
|
||||||
|
let offset = match buffer.plane_offsets.get(usize::from(planes.start)) {
|
||||||
|
Some(n) => *n,
|
||||||
|
None => {
|
||||||
|
return Err(TiffError::UsageError(UsageError::InvalidPlaneIndex(
|
||||||
|
planes.start,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let end = match buffer.plane_offsets.get(usize::from(planes.end)) {
|
||||||
|
Some(n) => *n,
|
||||||
|
None => buffer.total_bytes,
|
||||||
|
};
|
||||||
|
|
||||||
|
let buffer_bytes = end - offset;
|
||||||
|
let bits_per_sample = self.color.bit_depth();
|
||||||
|
|
||||||
|
let Some(sample_type) = BufferLayoutPreference::sample_type(self.sample_format, self.color)
|
||||||
|
else {
|
||||||
|
if matches!(
|
||||||
|
self.sample_format,
|
||||||
|
SampleFormat::Uint | SampleFormat::Int | SampleFormat::IEEEFP
|
||||||
|
) {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedSampleDepth(bits_per_sample),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return Err(TiffError::UnsupportedError(
|
||||||
|
TiffUnsupportedError::UnsupportedSampleFormat(vec![self.sample_format]),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(sample_type.extent_for_bytes(buffer_bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn assert_min_layout<T>(&self, buffer: &[T]) -> TiffResult<()> {
|
||||||
|
if core::mem::size_of_val(buffer) < self.plane_stride {
|
||||||
|
Err(TiffError::UsageError(
|
||||||
|
UsageError::InsufficientOutputBufferSize {
|
||||||
|
needed: self.plane_stride,
|
||||||
|
provided: buffer.len(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The count and matching discriminant for a `DecodingBuffer`.
|
/// The count and matching discriminant for a `DecodingBuffer`.
|
||||||
@@ -266,17 +436,19 @@ impl DecodingExtent {
|
|||||||
.map_err(overflow)
|
.map_err(overflow)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_layout(self, buffer: usize) -> TiffResult<()> {
|
fn sample_type(&self) -> DecodingSampleType {
|
||||||
let needed_bytes = self.preferred_layout()?.size();
|
match *self {
|
||||||
if buffer < needed_bytes {
|
DecodingExtent::U8(_) => DecodingSampleType::U8,
|
||||||
Err(TiffError::UsageError(
|
DecodingExtent::U16(_) => DecodingSampleType::U16,
|
||||||
UsageError::InsufficientOutputBufferSize {
|
DecodingExtent::U32(_) => DecodingSampleType::U32,
|
||||||
needed: needed_bytes,
|
DecodingExtent::U64(_) => DecodingSampleType::U64,
|
||||||
provided: buffer,
|
DecodingExtent::F16(_) => DecodingSampleType::F16,
|
||||||
},
|
DecodingExtent::F32(_) => DecodingSampleType::F32,
|
||||||
))
|
DecodingExtent::F64(_) => DecodingSampleType::F64,
|
||||||
} else {
|
DecodingExtent::I8(_) => DecodingSampleType::I8,
|
||||||
Ok(())
|
DecodingExtent::I16(_) => DecodingSampleType::I16,
|
||||||
|
DecodingExtent::I32(_) => DecodingSampleType::I32,
|
||||||
|
DecodingExtent::I64(_) => DecodingSampleType::I64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,7 +539,7 @@ pub struct IfdDecoder<'lt> {
|
|||||||
inner: tag_reader::TagReader<'lt, dyn tag_reader::EntryDecoder + 'lt>,
|
inner: tag_reader::TagReader<'lt, dyn tag_reader::EntryDecoder + 'lt>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
|
fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: u16) {
|
||||||
fn one_byte_predict<const N: usize>(buf: &mut [u8]) {
|
fn one_byte_predict<const N: usize>(buf: &mut [u8]) {
|
||||||
for i in N..buf.len() {
|
for i in N..buf.len() {
|
||||||
buf[i] = buf[i].wrapping_add(buf[i - N]);
|
buf[i] = buf[i].wrapping_add(buf[i - N]);
|
||||||
@@ -390,6 +562,8 @@ fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let samples = usize::from(samples);
|
||||||
|
|
||||||
match (bit_depth, samples) {
|
match (bit_depth, samples) {
|
||||||
// Note we can't use `windows` or so due to the overlap between each iteration. We split
|
// Note we can't use `windows` or so due to the overlap between each iteration. We split
|
||||||
// the cases by the samples / lookback constant so that each is optimized individually.
|
// the cases by the samples / lookback constant so that each is optimized individually.
|
||||||
@@ -456,7 +630,9 @@ fn rev_hpredict_nsamp(buf: &mut [u8], bit_depth: u8, samples: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) {
|
fn predict_f32(input: &mut [u8], output: &mut [u8], samples: u16) {
|
||||||
|
let samples = usize::from(samples);
|
||||||
|
|
||||||
for i in samples..input.len() {
|
for i in samples..input.len() {
|
||||||
input[i] = input[i].wrapping_add(input[i - samples]);
|
input[i] = input[i].wrapping_add(input[i - samples]);
|
||||||
}
|
}
|
||||||
@@ -471,7 +647,9 @@ fn predict_f32(input: &mut [u8], output: &mut [u8], samples: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) {
|
fn predict_f16(input: &mut [u8], output: &mut [u8], samples: u16) {
|
||||||
|
let samples = usize::from(samples);
|
||||||
|
|
||||||
for i in samples..input.len() {
|
for i in samples..input.len() {
|
||||||
input[i] = input[i].wrapping_add(input[i - samples]);
|
input[i] = input[i].wrapping_add(input[i - samples]);
|
||||||
}
|
}
|
||||||
@@ -484,7 +662,9 @@ fn predict_f16(input: &mut [u8], output: &mut [u8], samples: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) {
|
fn predict_f64(input: &mut [u8], output: &mut [u8], samples: u16) {
|
||||||
|
let samples = usize::from(samples);
|
||||||
|
|
||||||
for i in samples..input.len() {
|
for i in samples..input.len() {
|
||||||
input[i] = input[i].wrapping_add(input[i - samples]);
|
input[i] = input[i].wrapping_add(input[i - samples]);
|
||||||
}
|
}
|
||||||
@@ -506,7 +686,7 @@ fn predict_f64(input: &mut [u8], output: &mut [u8], samples: usize) {
|
|||||||
fn fix_endianness_and_predict(
|
fn fix_endianness_and_predict(
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
bit_depth: u8,
|
bit_depth: u8,
|
||||||
samples: usize,
|
samples: u16,
|
||||||
byte_order: ByteOrder,
|
byte_order: ByteOrder,
|
||||||
predictor: Predictor,
|
predictor: Predictor,
|
||||||
) {
|
) {
|
||||||
@@ -667,6 +847,8 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
height: 0,
|
height: 0,
|
||||||
bits_per_sample: 1,
|
bits_per_sample: 1,
|
||||||
samples: 1,
|
samples: 1,
|
||||||
|
extra_samples: vec![],
|
||||||
|
photometric_samples: 1,
|
||||||
sample_format: SampleFormat::Uint,
|
sample_format: SampleFormat::Uint,
|
||||||
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
|
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
|
||||||
compression_method: CompressionMethod::None,
|
compression_method: CompressionMethod::None,
|
||||||
@@ -678,6 +860,7 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
tile_attributes: None,
|
tile_attributes: None,
|
||||||
chunk_offsets: Vec::new(),
|
chunk_offsets: Vec::new(),
|
||||||
chunk_bytes: Vec::new(),
|
chunk_bytes: Vec::new(),
|
||||||
|
chroma_subsampling: (2, 2),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
decoder.next_image()?;
|
decoder.next_image()?;
|
||||||
@@ -975,107 +1158,21 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
Ok(u32::try_from(self.image().chunk_offsets.len())?)
|
Ok(u32::try_from(self.image().chunk_offsets.len())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_chunk_to_buffer(
|
|
||||||
&mut self,
|
|
||||||
mut buffer: DecodingBuffer,
|
|
||||||
chunk_index: u32,
|
|
||||||
output_width: usize,
|
|
||||||
) -> TiffResult<()> {
|
|
||||||
let output_row_stride = (output_width as u64)
|
|
||||||
.saturating_mul(self.image.samples_per_pixel() as u64)
|
|
||||||
.saturating_mul(self.image.bits_per_sample as u64)
|
|
||||||
.div_ceil(8);
|
|
||||||
|
|
||||||
self.read_chunk_to_bytes(buffer.as_bytes_mut(), chunk_index, output_row_stride)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_chunk_to_bytes(
|
fn read_chunk_to_bytes(
|
||||||
&mut self,
|
&mut self,
|
||||||
buffer: &mut [u8],
|
buffer: &mut [u8],
|
||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
output_row_stride: u64,
|
layout: &image::ReadoutLayout,
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let offset = self.image.chunk_file_range(chunk_index)?.0;
|
let offset = self.image.chunk_file_range(chunk_index)?.0;
|
||||||
self.goto_offset_u64(offset)?;
|
self.goto_offset_u64(offset)?;
|
||||||
|
|
||||||
self.image.expand_chunk(
|
self.image
|
||||||
&mut self.value_reader,
|
.expand_chunk(&mut self.value_reader, buffer, layout, chunk_index)?;
|
||||||
buffer,
|
|
||||||
output_row_stride.try_into()?,
|
|
||||||
chunk_index,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn result_buffer(&self, width: usize, height: usize) -> TiffResult<DecodingResult> {
|
|
||||||
self.result_extent(width, height)?
|
|
||||||
.to_result_buffer(&self.value_reader.limits)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: it's unusual for this method to take a `usize` dimension parameter since those would
|
|
||||||
// typically come from the image data. The preferred consistent representation should be `u32`
|
|
||||||
// and that would avoid some unusual casts `usize -> u64` with a `u64::from` instead.
|
|
||||||
fn result_extent(&self, width: usize, height: usize) -> TiffResult<DecodingExtent> {
|
|
||||||
let bits_per_sample = self.image.bits_per_sample;
|
|
||||||
|
|
||||||
// If samples take up more than one byte, we expand it to a full number (integer or float)
|
|
||||||
// and the number of samples in that integer type just counts the number in the underlying
|
|
||||||
// image itself. Otherwise, a row is represented as the byte slice of bitfields without
|
|
||||||
// expansion.
|
|
||||||
let row_samples = if bits_per_sample >= 8 {
|
|
||||||
width
|
|
||||||
} else {
|
|
||||||
((width as u64) * bits_per_sample as u64)
|
|
||||||
.div_ceil(8)
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| TiffError::LimitsExceeded)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let buffer_size = row_samples
|
|
||||||
.checked_mul(height)
|
|
||||||
.and_then(|x| x.checked_mul(self.image.samples_per_pixel()))
|
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
|
||||||
|
|
||||||
Ok(match self.image().sample_format {
|
|
||||||
SampleFormat::Uint => match bits_per_sample {
|
|
||||||
n if n <= 8 => DecodingExtent::U8(buffer_size),
|
|
||||||
n if n <= 16 => DecodingExtent::U16(buffer_size),
|
|
||||||
n if n <= 32 => DecodingExtent::U32(buffer_size),
|
|
||||||
n if n <= 64 => DecodingExtent::U64(buffer_size),
|
|
||||||
n => {
|
|
||||||
return Err(TiffError::UnsupportedError(
|
|
||||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SampleFormat::IEEEFP => match bits_per_sample {
|
|
||||||
16 => DecodingExtent::F16(buffer_size),
|
|
||||||
32 => DecodingExtent::F32(buffer_size),
|
|
||||||
64 => DecodingExtent::F64(buffer_size),
|
|
||||||
n => {
|
|
||||||
return Err(TiffError::UnsupportedError(
|
|
||||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SampleFormat::Int => match bits_per_sample {
|
|
||||||
n if n <= 8 => DecodingExtent::I8(buffer_size),
|
|
||||||
n if n <= 16 => DecodingExtent::I16(buffer_size),
|
|
||||||
n if n <= 32 => DecodingExtent::I32(buffer_size),
|
|
||||||
n if n <= 64 => DecodingExtent::I64(buffer_size),
|
|
||||||
n => {
|
|
||||||
return Err(TiffError::UnsupportedError(
|
|
||||||
TiffUnsupportedError::UnsupportedBitsPerChannel(n),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
format => {
|
|
||||||
return Err(TiffUnsupportedError::UnsupportedSampleFormat(vec![format]).into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the layout preferred to read the specified chunk with [`Self::read_chunk_bytes`].
|
/// Returns the layout preferred to read the specified chunk with [`Self::read_chunk_bytes`].
|
||||||
///
|
///
|
||||||
/// Returns the layout without being specific as to the underlying type for forward
|
/// Returns the layout without being specific as to the underlying type for forward
|
||||||
@@ -1083,41 +1180,78 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// channels of individual *bit* length and format each.
|
/// channels of individual *bit* length and format each.
|
||||||
///
|
///
|
||||||
/// See [`Self::colortype`] to describe the sample types.
|
/// See [`Self::colortype`] to describe the sample types.
|
||||||
///
|
|
||||||
/// # Bugs
|
|
||||||
///
|
|
||||||
/// When the image is stored as a planar configuration, this method will currently only
|
|
||||||
/// indicate the layout needed to read the first data plane. This will be fixed in a future
|
|
||||||
/// major version of `tiff`.
|
|
||||||
pub fn image_chunk_buffer_layout(
|
pub fn image_chunk_buffer_layout(
|
||||||
&mut self,
|
&mut self,
|
||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
) -> TiffResult<BufferLayoutPreference> {
|
) -> TiffResult<BufferLayoutPreference> {
|
||||||
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
||||||
let layout = self
|
let readout = self.image().readout_for_size(data_dims.0, data_dims.1)?;
|
||||||
.result_extent(data_dims.0 as usize, data_dims.1 as usize)?
|
|
||||||
.preferred_layout()?;
|
|
||||||
|
|
||||||
let row_stride = self.image.minimum_row_stride(data_dims);
|
let extent = readout.result_extent_for_planes(0..1)?;
|
||||||
|
let sample_type = extent.sample_type();
|
||||||
|
let layout = extent.preferred_layout()?;
|
||||||
|
|
||||||
|
let row_stride = core::num::NonZeroUsize::new(readout.minimum_row_stride);
|
||||||
|
let plane_stride = core::num::NonZeroUsize::new(readout.plane_stride);
|
||||||
|
|
||||||
Ok(BufferLayoutPreference {
|
Ok(BufferLayoutPreference {
|
||||||
len: layout.size(),
|
len: layout.size(),
|
||||||
row_stride,
|
row_stride,
|
||||||
|
planes: 1,
|
||||||
|
plane_stride,
|
||||||
|
complete_len: layout.size(),
|
||||||
|
sample_format: self.image().sample_format,
|
||||||
|
sample_type: Some(sample_type),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the layout preferred to read several planes corresponding to the specified region.
|
||||||
|
///
|
||||||
|
/// This is similar to [`Self::image_chunk_buffer_layout`] but can read chunks from all planes
|
||||||
|
/// at the corresponding coordinates of the image.
|
||||||
|
///
|
||||||
|
/// # Bugs
|
||||||
|
///
|
||||||
|
/// Sub-sampled images are not yet supported properly.
|
||||||
|
pub fn image_coding_unit_layout(
|
||||||
|
&mut self,
|
||||||
|
code_unit: TiffCodingUnit,
|
||||||
|
) -> TiffResult<BufferLayoutPreference> {
|
||||||
|
match self.image().planar_config {
|
||||||
|
PlanarConfiguration::Chunky => return self.image_chunk_buffer_layout(code_unit.0),
|
||||||
|
PlanarConfiguration::Planar => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (width, height) = self.image().chunk_data_dimensions(code_unit.0)?;
|
||||||
|
|
||||||
|
let layout = self
|
||||||
|
.image()
|
||||||
|
.readout_for_size(width, height)?
|
||||||
|
.to_plane_layout()?;
|
||||||
|
|
||||||
|
if code_unit.0 >= layout.readout.chunks_per_plane {
|
||||||
|
return Err(TiffError::UsageError(UsageError::InvalidCodingUnit(
|
||||||
|
code_unit.0,
|
||||||
|
layout.readout.chunks_per_plane,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(BufferLayoutPreference::from_planes(&layout))
|
||||||
|
}
|
||||||
|
|
||||||
/// 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
|
/// Note that for planar images each chunk contains only one sample of the underlying data.
|
||||||
///
|
|
||||||
/// 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 (width, height) = self.image().chunk_data_dimensions(chunk_index)?;
|
||||||
|
|
||||||
let mut result = self.result_buffer(data_dims.0 as usize, data_dims.1 as usize)?;
|
let readout = self.image().readout_for_size(width, height)?;
|
||||||
|
|
||||||
self.read_chunk_to_buffer(result.as_buffer(0), chunk_index, data_dims.0 as usize)?;
|
let mut result = readout
|
||||||
|
.result_extent_for_planes(0..1)?
|
||||||
|
.to_result_buffer(&self.value_reader.limits)?;
|
||||||
|
|
||||||
|
self.read_chunk_to_bytes(result.as_buffer(0).as_bytes_mut(), chunk_index, &readout)?;
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
@@ -1128,22 +1262,95 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// call to [`Self::image_chunk_buffer_layout`]. Note that the alignment may be arbitrary, but
|
/// 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.
|
/// an alignment smaller than the preferred alignment may perform worse.
|
||||||
///
|
///
|
||||||
|
/// Note that for planar images each chunk contains only one sample of the underlying data.
|
||||||
|
pub fn read_chunk_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> {
|
||||||
|
let (width, height) = self.image().chunk_data_dimensions(chunk_index)?;
|
||||||
|
|
||||||
|
let layout = self.image().readout_for_size(width, height)?;
|
||||||
|
layout.assert_min_layout(buffer)?;
|
||||||
|
|
||||||
|
self.read_chunk_to_bytes(buffer, chunk_index, &layout)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the specified chunk (at index `chunk_index`) into a provide buffer.
|
||||||
|
///
|
||||||
|
/// It will re-allocate the buffer into the correct type and size, within the decoder's
|
||||||
|
/// configured limits, and then pass it to the underlying method. This is essentially a
|
||||||
|
/// type-safe wrapper around the raw [`Self::read_chunk_bytes`] method.
|
||||||
|
///
|
||||||
|
/// Note that for planar images each chunk contains only one sample of the underlying data.
|
||||||
|
pub fn read_chunk_to_buffer(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut DecodingResult,
|
||||||
|
chunk_index: u32,
|
||||||
|
output_width: usize,
|
||||||
|
) -> TiffResult<()> {
|
||||||
|
let (width, height) = self.image().chunk_data_dimensions(chunk_index)?;
|
||||||
|
|
||||||
|
let mut layout = self.image().readout_for_size(width, height)?;
|
||||||
|
layout.set_row_stride(output_width)?;
|
||||||
|
|
||||||
|
let extent = layout.result_extent_for_planes(0..1)?;
|
||||||
|
buffer.resize_to_extent(extent, &self.value_reader.limits)?;
|
||||||
|
|
||||||
|
self.read_chunk_to_bytes(buffer.as_buffer(0).as_bytes_mut(), chunk_index, &layout)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read chunks corresponding to several planes of a region of pixels.
|
||||||
|
///
|
||||||
|
/// For non planar images this is equivalent to [`Self::read_chunk_bytes`] as there is only one
|
||||||
|
/// plane in the image. For planar images the planes are stored consecutively into the output
|
||||||
|
/// buffer. Returns an error if not enough space for at least one plane is provided. Otherwise
|
||||||
|
/// reads all planes that can be stored completely in the provided output buffer.
|
||||||
|
///
|
||||||
|
/// A region is a rectangular assortment of pixels in the image, depending on the chunk type
|
||||||
|
/// either strips or tiles. Borrowing terminology from JPEG we call the collection of all
|
||||||
|
/// chunks from all planes that encode samples from the same region a "coding unit".
|
||||||
|
///
|
||||||
/// # Bugs
|
/// # Bugs
|
||||||
///
|
///
|
||||||
/// When the image is stored as a planar configuration, this method will currently only read
|
/// Sub-sampled images are not yet supported properly.
|
||||||
/// the first sample's plane. This will be fixed in a future major version of `tiff`.
|
pub fn read_coding_unit_bytes(
|
||||||
pub fn read_chunk_bytes(&mut self, chunk_index: u32, buffer: &mut [u8]) -> TiffResult<()> {
|
&mut self,
|
||||||
let data_dims = self.image().chunk_data_dimensions(chunk_index)?;
|
slice: TiffCodingUnit,
|
||||||
|
buffer: &mut [u8],
|
||||||
|
) -> TiffResult<()> {
|
||||||
|
let (width, height) = self.image().chunk_data_dimensions(slice.0)?;
|
||||||
|
let readout = self.image().readout_for_size(width, height)?;
|
||||||
|
|
||||||
let extent = self.result_extent(data_dims.0 as usize, data_dims.1 as usize)?;
|
let ref layout @ image::PlaneLayout {
|
||||||
extent.assert_layout(buffer.len())?;
|
ref plane_offsets,
|
||||||
|
// We assume that is correct, so really it can be ignored.
|
||||||
|
total_bytes: _,
|
||||||
|
ref readout,
|
||||||
|
} = readout.to_plane_layout()?;
|
||||||
|
|
||||||
let output_row_stride = u64::from(data_dims.0)
|
if slice.0 >= readout.chunks_per_plane {
|
||||||
.saturating_mul(self.image.samples_per_pixel() as u64)
|
return Err(TiffError::UsageError(UsageError::InvalidCodingUnit(
|
||||||
.saturating_mul(self.image.bits_per_sample as u64)
|
slice.0,
|
||||||
.div_ceil(8);
|
readout.chunks_per_plane,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
self.read_chunk_to_bytes(buffer, chunk_index, output_row_stride)?;
|
// No subsamples planes support, for now.
|
||||||
|
let used_plane_offsets = usize::from(layout.used_planes(buffer)?);
|
||||||
|
debug_assert!(used_plane_offsets >= 1, "Should have errored");
|
||||||
|
|
||||||
|
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
|
||||||
|
let chunk = slice.0 + idx as u32 * readout.chunks_per_plane;
|
||||||
|
self.goto_offset_u64(self.image().chunk_offsets[chunk as usize])?;
|
||||||
|
|
||||||
|
self.image.expand_chunk(
|
||||||
|
&mut self.value_reader,
|
||||||
|
&mut buffer[plane_offset..],
|
||||||
|
readout,
|
||||||
|
chunk,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1176,111 +1383,121 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
/// indicate the layout needed to read the first data plane. This will be fixed in a future
|
/// indicate the layout needed to read the first data plane. This will be fixed in a future
|
||||||
/// major version of `tiff`.
|
/// major version of `tiff`.
|
||||||
pub fn image_buffer_layout(&mut self) -> TiffResult<BufferLayoutPreference> {
|
pub fn image_buffer_layout(&mut self) -> TiffResult<BufferLayoutPreference> {
|
||||||
let data_dims @ (width, height) = (self.image.width, self.image.height);
|
let layout = self.image().readout_for_image()?.to_plane_layout()?;
|
||||||
|
Ok(BufferLayoutPreference::from_planes(&layout))
|
||||||
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
|
||||||
///
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// This method is deprecated. For replacement usage see `examples/decode.rs`.
|
||||||
|
///
|
||||||
/// # Bugs
|
/// # Bugs
|
||||||
///
|
///
|
||||||
/// When the image is stored as a planar configuration, this method will currently only read
|
/// 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`.
|
/// the first sample's plane. This will be fixed in a future major version of `tiff`.
|
||||||
|
///
|
||||||
|
/// # Intent to deprecate
|
||||||
|
///
|
||||||
|
/// Use `DecodingResult::resize_to` and `read_image_bytes` instead where possible, preserving
|
||||||
|
/// the buffer across multiple calls. This old method will likely keep its bugged planar
|
||||||
|
/// behavior until it is fully replaced, to ensure that existing code will not run into
|
||||||
|
/// unexpectedly large allocations that will error on limits instead.
|
||||||
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
pub fn read_image(&mut self) -> TiffResult<DecodingResult> {
|
||||||
let width = self.image().width;
|
let readout = self.image().readout_for_image()?;
|
||||||
let height = self.image().height;
|
|
||||||
|
let mut result = readout
|
||||||
|
.result_extent_for_planes(0..1)?
|
||||||
|
.to_result_buffer(&self.value_reader.limits)?;
|
||||||
|
|
||||||
let mut result = self.result_buffer(width as usize, height as usize)?;
|
|
||||||
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
|
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decodes the entire image into a provided buffer.
|
||||||
|
///
|
||||||
|
/// It will re-allocate the buffer into the correct type and size, within the decoder's
|
||||||
|
/// configured limits, and then pass it to the underlying method. This is essentially a
|
||||||
|
/// type-safe wrapper around the raw [`Self::read_image_bytes`] method.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tiff::decoder::{Decoder, DecodingResult, Limits};
|
||||||
|
///
|
||||||
|
/// let mut result = DecodingResult::I8(vec![]);
|
||||||
|
///
|
||||||
|
/// let mut reader = /* */
|
||||||
|
/// # Decoder::new(std::io::Cursor::new(include_bytes!(concat!(
|
||||||
|
/// # env!("CARGO_MANIFEST_DIR"), "/tests/images/tiled-gray-i1.tif"
|
||||||
|
/// # )))).unwrap();
|
||||||
|
///
|
||||||
|
/// reader.read_image_to_buffer(&mut result)?;
|
||||||
|
///
|
||||||
|
/// # Ok::<_, tiff::TiffError>(())
|
||||||
|
/// ```
|
||||||
|
pub fn read_image_to_buffer(
|
||||||
|
&mut self,
|
||||||
|
result: &mut DecodingResult,
|
||||||
|
) -> TiffResult<BufferLayoutPreference> {
|
||||||
|
let readout = self.image().readout_for_image()?;
|
||||||
|
|
||||||
|
let planes = readout.to_plane_layout()?;
|
||||||
|
let layout = BufferLayoutPreference::from_planes(&planes);
|
||||||
|
|
||||||
|
let extent = readout.result_extent_for_planes(0..1)?;
|
||||||
|
result.resize_to_extent(extent, &self.value_reader.limits)?;
|
||||||
|
self.read_image_bytes(result.as_buffer(0).as_bytes_mut())?;
|
||||||
|
|
||||||
|
Ok(layout)
|
||||||
|
}
|
||||||
|
|
||||||
/// Decodes the entire image into a provided buffer.
|
/// Decodes the entire image into a provided buffer.
|
||||||
///
|
///
|
||||||
/// Returns a [`TiffError::UsageError`] if the chunk is smaller than the size indicated with a
|
/// 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
|
/// call to [`Self::image_buffer_layout`]. Note that the alignment may be arbitrary, but an
|
||||||
/// alignment smaller than the preferred alignment may perform worse.
|
/// alignment smaller than the preferred alignment may perform worse.
|
||||||
///
|
///
|
||||||
/// # Bugs
|
/// # Error
|
||||||
///
|
///
|
||||||
/// When the image is stored as a planar configuration, this method will currently only read
|
/// Returns an error if the buffer fits less than one plane. In particular, for non-planar
|
||||||
/// the first sample's plane. This will be fixed in a future major version of `tiff`.
|
/// 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 width = self.image().width;
|
let readout = self.image().readout_for_image()?;
|
||||||
let height = self.image().height;
|
|
||||||
|
|
||||||
let extent = self.result_extent(width as usize, height as usize)?;
|
let ref layout @ image::PlaneLayout {
|
||||||
extent.assert_layout(buffer.len())?;
|
ref plane_offsets,
|
||||||
|
// We assume that is correct, so really it can be ignored.
|
||||||
|
total_bytes: _,
|
||||||
|
ref readout,
|
||||||
|
} = readout.to_plane_layout()?;
|
||||||
|
|
||||||
if width == 0 || height == 0 {
|
let used_plane_offsets = usize::from(layout.used_planes(buffer)?);
|
||||||
return Ok(());
|
debug_assert!(used_plane_offsets >= 1, "Should have errored");
|
||||||
}
|
|
||||||
|
|
||||||
let chunk_dimensions = self.image().chunk_dimensions()?;
|
|
||||||
let chunk_dimensions = (
|
|
||||||
chunk_dimensions.0.min(width),
|
|
||||||
chunk_dimensions.1.min(height),
|
|
||||||
);
|
|
||||||
if chunk_dimensions.0 == 0 || chunk_dimensions.1 == 0 {
|
|
||||||
return Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let samples = self.image().samples_per_pixel();
|
|
||||||
if samples == 0 {
|
|
||||||
return Err(TiffError::FormatError(
|
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let output_row_bits = (width as u64 * self.image.bits_per_sample as u64)
|
|
||||||
.checked_mul(samples as u64)
|
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
|
||||||
let output_row_stride: usize = output_row_bits.div_ceil(8).try_into()?;
|
|
||||||
|
|
||||||
let chunk_row_bits = (chunk_dimensions.0 as u64 * self.image.bits_per_sample as u64)
|
|
||||||
.checked_mul(samples as u64)
|
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
|
||||||
let chunk_row_bytes: usize = chunk_row_bits.div_ceil(8).try_into()?;
|
|
||||||
|
|
||||||
let chunks_across = ((width - 1) / chunk_dimensions.0 + 1) as usize;
|
|
||||||
|
|
||||||
if chunks_across > 1 && chunk_row_bits % 8 != 0 {
|
|
||||||
return Err(TiffError::UnsupportedError(
|
|
||||||
TiffUnsupportedError::MisalignedTileBoundaries,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let image_chunks = self.image().chunk_offsets.len() / self.image().strips_per_pixel();
|
|
||||||
// For multi-band images, only the first band is read.
|
// For multi-band images, only the first band is read.
|
||||||
// Possible improvements:
|
// Possible improvements:
|
||||||
// * pass requested band as parameter
|
// * pass requested band as parameter
|
||||||
// * collect bands to a RGB encoding result in case of RGB bands
|
// * collect bands to a RGB encoding result in case of RGB bands
|
||||||
for chunk in 0..image_chunks {
|
for chunk in 0..readout.chunks_per_plane {
|
||||||
self.goto_offset_u64(self.image().chunk_offsets[chunk])?;
|
let x = (chunk % readout.chunks_across) as usize;
|
||||||
|
let y = (chunk / readout.chunks_across) as usize;
|
||||||
|
|
||||||
let x = chunk % chunks_across;
|
let buffer_offset = y * readout.chunk_col_stride + x * readout.chunk_row_stride;
|
||||||
let y = chunk / chunks_across;
|
|
||||||
let buffer_offset =
|
for (idx, &plane_offset) in plane_offsets[..used_plane_offsets].iter().enumerate() {
|
||||||
y * output_row_stride * chunk_dimensions.1 as usize + x * chunk_row_bytes;
|
let chunk = chunk + idx as u32 * readout.chunks_per_plane;
|
||||||
self.image.expand_chunk(
|
self.goto_offset_u64(self.image().chunk_offsets[chunk as usize])?;
|
||||||
&mut self.value_reader,
|
|
||||||
&mut buffer[buffer_offset..],
|
self.image.expand_chunk(
|
||||||
output_row_stride,
|
&mut self.value_reader,
|
||||||
chunk as u32,
|
&mut buffer[plane_offset..][buffer_offset..],
|
||||||
)?;
|
readout,
|
||||||
|
chunk,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+66
-4
@@ -11,7 +11,10 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
decoder::ifd::Entry,
|
decoder::ifd::Entry,
|
||||||
error::{TiffResult, UsageError},
|
error::{TiffResult, UsageError},
|
||||||
tags::{CompressionMethod, IfdPointer, ResolutionUnit, SampleFormat, Tag, Type},
|
tags::{
|
||||||
|
CompressionMethod, ExtraSamples, IfdPointer, PhotometricInterpretation, ResolutionUnit,
|
||||||
|
SampleFormat, Tag, Type,
|
||||||
|
},
|
||||||
Directory, TiffError, TiffFormatError,
|
Directory, TiffError, TiffFormatError,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -506,6 +509,7 @@ pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
|
|||||||
row_samples: u64,
|
row_samples: u64,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
extra_samples: Vec<u16>,
|
||||||
rows_per_strip: u64,
|
rows_per_strip: u64,
|
||||||
strip_offsets: Vec<K::OffsetType>,
|
strip_offsets: Vec<K::OffsetType>,
|
||||||
strip_byte_count: Vec<K::OffsetType>,
|
strip_byte_count: Vec<K::OffsetType>,
|
||||||
@@ -562,11 +566,14 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
encoder.write_tag(Tag::Compression, compression.tag().to_u16())?;
|
encoder.write_tag(Tag::Compression, compression.tag().to_u16())?;
|
||||||
encoder.write_tag(Tag::Predictor, predictor.to_u16())?;
|
encoder.write_tag(Tag::Predictor, predictor.to_u16())?;
|
||||||
|
|
||||||
encoder.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
|
|
||||||
let sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
|
|
||||||
encoder.write_tag(Tag::SampleFormat, &sample_format[..])?;
|
|
||||||
encoder.write_tag(Tag::PhotometricInterpretation, <T>::TIFF_VALUE.to_u16())?;
|
encoder.write_tag(Tag::PhotometricInterpretation, <T>::TIFF_VALUE.to_u16())?;
|
||||||
|
|
||||||
|
if matches!(<T>::TIFF_VALUE, PhotometricInterpretation::YCbCr) {
|
||||||
|
// The default for this tag is 2,2 for subsampling but we do not support such a
|
||||||
|
// transformation. Instead all samples must be provided.
|
||||||
|
encoder.write_tag(Tag::ChromaSubsampling, &[1u16, 1u16][..])?;
|
||||||
|
}
|
||||||
|
|
||||||
encoder.write_tag(Tag::RowsPerStrip, u32::try_from(rows_per_strip)?)?;
|
encoder.write_tag(Tag::RowsPerStrip, u32::try_from(rows_per_strip)?)?;
|
||||||
|
|
||||||
encoder.write_tag(
|
encoder.write_tag(
|
||||||
@@ -583,6 +590,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
strip_idx: 0,
|
strip_idx: 0,
|
||||||
row_samples,
|
row_samples,
|
||||||
rows_per_strip,
|
rows_per_strip,
|
||||||
|
extra_samples: vec![],
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
strip_offsets: Vec::new(),
|
strip_offsets: Vec::new(),
|
||||||
@@ -594,6 +602,27 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extra_samples(&mut self, extra: &[ExtraSamples]) -> Result<(), TiffError> {
|
||||||
|
if self.strip_idx != 0 {
|
||||||
|
return Err(TiffError::UsageError(
|
||||||
|
UsageError::ReconfiguredAfterImageWrite,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let samples = self.extra_samples.len() + extra.len() + <T>::BITS_PER_SAMPLE.len();
|
||||||
|
let row_samples = u64::from(self.width) * u64::try_from(samples)?;
|
||||||
|
|
||||||
|
self.extra_samples
|
||||||
|
.extend(extra.iter().map(ExtraSamples::to_u16));
|
||||||
|
|
||||||
|
self.encoder
|
||||||
|
.write_tag(Tag::ExtraSamples, &self.extra_samples[..])?;
|
||||||
|
|
||||||
|
self.row_samples = row_samples;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Number of samples the next strip should have.
|
/// Number of samples the next strip should have.
|
||||||
pub fn next_strip_sample_count(&self) -> u64 {
|
pub fn next_strip_sample_count(&self) -> u64 {
|
||||||
if self.strip_idx >= self.strip_count {
|
if self.strip_idx >= self.strip_count {
|
||||||
@@ -731,6 +760,39 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn finish_internal(&mut self) -> TiffResult<DirectoryOffset<K>> {
|
fn finish_internal(&mut self) -> TiffResult<DirectoryOffset<K>> {
|
||||||
|
if self.extra_samples.is_empty() {
|
||||||
|
self.encoder
|
||||||
|
.write_tag(Tag::BitsPerSample, <T>::BITS_PER_SAMPLE)?;
|
||||||
|
} else {
|
||||||
|
let mut sample_format: Vec<_> = <T>::BITS_PER_SAMPLE.to_vec();
|
||||||
|
let replicated =
|
||||||
|
core::iter::repeat(<T>::BITS_PER_SAMPLE[0]).take(self.extra_samples.len());
|
||||||
|
sample_format.extend(replicated);
|
||||||
|
|
||||||
|
self.encoder
|
||||||
|
.write_tag(Tag::BitsPerSample, &sample_format[..])?;
|
||||||
|
|
||||||
|
self.encoder.write_tag(
|
||||||
|
Tag::SamplesPerPixel,
|
||||||
|
u16::try_from(<T>::BITS_PER_SAMPLE.len() + self.extra_samples.len())?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sample_format: Vec<_> = <T>::SAMPLE_FORMAT.iter().map(|s| s.to_u16()).collect();
|
||||||
|
let extra_format = sample_format
|
||||||
|
.first()
|
||||||
|
.copied()
|
||||||
|
// Frankly should not occur, we have no sample format without at least one entry. We
|
||||||
|
// would need an interface upgrade to handle this however. Either decide to support
|
||||||
|
// heterogeneous sample formats or a way to provide fallback that is used for purely
|
||||||
|
// extra samples and not provided via a slice used for samples themselves.
|
||||||
|
.unwrap_or(SampleFormat::Void.to_u16());
|
||||||
|
|
||||||
|
sample_format.extend(core::iter::repeat(extra_format).take(self.extra_samples.len()));
|
||||||
|
|
||||||
|
self.encoder
|
||||||
|
.write_tag(Tag::SampleFormat, &sample_format[..])?;
|
||||||
|
|
||||||
self.encoder
|
self.encoder
|
||||||
.write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?;
|
.write_tag(Tag::StripOffsets, K::convert_slice(&self.strip_offsets))?;
|
||||||
self.encoder.write_tag(
|
self.encoder.write_tag(
|
||||||
|
|||||||
@@ -221,6 +221,8 @@ fn encoder_on_short_writes() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let imgdata = vec![42u8; 100 * 100];
|
let imgdata = vec![42u8; 100 * 100];
|
||||||
|
let mut data = crate::decoder::DecodingResult::U8(vec![]);
|
||||||
|
|
||||||
for comp in [
|
for comp in [
|
||||||
super::Compression::Uncompressed,
|
super::Compression::Uncompressed,
|
||||||
super::Compression::Packbits,
|
super::Compression::Packbits,
|
||||||
@@ -240,8 +242,12 @@ fn encoder_on_short_writes() {
|
|||||||
|
|
||||||
write.inner.set_position(0);
|
write.inner.set_position(0);
|
||||||
let mut tiff_reader = crate::decoder::Decoder::new(&mut write.inner).unwrap();
|
let mut tiff_reader = crate::decoder::Decoder::new(&mut write.inner).unwrap();
|
||||||
let data = tiff_reader.read_image().unwrap();
|
let _layout = tiff_reader.read_image_to_buffer(&mut data).unwrap();
|
||||||
|
|
||||||
assert!(matches!(data, crate::decoder::DecodingResult::U8(v) if v == imgdata));
|
assert!(
|
||||||
|
matches!(&data, crate::decoder::DecodingResult::U8(v) if *v == imgdata),
|
||||||
|
"{data:?} @ {}",
|
||||||
|
data.as_buffer(0).byte_len()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +90,9 @@ quick_error! {
|
|||||||
InvalidTypeForTag {
|
InvalidTypeForTag {
|
||||||
display("tag has invalid type")
|
display("tag has invalid type")
|
||||||
}
|
}
|
||||||
|
InvalidCountForTag(tag: Tag, len: usize) {
|
||||||
|
display("tag `{tag:?}` with incorrect number of elements ({len}) encountered")
|
||||||
|
}
|
||||||
StripTileTagConflict {
|
StripTileTagConflict {
|
||||||
display("file should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found")
|
display("file should contain either (StripByteCounts and StripOffsets) or (TileByteCounts and TileOffsets), other combination was found")
|
||||||
}
|
}
|
||||||
@@ -156,6 +159,9 @@ quick_error! {
|
|||||||
UnsupportedInterpretation(interpretation: PhotometricInterpretation) {
|
UnsupportedInterpretation(interpretation: PhotometricInterpretation) {
|
||||||
display("unsupported photometric interpretation \"{interpretation:?}\"")
|
display("unsupported photometric interpretation \"{interpretation:?}\"")
|
||||||
}
|
}
|
||||||
|
ChromaSubsampling {
|
||||||
|
display("chroma subsampling of YCbCr color is unsupported")
|
||||||
|
}
|
||||||
MisalignedTileBoundaries {
|
MisalignedTileBoundaries {
|
||||||
display("tile rows are not aligned to byte boundaries")
|
display("tile rows are not aligned to byte boundaries")
|
||||||
}
|
}
|
||||||
@@ -175,6 +181,12 @@ quick_error! {
|
|||||||
InvalidChunkIndex(index: u32) {
|
InvalidChunkIndex(index: u32) {
|
||||||
display("invalid chunk index ({index}) requested")
|
display("invalid chunk index ({index}) requested")
|
||||||
}
|
}
|
||||||
|
InvalidPlaneIndex(index: u16) {
|
||||||
|
display("invalid plane index ({index}) requested")
|
||||||
|
}
|
||||||
|
InvalidCodingUnit(index: u32, have: u32) {
|
||||||
|
display("out of bounds coding unit ({index}) requested, only {have} available")
|
||||||
|
}
|
||||||
PredictorCompressionMismatch {
|
PredictorCompressionMismatch {
|
||||||
display("requested predictor is not compatible with the requested compression")
|
display("requested predictor is not compatible with the requested compression")
|
||||||
}
|
}
|
||||||
@@ -187,9 +199,15 @@ quick_error! {
|
|||||||
InsufficientOutputBufferSize { needed: usize, provided: usize } {
|
InsufficientOutputBufferSize { needed: usize, provided: usize } {
|
||||||
display("the borrowed output buffer is not large enough for the decoded data, needed {needed} but have {provided}")
|
display("the borrowed output buffer is not large enough for the decoded data, needed {needed} but have {provided}")
|
||||||
}
|
}
|
||||||
|
InsufficientOutputRowStride { needed: usize, requested: usize } {
|
||||||
|
display("the provided output row stride would alias rows of decoded data, needed {needed} but have {requested}")
|
||||||
|
}
|
||||||
ZeroIfdPointer {
|
ZeroIfdPointer {
|
||||||
display("the offset 0 can not point to a valid IFD")
|
display("the offset 0 can not point to a valid IFD")
|
||||||
}
|
}
|
||||||
|
ReconfiguredAfterImageWrite {
|
||||||
|
display("attempted to reconfigure the encoder after image writing has started")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
@@ -44,6 +44,9 @@ pub enum ColorType {
|
|||||||
/// Pixel is YCbCr
|
/// Pixel is YCbCr
|
||||||
YCbCr(u8),
|
YCbCr(u8),
|
||||||
|
|
||||||
|
/// Pixel is CIE L*a*b* (ICC LAb)
|
||||||
|
Lab(u8),
|
||||||
|
|
||||||
/// Pixel has multiple bands/channels
|
/// Pixel has multiple bands/channels
|
||||||
Multiband { bit_depth: u8, num_samples: u16 },
|
Multiband { bit_depth: u8, num_samples: u16 },
|
||||||
}
|
}
|
||||||
@@ -58,7 +61,23 @@ impl ColorType {
|
|||||||
| ColorType::CMYK(b)
|
| ColorType::CMYK(b)
|
||||||
| ColorType::CMYKA(b)
|
| ColorType::CMYKA(b)
|
||||||
| ColorType::YCbCr(b)
|
| ColorType::YCbCr(b)
|
||||||
|
| ColorType::Lab(b)
|
||||||
| ColorType::Multiband { bit_depth: b, .. } => b,
|
| ColorType::Multiband { bit_depth: b, .. } => b,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn num_samples(&self) -> u16 {
|
||||||
|
match *self {
|
||||||
|
ColorType::Gray(_) => 1,
|
||||||
|
ColorType::RGB(_) => 3,
|
||||||
|
ColorType::Palette(_) => 1,
|
||||||
|
ColorType::GrayA(_) => 2,
|
||||||
|
ColorType::RGBA(_) => 4,
|
||||||
|
ColorType::CMYK(_) => 4,
|
||||||
|
ColorType::CMYKA(_) => 5,
|
||||||
|
ColorType::YCbCr(_) => 3,
|
||||||
|
ColorType::Lab(_) => 3,
|
||||||
|
ColorType::Multiband { num_samples, .. } => num_samples,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+31
-7
@@ -20,7 +20,7 @@ macro_rules! tags {
|
|||||||
|
|
||||||
impl $name {
|
impl $name {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn __from_inner_type(n: $ty) -> Result<Self, $ty> {
|
const fn __from_inner_type(n: $ty) -> Result<Self, $ty> {
|
||||||
match n {
|
match n {
|
||||||
$( $val => Ok($name::$tag), )*
|
$( $val => Ok($name::$tag), )*
|
||||||
n => Err(n),
|
n => Err(n),
|
||||||
@@ -28,7 +28,7 @@ macro_rules! tags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn __to_inner_type(&self) -> $ty {
|
const fn __to_inner_type(&self) -> $ty {
|
||||||
match *self {
|
match *self {
|
||||||
$( $name::$tag => $val, )*
|
$( $name::$tag => $val, )*
|
||||||
$( $name::Unknown($unknown_doc) => { $unknown_doc }, )*
|
$( $name::Unknown($unknown_doc) => { $unknown_doc }, )*
|
||||||
@@ -42,19 +42,25 @@ macro_rules! tags {
|
|||||||
($name:tt, u16, $($unknown_doc:ident)*) => {
|
($name:tt, u16, $($unknown_doc:ident)*) => {
|
||||||
impl $name {
|
impl $name {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn from_u16(val: u16) -> Option<Self> {
|
pub const fn from_u16(val: u16) -> Option<Self> {
|
||||||
Self::__from_inner_type(val).ok()
|
match Self::__from_inner_type(val) {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$(
|
$(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn from_u16_exhaustive($unknown_doc: u16) -> Self {
|
pub const fn from_u16_exhaustive($unknown_doc: u16) -> Self {
|
||||||
Self::__from_inner_type($unknown_doc).unwrap_or_else(|_| $name::Unknown($unknown_doc))
|
match Self::__from_inner_type($unknown_doc) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => $name::Unknown($unknown_doc),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn to_u16(&self) -> u16 {
|
pub const fn to_u16(&self) -> u16 {
|
||||||
Self::__to_inner_type(self)
|
Self::__to_inner_type(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,6 +128,11 @@ pub enum Tag(u16) unknown(
|
|||||||
SMaxSampleValue = 341, // TODO add support
|
SMaxSampleValue = 341, // TODO add support
|
||||||
// JPEG
|
// JPEG
|
||||||
JPEGTables = 347,
|
JPEGTables = 347,
|
||||||
|
// Subsampling
|
||||||
|
#[doc(alias = "YCbCrSubsampling")]
|
||||||
|
ChromaSubsampling = 530, // TODO add support
|
||||||
|
#[doc(alias = "YCbCrPositioning")]
|
||||||
|
ChromaPositioning = 531, // TODO add support
|
||||||
// GeoTIFF
|
// GeoTIFF
|
||||||
ModelPixelScaleTag = 33550, // (SoftDesk)
|
ModelPixelScaleTag = 33550, // (SoftDesk)
|
||||||
ModelTransformationTag = 34264, // (JPL Carto Group)
|
ModelTransformationTag = 34264, // (JPL Carto Group)
|
||||||
@@ -266,6 +277,8 @@ pub enum PhotometricInterpretation(u16) {
|
|||||||
CMYK = 5,
|
CMYK = 5,
|
||||||
YCbCr = 6,
|
YCbCr = 6,
|
||||||
CIELab = 8,
|
CIELab = 8,
|
||||||
|
IccLab = 9,
|
||||||
|
ItuLab = 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,6 +324,17 @@ pub enum SampleFormat(u16) unknown(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tags! {
|
||||||
|
pub enum ExtraSamples(u16) {
|
||||||
|
/// There is no specified association between the sample and the image.
|
||||||
|
Unspecified = 0,
|
||||||
|
/// The sample is associated alpha, i.e. pre-multiplied color.
|
||||||
|
AssociatedAlpha = 1,
|
||||||
|
/// The sample is unassociated alpha such as a mask. There might be more than one such sample.
|
||||||
|
UnassociatedAlpha = 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Byte order of the TIFF file.
|
/// Byte order of the TIFF file.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum ByteOrder {
|
pub enum ByteOrder {
|
||||||
|
|||||||
@@ -684,3 +684,39 @@ fn test_fax4() {
|
|||||||
fn test_fax4_white_is_min() {
|
fn test_fax4_white_is_min() {
|
||||||
test_image_sum_u8("imagemagick_group4.tiff", ColorType::Gray(1), 3742820);
|
test_image_sum_u8("imagemagick_group4.tiff", ColorType::Gray(1), 3742820);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_extra_bits_image() {
|
||||||
|
use tiff::{encoder, tags::ExtraSamples};
|
||||||
|
|
||||||
|
let path = PathBuf::from(TEST_IMAGE_DIR).join("extra_bits_rgb_8b.tiff");
|
||||||
|
let img_file = File::create(path).expect("Cannot find test image!");
|
||||||
|
|
||||||
|
let mut encoder = encoder::TiffEncoder::new(img_file).expect("Cannot create encoder");
|
||||||
|
let mut img = encoder.new_image::<encoder::colortype::RGB8>(8, 8).unwrap();
|
||||||
|
|
||||||
|
img.extra_samples(&[ExtraSamples::Unspecified]).unwrap();
|
||||||
|
|
||||||
|
// all pixels are supposed to be 0, the extra is 1
|
||||||
|
let data: [u8; 256] = core::array::from_fn(|i| (i as u8 % 4).saturating_sub(2));
|
||||||
|
|
||||||
|
img.write_data(&data)
|
||||||
|
.expect("correct amount of data to write");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extra_bits_gray() {
|
||||||
|
test_image_sum_u8(
|
||||||
|
"extra_bits_gray_8b.tiff",
|
||||||
|
ColorType::Multiband {
|
||||||
|
bit_depth: 8,
|
||||||
|
num_samples: 2,
|
||||||
|
},
|
||||||
|
64,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extra_bits_rgb() {
|
||||||
|
test_image_sum_u8("extra_bits_rgb_8b.tiff", ColorType::RGB(8), 0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
use tiff::decoder::{Decoder, TiffCodingUnit};
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const TEST_IMAGE_DIR: &str = "./tests/images/";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_all_planes() {
|
||||||
|
let path = PathBuf::from(TEST_IMAGE_DIR).join("planar-rgb-u8.tif");
|
||||||
|
let file = File::open(path).unwrap();
|
||||||
|
let io = std::io::BufReader::new(file);
|
||||||
|
|
||||||
|
let mut tif = Decoder::new(io).unwrap();
|
||||||
|
let layout = tif.image_buffer_layout().unwrap();
|
||||||
|
assert_eq!(layout.planes, 3);
|
||||||
|
let plane_stride = layout.plane_stride.map_or(0, |x| x.get());
|
||||||
|
|
||||||
|
eprintln!("{:?}", layout);
|
||||||
|
let mut buffer = vec![0u8; layout.complete_len];
|
||||||
|
tif.read_image_bytes(&mut buffer).unwrap();
|
||||||
|
|
||||||
|
// Mainly you can see: these are different and non-zero. Otherwise these magic constants just
|
||||||
|
// depend on the file itself that is being used.
|
||||||
|
const PLANE_EXCPECTED_SUMS: [u32; 3] = [15417630, 13007788, 11103530];
|
||||||
|
|
||||||
|
for (plane, expected) in (0..layout.planes).zip(PLANE_EXCPECTED_SUMS) {
|
||||||
|
let plane_data = &buffer[plane_stride * plane..][..layout.len];
|
||||||
|
let sum: u32 = plane_data.iter().copied().map(u32::from).sum();
|
||||||
|
assert_eq!(sum, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_first_coding_unit() {
|
||||||
|
let path = PathBuf::from(TEST_IMAGE_DIR).join("planar-rgb-u8.tif");
|
||||||
|
let file = File::open(path).unwrap();
|
||||||
|
let io = std::io::BufReader::new(file);
|
||||||
|
|
||||||
|
let mut tif = Decoder::new(io).unwrap();
|
||||||
|
let layout = tif.image_coding_unit_layout(TiffCodingUnit(0)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(layout.planes, 3);
|
||||||
|
let plane_stride = layout.plane_stride.map_or(0, |x| x.get());
|
||||||
|
eprintln!("{:?}", layout);
|
||||||
|
let mut buffer = vec![0u8; layout.complete_len];
|
||||||
|
|
||||||
|
struct Expected {
|
||||||
|
plane_sums: [u32; 3],
|
||||||
|
unit: TiffCodingUnit,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mainly you can see: these are different and non-zero. Otherwise these magic constants just
|
||||||
|
// depend on the file itself that is being used. Also this varies by what unit we are reading.
|
||||||
|
const PLANE_EXCPECTED_SUMS: [Expected; 2] = [
|
||||||
|
Expected {
|
||||||
|
plane_sums: [856853, 767686, 688532],
|
||||||
|
unit: TiffCodingUnit(0),
|
||||||
|
},
|
||||||
|
Expected {
|
||||||
|
plane_sums: [883679, 797329, 711086],
|
||||||
|
unit: TiffCodingUnit(1),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for expected in PLANE_EXCPECTED_SUMS {
|
||||||
|
tif.read_coding_unit_bytes(expected.unit, &mut buffer)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for (plane, expected) in (0..layout.planes).zip(expected.plane_sums) {
|
||||||
|
let plane_data = &buffer[plane_stride * plane..][..layout.len];
|
||||||
|
let sum: u32 = plane_data.iter().copied().map(u32::from).sum();
|
||||||
|
assert_eq!(sum, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
-6
@@ -57,7 +57,7 @@ fn encode_decode() {
|
|||||||
),
|
),
|
||||||
(Tag::Compression, ifd::Value::Short(1)),
|
(Tag::Compression, ifd::Value::Short(1)),
|
||||||
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
(Tag::PhotometricInterpretation, ifd::Value::Short(2)),
|
||||||
(Tag::StripOffsets, ifd::Value::Unsigned(47)),
|
(Tag::StripOffsets, ifd::Value::Unsigned(35)),
|
||||||
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
|
(Tag::SamplesPerPixel, ifd::Value::Short(3)),
|
||||||
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
(Tag::RowsPerStrip, ifd::Value::Unsigned(3334)),
|
||||||
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
|
(Tag::StripByteCounts, ifd::Value::Unsigned(30000)),
|
||||||
@@ -81,7 +81,10 @@ fn encode_decode() {
|
|||||||
decoder.get_tag(Tag::Artist).unwrap(),
|
decoder.get_tag(Tag::Artist).unwrap(),
|
||||||
ifd::Value::Ascii("Image-tiff".into())
|
ifd::Value::Ascii("Image-tiff".into())
|
||||||
);
|
);
|
||||||
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
|
||||||
|
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
||||||
|
let _layout = decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
|
if let tiff::decoder::DecodingResult::U8(img_res) = buffer {
|
||||||
assert_eq!(image_data, img_res);
|
assert_eq!(image_data, img_res);
|
||||||
} else {
|
} else {
|
||||||
panic!("Wrong data type");
|
panic!("Wrong data type");
|
||||||
@@ -161,7 +164,9 @@ fn encode_decode_big() {
|
|||||||
decoder.get_tag(Tag::Artist).unwrap(),
|
decoder.get_tag(Tag::Artist).unwrap(),
|
||||||
ifd::Value::Ascii("Image-tiff".into())
|
ifd::Value::Ascii("Image-tiff".into())
|
||||||
);
|
);
|
||||||
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
||||||
|
let _layout = decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
|
if let DecodingResult::U8(img_res) = buffer {
|
||||||
assert_eq!(image_data, img_res);
|
assert_eq!(image_data, img_res);
|
||||||
} else {
|
} else {
|
||||||
panic!("Wrong data type");
|
panic!("Wrong data type");
|
||||||
@@ -235,6 +240,7 @@ const TEST_IMAGE_DIR: &str = "./tests/images/";
|
|||||||
|
|
||||||
macro_rules! test_roundtrip {
|
macro_rules! test_roundtrip {
|
||||||
($name:ident, $buffer:ident, $buffer_ty:ty) => {
|
($name:ident, $buffer:ident, $buffer_ty:ty) => {
|
||||||
|
#[track_caller]
|
||||||
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
|
fn $name<C: colortype::ColorType<Inner = $buffer_ty>>(
|
||||||
file: &str,
|
file: &str,
|
||||||
expected_type: ColorType,
|
expected_type: ColorType,
|
||||||
@@ -244,7 +250,10 @@ macro_rules! test_roundtrip {
|
|||||||
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);
|
||||||
|
|
||||||
let image_data = match decoder.read_image().unwrap() {
|
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
||||||
|
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
|
|
||||||
|
let image_data = match buffer {
|
||||||
DecodingResult::$buffer(res) => res,
|
DecodingResult::$buffer(res) => res,
|
||||||
_ => panic!("Wrong data type"),
|
_ => panic!("Wrong data type"),
|
||||||
};
|
};
|
||||||
@@ -258,9 +267,11 @@ macro_rules! test_roundtrip {
|
|||||||
}
|
}
|
||||||
file.seek(SeekFrom::Start(0)).unwrap();
|
file.seek(SeekFrom::Start(0)).unwrap();
|
||||||
{
|
{
|
||||||
|
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
||||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||||
if let DecodingResult::$buffer(img_res) = decoder.read_image().unwrap() {
|
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||||
assert_eq!(image_data, img_res);
|
if let DecodingResult::$buffer(img_res) = buffer {
|
||||||
|
assert_eq!(*image_data, img_res);
|
||||||
} else {
|
} else {
|
||||||
panic!("Wrong data type");
|
panic!("Wrong data type");
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user