Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab0ce47230 | ||
|
|
f993cfb940 | ||
|
|
8c4bd58493 | ||
|
|
c073841a9d |
No files matched your search
@@ -9,14 +9,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust: ["1.85.0", stable, beta, nightly]
|
rust: ["1.74.0", stable, beta, nightly]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
- uses: dtolnay/rust-toolchain@nightly
|
||||||
if: ${{ matrix.rust == '1.85.0' }}
|
if: ${{ matrix.rust == '1.74.0' }}
|
||||||
- name: Generate Cargo.lock with minimal-version dependencies
|
- name: Generate Cargo.lock with minimal-version dependencies
|
||||||
if: ${{ matrix.rust == '1.85.0' }}
|
if: ${{ matrix.rust == '1.74.0' }}
|
||||||
run: cargo -Zminimal-versions generate-lockfile
|
run: cargo -Zminimal-versions generate-lockfile
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@v1
|
- uses: dtolnay/rust-toolchain@v1
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
- name: build
|
- name: build
|
||||||
run: cargo build -v
|
run: cargo build -v
|
||||||
- name: test
|
- name: test
|
||||||
if: ${{ matrix.rust != '1.85.0' }}
|
if: ${{ matrix.rust != '1.74.0' }}
|
||||||
run: cargo test -v && cargo doc -v
|
run: cargo test -v && cargo doc -v
|
||||||
|
|
||||||
features:
|
features:
|
||||||
|
|||||||
-137
@@ -1,140 +1,3 @@
|
|||||||
# Version 0.11.2
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- Planar files can now be read with `Decoder::read_image_to_buffer` if the
|
|
||||||
limits allow for such a large buffer size. Previously only the first plane
|
|
||||||
was read and the decoder will attempt a fallback to this behavior if the
|
|
||||||
limits do not allow all planes to be allocated.
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
- An off-by-one error in a `debug_assert` of `ValueBuffer` caused its use in
|
|
||||||
`IfdDecoder::find_tag_buf` to panic outside release mode when the buffer
|
|
||||||
exactly matched the required size.
|
|
||||||
|
|
||||||
Additions:
|
|
||||||
- The methods `ColorType::{num_samples, bit_depth}` are now public. This
|
|
||||||
promises that we only have homogeneous color depths in this major version.
|
|
||||||
|
|
||||||
# Version 0.11.1
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
- The enumeration types in `tags` are now all marked with a representation of
|
|
||||||
their underlying TIFF type (e.g. `repr(u16)`) and variants are explicitly
|
|
||||||
assigned their corresponding values. That is you may _read_ the raw
|
|
||||||
discriminant and interpret that as the value—except for `Unknown` variants.
|
|
||||||
- The variants `RationalBig` and `SRationalBig` of `decoder::ifd::Value` are
|
|
||||||
deprecated as they have no corresponding TIFF type and were not constructed
|
|
||||||
during decoding.
|
|
||||||
|
|
||||||
Additions:
|
|
||||||
- Types in `tags` now generally implement `TiffValue` and can be handed to the
|
|
||||||
`DirectoryEncoder::write_tag` method. Unlike primitive types they do _not_
|
|
||||||
always implement the trait for slices of themselves.
|
|
||||||
- Added `tags::ValueBuffer`, a byte-based buffer for tags with a runtime value
|
|
||||||
type and count.
|
|
||||||
- Added `IfdDecoder::find_tag_{buf,bytes}` to read tag values into a
|
|
||||||
`ValueBuffer` or byte slice respectively without interpreting them
|
|
||||||
immediately within the Rust type system.
|
|
||||||
- Added `DirectoryEncoder::write_{data,tag}_buf` that takes a `ValueBuffer`
|
|
||||||
instead of a statically typed argument.
|
|
||||||
- Added `DirectoryEncoder::write_entry{,_buf,_bytes}` that encode their
|
|
||||||
argument into the file and return an `Entry` for later use, without
|
|
||||||
immediately adding a tag-entry pair to the directory.
|
|
||||||
- The `TiffValue` trait is now implemented for several builtin `tags::*` types
|
|
||||||
or slices of them as appropriate for their use as values of their intended
|
|
||||||
TIFF tag.
|
|
||||||
- The `TiffValue` trait is now implemented for `[T; N]` for all types that
|
|
||||||
implement it for slices.
|
|
||||||
|
|
||||||
# Version 0.11.0
|
|
||||||
|
|
||||||
- `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
|
|
||||||
|
|
||||||
New features:
|
|
||||||
- Group 4 `fax` is now recognized as a compression type and decoded.
|
|
||||||
- A crate feature `fax` controls related dependencies, on by default.
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- The presence of PhotometricInterpretation::WhiteIsZero now returns an error
|
|
||||||
if the color type can not be inverted by the library where they were
|
|
||||||
previously silently ignored. Please report any missing color types.
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
- The PhotometricInterpretation::WhiteIsZero is now also applied to 1-bit
|
|
||||||
images, as well as 2-bit and 4-bit sample types where previously the presence
|
|
||||||
of this tag was ignored.
|
|
||||||
|
|
||||||
# Version 0.10.2
|
|
||||||
|
|
||||||
Republished 0.10.1, fixing an accidentally removed method (`Decoder::find_tag_unsigned_vec`).
|
|
||||||
|
|
||||||
# Version 0.10.1
|
|
||||||
|
|
||||||
New features:
|
|
||||||
- Added a dedicated `Directory` type (#277)
|
|
||||||
- Expose methods related to `Directory` publicly (#278)
|
|
||||||
- Implement writing directories (#281, #283)
|
|
||||||
- Add ICC (color profile) to the set of known tags (#287)
|
|
||||||
- Added `Decoder.inner()` to access the underlying reader (#289)
|
|
||||||
- Added methods for reading into a pre-allocated byte buffer (#285)
|
|
||||||
|
|
||||||
Changes:
|
|
||||||
- No longer perform color conversion when decoding embedded JPEG. This fixes decoding for JPEG images with alpha channel. (#274)
|
|
||||||
- More lenient LZW decoding to match libtiff (#231)
|
|
||||||
|
|
||||||
Fixes:
|
|
||||||
- Fixed seeking multiple times in a row (#279)
|
|
||||||
|
|
||||||
# Version 0.10.0
|
# Version 0.10.0
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|||||||
+6
-9
@@ -1,11 +1,11 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.11.2"
|
version = "0.10.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
||||||
rust-version = "1.85.0"
|
rust-version = "1.74.0"
|
||||||
|
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "TIFF decoding and encoding library in pure Rust"
|
description = "TIFF decoding and encoding library in pure Rust"
|
||||||
@@ -14,30 +14,27 @@ authors = ["The image-rs Developers"]
|
|||||||
repository = "https://github.com/image-rs/image-tiff"
|
repository = "https://github.com/image-rs/image-tiff"
|
||||||
categories = ["multimedia::images", "multimedia::encoding"]
|
categories = ["multimedia::images", "multimedia::encoding"]
|
||||||
|
|
||||||
exclude = ["tests/images/*", "tests/fuzz_images/*", "tests/**/*.tif"]
|
exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
half = { version = "2.4.1" }
|
half = { version = "2.4.1" }
|
||||||
quick-error = "2.0.1"
|
quick-error = "2.0.1"
|
||||||
|
|
||||||
# Rename to avoid confusion with the feature name.
|
|
||||||
fax34 = { package = "fax", version = "0.2.6", optional = true }
|
|
||||||
flate2 = { version = "1.0.20", optional = true }
|
flate2 = { version = "1.0.20", optional = true }
|
||||||
weezl = { version = "0.1.10", optional = true }
|
weezl = { version = "0.1.10", optional = true }
|
||||||
zstd = { version = "0.13", optional = true }
|
zstd = { version = "0.13", optional = true }
|
||||||
zune-jpeg = { version = "0.5.1", optional = true }
|
zune-jpeg = { version = "0.4.17", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3.1"
|
criterion = "0.3.1"
|
||||||
# We may already depend on this via flate2
|
# We may already depend on this via flate2
|
||||||
crc32fast = "1.5"
|
crc32fast = "1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["deflate", "fax", "jpeg", "lzw"]
|
default = ["deflate", "jpeg", "lzw"]
|
||||||
|
|
||||||
# Compression algorithms
|
# Compression algorithms
|
||||||
deflate = ["dep:flate2"]
|
deflate = ["dep:flate2"]
|
||||||
fax = ["dep:fax34"]
|
|
||||||
jpeg = ["dep:zune-jpeg"]
|
jpeg = ["dep:zune-jpeg"]
|
||||||
lzw = ["dep:weezl"]
|
lzw = ["dep:weezl"]
|
||||||
zstd = ["dep:zstd"]
|
zstd = ["dep:zstd"]
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ This table lists photometric interpretations and sample formats which are suppor
|
|||||||
| LZW | ✓ | ✓ |
|
| LZW | ✓ | ✓ |
|
||||||
| Deflate | ✓ | ✓ |
|
| Deflate | ✓ | ✓ |
|
||||||
| PackBits | ✓ | ✓ |
|
| PackBits | ✓ | ✓ |
|
||||||
| Fax4 | ✓ | not yet |
|
|
||||||
| JPEG | ✓ | not yet |
|
| JPEG | ✓ | not yet |
|
||||||
| ZSTD | ✓ | not yet |
|
| ZSTD | ✓ | not yet |
|
||||||
|
|
||||||
|
|||||||
@@ -1,206 +0,0 @@
|
|||||||
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 {
|
|
||||||
// Note: we will expand, so this is in fact not BLACKANDWHITE
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Number of samples in a line and number of sample lines in the image, depending on whether
|
|
||||||
// they are planar or not. This determines the layout of the image buffer when expanding bit
|
|
||||||
// packed samples to a full byte (we support power-of-two sample sizes, positive and negative,
|
|
||||||
// but only those).
|
|
||||||
let (swidth, sheight) = if layout.planes > 1 {
|
|
||||||
(
|
|
||||||
u64::from(width) * u64::from(colortype.num_samples()),
|
|
||||||
u64::from(height),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
u64::from(width),
|
|
||||||
u64::from(height) * u64::from(colortype.num_samples()),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut fallback_buffer;
|
|
||||||
let maxval = match data.as_buffer(0) {
|
|
||||||
// `U8` is also our representation of bit-packed color. PAM uses the smallest number of
|
|
||||||
// *bytes* needed to represent a sample but we must also adhere to maxval. We could explode
|
|
||||||
// them here (e.g. represent 1-bit as `{0, 1}`) but we'd also set `maxval` accordingly and
|
|
||||||
// this requires tupltype to be `BLACKANDWHITE` so introducing a bunch of complexity. If
|
|
||||||
// you want to do that in your real code, go ahead.
|
|
||||||
DecodingBuffer::U8(buf) => match colortype.bit_depth() {
|
|
||||||
8 => u64::from(u8::MAX),
|
|
||||||
4 => {
|
|
||||||
fallback_buffer = vec![0; (swidth * sheight) as usize];
|
|
||||||
expand_4bit(buf, swidth, &mut fallback_buffer);
|
|
||||||
*data = DecodingResult::U8(fallback_buffer);
|
|
||||||
u64::from(u8::MAX)
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
fallback_buffer = vec![0; (swidth * sheight) as usize];
|
|
||||||
expand_2bit(buf, swidth, &mut fallback_buffer);
|
|
||||||
*data = DecodingResult::U8(fallback_buffer);
|
|
||||||
u64::from(u8::MAX)
|
|
||||||
}
|
|
||||||
1 => {
|
|
||||||
fallback_buffer = vec![0; (swidth * sheight) as usize];
|
|
||||||
expand_1bit(buf, swidth, &mut fallback_buffer);
|
|
||||||
*data = DecodingResult::U8(fallback_buffer);
|
|
||||||
u64::from(u8::MAX)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
eprintln!(
|
|
||||||
"Unsupported bit depth for U8 buffer: {}",
|
|
||||||
colortype.bit_depth()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Put exactly the ith `SCALE`-bit chunk of `val` into the least significant bits.
|
|
||||||
#[inline(always)]
|
|
||||||
fn bits_extract<const SCALE: u8>(val: u8, i: u8) -> u8 {
|
|
||||||
// Shift left to discard high bits, then right to bring desired bits down. This way also
|
|
||||||
// conveniently avoids all cases of overflow.
|
|
||||||
(val << (i * SCALE)) >> (8 - SCALE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_1bit(from: &[u8], samples_per_padded: u64, into: &mut [u8]) {
|
|
||||||
let stride = samples_per_padded.div_ceil(8);
|
|
||||||
|
|
||||||
let from_row = from.chunks_exact(stride as usize);
|
|
||||||
let into_row = into.chunks_exact_mut(samples_per_padded as usize);
|
|
||||||
|
|
||||||
for (from_row, into_row) in from_row.zip(into_row) {
|
|
||||||
for (into, &from) in into_row.chunks_mut(8).zip(from_row) {
|
|
||||||
let data =
|
|
||||||
core::array::from_fn::<u8, 8, _>(|i| 0xff * bits_extract::<1>(from, i as u8));
|
|
||||||
into.copy_from_slice(&data[..into.len()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_2bit(from: &[u8], samples_per_padded: u64, into: &mut [u8]) {
|
|
||||||
let stride = samples_per_padded.div_ceil(4);
|
|
||||||
|
|
||||||
let from_row = from.chunks_exact(stride as usize);
|
|
||||||
let into_row = into.chunks_exact_mut(samples_per_padded as usize);
|
|
||||||
|
|
||||||
for (from_row, into_row) in from_row.zip(into_row) {
|
|
||||||
for (into, &from) in into_row.chunks_mut(4).zip(from_row) {
|
|
||||||
let data =
|
|
||||||
core::array::from_fn::<u8, 4, _>(|i| 0xaa * bits_extract::<2>(from, i as u8));
|
|
||||||
into.copy_from_slice(&data[..into.len()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_4bit(from: &[u8], samples_per_padded: u64, into: &mut [u8]) {
|
|
||||||
let stride = samples_per_padded.div_ceil(2);
|
|
||||||
|
|
||||||
let from_row = from.chunks_exact(stride as usize);
|
|
||||||
let into_row = into.chunks_exact_mut(samples_per_padded as usize);
|
|
||||||
|
|
||||||
for (from_row, into_row) in from_row.zip(into_row) {
|
|
||||||
for (into, &from) in into_row.chunks_mut(2).zip(from_row) {
|
|
||||||
let data =
|
|
||||||
core::array::from_fn::<u8, 2, _>(|i| 0x88 * bits_extract::<4>(from, i as u8));
|
|
||||||
into.copy_from_slice(&data[..into.len()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+128
-366
@@ -6,12 +6,12 @@ use std::mem;
|
|||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
use super::stream::{ByteOrder, EndianReader};
|
use super::stream::{ByteOrder, EndianReader};
|
||||||
use crate::tags::{IfdPointer, Tag, Type, ValueBuffer};
|
use crate::tags::{IfdPointer, Tag, Type};
|
||||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||||
|
|
||||||
use self::Value::{
|
use self::Value::{
|
||||||
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, SRational, Short, Signed, SignedBig,
|
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
|
||||||
SignedByte, SignedShort, Unsigned, UnsignedBig,
|
Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(unused_qualifications)]
|
#[allow(unused_qualifications)]
|
||||||
@@ -30,16 +30,8 @@ pub enum Value {
|
|||||||
Double(f64),
|
Double(f64),
|
||||||
List(Vec<Value>),
|
List(Vec<Value>),
|
||||||
Rational(u32, u32),
|
Rational(u32, u32),
|
||||||
#[deprecated(
|
|
||||||
note = "Not implemented in BigTIFF with a standard tag value",
|
|
||||||
since = "0.11.1"
|
|
||||||
)]
|
|
||||||
RationalBig(u64, u64),
|
RationalBig(u64, u64),
|
||||||
SRational(i32, i32),
|
SRational(i32, i32),
|
||||||
#[deprecated(
|
|
||||||
note = "Not implemented in BigTIFF with a standard tag value",
|
|
||||||
since = "0.11.1"
|
|
||||||
)]
|
|
||||||
SRationalBig(i64, i64),
|
SRationalBig(i64, i64),
|
||||||
Ascii(String),
|
Ascii(String),
|
||||||
Ifd(u32),
|
Ifd(u32),
|
||||||
@@ -178,8 +170,7 @@ impl Value {
|
|||||||
Unsigned(val) => Ok(vec![val]),
|
Unsigned(val) => Ok(vec![val]),
|
||||||
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
|
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
|
||||||
Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||||
#[expect(deprecated)]
|
RationalBig(numerator, denominator) => {
|
||||||
Value::RationalBig(numerator, denominator) => {
|
|
||||||
Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?])
|
Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?])
|
||||||
}
|
}
|
||||||
Ifd(val) => Ok(vec![val]),
|
Ifd(val) => Ok(vec![val]),
|
||||||
@@ -229,8 +220,7 @@ impl Value {
|
|||||||
new_vec.push(numerator);
|
new_vec.push(numerator);
|
||||||
new_vec.push(denominator);
|
new_vec.push(denominator);
|
||||||
}
|
}
|
||||||
#[expect(deprecated)]
|
SRationalBig(numerator, denominator) => {
|
||||||
Value::SRationalBig(numerator, denominator) => {
|
|
||||||
new_vec.push(i32::try_from(numerator)?);
|
new_vec.push(i32::try_from(numerator)?);
|
||||||
new_vec.push(i32::try_from(denominator)?);
|
new_vec.push(i32::try_from(denominator)?);
|
||||||
}
|
}
|
||||||
@@ -244,8 +234,7 @@ impl Value {
|
|||||||
Signed(val) => Ok(vec![val]),
|
Signed(val) => Ok(vec![val]),
|
||||||
SignedBig(val) => Ok(vec![i32::try_from(val)?]),
|
SignedBig(val) => Ok(vec![i32::try_from(val)?]),
|
||||||
SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||||
#[expect(deprecated)]
|
SRationalBig(numerator, denominator) => {
|
||||||
Value::SRationalBig(numerator, denominator) => {
|
|
||||||
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
|
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
|
||||||
}
|
}
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||||
@@ -294,8 +283,7 @@ impl Value {
|
|||||||
Unsigned(val) => Ok(vec![val.into()]),
|
Unsigned(val) => Ok(vec![val.into()]),
|
||||||
UnsignedBig(val) => Ok(vec![val]),
|
UnsignedBig(val) => Ok(vec![val]),
|
||||||
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
||||||
#[expect(deprecated)]
|
RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||||
Value::RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
|
||||||
Ifd(val) => Ok(vec![val.into()]),
|
Ifd(val) => Ok(vec![val.into()]),
|
||||||
IfdBig(val) => Ok(vec![val]),
|
IfdBig(val) => Ok(vec![val]),
|
||||||
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
|
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
|
||||||
@@ -313,8 +301,7 @@ impl Value {
|
|||||||
new_vec.push(numerator.into());
|
new_vec.push(numerator.into());
|
||||||
new_vec.push(denominator.into());
|
new_vec.push(denominator.into());
|
||||||
}
|
}
|
||||||
#[expect(deprecated)]
|
SRationalBig(numerator, denominator) => {
|
||||||
Value::SRationalBig(numerator, denominator) => {
|
|
||||||
new_vec.push(numerator);
|
new_vec.push(numerator);
|
||||||
new_vec.push(denominator);
|
new_vec.push(denominator);
|
||||||
}
|
}
|
||||||
@@ -328,8 +315,7 @@ impl Value {
|
|||||||
Signed(val) => Ok(vec![val.into()]),
|
Signed(val) => Ok(vec![val.into()]),
|
||||||
SignedBig(val) => Ok(vec![val]),
|
SignedBig(val) => Ok(vec![val]),
|
||||||
SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
||||||
#[expect(deprecated)]
|
SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||||
Value::SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
|
||||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,12 +332,6 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A combination of type, count, and offset.
|
|
||||||
///
|
|
||||||
/// In a TIFF the data offset portion of an entry is used for inline data in case the length of the
|
|
||||||
/// encoded value does not exceed the size of the offset field. Since the size of the offset field
|
|
||||||
/// depends on the file kind (4 bytes for standard TIFF, 8 bytes for BigTIFF) the interpretation of
|
|
||||||
/// this struct is only complete in combination with file metadata.
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Entry {
|
pub struct Entry {
|
||||||
type_: Type,
|
type_: Type,
|
||||||
@@ -369,14 +349,12 @@ impl ::std::fmt::Debug for Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
/// Create a new entry fit to be added to a standard TIFF IFD.
|
|
||||||
pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry {
|
pub fn new(type_: Type, count: u32, offset: [u8; 4]) -> Entry {
|
||||||
let mut entry_off = [0u8; 8];
|
let mut entry_off = [0u8; 8];
|
||||||
entry_off[..4].copy_from_slice(&offset);
|
entry_off[..4].copy_from_slice(&offset);
|
||||||
Entry::new_u64(type_, count.into(), entry_off)
|
Entry::new_u64(type_, count.into(), entry_off)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new entry with data for a Big TIFF IFD.
|
|
||||||
pub fn new_u64(type_: Type, count: u64, offset: [u8; 8]) -> Entry {
|
pub fn new_u64(type_: Type, count: u64, offset: [u8; 8]) -> Entry {
|
||||||
Entry {
|
Entry {
|
||||||
type_,
|
type_,
|
||||||
@@ -398,10 +376,7 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mem_reader for the offset/value field
|
/// Returns a mem_reader for the offset/value field
|
||||||
pub(crate) fn offset_field_reader(
|
fn r(&self, byte_order: ByteOrder) -> EndianReader<io::Cursor<Vec<u8>>> {
|
||||||
&self,
|
|
||||||
byte_order: ByteOrder,
|
|
||||||
) -> EndianReader<io::Cursor<Vec<u8>>> {
|
|
||||||
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,25 +392,43 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let bo = reader.byte_order;
|
let bo = reader.byte_order;
|
||||||
let value_bytes = self.type_.value_bytes(self.count)?;
|
|
||||||
|
let tag_size = match self.type_ {
|
||||||
|
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
||||||
|
Type::SHORT | Type::SSHORT => 2,
|
||||||
|
Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
|
||||||
|
Type::LONG8
|
||||||
|
| Type::SLONG8
|
||||||
|
| Type::DOUBLE
|
||||||
|
| Type::RATIONAL
|
||||||
|
| Type::SRATIONAL
|
||||||
|
| Type::IFD8 => 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
let value_bytes = match self.count.checked_mul(tag_size) {
|
||||||
|
Some(n) => n,
|
||||||
|
None => {
|
||||||
|
return Err(TiffError::LimitsExceeded);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Case 2: there is one value.
|
// Case 2: there is one value.
|
||||||
if self.count == 1 {
|
if self.count == 1 {
|
||||||
// 2a: the value is 5-8 bytes and we're in BigTiff mode.
|
// 2a: the value is 5-8 bytes and we're in BigTiff mode.
|
||||||
if bigtiff && value_bytes > 4 && value_bytes <= 8 {
|
if bigtiff && value_bytes > 4 && value_bytes <= 8 {
|
||||||
return Ok(match self.type_ {
|
return Ok(match self.type_ {
|
||||||
Type::LONG8 => UnsignedBig(self.offset_field_reader(bo).read_u64()?),
|
Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?),
|
||||||
Type::SLONG8 => SignedBig(self.offset_field_reader(bo).read_i64()?),
|
Type::SLONG8 => SignedBig(self.r(bo).read_i64()?),
|
||||||
Type::DOUBLE => Double(self.offset_field_reader(bo).read_f64()?),
|
Type::DOUBLE => Double(self.r(bo).read_f64()?),
|
||||||
Type::RATIONAL => {
|
Type::RATIONAL => {
|
||||||
let mut r = self.offset_field_reader(bo);
|
let mut r = self.r(bo);
|
||||||
Rational(r.read_u32()?, r.read_u32()?)
|
Rational(r.read_u32()?, r.read_u32()?)
|
||||||
}
|
}
|
||||||
Type::SRATIONAL => {
|
Type::SRATIONAL => {
|
||||||
let mut r = self.offset_field_reader(bo);
|
let mut r = self.r(bo);
|
||||||
SRational(r.read_i32()?, r.read_i32()?)
|
SRational(r.read_i32()?, r.read_i32()?)
|
||||||
}
|
}
|
||||||
Type::IFD8 => IfdBig(self.offset_field_reader(bo).read_u64()?),
|
Type::IFD8 => IfdBig(self.r(bo).read_u64()?),
|
||||||
Type::BYTE
|
Type::BYTE
|
||||||
| Type::SBYTE
|
| Type::SBYTE
|
||||||
| Type::ASCII
|
| Type::ASCII
|
||||||
@@ -454,11 +447,11 @@ impl Entry {
|
|||||||
Type::BYTE => Byte(self.offset[0]),
|
Type::BYTE => Byte(self.offset[0]),
|
||||||
Type::SBYTE => SignedByte(self.offset[0] as i8),
|
Type::SBYTE => SignedByte(self.offset[0] as i8),
|
||||||
Type::UNDEFINED => Byte(self.offset[0]),
|
Type::UNDEFINED => Byte(self.offset[0]),
|
||||||
Type::SHORT => Short(self.offset_field_reader(bo).read_u16()?),
|
Type::SHORT => Short(self.r(bo).read_u16()?),
|
||||||
Type::SSHORT => SignedShort(self.offset_field_reader(bo).read_i16()?),
|
Type::SSHORT => SignedShort(self.r(bo).read_i16()?),
|
||||||
Type::LONG => Unsigned(self.offset_field_reader(bo).read_u32()?),
|
Type::LONG => Unsigned(self.r(bo).read_u32()?),
|
||||||
Type::SLONG => Signed(self.offset_field_reader(bo).read_i32()?),
|
Type::SLONG => Signed(self.r(bo).read_i32()?),
|
||||||
Type::FLOAT => Float(self.offset_field_reader(bo).read_f32()?),
|
Type::FLOAT => Float(self.r(bo).read_f32()?),
|
||||||
Type::ASCII => {
|
Type::ASCII => {
|
||||||
if self.offset[0] == 0 {
|
if self.offset[0] == 0 {
|
||||||
Ascii("".to_string())
|
Ascii("".to_string())
|
||||||
@@ -467,28 +460,28 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::LONG8 => {
|
Type::LONG8 => {
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
UnsignedBig(reader.read_u64()?)
|
UnsignedBig(reader.read_u64()?)
|
||||||
}
|
}
|
||||||
Type::SLONG8 => {
|
Type::SLONG8 => {
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
SignedBig(reader.read_i64()?)
|
SignedBig(reader.read_i64()?)
|
||||||
}
|
}
|
||||||
Type::DOUBLE => {
|
Type::DOUBLE => {
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
Double(reader.read_f64()?)
|
Double(reader.read_f64()?)
|
||||||
}
|
}
|
||||||
Type::RATIONAL => {
|
Type::RATIONAL => {
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
Rational(reader.read_u32()?, reader.read_u32()?)
|
Rational(reader.read_u32()?, reader.read_u32()?)
|
||||||
}
|
}
|
||||||
Type::SRATIONAL => {
|
Type::SRATIONAL => {
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
SRational(reader.read_i32()?, reader.read_i32()?)
|
SRational(reader.read_i32()?, reader.read_i32()?)
|
||||||
}
|
}
|
||||||
Type::IFD => Ifd(self.offset_field_reader(bo).read_u32()?),
|
Type::IFD => Ifd(self.r(bo).read_u32()?),
|
||||||
Type::IFD8 => {
|
Type::IFD8 => {
|
||||||
reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?;
|
reader.goto_offset(self.r(bo).read_u32()?.into())?;
|
||||||
IfdBig(reader.read_u64()?)
|
IfdBig(reader.read_u64()?)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -519,7 +512,7 @@ impl Entry {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
Type::SHORT => {
|
Type::SHORT => {
|
||||||
let mut r = self.offset_field_reader(bo);
|
let mut r = self.r(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(Short(r.read_u16()?));
|
v.push(Short(r.read_u16()?));
|
||||||
@@ -527,7 +520,7 @@ impl Entry {
|
|||||||
return Ok(List(v));
|
return Ok(List(v));
|
||||||
}
|
}
|
||||||
Type::SSHORT => {
|
Type::SSHORT => {
|
||||||
let mut r = self.offset_field_reader(bo);
|
let mut r = self.r(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(SignedShort(r.read_i16()?));
|
v.push(SignedShort(r.read_i16()?));
|
||||||
@@ -535,7 +528,7 @@ impl Entry {
|
|||||||
return Ok(List(v));
|
return Ok(List(v));
|
||||||
}
|
}
|
||||||
Type::LONG => {
|
Type::LONG => {
|
||||||
let mut r = self.offset_field_reader(bo);
|
let mut r = self.r(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(Unsigned(r.read_u32()?));
|
v.push(Unsigned(r.read_u32()?));
|
||||||
@@ -543,7 +536,7 @@ impl Entry {
|
|||||||
return Ok(List(v));
|
return Ok(List(v));
|
||||||
}
|
}
|
||||||
Type::SLONG => {
|
Type::SLONG => {
|
||||||
let mut r = self.offset_field_reader(bo);
|
let mut r = self.r(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(Signed(r.read_i32()?));
|
v.push(Signed(r.read_i32()?));
|
||||||
@@ -551,7 +544,7 @@ impl Entry {
|
|||||||
return Ok(List(v));
|
return Ok(List(v));
|
||||||
}
|
}
|
||||||
Type::FLOAT => {
|
Type::FLOAT => {
|
||||||
let mut r = self.offset_field_reader(bo);
|
let mut r = self.r(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(Float(r.read_f32()?));
|
v.push(Float(r.read_f32()?));
|
||||||
@@ -559,7 +552,7 @@ impl Entry {
|
|||||||
return Ok(List(v));
|
return Ok(List(v));
|
||||||
}
|
}
|
||||||
Type::IFD => {
|
Type::IFD => {
|
||||||
let mut r = self.offset_field_reader(bo);
|
let mut r = self.r(bo);
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
for _ in 0..self.count {
|
for _ in 0..self.count {
|
||||||
v.push(Ifd(r.read_u32()?));
|
v.push(Ifd(r.read_u32()?));
|
||||||
@@ -578,149 +571,74 @@ impl Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Case 4: there is more than one value, and it doesn't fit in the offset field.
|
// Case 4: there is more than one value, and it doesn't fit in the offset field.
|
||||||
let mut v;
|
|
||||||
self.set_reader_offset_relative(bigtiff, reader, 0)?;
|
|
||||||
|
|
||||||
match self.type_ {
|
match self.type_ {
|
||||||
Type::BYTE | Type::UNDEFINED => {
|
// TODO check if this could give wrong results
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
// at a different endianess of file/computer.
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
v.extend(bytes.iter().copied().map(Byte))
|
let mut buf = [0; 1];
|
||||||
})
|
reader.inner().read_exact(&mut buf)?;
|
||||||
}
|
Ok(UnsignedBig(u64::from(buf[0])))
|
||||||
Type::SBYTE => {
|
}),
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
Ok(SignedBig(i64::from(reader.read_i8()?)))
|
||||||
v.extend(bytes.iter().copied().map(|v| SignedByte(v as i8)))
|
}),
|
||||||
})
|
Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
}
|
Ok(Short(reader.read_u16()?))
|
||||||
Type::SHORT => {
|
}),
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
Ok(SignedShort(reader.read_i16()?))
|
||||||
v.extend(
|
}),
|
||||||
bytes
|
Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
.chunks_exact(2)
|
Ok(Unsigned(reader.read_u32()?))
|
||||||
.map(|ch| Short(u16::from_ne_bytes(ch.try_into().unwrap()))),
|
}),
|
||||||
)
|
Type::SLONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
})
|
Ok(Signed(reader.read_i32()?))
|
||||||
}
|
}),
|
||||||
Type::SSHORT => {
|
Type::FLOAT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
Ok(Float(reader.read_f32()?))
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
}),
|
||||||
v.extend(
|
Type::DOUBLE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
bytes
|
Ok(Double(reader.read_f64()?))
|
||||||
.chunks_exact(2)
|
}),
|
||||||
.map(|ch| SignedShort(i16::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::LONG => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|ch| Unsigned(u32::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::SLONG => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|ch| Signed(i32::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::FLOAT => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|ch| Float(f32::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::DOUBLE => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(8)
|
|
||||||
.map(|ch| Double(f64::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::RATIONAL => {
|
Type::RATIONAL => {
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
Ok(Rational(reader.read_u32()?, reader.read_u32()?))
|
||||||
v.extend(bytes.chunks_exact(8).map(|ch| {
|
|
||||||
Rational(
|
|
||||||
u32::from_ne_bytes(ch[..4].try_into().unwrap()),
|
|
||||||
u32::from_ne_bytes(ch[4..].try_into().unwrap()),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Type::SRATIONAL => {
|
Type::SRATIONAL => {
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
Ok(SRational(reader.read_i32()?, reader.read_i32()?))
|
||||||
v.extend(bytes.chunks_exact(8).map(|ch| {
|
|
||||||
SRational(
|
|
||||||
i32::from_ne_bytes(ch[..4].try_into().unwrap()),
|
|
||||||
i32::from_ne_bytes(ch[4..].try_into().unwrap()),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Type::LONG8 => {
|
Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
Ok(UnsignedBig(reader.read_u64()?))
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
}),
|
||||||
v.extend(
|
Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
bytes
|
Ok(SignedBig(reader.read_i64()?))
|
||||||
.chunks_exact(8)
|
}),
|
||||||
.map(|ch| UnsignedBig(u64::from_ne_bytes(ch.try_into().unwrap()))),
|
Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
)
|
Ok(Ifd(reader.read_u32()?))
|
||||||
})
|
}),
|
||||||
}
|
Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
Type::SLONG8 => {
|
Ok(IfdBig(reader.read_u64()?))
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
}),
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
Type::UNDEFINED => {
|
||||||
v.extend(
|
self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| {
|
||||||
bytes
|
let mut buf = [0; 1];
|
||||||
.chunks_exact(8)
|
reader.inner().read_exact(&mut buf)?;
|
||||||
.map(|ch| SignedBig(i64::from_ne_bytes(ch.try_into().unwrap()))),
|
Ok(Byte(buf[0]))
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::IFD => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(4)
|
|
||||||
.map(|ch| Ifd(u32::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Type::IFD8 => {
|
|
||||||
v = Self::vec_with_capacity(self.count, limits)?;
|
|
||||||
self.decode_values(self.count, self.type_, reader, |bytes| {
|
|
||||||
v.extend(
|
|
||||||
bytes
|
|
||||||
.chunks_exact(8)
|
|
||||||
.map(|ch| IfdBig(u64::from_ne_bytes(ch.try_into().unwrap()))),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Type::ASCII => {
|
Type::ASCII => {
|
||||||
let n = usize::try_from(self.count)?;
|
let n = usize::try_from(self.count)?;
|
||||||
|
|
||||||
if n > limits.decoding_buffer_size {
|
if n > limits.decoding_buffer_size {
|
||||||
return Err(dbg!(TiffError::LimitsExceeded));
|
return Err(TiffError::LimitsExceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
if bigtiff {
|
||||||
|
reader.goto_offset(self.r(bo).read_u64()?)?
|
||||||
|
} else {
|
||||||
|
reader.goto_offset(self.r(bo).read_u32()?.into())?
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut out = vec![0; n];
|
let mut out = vec![0; n];
|
||||||
@@ -729,199 +647,43 @@ impl Entry {
|
|||||||
if let Some(first) = out.iter().position(|&b| b == 0) {
|
if let Some(first) = out.iter().position(|&b| b == 0) {
|
||||||
out.truncate(first);
|
out.truncate(first);
|
||||||
}
|
}
|
||||||
|
Ok(Ascii(String::from_utf8(out)?))
|
||||||
return Ok(Ascii(String::from_utf8(out)?));
|
|
||||||
}
|
}
|
||||||
}?;
|
}
|
||||||
|
|
||||||
Ok(List(v))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn buffered_value<R: Read + Seek>(
|
#[inline]
|
||||||
|
fn decode_offset<R, F>(
|
||||||
&self,
|
&self,
|
||||||
buf: &mut ValueBuffer,
|
|
||||||
limits: &super::Limits,
|
|
||||||
bigtiff: bool,
|
|
||||||
reader: &mut EndianReader<R>,
|
|
||||||
) -> TiffResult<()> {
|
|
||||||
if self.count == 0 {
|
|
||||||
buf.assume_type(self.type_, 0, reader.byte_order);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let value_bytes = self.buffer_with_capacity(buf, limits)?;
|
|
||||||
|
|
||||||
// Case 1: the value fits in the offset field.
|
|
||||||
if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
|
|
||||||
let src = &self.offset[..value_bytes];
|
|
||||||
buf.raw_bytes_mut()[..value_bytes].copy_from_slice(src);
|
|
||||||
buf.assume_type(self.type_, self.count, reader.byte_order);
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case 2: the value is stored in the reader at an offset.
|
|
||||||
self.set_reader_offset_relative(bigtiff, reader, 0)?;
|
|
||||||
|
|
||||||
// In case of an error we set the type and endianess.
|
|
||||||
buf.assume_type(self.type_, 0, reader.byte_order);
|
|
||||||
let target = &mut buf.raw_bytes_mut()[..value_bytes];
|
|
||||||
// FIXME: if the read fails we have already grown to full size, which is not great.
|
|
||||||
reader.inner().read_exact(target)?;
|
|
||||||
buf.assume_type(self.type_, self.count, reader.byte_order);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn raw_value_at<R: Read + Seek>(
|
|
||||||
&self,
|
|
||||||
buf: &mut [u8],
|
|
||||||
bigtiff: bool,
|
|
||||||
reader: &mut EndianReader<R>,
|
|
||||||
at: u64,
|
|
||||||
) -> TiffResult<usize> {
|
|
||||||
if self.count == 0 {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have no limits to handle, we do not allocate.
|
|
||||||
let value_bytes = self.type_.value_bytes(self.count)?;
|
|
||||||
|
|
||||||
// No bytes to fill into the buffer.
|
|
||||||
if at >= value_bytes {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case 1: the value fits in the offset field.
|
|
||||||
if value_bytes <= 4 || bigtiff && value_bytes <= 8 {
|
|
||||||
// `at < value_bytes` and `value_bytes <= 8` so casting is mathematical
|
|
||||||
let src = &self.offset[..value_bytes as usize][at as usize..];
|
|
||||||
let len = src.len().min(buf.len());
|
|
||||||
buf[..len].copy_from_slice(&src[..len]);
|
|
||||||
return Ok(value_bytes as usize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case 2: the value is stored in the reader at an offset. We will find the offset
|
|
||||||
// encoded in the entry, apply the relative start position and seek there.
|
|
||||||
self.set_reader_offset_relative(bigtiff, reader, at)?;
|
|
||||||
|
|
||||||
let remainder = value_bytes - at;
|
|
||||||
let len = usize::try_from(remainder)
|
|
||||||
.unwrap_or(usize::MAX)
|
|
||||||
.min(buf.len());
|
|
||||||
|
|
||||||
let target = &mut buf[..len];
|
|
||||||
reader.inner().read_exact(target)?;
|
|
||||||
|
|
||||||
// Design note: in a previous draft we would consume the rest of the bytes of this value
|
|
||||||
// here (into a stack buffer if need be) to verify the stream itself. But in the end we
|
|
||||||
// have `Seek` so we better verify this by seeking over the rest of the bytes, finding if
|
|
||||||
// the stream continues that far. Even that is maybe bad if we wanted to provide a
|
|
||||||
// async-adaptor that `WouldBlock` errors to fill back a read window then the seek is
|
|
||||||
// poison to that, too.
|
|
||||||
|
|
||||||
// So a really simple choice: The caller is responsible for handling the fact that this did
|
|
||||||
// not verify the whole value. Attempt a 1-byte read at the end of the value instead?
|
|
||||||
Ok(len)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns `Ok(bytes)` if our value's bytes through type and count fit into `usize` and are
|
|
||||||
// within the limits. Extends the buffer to that many bytes.
|
|
||||||
fn buffer_with_capacity(
|
|
||||||
&self,
|
|
||||||
buf: &mut ValueBuffer,
|
|
||||||
limits: &super::Limits,
|
|
||||||
) -> TiffResult<usize> {
|
|
||||||
let bytes = self.type_.value_bytes(self.count())?;
|
|
||||||
|
|
||||||
let allowed_length = usize::try_from(bytes)
|
|
||||||
.ok()
|
|
||||||
.filter(|&n| n <= limits.decoding_buffer_size)
|
|
||||||
.ok_or(TiffError::LimitsExceeded)?;
|
|
||||||
|
|
||||||
buf.prepare_length(allowed_length);
|
|
||||||
|
|
||||||
Ok(allowed_length)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vec_with_capacity(
|
|
||||||
value_count: u64,
|
value_count: u64,
|
||||||
|
bo: ByteOrder,
|
||||||
|
bigtiff: bool,
|
||||||
limits: &super::Limits,
|
limits: &super::Limits,
|
||||||
) -> Result<Vec<Value>, TiffError> {
|
reader: &mut EndianReader<R>,
|
||||||
|
decode_fn: F,
|
||||||
|
) -> TiffResult<Value>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
F: Fn(&mut EndianReader<R>) -> TiffResult<Value>,
|
||||||
|
{
|
||||||
let value_count = usize::try_from(value_count)?;
|
let value_count = usize::try_from(value_count)?;
|
||||||
|
|
||||||
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
|
if value_count > limits.decoding_buffer_size / mem::size_of::<Value>() {
|
||||||
return Err(TiffError::LimitsExceeded);
|
return Err(TiffError::LimitsExceeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Vec::with_capacity(value_count))
|
let mut v = Vec::with_capacity(value_count);
|
||||||
}
|
|
||||||
|
|
||||||
/// Seek to an offset within a value stored in the offset defined by this entry.
|
|
||||||
fn set_reader_offset_relative<R>(
|
|
||||||
&self,
|
|
||||||
bigtiff: bool,
|
|
||||||
reader: &mut EndianReader<R>,
|
|
||||||
at: u64,
|
|
||||||
) -> TiffResult<()>
|
|
||||||
where
|
|
||||||
R: Read + Seek,
|
|
||||||
{
|
|
||||||
let bo = reader.byte_order;
|
|
||||||
|
|
||||||
let offset = if bigtiff {
|
let offset = if bigtiff {
|
||||||
self.offset_field_reader(bo).read_u64()?
|
self.r(bo).read_u64()?
|
||||||
} else {
|
} else {
|
||||||
self.offset_field_reader(bo).read_u32()?.into()
|
self.r(bo).read_u32()?.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: `at` should be within `self.type_.value_bytes(self.count)` and that itself should
|
|
||||||
// be within the bounds of the stream. But we do not check this eagerly so this below will
|
|
||||||
// fail sometimes differently for exotic streams, depending on the method by which we read
|
|
||||||
// (at once or through multiple raw into-byte-slice reads).
|
|
||||||
let offset = offset.checked_add(at).ok_or(TiffError::FormatError(
|
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
|
||||||
))?;
|
|
||||||
|
|
||||||
reader.goto_offset(offset)?;
|
reader.goto_offset(offset)?;
|
||||||
|
|
||||||
Ok(())
|
for _ in 0..value_count {
|
||||||
}
|
v.push(decode_fn(reader)?)
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn decode_values<R, F>(
|
|
||||||
&self,
|
|
||||||
value_count: u64,
|
|
||||||
type_: Type,
|
|
||||||
reader: &mut EndianReader<R>,
|
|
||||||
mut collect: F,
|
|
||||||
) -> TiffResult<()>
|
|
||||||
where
|
|
||||||
R: Read + Seek,
|
|
||||||
F: FnMut(&[u8]),
|
|
||||||
{
|
|
||||||
let mut total_bytes = type_.value_bytes(value_count)?;
|
|
||||||
let mut buffer = [0u8; 512];
|
|
||||||
|
|
||||||
let buf_unit = usize::from(type_.byte_len());
|
|
||||||
let mul_of_ty = buffer.len() / buf_unit * buf_unit;
|
|
||||||
|
|
||||||
let cls = type_.endian_bytes();
|
|
||||||
let native = ByteOrder::native();
|
|
||||||
|
|
||||||
while total_bytes > 0 {
|
|
||||||
// `now <= mul_of_ty < 512` so casting is mathematical
|
|
||||||
let now = total_bytes.min(mul_of_ty as u64);
|
|
||||||
total_bytes -= now;
|
|
||||||
|
|
||||||
let buffer = &mut buffer[..now as usize];
|
|
||||||
reader.inner().read_exact(buffer)?;
|
|
||||||
|
|
||||||
reader.byte_order.convert_endian_bytes(cls, buffer, native);
|
|
||||||
collect(buffer);
|
|
||||||
}
|
}
|
||||||
|
Ok(List(v))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+85
-512
@@ -4,14 +4,13 @@ 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, ExtraSamples, PhotometricInterpretation, PlanarConfiguration, Predictor,
|
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag,
|
||||||
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,11 +68,6 @@ 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,
|
||||||
@@ -85,58 +79,6 @@ 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 {
|
||||||
@@ -144,7 +86,19 @@ impl Image {
|
|||||||
decoder: &mut ValueReader<R>,
|
decoder: &mut ValueReader<R>,
|
||||||
ifd: Directory,
|
ifd: Directory,
|
||||||
) -> TiffResult<Image> {
|
) -> TiffResult<Image> {
|
||||||
let mut tag_reader = TagReader { decoder, ifd: &ifd };
|
let img = Self::from_ref(decoder, &ifd)?;
|
||||||
|
|
||||||
|
Ok(Image {
|
||||||
|
ifd: Some(ifd),
|
||||||
|
..img
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_ref<R: Read + Seek>(
|
||||||
|
decoder: &mut ValueReader<R>,
|
||||||
|
ifd: &Directory,
|
||||||
|
) -> TiffResult<Image> {
|
||||||
|
let mut tag_reader = TagReader { decoder, ifd };
|
||||||
|
|
||||||
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
||||||
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
||||||
@@ -191,30 +145,10 @@ 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
|
||||||
@@ -222,15 +156,12 @@ impl Image {
|
|||||||
.map(SampleFormat::from_u16_exhaustive)
|
.map(SampleFormat::from_u16_exhaustive)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let Some(format) = sample_format.first().copied() else {
|
|
||||||
// Reject empty sample formats
|
|
||||||
return Err(TiffFormatError::InvalidTagValueType(Tag::SampleFormat).into());
|
|
||||||
};
|
|
||||||
// TODO: for now, only homogenous formats across samples are supported.
|
// TODO: for now, only homogenous formats across samples are supported.
|
||||||
if !sample_format.iter().all(|&s| s == format) {
|
if !sample_format.windows(2).all(|s| s[0] == s[1]) {
|
||||||
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
|
return Err(TiffUnsupportedError::UnsupportedSampleFormat(sample_format).into());
|
||||||
}
|
}
|
||||||
format
|
|
||||||
|
sample_format[0]
|
||||||
}
|
}
|
||||||
None => SampleFormat::Uint,
|
None => SampleFormat::Uint,
|
||||||
};
|
};
|
||||||
@@ -241,7 +172,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() != usize::from(samples) && bits_per_sample.len() != 1 {
|
if bits_per_sample.len() != samples.into() && bits_per_sample.len() != 1 {
|
||||||
return Err(TiffError::FormatError(
|
return Err(TiffError::FormatError(
|
||||||
TiffFormatError::InconsistentSizesEncountered,
|
TiffFormatError::InconsistentSizesEncountered,
|
||||||
));
|
));
|
||||||
@@ -276,25 +207,6 @@ 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,
|
||||||
@@ -388,13 +300,11 @@ impl Image {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(Image {
|
Ok(Image {
|
||||||
ifd: Some(ifd),
|
ifd: None,
|
||||||
width,
|
width,
|
||||||
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,
|
||||||
@@ -406,24 +316,19 @@ 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.photometric_samples {
|
PhotometricInterpretation::RGB => match self.samples {
|
||||||
3 => Ok(if is_alpha_extra_samples {
|
3 => Ok(ColorType::RGB(self.bits_per_sample)),
|
||||||
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,
|
||||||
@@ -431,12 +336,8 @@ impl Image {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
PhotometricInterpretation::CMYK => match self.photometric_samples {
|
PhotometricInterpretation::CMYK => match self.samples {
|
||||||
4 => Ok(if is_alpha_extra_samples {
|
4 => Ok(ColorType::CMYK(self.bits_per_sample)),
|
||||||
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(
|
||||||
@@ -445,7 +346,7 @@ impl Image {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
PhotometricInterpretation::YCbCr => match self.photometric_samples {
|
PhotometricInterpretation::YCbCr => match self.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(
|
||||||
@@ -454,13 +355,7 @@ impl Image {
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
// TODO: treatment of WhiteIsZero is not quite consistent with `invert_colors` that is
|
|
||||||
// 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.
|
|
||||||
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 {
|
||||||
@@ -469,70 +364,39 @@ impl Image {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ```
|
// TODO: this is bad we should not fail at this point
|
||||||
// struct IccLab /* Interpretation 9* {
|
PhotometricInterpretation::RGBPalette
|
||||||
// pub L: u8, // SampleFormat::Uint
|
| PhotometricInterpretation::TransparencyMask
|
||||||
// pub a: u8, // SampleFormat::Uint, defined as TiffLab::a + 128
|
| PhotometricInterpretation::CIELab => Err(TiffError::UnsupportedError(
|
||||||
// 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(
|
||||||
PhotometricInterpretation::CIELab,
|
self.photometric_interpretation,
|
||||||
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,
|
||||||
compressed_length: u64,
|
compressed_length: u64,
|
||||||
// FIXME: these should be `expect` attributes or we choose another way of passing them.
|
#[allow(unused_variables)] jpeg_tables: Option<&[u8]>,
|
||||||
#[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>,
|
|
||||||
#[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32),
|
|
||||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||||
Ok(match compression_method {
|
Ok(match compression_method {
|
||||||
CompressionMethod::None => Box::new(reader),
|
CompressionMethod::None => Box::new(reader),
|
||||||
@@ -584,14 +448,13 @@ impl Image {
|
|||||||
let mut jpeg_data = Vec::new();
|
let mut jpeg_data = Vec::new();
|
||||||
jpeg_reader.read_to_end(&mut jpeg_data)?;
|
jpeg_reader.read_to_end(&mut jpeg_data)?;
|
||||||
|
|
||||||
let mut decoder =
|
let mut decoder = zune_jpeg::JpegDecoder::new(jpeg_data);
|
||||||
zune_jpeg::JpegDecoder::new(zune_core::bytestream::ZCursor::new(jpeg_data));
|
|
||||||
let mut options: zune_core::options::DecoderOptions = Default::default();
|
let mut options: zune_core::options::DecoderOptions = Default::default();
|
||||||
|
|
||||||
// Disable color conversion by setting the output colorspace to the input
|
// Disable color conversion by setting the output colorspace to the input
|
||||||
// colorspace.
|
// colorspace.
|
||||||
decoder.decode_headers()?;
|
decoder.decode_headers()?;
|
||||||
if let Some(colorspace) = decoder.input_colorspace() {
|
if let Some(colorspace) = decoder.get_input_colorspace() {
|
||||||
options = options.jpeg_set_out_colorspace(colorspace);
|
options = options.jpeg_set_out_colorspace(colorspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,12 +464,6 @@ impl Image {
|
|||||||
|
|
||||||
Box::new(Cursor::new(data))
|
Box::new(Cursor::new(data))
|
||||||
}
|
}
|
||||||
#[cfg(feature = "fax")]
|
|
||||||
CompressionMethod::Fax4 => Box::new(super::stream::Group4Reader::new(
|
|
||||||
dimensions,
|
|
||||||
reader,
|
|
||||||
compressed_length,
|
|
||||||
)?),
|
|
||||||
method => {
|
method => {
|
||||||
return Err(TiffError::UnsupportedError(
|
return Err(TiffError::UnsupportedError(
|
||||||
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
||||||
@@ -622,25 +479,18 @@ 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) -> u16 {
|
pub(crate) fn samples_per_pixel(&self) -> usize {
|
||||||
match self.planar_config {
|
match self.planar_config {
|
||||||
PlanarConfiguration::Chunky => self.samples,
|
PlanarConfiguration::Chunky => self.samples.into(),
|
||||||
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) -> u16 {
|
pub(crate) fn strips_per_pixel(&self) -> usize {
|
||||||
match self.planar_config {
|
match self.planar_config {
|
||||||
PlanarConfiguration::Chunky => 1,
|
PlanarConfiguration::Chunky => 1,
|
||||||
PlanarConfiguration::Planar => self.samples,
|
PlanarConfiguration::Planar => self.samples.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,107 +528,14 @@ 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 rows_per_strip = dims.1;
|
let strip_attrs = self.strip_decoder.as_ref().unwrap();
|
||||||
let strips_per_band = self.height.div_ceil(rows_per_strip);
|
let strips_per_band =
|
||||||
|
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))
|
||||||
@@ -807,7 +564,7 @@ impl Image {
|
|||||||
&self,
|
&self,
|
||||||
reader: &mut ValueReader<impl Read>,
|
reader: &mut ValueReader<impl Read>,
|
||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
layout: &ReadoutLayout,
|
output_row_stride: usize,
|
||||||
chunk_index: u32,
|
chunk_index: u32,
|
||||||
) -> TiffResult<()> {
|
) -> TiffResult<()> {
|
||||||
let ValueReader {
|
let ValueReader {
|
||||||
@@ -817,10 +574,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 = layout.color;
|
let color_type = self.colortype()?;
|
||||||
|
|
||||||
match color_type {
|
match color_type {
|
||||||
ColorType::RGB(n)
|
ColorType::RGB(n)
|
||||||
| ColorType::RGBA(n)
|
| ColorType::RGBA(n)
|
||||||
@@ -882,7 +639,6 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -890,39 +646,33 @@ 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_bytes: usize = layout.tiff_row_bytes;
|
let chunk_row_bits = (u64::from(chunk_dims.0) * u64::from(self.bits_per_sample))
|
||||||
let data_row_bytes: usize = layout.chunk_row_bytes(data_dims.0)?;
|
.checked_mul(samples as u64)
|
||||||
|
.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!(layout.minimum_row_stride >= data_row_bytes);
|
assert!(output_row_stride >= data_row_bytes);
|
||||||
assert!(buf.len() >= layout.row_stride * (data_dims.1 as usize - 1) + data_row_bytes);
|
assert!(buf.len() >= output_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.inner(),
|
reader,
|
||||||
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,
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if is_output_chunk_rows && is_all_bits {
|
if output_row_stride == chunk_row_bytes {
|
||||||
// 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)?;
|
||||||
|
|
||||||
@@ -935,15 +685,14 @@ 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);
|
||||||
}
|
}
|
||||||
} else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint {
|
} else if chunk_row_bytes > data_row_bytes && self.predictor == Predictor::FloatingPoint {
|
||||||
// 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(layout.row_stride).take(data_dims.1 as usize) {
|
for row in buf.chunks_mut(output_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];
|
||||||
@@ -954,19 +703,14 @@ impl Image {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if is_all_bits {
|
} else {
|
||||||
// We read row-by-row but each row fits in its output buffer.
|
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) {
|
|
||||||
let row = &mut row[..data_row_bytes];
|
let row = &mut row[..data_row_bytes];
|
||||||
let used = data_row_bytes.min(chunk_row_bytes);
|
reader.read_exact(row)?;
|
||||||
|
|
||||||
// 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)?;
|
||||||
@@ -980,183 +724,12 @@ impl Image {
|
|||||||
byte_order,
|
byte_order,
|
||||||
predictor,
|
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);
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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 {
|
|
||||||
super::invert_colors(row, color_type, self.sample_format)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+428
-757
File diff suppressed because it is too large.
Load diff
+23
-101
@@ -1,7 +1,15 @@
|
|||||||
//! All IO functionality needed for TIFF decoding
|
//! All IO functionality needed for TIFF decoding
|
||||||
|
|
||||||
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
||||||
|
|
||||||
pub use crate::tags::ByteOrder;
|
/// Byte order of the TIFF file.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum ByteOrder {
|
||||||
|
/// little endian byte order
|
||||||
|
LittleEndian,
|
||||||
|
/// big endian byte order
|
||||||
|
BigEndian,
|
||||||
|
}
|
||||||
|
|
||||||
/// Reader that is aware of the byte order.
|
/// Reader that is aware of the byte order.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -38,6 +46,17 @@ impl<R: Read> EndianReader<R> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reads an i8
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn read_i8(&mut self) -> Result<i8, io::Error> {
|
||||||
|
let mut n = [0u8; 1];
|
||||||
|
self.reader.read_exact(&mut n)?;
|
||||||
|
Ok(match self.byte_order {
|
||||||
|
ByteOrder::LittleEndian => i8::from_le_bytes(n),
|
||||||
|
ByteOrder::BigEndian => i8::from_be_bytes(n),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads an i16
|
/// Reads an i16
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn read_i16(&mut self) -> Result<i16, io::Error> {
|
pub fn read_i16(&mut self) -> Result<i16, io::Error> {
|
||||||
@@ -168,6 +187,9 @@ impl<R: Read> Read for LZWReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(weezl::LzwStatus::NoProgress) => {
|
Ok(weezl::LzwStatus::NoProgress) => {
|
||||||
|
assert_eq!(result.consumed_in, 0);
|
||||||
|
assert_eq!(result.consumed_out, 0);
|
||||||
|
assert!(self.reader.buffer().is_empty());
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::UnexpectedEof,
|
io::ErrorKind::UnexpectedEof,
|
||||||
"no lzw end code found",
|
"no lzw end code found",
|
||||||
@@ -254,106 +276,6 @@ impl<R: Read> Read for PackBitsReader<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "fax")]
|
|
||||||
pub struct Group4Reader<R: Read> {
|
|
||||||
decoder: fax34::decoder::Group4Decoder<io::Bytes<io::BufReader<io::Take<R>>>>,
|
|
||||||
line_buf: io::Cursor<Vec<u8>>,
|
|
||||||
height: u16,
|
|
||||||
width: u16,
|
|
||||||
y: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "fax")]
|
|
||||||
impl<R: Read> Group4Reader<R> {
|
|
||||||
pub fn new(
|
|
||||||
dimensions: (u32, u32),
|
|
||||||
reader: R,
|
|
||||||
compressed_length: u64,
|
|
||||||
) -> crate::TiffResult<Self> {
|
|
||||||
let width = u16::try_from(dimensions.0)?;
|
|
||||||
let height = u16::try_from(dimensions.1)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
decoder: fax34::decoder::Group4Decoder::new(
|
|
||||||
io::BufReader::new(reader.take(compressed_length)).bytes(),
|
|
||||||
width,
|
|
||||||
)?,
|
|
||||||
line_buf: io::Cursor::new(Vec::with_capacity(width.into())),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
y: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "fax")]
|
|
||||||
impl<R: Read> Read for Group4Reader<R> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
||||||
// Either we have not read any line or we are at the end of a line.
|
|
||||||
if self.line_buf.position() as usize == self.line_buf.get_ref().len()
|
|
||||||
&& self.y < self.height
|
|
||||||
{
|
|
||||||
let next = self.decoder.advance().map_err(std::io::Error::other)?;
|
|
||||||
|
|
||||||
match next {
|
|
||||||
fax34::decoder::DecodeStatus::End => (),
|
|
||||||
fax34::decoder::DecodeStatus::Incomplete => {
|
|
||||||
self.y += 1;
|
|
||||||
|
|
||||||
// We known `transitions` yields exactly `self.width` items (per doc).
|
|
||||||
// FIXME: performance. We do not need an individual pixel iterator, filling
|
|
||||||
// memory especially for long runs can be much quicker. The `transitions` are
|
|
||||||
// the positions at which each run-length ends, i.e. the prefix sum of run
|
|
||||||
// lengths. Runs in fax4 start with white.
|
|
||||||
let transitions = fax34::decoder::pels(self.decoder.transition(), self.width);
|
|
||||||
|
|
||||||
let buffer = self.line_buf.get_mut();
|
|
||||||
buffer.resize(usize::from(self.width).div_ceil(8), 0u8);
|
|
||||||
|
|
||||||
let target = &mut buffer[..];
|
|
||||||
|
|
||||||
// Note: it may seem strange to treat black as 0b1 and white as 0b0 despite all
|
|
||||||
// our streams by default decoding as-if PhotometricInterpretation::BlackIsMin.
|
|
||||||
// This is however consistent with libtiff. It seems that fax4's "White"
|
|
||||||
// differs from what libtiff thinks of as "White". For content, a line of data
|
|
||||||
// is in runlength encoding of white-black-white-black always starting with
|
|
||||||
// white. In libtiff, the loop always does both colors in one go and the
|
|
||||||
// structure is:
|
|
||||||
//
|
|
||||||
// ```
|
|
||||||
// for (; runs < erun; runs += 2)
|
|
||||||
// // white run
|
|
||||||
// do { *lp++ = 0L; } while (…)
|
|
||||||
// // black run
|
|
||||||
// do { *lp++ = -1L; } while (…)
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// So indeed the Fax4::White run is implemented by filling with zeros.
|
|
||||||
let mut bits = transitions.map(|c| match c {
|
|
||||||
fax34::Color::Black => true,
|
|
||||||
fax34::Color::White => false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assemble bits in MSB as per our library representation for buffer.
|
|
||||||
for byte in target {
|
|
||||||
let mut val = 0;
|
|
||||||
|
|
||||||
for (idx, bit) in bits.by_ref().take(8).enumerate() {
|
|
||||||
val |= u8::from(bit) << (7 - idx % 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
*byte = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.line_buf.set_position(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.line_buf.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use std::io::{Read, Seek};
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
use crate::{
|
use crate::{tags::Tag, Directory};
|
||||||
tags::{Tag, ValueBuffer},
|
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||||
Directory, TiffError, TiffFormatError, TiffResult,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::ifd::{Entry, Value};
|
use super::ifd::{Entry, Value};
|
||||||
use super::ValueReader;
|
use super::ValueReader;
|
||||||
@@ -16,25 +14,12 @@ pub(crate) struct TagReader<'lt, R: EntryDecoder + ?Sized> {
|
|||||||
pub(crate) trait EntryDecoder {
|
pub(crate) trait EntryDecoder {
|
||||||
/// Turn an entry into a value by fetching the required bytes in the stream.
|
/// Turn an entry into a value by fetching the required bytes in the stream.
|
||||||
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value>;
|
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value>;
|
||||||
/// Fill a `ValueBuffer` by fetching the required bytes.
|
|
||||||
fn entry_buf(&mut self, entry: &Entry, _: &mut ValueBuffer) -> TiffResult<()>;
|
|
||||||
/// Fill the prefix of the buffer and return the actual bytes. Start interpreting the inner
|
|
||||||
/// value at `offset`.
|
|
||||||
fn entry_raw(&mut self, entry: &Entry, _: &mut [u8], offset: u64) -> TiffResult<usize>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Seek + Read> EntryDecoder for ValueReader<R> {
|
impl<R: Seek + Read> EntryDecoder for ValueReader<R> {
|
||||||
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value> {
|
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value> {
|
||||||
entry.val(&self.limits, self.bigtiff, &mut self.reader)
|
entry.val(&self.limits, self.bigtiff, &mut self.reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entry_buf(&mut self, entry: &Entry, buf: &mut ValueBuffer) -> TiffResult<()> {
|
|
||||||
entry.buffered_value(buf, &self.limits, self.bigtiff, &mut self.reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn entry_raw(&mut self, entry: &Entry, buf: &mut [u8], offset: u64) -> TiffResult<usize> {
|
|
||||||
entry.raw_value_at(buf, self.bigtiff, &mut self.reader, offset)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, R: EntryDecoder + ?Sized> TagReader<'a, R> {
|
impl<'a, R: EntryDecoder + ?Sized> TagReader<'a, R> {
|
||||||
@@ -47,39 +32,6 @@ impl<'a, R: EntryDecoder + ?Sized> TagReader<'a, R> {
|
|||||||
self.decoder.entry_val(&entry).map(Some)
|
self.decoder.entry_val(&entry).map(Some)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find a tag and fill the provided buffer with its raw data.
|
|
||||||
///
|
|
||||||
/// Returns the entry of the tag if found. This signals that the buffer was overwritten.
|
|
||||||
/// Otherwise the buffer is preserved as-is.
|
|
||||||
pub(crate) fn find_tag_buf(
|
|
||||||
&mut self,
|
|
||||||
tag: Tag,
|
|
||||||
buf: &mut ValueBuffer,
|
|
||||||
) -> TiffResult<Option<Entry>> {
|
|
||||||
let entry = match self.ifd.get(tag) {
|
|
||||||
None => return Ok(None),
|
|
||||||
Some(entry) => entry.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.decoder.entry_buf(&entry, buf)?;
|
|
||||||
Ok(Some(entry))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn find_tag_raw(
|
|
||||||
&mut self,
|
|
||||||
tag: Tag,
|
|
||||||
buf: &mut [u8],
|
|
||||||
at: u64,
|
|
||||||
) -> TiffResult<Option<usize>> {
|
|
||||||
let entry = match self.ifd.get(tag) {
|
|
||||||
None => return Ok(None),
|
|
||||||
Some(entry) => entry.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let len = self.decoder.entry_raw(&entry, buf, at)?;
|
|
||||||
Ok(Some(len))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
|
pub(crate) fn require_tag(&mut self, tag: Tag) -> TiffResult<Value> {
|
||||||
match self.find_tag(tag)? {
|
match self.find_tag(tag)? {
|
||||||
Some(val) => Ok(val),
|
Some(val) => Ok(val),
|
||||||
|
|||||||
+1
-31
@@ -29,7 +29,7 @@ pub struct Directory {
|
|||||||
impl Directory {
|
impl Directory {
|
||||||
/// Create a directory in an initial state without entries. Note that an empty directory can
|
/// Create a directory in an initial state without entries. Note that an empty directory can
|
||||||
/// not be encoded in a file, it must contain at least one entry.
|
/// not be encoded in a file, it must contain at least one entry.
|
||||||
pub fn empty() -> Self {
|
pub const fn empty() -> Self {
|
||||||
Directory {
|
Directory {
|
||||||
entries: BTreeMap::new(),
|
entries: BTreeMap::new(),
|
||||||
next_ifd: None,
|
next_ifd: None,
|
||||||
@@ -104,28 +104,6 @@ impl Directory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn encoded_len<K: crate::encoder::TiffKind>(&self) -> u64 {
|
|
||||||
let (len_count, len_offset);
|
|
||||||
|
|
||||||
match std::mem::size_of::<K::OffsetType>() {
|
|
||||||
offset @ 4 => {
|
|
||||||
// non-BigTiff uses a `u16` here.
|
|
||||||
len_count = 2;
|
|
||||||
len_offset = offset as u64;
|
|
||||||
}
|
|
||||||
offset => {
|
|
||||||
let offset = offset as u64;
|
|
||||||
// assuming BigTIFF
|
|
||||||
len_count = offset;
|
|
||||||
len_offset = offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each entry is 12 bytes in classic TIFF, 20 bytes in BigTIFF.
|
|
||||||
let entry_bytes = 4 + 2 * len_offset;
|
|
||||||
len_count + entry_bytes * self.entries.len() as u64 + len_offset
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Directory {
|
impl fmt::Debug for Directory {
|
||||||
@@ -140,14 +118,6 @@ impl fmt::Debug for Directory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::iter::FromIterator<(Tag, Entry)> for Directory {
|
|
||||||
fn from_iter<T: IntoIterator<Item = (Tag, Entry)>>(iter: T) -> Self {
|
|
||||||
let mut dir = Directory::empty();
|
|
||||||
dir.extend(iter);
|
|
||||||
dir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Directory;
|
use super::Directory;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ impl Compression for Uncompressed {
|
|||||||
|
|
||||||
impl CompressionAlgorithm for Uncompressed {
|
impl CompressionAlgorithm for Uncompressed {
|
||||||
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
fn write_to<W: Write>(&mut self, writer: &mut W, bytes: &[u8]) -> Result<u64, io::Error> {
|
||||||
writer.write_all(bytes).map(|()| bytes.len() as u64)
|
writer.write(bytes).map(|byte_count| byte_count as u64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-316
@@ -11,10 +11,7 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
decoder::ifd::Entry,
|
decoder::ifd::Entry,
|
||||||
error::{TiffResult, UsageError},
|
error::{TiffResult, UsageError},
|
||||||
tags::{
|
tags::{CompressionMethod, IfdPointer, ResolutionUnit, SampleFormat, Tag, Type},
|
||||||
ByteOrder, CompressionMethod, ExtraSamples, IfdPointer, PhotometricInterpretation,
|
|
||||||
ResolutionUnit, SampleFormat, Tag, Type, ValueBuffer,
|
|
||||||
},
|
|
||||||
Directory, TiffError, TiffFormatError,
|
Directory, TiffError, TiffFormatError,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,9 +37,8 @@ pub type Predictor = crate::tags::Predictor;
|
|||||||
#[cfg(feature = "deflate")]
|
#[cfg(feature = "deflate")]
|
||||||
pub type DeflateLevel = compression::DeflateLevel;
|
pub type DeflateLevel = compression::DeflateLevel;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Default)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum Compression {
|
pub enum Compression {
|
||||||
#[default]
|
|
||||||
Uncompressed,
|
Uncompressed,
|
||||||
#[cfg(feature = "lzw")]
|
#[cfg(feature = "lzw")]
|
||||||
Lzw,
|
Lzw,
|
||||||
@@ -51,6 +47,12 @@ pub enum Compression {
|
|||||||
Packbits,
|
Packbits,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Compression {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Uncompressed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Compression {
|
impl Compression {
|
||||||
fn tag(&self) -> CompressionMethod {
|
fn tag(&self) -> CompressionMethod {
|
||||||
match self {
|
match self {
|
||||||
@@ -77,10 +79,10 @@ impl Compression {
|
|||||||
|
|
||||||
/// Encoder for Tiff and BigTiff files.
|
/// Encoder for Tiff and BigTiff files.
|
||||||
///
|
///
|
||||||
/// With this type you can get a [`DirectoryEncoder`] or a [`ImageEncoder`]
|
/// With this type you can get a `DirectoryEncoder` or a `ImageEncoder`
|
||||||
/// to encode Tiff/BigTiff ifd directories with images.
|
/// to encode Tiff/BigTiff ifd directories with images.
|
||||||
///
|
///
|
||||||
/// See [`DirectoryEncoder`] and [`ImageEncoder`].
|
/// See `DirectoryEncoder` and `ImageEncoder`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
@@ -174,7 +176,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
|||||||
Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)
|
Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a [`DirectoryEncoder`] to encode an ifd (directory).
|
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||||
///
|
///
|
||||||
/// The caller is responsible for ensuring that the directory is a valid image in the main TIFF
|
/// The caller is responsible for ensuring that the directory is a valid image in the main TIFF
|
||||||
/// IFD sequence. To encode additional directories that are not linked into the sequence, use
|
/// IFD sequence. To encode additional directories that are not linked into the sequence, use
|
||||||
@@ -183,7 +185,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
|||||||
Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)
|
Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a [`DirectoryEncoder`] to encode a non-image directory.
|
/// Create a [`DirectoryEncoder`] to encode an ifd directory.
|
||||||
///
|
///
|
||||||
/// The directory is not linked into the sequence of directories. For instance, encode Exif
|
/// The directory is not linked into the sequence of directories. For instance, encode Exif
|
||||||
/// directories or SubIfd directories with this method.
|
/// directories or SubIfd directories with this method.
|
||||||
@@ -196,7 +198,7 @@ impl<W: Write + Seek, K: TiffKind> TiffEncoder<W, K> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
) -> TiffResult<ImageEncoder<'_, W, C, K>> {
|
) -> TiffResult<ImageEncoder<W, C, K>> {
|
||||||
let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?;
|
let encoder = Self::chain_directory(&mut self.writer, &mut self.last_ifd_chain)?;
|
||||||
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
ImageEncoder::new(encoder, width, height, self.compression, self.predictor)
|
||||||
}
|
}
|
||||||
@@ -247,12 +249,6 @@ pub struct DirectoryEncoder<'a, W: 'a + Write + Seek, K: TiffKind> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The offset of an encoded directory in the file.
|
/// The offset of an encoded directory in the file.
|
||||||
///
|
|
||||||
/// Both the `offset` and `pointer` field are a complete description of the start offset of the
|
|
||||||
/// dictionary which is used to point to it. The struct also describes the extent covered by the
|
|
||||||
/// encoding of the directory. The constructors [`new`][DirectoryOffset::new] allow the checked
|
|
||||||
/// conversion. Note that even though the offsets are public do not expect their information to
|
|
||||||
/// propagate when modified.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct DirectoryOffset<K: TiffKind> {
|
pub struct DirectoryOffset<K: TiffKind> {
|
||||||
/// The start of the directory as a Tiff value.
|
/// The start of the directory as a Tiff value.
|
||||||
@@ -264,9 +260,7 @@ pub struct DirectoryOffset<K: TiffKind> {
|
|||||||
pub offset: K::OffsetType,
|
pub offset: K::OffsetType,
|
||||||
/// The start of the directory as a pure offset.
|
/// The start of the directory as a pure offset.
|
||||||
pub pointer: IfdPointer,
|
pub pointer: IfdPointer,
|
||||||
/// The offset of its sequence link field, in our private representation. Before exposing this
|
/// The offset of its sequence link field, in our private representation.
|
||||||
/// make sure that we don't want any invariants (e.g. pointer being smaller than this). The
|
|
||||||
/// type should be fine.
|
|
||||||
ifd_chain: NonZeroU64,
|
ifd_chain: NonZeroU64,
|
||||||
/// The kind of Tiff file the offset is for.
|
/// The kind of Tiff file the offset is for.
|
||||||
kind: PhantomData<K>,
|
kind: PhantomData<K>,
|
||||||
@@ -301,12 +295,12 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
value.write(&mut writer)?;
|
value.write(&mut writer)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = Self::write_entry_inner(
|
let entry = Self::write_value(
|
||||||
self.writer,
|
self.writer,
|
||||||
DirectoryEntry {
|
&DirectoryEntry {
|
||||||
data_type: <T>::FIELD_TYPE,
|
data_type: <T>::FIELD_TYPE,
|
||||||
count: value.count().try_into()?,
|
count: value.count().try_into()?,
|
||||||
data: bytes.into(),
|
data: bytes,
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -315,125 +309,15 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write a tag-value pair with prepared byte data.
|
|
||||||
///
|
|
||||||
/// Note that the library will _not_ attempt to verify that the data type or the count of the
|
|
||||||
/// buffered value is permissible for the given tag. The data will be associated with the tag
|
|
||||||
/// exactly as-is.
|
|
||||||
///
|
|
||||||
/// An error is returned if the byte order of the presented value does not match the byte order
|
|
||||||
/// of the underlying file (i.e. the [native byte order](`crate::tags::ByteOrder::native`)).
|
|
||||||
pub fn write_tag_buf(&mut self, tag: Tag, value: &ValueBuffer) -> TiffResult<()> {
|
|
||||||
let entry = self.write_entry_buf(value)?;
|
|
||||||
self.directory.extend([(tag, entry)]);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write some data to the tiff file, the offset of the data is returned.
|
/// Write some data to the tiff file, the offset of the data is returned.
|
||||||
///
|
///
|
||||||
/// This could be used to write tiff strips. All of the data will be written into the file as a
|
/// This could be used to write tiff strips.
|
||||||
/// slice of bytes. It can not be used as an entry in a directory directly, where very short
|
|
||||||
/// values are instead represented in the offset field directly. Use [`Self::write_entry`]
|
|
||||||
/// instead.
|
|
||||||
pub fn write_data<T: TiffValue>(&mut self, value: T) -> TiffResult<u64> {
|
pub fn write_data<T: TiffValue>(&mut self, value: T) -> TiffResult<u64> {
|
||||||
let offset = self.writer.offset();
|
let offset = self.writer.offset();
|
||||||
value.write(self.writer)?;
|
value.write(self.writer)?;
|
||||||
Ok(offset)
|
Ok(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write some data directly to the tiff file, the offset of the data is returned.
|
|
||||||
///
|
|
||||||
/// All of the data will be written into the file as a slice of bytes. It can not be used as an
|
|
||||||
/// entry in a directory directly, where very short values are instead represented in the
|
|
||||||
/// offset field directly. Use [`Self::write_entry_buf`] instead.
|
|
||||||
///
|
|
||||||
/// An error is returned if the byte order of the presented value does not match the byte order
|
|
||||||
/// of the underlying file (i.e. the [native byte order](`crate::tags::ByteOrder::native`)).
|
|
||||||
pub fn write_data_buf(&mut self, value: &ValueBuffer) -> TiffResult<u64> {
|
|
||||||
self.check_value_byteorder(value)?;
|
|
||||||
let offset = self.writer.offset();
|
|
||||||
self.writer.write_bytes(value.as_bytes())?;
|
|
||||||
Ok(offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a directory value into the file.
|
|
||||||
///
|
|
||||||
/// Returns an [`Entry`]. If the value is short enough it will *not* be written in the file but
|
|
||||||
/// stored in the entry's offset field.
|
|
||||||
pub fn write_entry<T: TiffValue>(&mut self, value: T) -> TiffResult<Entry> {
|
|
||||||
Self::write_entry_inner(
|
|
||||||
self.writer,
|
|
||||||
DirectoryEntry {
|
|
||||||
data_type: T::FIELD_TYPE,
|
|
||||||
count: K::convert_offset(value.count() as u64)?,
|
|
||||||
// FIXME: not optimal we always allocate here. But the only other method we have
|
|
||||||
// available is `T::write(&mut TiffWriter<W>)` and we must pass that into
|
|
||||||
// `write_entry_inner` but then have a combinatorial explosion of instantiations
|
|
||||||
// which is absurd.
|
|
||||||
data: value.data(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a directory value into the file.
|
|
||||||
///
|
|
||||||
/// An error is returned if the byte order of the presented value does not match the byte order
|
|
||||||
/// of the underlying file (i.e. the [native byte order](`crate::tags::ByteOrder::native`)).
|
|
||||||
///
|
|
||||||
/// Returns an [`Entry`]. If the value is short enough it will *not* be written in the file but
|
|
||||||
/// stored in the entry's offset field.
|
|
||||||
pub fn write_entry_buf(&mut self, value: &ValueBuffer) -> TiffResult<Entry> {
|
|
||||||
self.check_value_byteorder(value)?;
|
|
||||||
|
|
||||||
Self::write_entry_inner(
|
|
||||||
self.writer,
|
|
||||||
DirectoryEntry {
|
|
||||||
data_type: value.data_type(),
|
|
||||||
count: K::convert_offset(value.count())?,
|
|
||||||
data: value.as_bytes().into(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a directory entry by its byte data.
|
|
||||||
///
|
|
||||||
/// The data must be pre-formatted to the byte order expected by the file.
|
|
||||||
///
|
|
||||||
/// Returns an [`Entry`]. If the value is short enough it will *not* be written in the file but
|
|
||||||
/// stored in the entry's offset field.
|
|
||||||
pub fn write_entry_bytes(&mut self, ty: Type, data: &[u8]) -> TiffResult<Entry> {
|
|
||||||
if data.len() % usize::from(ty.byte_len()) != 0 {
|
|
||||||
return Err(TiffError::UsageError(UsageError::MismatchedEntryLength {
|
|
||||||
ty,
|
|
||||||
found: data.len(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = data.len() / usize::from(ty.byte_len());
|
|
||||||
let count = u64::try_from(count)?;
|
|
||||||
|
|
||||||
Self::write_entry_inner(
|
|
||||||
self.writer,
|
|
||||||
DirectoryEntry {
|
|
||||||
data_type: ty,
|
|
||||||
count: K::convert_offset(count)?,
|
|
||||||
data: data.into(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert previously written key-value entries.
|
|
||||||
///
|
|
||||||
/// It is the caller's responsibility to ensure that the data referred to by the entries is
|
|
||||||
/// actually (or will be) written to the file. Note that small values are necessarily inline in
|
|
||||||
/// the entry, the size limit depends on the Tiff kind. So do not confuse a directory from a
|
|
||||||
/// BigTiff in a standard tiff or the other way around.
|
|
||||||
pub fn extend_from(&mut self, dir: &Directory) {
|
|
||||||
let entries = dir.iter().map(|(tag, val)| (tag, val.clone()));
|
|
||||||
self.directory.extend(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define the parent directory.
|
/// Define the parent directory.
|
||||||
///
|
///
|
||||||
/// Each directory has an offset based link to its successor, forming a linked list in the
|
/// Each directory has an offset based link to its successor, forming a linked list in the
|
||||||
@@ -450,11 +334,6 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the IFD to the file and return the offset it is written to.
|
|
||||||
///
|
|
||||||
/// The offset can be used as the value, or as part of a list of values, in the entry of
|
|
||||||
/// another directory. If you're constructing the entry directly you'll want to use an
|
|
||||||
/// appropriate type variant for this such as [`Type::IFD`].
|
|
||||||
pub fn finish_with_offsets(mut self) -> TiffResult<DirectoryOffset<K>> {
|
pub fn finish_with_offsets(mut self) -> TiffResult<DirectoryOffset<K>> {
|
||||||
self.finish_internal()
|
self.finish_internal()
|
||||||
}
|
}
|
||||||
@@ -476,21 +355,17 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
Ok(offset)
|
Ok(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Does it really make sense that this would be a (potentially) compressible write? For actual
|
fn write_value(
|
||||||
// directory values their size must match the data written so there's going to be a silent
|
|
||||||
// problem if that actually occurs.
|
|
||||||
fn write_entry_inner(
|
|
||||||
writer: &mut TiffWriter<W>,
|
writer: &mut TiffWriter<W>,
|
||||||
value: DirectoryEntry<K::OffsetType>,
|
value: &DirectoryEntry<K::OffsetType>,
|
||||||
) -> TiffResult<Entry> {
|
) -> TiffResult<Entry> {
|
||||||
let DirectoryEntry {
|
let &DirectoryEntry {
|
||||||
data: ref bytes,
|
data: ref bytes,
|
||||||
ref count,
|
ref count,
|
||||||
data_type,
|
data_type,
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
let in_entry_bytes = mem::size_of::<K::OffsetType>();
|
let in_entry_bytes = mem::size_of::<K::OffsetType>();
|
||||||
|
|
||||||
let mut offset_bytes = [0; 8];
|
let mut offset_bytes = [0; 8];
|
||||||
|
|
||||||
if bytes.len() > in_entry_bytes {
|
if bytes.len() > in_entry_bytes {
|
||||||
@@ -509,7 +384,6 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
// and some oversight, we can not clone the `count: K::OffsetType` and thus not convert it.
|
// and some oversight, we can not clone the `count: K::OffsetType` and thus not convert it.
|
||||||
// Instead, write it to a buffer...
|
// Instead, write it to a buffer...
|
||||||
let mut count_bytes = [0; 8];
|
let mut count_bytes = [0; 8];
|
||||||
debug_assert!(in_entry_bytes == 4 || in_entry_bytes == 8);
|
|
||||||
// Nominally Cow but we only expect `Cow::Borrowed`.
|
// Nominally Cow but we only expect `Cow::Borrowed`.
|
||||||
count_bytes[..count.bytes()].copy_from_slice(&count.data());
|
count_bytes[..count.bytes()].copy_from_slice(&count.data());
|
||||||
|
|
||||||
@@ -523,15 +397,6 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_value_byteorder(&self, value: &ValueBuffer) -> TiffResult<()> {
|
|
||||||
// FIXME: we should enable writing files with any chosen byte order.
|
|
||||||
if value.byte_order() != ByteOrder::native() {
|
|
||||||
Err(TiffError::UsageError(UsageError::ByteOrderMismatch))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Provides the number of bytes written by the underlying TiffWriter during the last call.
|
/// Provides the number of bytes written by the underlying TiffWriter during the last call.
|
||||||
fn last_written(&self) -> u64 {
|
fn last_written(&self) -> u64 {
|
||||||
self.writer.last_written()
|
self.writer.last_written()
|
||||||
@@ -622,7 +487,6 @@ 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>,
|
||||||
@@ -679,14 +543,11 @@ 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(
|
||||||
@@ -695,7 +556,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
|||||||
)?;
|
)?;
|
||||||
encoder.write_tag(Tag::XResolution, Rational { n: 1, d: 1 })?;
|
encoder.write_tag(Tag::XResolution, Rational { n: 1, d: 1 })?;
|
||||||
encoder.write_tag(Tag::YResolution, Rational { n: 1, d: 1 })?;
|
encoder.write_tag(Tag::YResolution, Rational { n: 1, d: 1 })?;
|
||||||
encoder.write_tag(Tag::ResolutionUnit, ResolutionUnit::None)?;
|
encoder.write_tag(Tag::ResolutionUnit, ResolutionUnit::None.to_u16())?;
|
||||||
|
|
||||||
Ok(ImageEncoder {
|
Ok(ImageEncoder {
|
||||||
encoder,
|
encoder,
|
||||||
@@ -703,7 +564,6 @@ 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(),
|
||||||
@@ -715,27 +575,6 @@ 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 {
|
||||||
@@ -873,39 +712,6 @@ 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_n(<T>::BITS_PER_SAMPLE[0], 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_n(extra_format, 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(
|
||||||
@@ -937,10 +743,10 @@ impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DirectoryEntry<'data, S> {
|
struct DirectoryEntry<S> {
|
||||||
data_type: Type,
|
data_type: Type,
|
||||||
count: S,
|
count: S,
|
||||||
data: std::borrow::Cow<'data, [u8]>,
|
data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait to abstract over Tiff/BigTiff differences.
|
/// Trait to abstract over Tiff/BigTiff differences.
|
||||||
@@ -985,71 +791,6 @@ pub trait TiffKind {
|
|||||||
fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType;
|
fn convert_slice(slice: &[Self::OffsetType]) -> &Self::OffsetArrayType;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: TiffKind> DirectoryOffset<K> {
|
|
||||||
/// Construct the directory pointer information from its raw offset.
|
|
||||||
///
|
|
||||||
/// Fails in these cases:
|
|
||||||
///
|
|
||||||
/// - if start offset is too large to be represented with the chosen TIFF format, i.e. a value
|
|
||||||
/// larger than [`u32::MAX`] with a standard tiff which is only valid in a BigTiff. This
|
|
||||||
/// returns [`TiffError::IntSizeError`].
|
|
||||||
/// - the extent of the directory would overflow the file length. This returns a
|
|
||||||
/// [`TiffError::UsageError`].
|
|
||||||
///
|
|
||||||
/// The library does *not* validate the contents or offset in any way, it just performs the
|
|
||||||
/// necessary math assuming the data is an accurate representation of existing content.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// If you have any custom way of writing a directory to a file that can not go through the
|
|
||||||
/// usual [`TiffEncoder::extra_directory`] then you can reconstruct the offset information as
|
|
||||||
/// long as you provide all the necessary data to the library. You can then use it as a parent
|
|
||||||
/// directory, i.e. have the library overwrite its `next` field.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use tiff::{
|
|
||||||
/// encoder::{TiffEncoder, DirectoryOffset},
|
|
||||||
/// Directory,
|
|
||||||
/// tags::IfdPointer,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// # let mut file = std::io::Cursor::new(Vec::new());
|
|
||||||
/// let mut tiff = TiffEncoder::new(&mut file)?;
|
|
||||||
///
|
|
||||||
/// // … some custom data writes, assume we know where a directory was written
|
|
||||||
/// # fn reconstruction_of_your_directory() -> Directory { Directory::from_iter([]) }
|
|
||||||
/// let known_offset = IfdPointer(1024);
|
|
||||||
/// let known_contents: Directory = reconstruction_of_your_directory();
|
|
||||||
///
|
|
||||||
/// let reconstructed = DirectoryOffset::new(known_offset, &known_contents)?;
|
|
||||||
///
|
|
||||||
/// // Now we can use it as if the directory was written by the `TiffEncoder` itself.
|
|
||||||
/// let mut chain = tiff.extra_directory()?;
|
|
||||||
/// chain.set_parent(&reconstructed);
|
|
||||||
/// chain.finish_with_offsets()?;
|
|
||||||
///
|
|
||||||
/// # Ok::<_, tiff::TiffError>(())
|
|
||||||
/// ```
|
|
||||||
pub fn new(pointer: IfdPointer, dir: &Directory) -> TiffResult<Self> {
|
|
||||||
let offset = K::convert_offset(pointer.0)?;
|
|
||||||
let encoded = dir.encoded_len::<K>();
|
|
||||||
let offset_field_offset = encoded - mem::size_of_val(&offset) as u64;
|
|
||||||
|
|
||||||
let ifd_chain = pointer
|
|
||||||
.0
|
|
||||||
.checked_add(offset_field_offset)
|
|
||||||
.and_then(NonZeroU64::new)
|
|
||||||
.ok_or(TiffError::UsageError(UsageError::ZeroIfdPointer))?;
|
|
||||||
|
|
||||||
Ok(DirectoryOffset {
|
|
||||||
pointer,
|
|
||||||
offset,
|
|
||||||
ifd_chain,
|
|
||||||
kind: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a standard Tiff file.
|
/// Create a standard Tiff file.
|
||||||
pub struct TiffKindStandard;
|
pub struct TiffKindStandard;
|
||||||
|
|
||||||
@@ -1118,33 +859,3 @@ impl TiffKind for TiffKindBig {
|
|||||||
slice
|
slice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn directory_offset_new_equivalent_to_writing() {
|
|
||||||
type K = TiffKindStandard;
|
|
||||||
|
|
||||||
let dir = Directory::from_iter(vec![
|
|
||||||
(
|
|
||||||
Tag::ImageWidth,
|
|
||||||
Entry::new(Type::LONG, 1, 100u32.to_ne_bytes()),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Tag::ImageLength,
|
|
||||||
Entry::new(Type::LONG, 1, 200u32.to_ne_bytes()),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let data = std::io::Cursor::new(vec![]);
|
|
||||||
let mut file = TiffEncoder::new(data).unwrap();
|
|
||||||
|
|
||||||
let mut as_dir_encoder = file.extra_directory().unwrap();
|
|
||||||
as_dir_encoder.extend_from(&dir);
|
|
||||||
let dir_extent = as_dir_encoder.finish_with_offsets().unwrap();
|
|
||||||
|
|
||||||
// Check we can recreate the extent had we written ourselves.
|
|
||||||
let synth = DirectoryOffset::<K>::new(dir_extent.pointer, &dir).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(synth.pointer, dir_extent.pointer);
|
|
||||||
assert_eq!(synth.offset, dir_extent.offset);
|
|
||||||
assert_eq!(synth.ifd_chain, dir_extent.ifd_chain);
|
|
||||||
}
|
|
||||||
+29
-193
@@ -1,34 +1,21 @@
|
|||||||
use std::{borrow::Cow, io::Write, slice::from_ref};
|
use std::{borrow::Cow, io::Write, slice::from_ref};
|
||||||
|
|
||||||
use crate::{bytecast, tags, tags::Type, TiffError, TiffFormatError, TiffResult};
|
use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult};
|
||||||
|
|
||||||
use super::writer::TiffWriter;
|
use super::writer::TiffWriter;
|
||||||
|
|
||||||
/// Trait for types that can be encoded in a tiff file
|
/// Trait for types that can be encoded in a tiff file
|
||||||
#[diagnostic::on_unimplemented(
|
|
||||||
message = "the type `{Self}` does not implement `TiffValue`",
|
|
||||||
note = "the trait is implemented for primitive types (`u8`, `i16`, `f32`, etc.)",
|
|
||||||
note = "the trait is implemented for shared references to values",
|
|
||||||
note = "the trait is implemented for slices, pass them by reference (e.g. `&[u8]`)",
|
|
||||||
note = "the trait is implemented for arrays if it is implemented for a slice",
|
|
||||||
note = "values in a `Vec` or `Box` should be dereferenced, e.g. `&vec[..]`"
|
|
||||||
)]
|
|
||||||
pub trait TiffValue {
|
pub trait TiffValue {
|
||||||
const BYTE_LEN: u8;
|
const BYTE_LEN: u8;
|
||||||
const FIELD_TYPE: Type;
|
const FIELD_TYPE: Type;
|
||||||
|
|
||||||
// FIXME: If we want this to mean 'count' in the sense of `Entry` it should return `u32` or
|
|
||||||
// `u64`, probably the latter with `convert_offset` taking care of the check against `u32::MAX`
|
|
||||||
// for classic tiff.
|
|
||||||
fn count(&self) -> usize;
|
fn count(&self) -> usize;
|
||||||
|
|
||||||
fn bytes(&self) -> usize {
|
fn bytes(&self) -> usize {
|
||||||
self.count() * usize::from(Self::BYTE_LEN)
|
self.count() * usize::from(Self::BYTE_LEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access this value as an contiguous sequence of bytes.
|
/// Access this value as an contiguous sequence of bytes.
|
||||||
/// If their is no trivial representation, allocate it on the heap.
|
/// If their is no trivial representation, allocate it on the heap.
|
||||||
fn data(&self) -> Cow<'_, [u8]>;
|
fn data(&self) -> Cow<[u8]>;
|
||||||
|
|
||||||
/// Write this value to a TiffWriter.
|
/// Write this value to a TiffWriter.
|
||||||
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
/// While the default implementation will work in all cases, it may require unnecessary allocations.
|
||||||
@@ -47,7 +34,7 @@ impl TiffValue for [u8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(self)
|
Cow::Borrowed(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +47,7 @@ impl TiffValue for [i8] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +60,7 @@ impl TiffValue for [u16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +73,7 @@ impl TiffValue for [i16] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +86,7 @@ impl TiffValue for [u32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +99,7 @@ impl TiffValue for [i32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +112,7 @@ impl TiffValue for [u64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +125,7 @@ impl TiffValue for [i64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +138,7 @@ impl TiffValue for [f32] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
// We write using native endian so this should be safe
|
// We write using native endian so this should be safe
|
||||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
@@ -165,7 +152,7 @@ impl TiffValue for [f64] {
|
|||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
// We write using native endian so this should be safe
|
// We write using native endian so this should be safe
|
||||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(self))
|
||||||
}
|
}
|
||||||
@@ -184,7 +171,7 @@ impl TiffValue for u8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(from_ref(self))
|
Cow::Borrowed(from_ref(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +189,7 @@ impl TiffValue for i8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i8_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +207,7 @@ impl TiffValue for u16 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u16_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,7 +225,7 @@ impl TiffValue for i16 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i16_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +243,7 @@ impl TiffValue for u32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,7 +261,7 @@ impl TiffValue for i32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,7 +279,7 @@ impl TiffValue for u64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,7 +297,7 @@ impl TiffValue for i64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::i64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +315,7 @@ impl TiffValue for f32 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::f32_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,7 +333,7 @@ impl TiffValue for f64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
Cow::Borrowed(bytecast::f64_as_ne_bytes(from_ref(self)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,7 +351,7 @@ impl TiffValue for Ifd {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
Cow::Borrowed(bytecast::u32_as_ne_bytes(from_ref(&self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,7 +369,7 @@ impl TiffValue for Ifd8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
Cow::Borrowed(bytecast::u64_as_ne_bytes(from_ref(&self.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,7 +388,7 @@ impl TiffValue for Rational {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
let first_dword = bytecast::u32_as_ne_bytes(from_ref(&self.n));
|
||||||
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
let second_dword = bytecast::u32_as_ne_bytes(from_ref(&self.d));
|
||||||
@@ -424,7 +411,7 @@ impl TiffValue for SRational {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
let first_dword = bytecast::i32_as_ne_bytes(from_ref(&self.n));
|
||||||
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
let second_dword = bytecast::i32_as_ne_bytes(from_ref(&self.d));
|
||||||
@@ -451,7 +438,7 @@ impl TiffValue for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
Cow::Owned({
|
Cow::Owned({
|
||||||
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
if self.is_ascii() && !self.bytes().any(|b| b == 0) {
|
||||||
let bytes: &[u8] = self.as_bytes();
|
let bytes: &[u8] = self.as_bytes();
|
||||||
@@ -463,136 +450,6 @@ impl TiffValue for str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: held up on reading the discriminant for all but the unknown variant.
|
|
||||||
//
|
|
||||||
// It needs to have `repr(u16)` to have a defined layout and then we may read the tag from its
|
|
||||||
// value representation by casting a u16 pointer to a pointer to the enum type.
|
|
||||||
// unsafe { *<*const _>::from(self).cast::<u16>() }
|
|
||||||
// But that is quite unsafe and needs careful review to maintain the safety requirements. Maybe we
|
|
||||||
// could get `bytemuck` to add a trait for this (it has `TransparentWrapper`) with a derive for
|
|
||||||
// enum types that have an explicit repr.
|
|
||||||
//
|
|
||||||
// This would allow returning borrowed data in more cases. For all types without an unknown, in all
|
|
||||||
// cases including as slices, and for all types with and unknown variant when we have a single
|
|
||||||
// value (either returning a borrowed discriminant or a borrowed value within `Unknown`).
|
|
||||||
impl TiffValue for tags::CompressionMethod {
|
|
||||||
const BYTE_LEN: u8 = <u16 as TiffValue>::BYTE_LEN;
|
|
||||||
const FIELD_TYPE: Type = Type::SHORT;
|
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
|
||||||
let bytes = self.to_u16().to_ne_bytes();
|
|
||||||
Cow::Owned(bytes.to_vec())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TiffValue for tags::PhotometricInterpretation {
|
|
||||||
const BYTE_LEN: u8 = <u16 as TiffValue>::BYTE_LEN;
|
|
||||||
const FIELD_TYPE: Type = Type::SHORT;
|
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
|
||||||
self.to_u16().to_ne_bytes().to_vec().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TiffValue for tags::PlanarConfiguration {
|
|
||||||
const BYTE_LEN: u8 = 2;
|
|
||||||
const FIELD_TYPE: Type = Type::SHORT;
|
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
|
||||||
self.to_u16().to_ne_bytes().to_vec().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TiffValue for tags::Predictor {
|
|
||||||
const BYTE_LEN: u8 = <u16 as TiffValue>::BYTE_LEN;
|
|
||||||
const FIELD_TYPE: Type = Type::SHORT;
|
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
|
||||||
self.to_u16().to_ne_bytes().to_vec().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TiffValue for tags::ResolutionUnit {
|
|
||||||
const BYTE_LEN: u8 = <u16 as TiffValue>::BYTE_LEN;
|
|
||||||
const FIELD_TYPE: Type = Type::SHORT;
|
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
|
||||||
self.to_u16().to_ne_bytes().to_vec().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is implemented for slices as the `SampleFormat` tag takes a count of `N`.
|
|
||||||
///
|
|
||||||
/// Use `core::slice::from_ref` if you really need to write a single element.
|
|
||||||
///
|
|
||||||
/// See: <https://web.archive.org/web/20191120220815/https://www.awaresystems.be/imaging/tiff/tifftags/sampleformat.html>
|
|
||||||
impl TiffValue for [tags::SampleFormat] {
|
|
||||||
const BYTE_LEN: u8 = <u16 as TiffValue>::BYTE_LEN;
|
|
||||||
const FIELD_TYPE: Type = Type::SHORT;
|
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
|
||||||
self.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(self.len() * 2);
|
|
||||||
for x in self {
|
|
||||||
buf.extend_from_slice(&x.to_u16().to_ne_bytes());
|
|
||||||
}
|
|
||||||
Cow::Owned(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is implemented for slices as the `ExtraSamples` tag takes a count of `N`.
|
|
||||||
///
|
|
||||||
/// Use `core::slice::from_ref` if you really need to write a single element.
|
|
||||||
///
|
|
||||||
/// See: <https://web.archive.org/web/20191120220815/https://www.awaresystems.be/imaging/tiff/tifftags/extrasamples.html>
|
|
||||||
impl TiffValue for [tags::ExtraSamples] {
|
|
||||||
const BYTE_LEN: u8 = <u16 as TiffValue>::BYTE_LEN;
|
|
||||||
const FIELD_TYPE: Type = Type::SHORT;
|
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
|
||||||
self.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(self.len() * 2);
|
|
||||||
for x in self {
|
|
||||||
buf.extend_from_slice(&x.to_u16().to_ne_bytes());
|
|
||||||
}
|
|
||||||
Cow::Owned(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you pass `&Vec<_>` then you'd get at first sight the complaint:
|
|
||||||
//
|
|
||||||
// `Vec<T>` does not implement `TiffValue`
|
|
||||||
//
|
|
||||||
// and a list of implementations of `TiffValue` that are quite unrelated. That is not very helpful.
|
|
||||||
// We do not *want* `Vec` to implement the trait as an owning type, instead you should pass a
|
|
||||||
// reference to a slice. The error message is further customized in the diagnostics of the trait.
|
|
||||||
#[diagnostic::do_not_recommend]
|
|
||||||
impl<T: TiffValue + ?Sized> TiffValue for &'_ T {
|
impl<T: TiffValue + ?Sized> TiffValue for &'_ T {
|
||||||
const BYTE_LEN: u8 = T::BYTE_LEN;
|
const BYTE_LEN: u8 = T::BYTE_LEN;
|
||||||
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
||||||
@@ -605,32 +462,11 @@ impl<T: TiffValue + ?Sized> TiffValue for &'_ T {
|
|||||||
(*self).write(writer)
|
(*self).write(writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
T::data(self)
|
T::data(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An array is treated like a slice of the same length.
|
|
||||||
impl<T, const N: usize> TiffValue for [T; N]
|
|
||||||
where
|
|
||||||
[T]: TiffValue,
|
|
||||||
{
|
|
||||||
const BYTE_LEN: u8 = <[T]>::BYTE_LEN;
|
|
||||||
const FIELD_TYPE: Type = <[T]>::FIELD_TYPE;
|
|
||||||
|
|
||||||
fn count(&self) -> usize {
|
|
||||||
self.as_slice().count()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write<W: Write>(&self, writer: &mut TiffWriter<W>) -> TiffResult<()> {
|
|
||||||
self.as_slice().write(writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
|
||||||
self.as_slice().data()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_tiff_value_for_contiguous_sequence {
|
macro_rules! impl_tiff_value_for_contiguous_sequence {
|
||||||
($inner_type:ty; $bytes:expr; $field_type:expr) => {
|
($inner_type:ty; $bytes:expr; $field_type:expr) => {
|
||||||
impl $crate::encoder::TiffValue for [$inner_type] {
|
impl $crate::encoder::TiffValue for [$inner_type] {
|
||||||
@@ -648,7 +484,7 @@ macro_rules! impl_tiff_value_for_contiguous_sequence {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data(&self) -> Cow<'_, [u8]> {
|
fn data(&self) -> Cow<[u8]> {
|
||||||
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
let mut buf: Vec<u8> = Vec::with_capacity(Self::BYTE_LEN as usize * self.len());
|
||||||
for x in self {
|
for x in self {
|
||||||
buf.extend_from_slice(&x.data());
|
buf.extend_from_slice(&x.data());
|
||||||
|
|||||||
@@ -190,64 +190,3 @@ impl<W: Seek> TiffWriter<W> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encoder_on_short_writes() {
|
|
||||||
struct ShortWriter<T> {
|
|
||||||
inner: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: std::io::Write> std::io::Write for ShortWriter<T> {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
|
||||||
let lower_bound = buf.len().min(1);
|
|
||||||
let lower = buf.len().saturating_sub(1).max(lower_bound);
|
|
||||||
|
|
||||||
self.inner.write(&buf[..lower])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&mut self) -> std::io::Result<()> {
|
|
||||||
self.inner.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: std::io::Seek> std::io::Seek for ShortWriter<T> {
|
|
||||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
|
||||||
self.inner.seek(pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut write = ShortWriter {
|
|
||||||
inner: std::io::Cursor::new(vec![0u8; 0]),
|
|
||||||
};
|
|
||||||
|
|
||||||
let imgdata = vec![42u8; 100 * 100];
|
|
||||||
let mut data = crate::decoder::DecodingResult::U8(vec![]);
|
|
||||||
|
|
||||||
for comp in [
|
|
||||||
super::Compression::Uncompressed,
|
|
||||||
super::Compression::Packbits,
|
|
||||||
] {
|
|
||||||
write.inner.set_position(0);
|
|
||||||
write.inner.get_mut().clear();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut tiff_writer = super::TiffEncoder::new(&mut write)
|
|
||||||
.unwrap()
|
|
||||||
.with_compression(comp);
|
|
||||||
|
|
||||||
tiff_writer
|
|
||||||
.write_image::<super::Gray8>(100, 100, &imgdata)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
write.inner.set_position(0);
|
|
||||||
let mut tiff_reader = crate::decoder::Decoder::new(&mut write.inner).unwrap();
|
|
||||||
let _layout = tiff_reader.read_image_to_buffer(&mut data).unwrap();
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
matches!(&data, crate::decoder::DecodingResult::U8(v) if *v == imgdata),
|
|
||||||
"{data:?} @ {}",
|
|
||||||
data.as_buffer(0).byte_len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ use std::string;
|
|||||||
use quick_error::quick_error;
|
use quick_error::quick_error;
|
||||||
|
|
||||||
use crate::decoder::ChunkType;
|
use crate::decoder::ChunkType;
|
||||||
use crate::tags::Type;
|
|
||||||
use crate::tags::{
|
use crate::tags::{
|
||||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
|
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
|
||||||
};
|
};
|
||||||
@@ -91,9 +90,6 @@ 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")
|
||||||
}
|
}
|
||||||
@@ -160,9 +156,6 @@ 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")
|
||||||
}
|
}
|
||||||
@@ -182,12 +175,6 @@ 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")
|
||||||
}
|
}
|
||||||
@@ -200,21 +187,6 @@ 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 {
|
|
||||||
display("the offset 0 can not point to a valid IFD")
|
|
||||||
}
|
|
||||||
ReconfiguredAfterImageWrite {
|
|
||||||
display("attempted to reconfigure the encoder after image writing has started")
|
|
||||||
}
|
|
||||||
ByteOrderMismatch {
|
|
||||||
display("attempted to use data with a byte order that does not match the required one")
|
|
||||||
}
|
|
||||||
MismatchedEntryLength {ty: Type, found: usize } {
|
|
||||||
display("the length of data is not a multiple of the size of the type {ty:?}, found {found}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-22
@@ -44,15 +44,11 @@ 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 },
|
||||||
}
|
}
|
||||||
impl ColorType {
|
impl ColorType {
|
||||||
/// The number of bits used to store each sample.
|
fn bit_depth(&self) -> u8 {
|
||||||
pub fn bit_depth(&self) -> u8 {
|
|
||||||
match *self {
|
match *self {
|
||||||
ColorType::Gray(b)
|
ColorType::Gray(b)
|
||||||
| ColorType::RGB(b)
|
| ColorType::RGB(b)
|
||||||
@@ -62,24 +58,7 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The number of samples in each representation of a single color.
|
|
||||||
pub 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+8
-268
@@ -1,5 +1,3 @@
|
|||||||
use crate::encoder::TiffValue;
|
|
||||||
|
|
||||||
macro_rules! tags {
|
macro_rules! tags {
|
||||||
{
|
{
|
||||||
// Permit arbitrary meta items, which include documentation.
|
// Permit arbitrary meta items, which include documentation.
|
||||||
@@ -12,9 +10,8 @@ macro_rules! tags {
|
|||||||
$( #[$enum_attr] )*
|
$( #[$enum_attr] )*
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[repr($ty)]
|
|
||||||
pub enum $name {
|
pub enum $name {
|
||||||
$($(#[$ident_attr])* $tag = $val,)*
|
$($(#[$ident_attr])* $tag,)*
|
||||||
$(
|
$(
|
||||||
#[$unknown_meta]
|
#[$unknown_meta]
|
||||||
Unknown($ty),
|
Unknown($ty),
|
||||||
@@ -23,7 +20,7 @@ macro_rules! tags {
|
|||||||
|
|
||||||
impl $name {
|
impl $name {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
const fn __from_inner_type(n: $ty) -> Result<Self, $ty> {
|
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),
|
||||||
@@ -31,7 +28,7 @@ macro_rules! tags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
const fn __to_inner_type(&self) -> $ty {
|
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 }, )*
|
||||||
@@ -45,25 +42,19 @@ macro_rules! tags {
|
|||||||
($name:tt, u16, $($unknown_doc:ident)*) => {
|
($name:tt, u16, $($unknown_doc:ident)*) => {
|
||||||
impl $name {
|
impl $name {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub const fn from_u16(val: u16) -> Option<Self> {
|
pub fn from_u16(val: u16) -> Option<Self> {
|
||||||
match Self::__from_inner_type(val) {
|
Self::__from_inner_type(val).ok()
|
||||||
Ok(v) => Some(v),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(
|
$(
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub const fn from_u16_exhaustive($unknown_doc: u16) -> Self {
|
pub fn from_u16_exhaustive($unknown_doc: u16) -> Self {
|
||||||
match Self::__from_inner_type($unknown_doc) {
|
Self::__from_inner_type($unknown_doc).unwrap_or_else(|_| $name::Unknown($unknown_doc))
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => $name::Unknown($unknown_doc),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub const fn to_u16(&self) -> u16 {
|
pub fn to_u16(&self) -> u16 {
|
||||||
Self::__to_inner_type(self)
|
Self::__to_inner_type(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,11 +122,6 @@ 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)
|
||||||
@@ -207,45 +193,6 @@ pub enum Type(u16) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Type {
|
|
||||||
pub(crate) fn byte_len(&self) -> u8 {
|
|
||||||
match *self {
|
|
||||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1,
|
|
||||||
Type::SHORT | Type::SSHORT => 2,
|
|
||||||
Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4,
|
|
||||||
Type::LONG8
|
|
||||||
| Type::SLONG8
|
|
||||||
| Type::DOUBLE
|
|
||||||
| Type::RATIONAL
|
|
||||||
| Type::SRATIONAL
|
|
||||||
| Type::IFD8 => 8,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn value_bytes(&self, count: u64) -> Result<u64, crate::error::TiffError> {
|
|
||||||
let tag_size = u64::from(self.byte_len());
|
|
||||||
|
|
||||||
match count.checked_mul(tag_size) {
|
|
||||||
Some(n) => Ok(n),
|
|
||||||
None => Err(crate::error::TiffError::LimitsExceeded),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn endian_bytes(self) -> EndianBytes {
|
|
||||||
match self {
|
|
||||||
Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => EndianBytes::One,
|
|
||||||
Type::SHORT | Type::SSHORT => EndianBytes::Two,
|
|
||||||
Type::LONG
|
|
||||||
| Type::SLONG
|
|
||||||
| Type::FLOAT
|
|
||||||
| Type::IFD
|
|
||||||
| Type::RATIONAL
|
|
||||||
| Type::SRATIONAL => EndianBytes::Four,
|
|
||||||
Type::LONG8 | Type::SLONG8 | Type::DOUBLE | Type::IFD8 => EndianBytes::Eight,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tags! {
|
tags! {
|
||||||
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
/// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html)
|
||||||
/// for reference.
|
/// for reference.
|
||||||
@@ -280,8 +227,6 @@ pub enum PhotometricInterpretation(u16) {
|
|||||||
CMYK = 5,
|
CMYK = 5,
|
||||||
YCbCr = 6,
|
YCbCr = 6,
|
||||||
CIELab = 8,
|
CIELab = 8,
|
||||||
IccLab = 9,
|
|
||||||
ItuLab = 10,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,208 +271,3 @@ pub enum SampleFormat(u16) unknown(
|
|||||||
Void = 4,
|
Void = 4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A value represented as in-memory bytes with flexible byteorder.
|
|
||||||
pub struct ValueBuffer {
|
|
||||||
/// The raw bytes of the value.
|
|
||||||
bytes: Vec<u8>,
|
|
||||||
|
|
||||||
/// The type of the value.
|
|
||||||
ty: Type,
|
|
||||||
|
|
||||||
/// The number of items, as `bytes` may be oversized while holding bytes that are initialized
|
|
||||||
/// but not used by any value.
|
|
||||||
count: u64,
|
|
||||||
|
|
||||||
/// The byte order of the value.
|
|
||||||
byte_order: ByteOrder,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValueBuffer {
|
|
||||||
/// A value with a count of zero.
|
|
||||||
///
|
|
||||||
/// The byte order is set to the native byte order of the platform.
|
|
||||||
pub fn empty(ty: Type) -> Self {
|
|
||||||
ValueBuffer {
|
|
||||||
bytes: vec![],
|
|
||||||
ty,
|
|
||||||
count: 0,
|
|
||||||
byte_order: ByteOrder::native(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a value with native byte order from in-memory data.
|
|
||||||
pub fn from_value<T: TiffValue>(value: &T) -> Self {
|
|
||||||
ValueBuffer {
|
|
||||||
bytes: value.data().into_owned(),
|
|
||||||
ty: <T as TiffValue>::FIELD_TYPE,
|
|
||||||
count: value.count() as u64,
|
|
||||||
byte_order: ByteOrder::native(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn byte_order(&self) -> ByteOrder {
|
|
||||||
self.byte_order
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data_type(&self) -> Type {
|
|
||||||
self.ty
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The count of items in the value.
|
|
||||||
pub fn count(&self) -> u64 {
|
|
||||||
debug_assert!({
|
|
||||||
self.ty
|
|
||||||
.value_bytes(self.count)
|
|
||||||
.is_ok_and(|n| n <= self.bytes.len() as u64)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.count
|
|
||||||
}
|
|
||||||
|
|
||||||
/// View the underlying raw bytes of this value.
|
|
||||||
pub fn as_bytes(&self) -> &[u8] {
|
|
||||||
&self.bytes[..self.assumed_len_from_count()]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// View the underlying mutable raw bytes of this value.
|
|
||||||
pub fn as_bytes_mut(&mut self) -> &mut [u8] {
|
|
||||||
let len = self.assumed_len_from_count();
|
|
||||||
&mut self.bytes[..len]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the byte order of the value representation.
|
|
||||||
pub fn set_byte_order(&mut self, byte_order: ByteOrder) {
|
|
||||||
let len = self.assumed_len_from_count();
|
|
||||||
|
|
||||||
self.byte_order
|
|
||||||
.convert(self.ty, &mut self.bytes[..len], byte_order);
|
|
||||||
|
|
||||||
self.byte_order = byte_order;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prepare the internal for a value `to_len` bytes long.
|
|
||||||
///
|
|
||||||
/// Shrinks the allocation if it is far too large or extends it if it is too small. In either
|
|
||||||
/// case ensures that at least `to_len` bytes are initialized for [`Self::raw_bytes_mut`].
|
|
||||||
pub(crate) fn prepare_length(&mut self, to_len: usize) {
|
|
||||||
if to_len > self.bytes.len() {
|
|
||||||
self.bytes.resize(to_len, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.bytes.len() < to_len / 2 {
|
|
||||||
self.bytes.truncate(to_len);
|
|
||||||
self.bytes.shrink_to_fit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal method to change the type and count while re-interpreting the byte buffer.
|
|
||||||
///
|
|
||||||
/// Should only be called after writing bytes to the internal buffer prepared with
|
|
||||||
/// `Self::prepare_length`.
|
|
||||||
pub(crate) fn assume_type(&mut self, ty: Type, count: u64, bo: ByteOrder) {
|
|
||||||
debug_assert!({
|
|
||||||
ty.value_bytes(count)
|
|
||||||
.is_ok_and(|n| n <= self.bytes.len() as u64)
|
|
||||||
});
|
|
||||||
|
|
||||||
self.byte_order = bo;
|
|
||||||
self.ty = ty;
|
|
||||||
self.count = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn raw_bytes_mut(&mut self) -> &mut [u8] {
|
|
||||||
&mut self.bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assumed_len_from_count(&self) -> usize {
|
|
||||||
usize::from(self.ty.byte_len()) * self.count as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Byte order of the TIFF file.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ByteOrder {
|
|
||||||
/// little endian byte order
|
|
||||||
LittleEndian,
|
|
||||||
/// big endian byte order
|
|
||||||
BigEndian,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ByteOrder {
|
|
||||||
/// Get the byte order representing the running target.
|
|
||||||
///
|
|
||||||
/// The infallibility of this method represents the fact that only little and big endian
|
|
||||||
/// systems are supported by the library. No mixed endian and no other weird stuff. (Note: as
|
|
||||||
/// of Rust 1.90 this is a tautology as Rust itself only has those two kinds).
|
|
||||||
pub const fn native() -> Self {
|
|
||||||
match () {
|
|
||||||
#[cfg(target_endian = "little")]
|
|
||||||
() => ByteOrder::LittleEndian,
|
|
||||||
#[cfg(target_endian = "big")]
|
|
||||||
() => ByteOrder::BigEndian,
|
|
||||||
#[cfg(not(any(target_endian = "big", target_endian = "little")))]
|
|
||||||
() => compile_error!("Unsupported target"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given a typed buffer, convert its contents to the specified byte order in-place.
|
|
||||||
///
|
|
||||||
/// The buffer is assumed to represent an array of the given type. If the length of the buffer
|
|
||||||
/// is not divisible into an integer number of values, the behavior for the remaining bytes it
|
|
||||||
/// not specified.
|
|
||||||
pub fn convert(self, ty: Type, buffer: &mut [u8], to: ByteOrder) {
|
|
||||||
self.convert_endian_bytes(ty.endian_bytes(), buffer, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn convert_endian_bytes(self, cls: EndianBytes, buffer: &mut [u8], to: ByteOrder) {
|
|
||||||
if self == to {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: at MSRV 1.89 or higher use `slice::as_chunks_mut`.
|
|
||||||
match cls {
|
|
||||||
EndianBytes::One => {
|
|
||||||
// No change needed
|
|
||||||
}
|
|
||||||
EndianBytes::Two => {
|
|
||||||
for chunk in buffer.chunks_exact_mut(2) {
|
|
||||||
let chunk: &mut [u8; 2] = chunk.try_into().unwrap();
|
|
||||||
*chunk = u16::from_be_bytes(*chunk).to_le_bytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EndianBytes::Four => {
|
|
||||||
for chunk in buffer.chunks_exact_mut(4) {
|
|
||||||
let chunk: &mut [u8; 4] = chunk.try_into().unwrap();
|
|
||||||
*chunk = u32::from_be_bytes(*chunk).to_le_bytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EndianBytes::Eight => {
|
|
||||||
for chunk in buffer.chunks_exact_mut(8) {
|
|
||||||
let chunk: &mut [u8; 8] = chunk.try_into().unwrap();
|
|
||||||
*chunk = u64::from_be_bytes(*chunk).to_le_bytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The size of individual byte-order corrected elements.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub(crate) enum EndianBytes {
|
|
||||||
One,
|
|
||||||
Two,
|
|
||||||
Four,
|
|
||||||
Eight,
|
|
||||||
}
|
|
||||||
@@ -672,51 +672,3 @@ fn bytes_gray_f32() {
|
|||||||
byte_order_u32,
|
byte_order_u32,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "fax")]
|
|
||||||
fn test_fax4() {
|
|
||||||
test_image_sum_u8("fax4.tiff", ColorType::Gray(1), 7802706);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "fax")]
|
|
||||||
fn test_fax4_white_is_min() {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
use tiff::decoder::{Decoder, DecodingResult, Limits, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn read_planes_by_limits() {
|
|
||||||
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 limits = Limits::default();
|
|
||||||
let mut buffer = DecodingResult::U8(vec![]);
|
|
||||||
|
|
||||||
// Make room for 1 plane.
|
|
||||||
let mut tif = Decoder::new(io).unwrap();
|
|
||||||
tif = tif.with_limits({
|
|
||||||
limits.decoding_buffer_size = 374 * 499;
|
|
||||||
limits.clone()
|
|
||||||
});
|
|
||||||
|
|
||||||
tif.read_image_to_buffer(&mut buffer).unwrap();
|
|
||||||
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499);
|
|
||||||
|
|
||||||
// Make room for 2 planes.
|
|
||||||
tif = tif.with_limits({
|
|
||||||
limits.decoding_buffer_size = 374 * 499 * 2;
|
|
||||||
limits.clone()
|
|
||||||
});
|
|
||||||
|
|
||||||
tif.read_image_to_buffer(&mut buffer).unwrap();
|
|
||||||
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499);
|
|
||||||
|
|
||||||
// Make room for 3 planes.
|
|
||||||
tif = tif.with_limits({
|
|
||||||
limits.decoding_buffer_size = 374 * 499 * 3;
|
|
||||||
limits.clone()
|
|
||||||
});
|
|
||||||
|
|
||||||
tif.read_image_to_buffer(&mut buffer).unwrap();
|
|
||||||
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499 * 3);
|
|
||||||
|
|
||||||
// Make room for 4 planes?
|
|
||||||
tif = tif.with_limits({
|
|
||||||
limits.decoding_buffer_size = 374 * 499 * 4;
|
|
||||||
limits.clone()
|
|
||||||
});
|
|
||||||
|
|
||||||
tif.read_image_to_buffer(&mut buffer).unwrap();
|
|
||||||
assert_eq!(buffer.as_buffer(0).byte_len(), 374 * 499 * 3);
|
|
||||||
}
|
|
||||||
+6
-17
@@ -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(35)),
|
(Tag::StripOffsets, ifd::Value::Unsigned(47)),
|
||||||
(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,10 +81,7 @@ 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");
|
||||||
@@ -164,9 +161,7 @@ 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())
|
||||||
);
|
);
|
||||||
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
||||||
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");
|
||||||
@@ -240,7 +235,6 @@ 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,
|
||||||
@@ -250,10 +244,7 @@ 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 mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
let image_data = match decoder.read_image().unwrap() {
|
||||||
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"),
|
||||||
};
|
};
|
||||||
@@ -267,11 +258,9 @@ 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();
|
||||||
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
if let DecodingResult::$buffer(img_res) = decoder.read_image().unwrap() {
|
||||||
if let DecodingResult::$buffer(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");
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+129
-1
@@ -1,4 +1,7 @@
|
|||||||
use tiff::decoder::Decoder;
|
use tiff::{
|
||||||
|
decoder::Decoder,
|
||||||
|
tags::{IfdPointer, Tag},
|
||||||
|
};
|
||||||
|
|
||||||
// This file has the following IFD structure:
|
// This file has the following IFD structure:
|
||||||
//
|
//
|
||||||
@@ -39,3 +42,128 @@ fn decode_seek_chain() {
|
|||||||
assert_eq!(Some(offset2), decoder.ifd_pointer());
|
assert_eq!(Some(offset2), decoder.ifd_pointer());
|
||||||
assert!(!decoder.more_images());
|
assert!(!decoder.more_images());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_seek_recover() {
|
||||||
|
let file = File::open(TEST_IMAGE_SUBIFD).expect("Cannot open test image");
|
||||||
|
let mut decoder = Decoder::new(file).expect("Invalid format to create decoder");
|
||||||
|
|
||||||
|
// Remember the offsets in the first iteration.
|
||||||
|
let offset0 = decoder.ifd_pointer().expect("First IFD pointer not found");
|
||||||
|
decoder.next_image().unwrap();
|
||||||
|
let offset1 = decoder.ifd_pointer().expect("Second IFD pointer not found");
|
||||||
|
decoder.next_image().unwrap();
|
||||||
|
let offset2 = decoder.ifd_pointer().expect("Third IFD pointer not found");
|
||||||
|
|
||||||
|
// oops!
|
||||||
|
let fails = decoder.restart_at_image(IfdPointer(0xdead_beef));
|
||||||
|
assert!(fails.is_err());
|
||||||
|
|
||||||
|
// However we can recover by restarting our seek.
|
||||||
|
decoder
|
||||||
|
.restart_at_image(offset0)
|
||||||
|
.expect("Failed to restart IFDs from first image");
|
||||||
|
assert_eq!(Some(offset0), decoder.ifd_pointer());
|
||||||
|
decoder.next_image().unwrap();
|
||||||
|
assert_eq!(Some(offset1), decoder.ifd_pointer());
|
||||||
|
decoder.next_image().unwrap();
|
||||||
|
assert_eq!(Some(offset2), decoder.ifd_pointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_seek_directory() {
|
||||||
|
let file = File::open(TEST_IMAGE_SUBIFD).expect("Cannot open test image");
|
||||||
|
let mut decoder = Decoder::new(file).expect("Invalid format to create decoder");
|
||||||
|
|
||||||
|
let offset = decoder.ifd_pointer().expect("First IFD pointer not found");
|
||||||
|
let img0 = decoder.read_image().unwrap();
|
||||||
|
|
||||||
|
decoder.next_image().unwrap();
|
||||||
|
let img1 = decoder.read_image().unwrap();
|
||||||
|
assert_ne!(img0, img1);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Due to the special test file structure.
|
||||||
|
let subifd = decoder
|
||||||
|
.get_tag(Tag::SubIfd)
|
||||||
|
.unwrap()
|
||||||
|
.into_ifd_vec()
|
||||||
|
.unwrap()[0];
|
||||||
|
|
||||||
|
// Let's try to manually interpret this as an image.
|
||||||
|
decoder
|
||||||
|
.restart_at_directory(subifd)
|
||||||
|
.expect("Failed to restart IFDs at SubIfd");
|
||||||
|
decoder
|
||||||
|
.current_directory_as_image()
|
||||||
|
.expect("Failed to read SubIfd image");
|
||||||
|
|
||||||
|
assert_eq!(decoder.dimensions().unwrap(), (32, 32));
|
||||||
|
let subimage = decoder.read_image().unwrap();
|
||||||
|
// Verify we did not accidentally read another image.
|
||||||
|
assert_ne!(img0, subimage);
|
||||||
|
assert_ne!(img1, subimage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And finally, seek back to the first image.
|
||||||
|
decoder
|
||||||
|
.restart_at_image(offset)
|
||||||
|
.expect("First IFD no longer readable");
|
||||||
|
assert_eq!(Some(offset), decoder.ifd_pointer());
|
||||||
|
|
||||||
|
let img0_try2 = decoder.read_image().unwrap();
|
||||||
|
assert_eq!(img0, img0_try2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_subifds() {
|
||||||
|
let explore_chain = |decoder: &mut Decoder<_>, subifds: &mut Vec<_>| {
|
||||||
|
let mut len = 0usize;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
len += 1;
|
||||||
|
|
||||||
|
if let Ok(subifd) = decoder.get_tag(Tag::SubIfd) {
|
||||||
|
let prelen = subifds.len();
|
||||||
|
subifds.extend(subifd.into_ifd_vec().expect("Failed to decode SubIfd tag"));
|
||||||
|
eprintln!("New roots: {:?}", &subifds[prelen..]);
|
||||||
|
} else {
|
||||||
|
eprintln!("No roots");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !decoder.more_images() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder.next_image().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
len
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = File::open(TEST_IMAGE_SUBIFD).expect("Cannot open test image");
|
||||||
|
let mut decoder = Decoder::new(file).expect("Invalid format to create decoder");
|
||||||
|
|
||||||
|
let mut subifds = vec![];
|
||||||
|
let mut chain_lengths = vec![];
|
||||||
|
chain_lengths.push(explore_chain(&mut decoder, &mut subifds));
|
||||||
|
|
||||||
|
while !subifds.is_empty() {
|
||||||
|
let root = subifds.remove(0);
|
||||||
|
let manual_directory = decoder.read_directory(root).unwrap();
|
||||||
|
eprintln!("Manual directory: {:?}", manual_directory);
|
||||||
|
|
||||||
|
decoder.restart_at_directory(root).unwrap();
|
||||||
|
let prelen = subifds.len();
|
||||||
|
chain_lengths.push(explore_chain(&mut decoder, &mut subifds));
|
||||||
|
eprintln!("Chains roots: {:?}", &subifds[prelen..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traversal Order in this graph:
|
||||||
|
// Image 0 -> Image 1 -> Image 2
|
||||||
|
// |-> Image 3
|
||||||
|
// |-> Image 4 -> Image 5
|
||||||
|
// | \-> Image 7 -> Image 8
|
||||||
|
// \-> Image 6
|
||||||
|
assert_eq!(chain_lengths, [3, 1, 2, 1, 2]);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user