Compare commits
84
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
812bc970ba | ||
|
|
e8be8656f2 | ||
|
|
e1e9f36c5a | ||
|
|
0ad653433a | ||
|
|
bf79af8de4 | ||
|
|
54dddc4943 | ||
|
|
be9b3a04b9 | ||
|
|
08f1b25fac | ||
|
|
c7b97519a0 | ||
|
|
e093e15bbe | ||
|
|
647e9b4a81 | ||
|
|
31c49ea6ef | ||
|
|
0362e6e31d | ||
|
|
0fe5b2804e | ||
|
|
a589010099 | ||
|
|
53498074a4 | ||
|
|
cb543cded7 | ||
|
|
cf8f96568e | ||
|
|
4ff88e658d | ||
|
|
14ebf376f3 | ||
|
|
37578add53 | ||
|
|
2b9476566b | ||
|
|
448a0e61d1 | ||
|
|
d872ff8751 | ||
|
|
0a878888db | ||
|
|
a0a95ef86e | ||
|
|
bbb5e5a41b | ||
|
|
f74169d4af | ||
|
|
c304a3cc13 | ||
|
|
9a9193345c | ||
|
|
22046d4272 | ||
|
|
2c5fe2c2f5 | ||
|
|
eceaa1f14d | ||
|
|
d42db25c27 | ||
|
|
c74f23784c | ||
|
|
8b0da5939a | ||
|
|
fdde3467fc | ||
|
|
3f2066bb20 | ||
|
|
2ac8be1177 | ||
|
|
c81d0a9c94 | ||
|
|
983c5baea0 | ||
|
|
abf3cc617d | ||
|
|
fb4ee9b377 | ||
|
|
45cd3c3b63 | ||
|
|
701fcc46b8 | ||
|
|
e41159ce05 | ||
|
|
03979faef8 | ||
|
|
857973cb21 | ||
|
|
cef21c47fb | ||
|
|
a0191ed1a7 | ||
|
|
99a7b8cba7 | ||
|
|
418ea182c3 | ||
|
|
529fa64e13 | ||
|
|
3d6b94b65a | ||
|
|
a51b23091e | ||
|
|
ac65bd8477 | ||
|
|
1bd6293857 | ||
|
|
87341f9075 | ||
|
|
43343f64cd | ||
|
|
be04326264 | ||
|
|
93a68f52f5 | ||
|
|
5f329840c8 | ||
|
|
1d138a5568 | ||
|
|
9a23090b6e | ||
|
|
9d409433e8 | ||
|
|
cf680424d9 | ||
|
|
d3b777c961 | ||
|
|
24b008a7c3 | ||
|
|
34bd1da99e | ||
|
|
cfff04c97d | ||
|
|
915cc20945 | ||
|
|
74b2857364 | ||
|
|
1dab89f10e | ||
|
|
45f617b9f5 | ||
|
|
170bd3697c | ||
|
|
855eb4ef23 | ||
|
|
fee650d7f6 | ||
|
|
ee8d13249e | ||
|
|
169a896851 | ||
|
|
082a35adb9 | ||
|
|
923df5f0bd | ||
|
|
c4328644b8 | ||
|
|
27bfcf5562 | ||
|
|
7d498a0284 |
No files matched your search
@@ -9,14 +9,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust: ["1.74.0", stable, beta, nightly]
|
||||
rust: ["1.88.0", stable, beta, nightly]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
if: ${{ matrix.rust == '1.74.0' }}
|
||||
if: ${{ matrix.rust == '1.88.0' }}
|
||||
- name: Generate Cargo.lock with minimal-version dependencies
|
||||
if: ${{ matrix.rust == '1.74.0' }}
|
||||
if: ${{ matrix.rust == '1.88.0' }}
|
||||
run: cargo -Zminimal-versions generate-lockfile
|
||||
|
||||
- uses: dtolnay/rust-toolchain@v1
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: build
|
||||
run: cargo build -v
|
||||
- name: test
|
||||
if: ${{ matrix.rust != '1.74.0' }}
|
||||
if: ${{ matrix.rust != '1.88.0' }}
|
||||
run: cargo test -v && cargo doc -v
|
||||
|
||||
features:
|
||||
|
||||
+72
@@ -1,3 +1,75 @@
|
||||
# Version 0.12.0 (unreleased)
|
||||
|
||||
Additions:
|
||||
- Added the `TiffHeader` type. It can be used to create a `Decoder` in a state
|
||||
before the first IFD and allows opening files at an arbitrary first IFD
|
||||
offset rather than one indicated by an encoded header at the start.
|
||||
|
||||
Changes:
|
||||
- The IFD iteration methods on `Decoder` (`next_image`, `seek_to_image`, etc)
|
||||
now come with a strong error guarantee. They will not modify the current
|
||||
directory state if they fail. This includes the failure to interpret an IFD
|
||||
as a supported image in the case of `*_image` variants.
|
||||
|
||||
# Version 0.11.3
|
||||
|
||||
Additions:
|
||||
- Added support for `webp` compression (as per GDAL) under the `webp` feature flag (default: off).
|
||||
- Added `Decoder::image_ifd`, creating an `IfdDecoder` of the current IFD.
|
||||
- Added `IfdDecoder::directory`, returning the `Directory` underlying it.
|
||||
- Added `LowerHex` and `UpwerHex` implementations for `IfdPointer`.
|
||||
|
||||
Notes:
|
||||
- A new example `tiff-ls` demonstrates use of the new features.
|
||||
|
||||
# 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)>`.
|
||||
|
||||
+16
-3
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "tiff"
|
||||
version = "0.11.0"
|
||||
version = "0.11.3"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
||||
rust-version = "1.74.0"
|
||||
rust-version = "1.88.0"
|
||||
|
||||
license = "MIT"
|
||||
description = "TIFF decoding and encoding library in pure Rust"
|
||||
@@ -14,7 +14,7 @@ authors = ["The image-rs Developers"]
|
||||
repository = "https://github.com/image-rs/image-tiff"
|
||||
categories = ["multimedia::images", "multimedia::encoding"]
|
||||
|
||||
exclude = ["tests/images/*", "tests/fuzz_images/*"]
|
||||
exclude = ["tests/images/*", "tests/fuzz_images/*", "tests/**/*.tif"]
|
||||
|
||||
[dependencies]
|
||||
half = { version = "2.4.1" }
|
||||
@@ -26,11 +26,14 @@ flate2 = { version = "1.0.20", optional = true }
|
||||
weezl = { version = "0.1.10", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
zune-jpeg = { version = "0.5.1", optional = true }
|
||||
image-webp = {version = "0.2.4", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3.1"
|
||||
# We may already depend on this via flate2
|
||||
crc32fast = "1.5"
|
||||
libtest-mimic = "0.8.1"
|
||||
walkdir = "2.5"
|
||||
|
||||
[features]
|
||||
default = ["deflate", "fax", "jpeg", "lzw"]
|
||||
@@ -40,8 +43,18 @@ deflate = ["dep:flate2"]
|
||||
fax = ["dep:fax34"]
|
||||
jpeg = ["dep:zune-jpeg"]
|
||||
lzw = ["dep:weezl"]
|
||||
webp = ["dep:image-webp"]
|
||||
zstd = ["dep:zstd"]
|
||||
|
||||
[[bench]]
|
||||
name = "lzw"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "encode"
|
||||
harness = false
|
||||
|
||||
[[test]]
|
||||
name = "decode_libtiffpic"
|
||||
path = "tests/decode_libtiffpic.rs"
|
||||
harness = false
|
||||
@@ -0,0 +1,44 @@
|
||||
extern crate criterion;
|
||||
extern crate tiff;
|
||||
|
||||
use criterion::{black_box, Criterion, Throughput};
|
||||
use std::io::Cursor;
|
||||
use tiff::encoder::colortype::RGB32Float;
|
||||
use tiff::encoder::{Compression, Predictor, TiffEncoder};
|
||||
|
||||
const WIDTH: u32 = 500;
|
||||
const HEIGHT: u32 = 500;
|
||||
|
||||
fn encode_image_f32_float_predictor_deflate(data: &[f32]) {
|
||||
let mut buf = Cursor::new(Vec::with_capacity(WIDTH as usize * HEIGHT as usize * 4));
|
||||
TiffEncoder::new(&mut buf)
|
||||
.unwrap()
|
||||
.with_predictor(Predictor::FloatingPoint)
|
||||
.with_compression(Compression::Deflate(1))
|
||||
.write_image::<RGB32Float>(WIDTH, HEIGHT, black_box(data))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Generate synthetic f32 image data with a gradient pattern
|
||||
let data: Vec<f32> = (0..WIDTH as usize * HEIGHT as usize * 3)
|
||||
.map(|i| {
|
||||
let pixel = i / 3;
|
||||
let x = (pixel % WIDTH as usize) as f32 / WIDTH as f32;
|
||||
let y = (pixel / WIDTH as usize) as f32 / HEIGHT as f32;
|
||||
x * 0.5 + y * 0.5
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut c = Criterion::default().configure_from_args();
|
||||
let mut group = c.benchmark_group("tiff-encode");
|
||||
|
||||
group
|
||||
.sample_size(20)
|
||||
.throughput(Throughput::Bytes(
|
||||
(WIDTH as u64) * (HEIGHT as u64) * 3 * std::mem::size_of::<f32>() as u64,
|
||||
))
|
||||
.bench_function("f32-float-predictor-deflate-fast", |b| {
|
||||
b.iter(|| encode_image_f32_float_predictor_deflate(&data))
|
||||
});
|
||||
}
|
||||
+4
-5
@@ -8,13 +8,12 @@ use tiff::decoder::Decoder;
|
||||
|
||||
fn read_image(image: &[u8]) {
|
||||
let image = std::io::Cursor::new(image);
|
||||
let decoder = Decoder::new(black_box(image));
|
||||
let mut reader = decoder.unwrap();
|
||||
let decoder = Decoder::open(black_box(image));
|
||||
|
||||
while {
|
||||
let mut reader = decoder.unwrap();
|
||||
while reader.more_images() {
|
||||
reader.read_image().unwrap();
|
||||
reader.more_images()
|
||||
} {}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
+130
-2
@@ -8,7 +8,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let file = std::fs::File::open(image)?;
|
||||
let io = std::io::BufReader::new(file);
|
||||
let mut reader = Decoder::new(io)?;
|
||||
let mut reader = Decoder::open(io)?;
|
||||
|
||||
let mut data = DecodingResult::I8(vec![]);
|
||||
|
||||
@@ -36,7 +36,33 @@ fn debug_planes(
|
||||
colortype: &tiff::ColorType,
|
||||
(width, height): (u32, u32),
|
||||
) {
|
||||
match data {
|
||||
DecodingResult::F16(v) => {
|
||||
let samples = v
|
||||
.iter()
|
||||
.map(|&f| (f64::from(f) * f64::from(u16::MAX)) as u16)
|
||||
.collect::<Vec<_>>();
|
||||
*data = DecodingResult::U16(samples);
|
||||
}
|
||||
DecodingResult::F32(v) => {
|
||||
let samples = v
|
||||
.iter()
|
||||
.map(|&f| (f64::from(f) * f64::from(u16::MAX)) as u16)
|
||||
.collect::<Vec<_>>();
|
||||
*data = DecodingResult::U16(samples);
|
||||
}
|
||||
DecodingResult::F64(v) => {
|
||||
let samples = v
|
||||
.iter()
|
||||
.map(|&f| (f * f64::from(u16::MAX)) as u16)
|
||||
.collect::<Vec<_>>();
|
||||
*data = DecodingResult::U16(samples);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
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"),
|
||||
@@ -49,8 +75,57 @@ fn debug_planes(
|
||||
}
|
||||
};
|
||||
|
||||
// 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) {
|
||||
DecodingBuffer::U8(_) => u64::from(u8::MAX),
|
||||
// `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,
|
||||
@@ -101,3 +176,56 @@ fn debug_planes(
|
||||
.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()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use tiff::decoder::Decoder;
|
||||
|
||||
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::open(io)?;
|
||||
|
||||
while reader.more_images() {
|
||||
reader.next_directory()?;
|
||||
ls_dir(&mut reader);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ls_dir<R: std::io::Read + std::io::Seek>(reader: &mut Decoder<R>) {
|
||||
if let Some(ifd) = reader.ifd_pointer() {
|
||||
println!("Directory at {ifd:x}");
|
||||
}
|
||||
|
||||
println!("Name\tHex\tType\tCount");
|
||||
let ifd = reader.current_ifd();
|
||||
|
||||
for (tag, entry) in ifd.directory().iter() {
|
||||
let name: &'static str = match tag {
|
||||
tiff::tags::Tag::Artist => "Artist",
|
||||
tiff::tags::Tag::BitsPerSample => "BitsPerSample",
|
||||
tiff::tags::Tag::CellLength => "CellLength",
|
||||
tiff::tags::Tag::CellWidth => "CellWidth",
|
||||
tiff::tags::Tag::ColorMap => "ColorMap",
|
||||
tiff::tags::Tag::Compression => "Compression",
|
||||
tiff::tags::Tag::DateTime => "DateTime",
|
||||
tiff::tags::Tag::ExtraSamples => "ExtraSamples",
|
||||
tiff::tags::Tag::FillOrder => "FillOrder",
|
||||
tiff::tags::Tag::FreeByteCounts => "FreeByteCounts",
|
||||
tiff::tags::Tag::FreeOffsets => "FreeOffsets",
|
||||
tiff::tags::Tag::GrayResponseCurve => "GrayResponseCurve",
|
||||
tiff::tags::Tag::GrayResponseUnit => "GrayResponseUnit",
|
||||
tiff::tags::Tag::HostComputer => "HostComputer",
|
||||
tiff::tags::Tag::ImageDescription => "ImageDescription",
|
||||
tiff::tags::Tag::ImageLength => "ImageLength",
|
||||
tiff::tags::Tag::ImageWidth => "ImageWidth",
|
||||
tiff::tags::Tag::Make => "Make",
|
||||
tiff::tags::Tag::MaxSampleValue => "MaxSampleValue",
|
||||
tiff::tags::Tag::MinSampleValue => "MinSampleValue",
|
||||
tiff::tags::Tag::Model => "Model",
|
||||
tiff::tags::Tag::NewSubfileType => "NewSubfileType",
|
||||
tiff::tags::Tag::Orientation => "Orientation",
|
||||
tiff::tags::Tag::PhotometricInterpretation => "PhotometricInterpretation",
|
||||
tiff::tags::Tag::PlanarConfiguration => "PlanarConfiguration",
|
||||
tiff::tags::Tag::ResolutionUnit => "ResolutionUnit",
|
||||
tiff::tags::Tag::RowsPerStrip => "RowsPerStrip",
|
||||
tiff::tags::Tag::SamplesPerPixel => "SamplesPerPixel",
|
||||
tiff::tags::Tag::Software => "Software",
|
||||
tiff::tags::Tag::StripByteCounts => "StripByteCounts",
|
||||
tiff::tags::Tag::StripOffsets => "StripOffsets",
|
||||
tiff::tags::Tag::SubfileType => "SubfileType",
|
||||
tiff::tags::Tag::Threshholding => "Threshholding",
|
||||
tiff::tags::Tag::XResolution => "XResolution",
|
||||
tiff::tags::Tag::YResolution => "YResolution",
|
||||
tiff::tags::Tag::Predictor => "Predictor",
|
||||
tiff::tags::Tag::TileWidth => "TileWidth",
|
||||
tiff::tags::Tag::TileLength => "TileLength",
|
||||
tiff::tags::Tag::TileOffsets => "TileOffsets",
|
||||
tiff::tags::Tag::TileByteCounts => "TileByteCounts",
|
||||
tiff::tags::Tag::SubIfd => "SubIfd",
|
||||
tiff::tags::Tag::SampleFormat => "SampleFormat",
|
||||
tiff::tags::Tag::SMinSampleValue => "SMinSampleValue",
|
||||
tiff::tags::Tag::SMaxSampleValue => "SMaxSampleValue",
|
||||
tiff::tags::Tag::JPEGTables => "JPEGTables",
|
||||
tiff::tags::Tag::ChromaSubsampling => "ChromaSubsampling",
|
||||
tiff::tags::Tag::ChromaPositioning => "ChromaPositioning",
|
||||
tiff::tags::Tag::ModelPixelScaleTag => "ModelPixelScaleTag",
|
||||
tiff::tags::Tag::ModelTransformationTag => "ModelTransformationTag",
|
||||
tiff::tags::Tag::ModelTiepointTag => "ModelTiepointTag",
|
||||
tiff::tags::Tag::Copyright => "Copyright",
|
||||
tiff::tags::Tag::ExifDirectory => "ExifDirectory",
|
||||
tiff::tags::Tag::GpsDirectory => "GpsDirectory",
|
||||
tiff::tags::Tag::IccProfile => "IccProfile",
|
||||
tiff::tags::Tag::GeoKeyDirectoryTag => "GeoKeyDirectoryTag",
|
||||
tiff::tags::Tag::GeoDoubleParamsTag => "GeoDoubleParamsTag",
|
||||
tiff::tags::Tag::GeoAsciiParamsTag => "GeoAsciiParamsTag",
|
||||
tiff::tags::Tag::ExifVersion => "ExifVersion",
|
||||
tiff::tags::Tag::GdalNodata => "GdalNodata",
|
||||
_ => "<unknown>",
|
||||
};
|
||||
|
||||
let ty: Cow<'static, str> = match entry.field_type() {
|
||||
tiff::tags::Type::BYTE => "u8".into(),
|
||||
tiff::tags::Type::ASCII => "ascii".into(),
|
||||
tiff::tags::Type::SHORT => "u16".into(),
|
||||
tiff::tags::Type::LONG => "u32".into(),
|
||||
tiff::tags::Type::RATIONAL => "r32".into(),
|
||||
tiff::tags::Type::SBYTE => "i8".into(),
|
||||
tiff::tags::Type::UNDEFINED => "byte".into(),
|
||||
tiff::tags::Type::SSHORT => "s16".into(),
|
||||
tiff::tags::Type::SLONG => "s32".into(),
|
||||
tiff::tags::Type::SRATIONAL => "sr32".into(),
|
||||
tiff::tags::Type::FLOAT => "f32".into(),
|
||||
tiff::tags::Type::DOUBLE => "f64".into(),
|
||||
tiff::tags::Type::IFD => "ifd32".into(),
|
||||
tiff::tags::Type::LONG8 => "u64".into(),
|
||||
tiff::tags::Type::SLONG8 => "i64".into(),
|
||||
tiff::tags::Type::IFD8 => "ifd64".into(),
|
||||
other => format!("{:x}", other.to_u16()).into(),
|
||||
};
|
||||
|
||||
eprintln!(
|
||||
"{name:16}\t{tag:4x}\t{ty}\t{count}",
|
||||
tag = tag.to_u16(),
|
||||
count = entry.count(),
|
||||
);
|
||||
}
|
||||
}
|
||||
+144
-12
@@ -6,12 +6,12 @@ use std::mem;
|
||||
use std::str;
|
||||
|
||||
use super::stream::{ByteOrder, EndianReader};
|
||||
use crate::tags::{IfdPointer, Tag, Type};
|
||||
use crate::tags::{IfdPointer, Tag, Type, ValueBuffer};
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use self::Value::{
|
||||
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
|
||||
Short, Signed, SignedBig, SignedByte, SignedShort, Unsigned, UnsignedBig,
|
||||
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, SRational, Short, Signed, SignedBig,
|
||||
SignedByte, SignedShort, Unsigned, UnsignedBig,
|
||||
};
|
||||
|
||||
#[allow(unused_qualifications)]
|
||||
@@ -30,8 +30,16 @@ pub enum Value {
|
||||
Double(f64),
|
||||
List(Vec<Value>),
|
||||
Rational(u32, u32),
|
||||
#[deprecated(
|
||||
note = "Not implemented in BigTIFF with a standard tag value",
|
||||
since = "0.11.1"
|
||||
)]
|
||||
RationalBig(u64, u64),
|
||||
SRational(i32, i32),
|
||||
#[deprecated(
|
||||
note = "Not implemented in BigTIFF with a standard tag value",
|
||||
since = "0.11.1"
|
||||
)]
|
||||
SRationalBig(i64, i64),
|
||||
Ascii(String),
|
||||
Ifd(u32),
|
||||
@@ -170,7 +178,8 @@ impl Value {
|
||||
Unsigned(val) => Ok(vec![val]),
|
||||
UnsignedBig(val) => Ok(vec![u32::try_from(val)?]),
|
||||
Rational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
RationalBig(numerator, denominator) => {
|
||||
#[expect(deprecated)]
|
||||
Value::RationalBig(numerator, denominator) => {
|
||||
Ok(vec![u32::try_from(numerator)?, u32::try_from(denominator)?])
|
||||
}
|
||||
Ifd(val) => Ok(vec![val]),
|
||||
@@ -220,7 +229,8 @@ impl Value {
|
||||
new_vec.push(numerator);
|
||||
new_vec.push(denominator);
|
||||
}
|
||||
SRationalBig(numerator, denominator) => {
|
||||
#[expect(deprecated)]
|
||||
Value::SRationalBig(numerator, denominator) => {
|
||||
new_vec.push(i32::try_from(numerator)?);
|
||||
new_vec.push(i32::try_from(denominator)?);
|
||||
}
|
||||
@@ -234,7 +244,8 @@ impl Value {
|
||||
Signed(val) => Ok(vec![val]),
|
||||
SignedBig(val) => Ok(vec![i32::try_from(val)?]),
|
||||
SRational(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
SRationalBig(numerator, denominator) => {
|
||||
#[expect(deprecated)]
|
||||
Value::SRationalBig(numerator, denominator) => {
|
||||
Ok(vec![i32::try_from(numerator)?, i32::try_from(denominator)?])
|
||||
}
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
@@ -283,7 +294,8 @@ impl Value {
|
||||
Unsigned(val) => Ok(vec![val.into()]),
|
||||
UnsignedBig(val) => Ok(vec![val]),
|
||||
Rational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
||||
RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
#[expect(deprecated)]
|
||||
Value::RationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
Ifd(val) => Ok(vec![val.into()]),
|
||||
IfdBig(val) => Ok(vec![val]),
|
||||
Ascii(val) => Ok(val.chars().map(u32::from).map(u64::from).collect()),
|
||||
@@ -301,7 +313,8 @@ impl Value {
|
||||
new_vec.push(numerator.into());
|
||||
new_vec.push(denominator.into());
|
||||
}
|
||||
SRationalBig(numerator, denominator) => {
|
||||
#[expect(deprecated)]
|
||||
Value::SRationalBig(numerator, denominator) => {
|
||||
new_vec.push(numerator);
|
||||
new_vec.push(denominator);
|
||||
}
|
||||
@@ -315,7 +328,8 @@ impl Value {
|
||||
Signed(val) => Ok(vec![val.into()]),
|
||||
SignedBig(val) => Ok(vec![val]),
|
||||
SRational(numerator, denominator) => Ok(vec![numerator.into(), denominator.into()]),
|
||||
SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
#[expect(deprecated)]
|
||||
Value::SRationalBig(numerator, denominator) => Ok(vec![numerator, denominator]),
|
||||
_ => Err(TiffError::FormatError(TiffFormatError::InvalidTypeForTag)),
|
||||
}
|
||||
}
|
||||
@@ -565,7 +579,7 @@ impl Entry {
|
||||
|
||||
// Case 4: there is more than one value, and it doesn't fit in the offset field.
|
||||
let mut v;
|
||||
self.set_reader_offset(bo, bigtiff, reader)?;
|
||||
self.set_reader_offset_relative(bigtiff, reader, 0)?;
|
||||
|
||||
match self.type_ {
|
||||
Type::BYTE | Type::UNDEFINED => {
|
||||
@@ -723,6 +737,113 @@ impl Entry {
|
||||
Ok(List(v))
|
||||
}
|
||||
|
||||
pub(crate) fn buffered_value<R: Read + Seek>(
|
||||
&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,
|
||||
limits: &super::Limits,
|
||||
@@ -736,21 +857,32 @@ impl Entry {
|
||||
Ok(Vec::with_capacity(value_count))
|
||||
}
|
||||
|
||||
fn set_reader_offset<R>(
|
||||
/// Seek to an offset within a value stored in the offset defined by this entry.
|
||||
fn set_reader_offset_relative<R>(
|
||||
&self,
|
||||
bo: ByteOrder,
|
||||
bigtiff: bool,
|
||||
reader: &mut EndianReader<R>,
|
||||
at: u64,
|
||||
) -> TiffResult<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let bo = reader.byte_order;
|
||||
|
||||
let offset = if bigtiff {
|
||||
self.offset_field_reader(bo).read_u64()?
|
||||
} else {
|
||||
self.offset_field_reader(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)?;
|
||||
|
||||
Ok(())
|
||||
|
||||
+175
-24
@@ -4,8 +4,8 @@ use super::tag_reader::TagReader;
|
||||
use super::ChunkType;
|
||||
use super::{predict_f16, predict_f32, predict_f64, ValueReader};
|
||||
use crate::tags::{
|
||||
CompressionMethod, ExtraSamples, PhotometricInterpretation, PlanarConfiguration, Predictor,
|
||||
SampleFormat, Tag,
|
||||
ByteOrder, CompressionMethod, ExtraSamples, PhotometricInterpretation, PlanarConfiguration,
|
||||
Predictor, SampleFormat, Tag,
|
||||
};
|
||||
use crate::{
|
||||
ColorType, Directory, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
|
||||
@@ -64,7 +64,6 @@ impl TileAttributes {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Image {
|
||||
pub ifd: Option<Directory>,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub bits_per_sample: u8,
|
||||
@@ -86,6 +85,13 @@ pub(crate) struct Image {
|
||||
pub chunk_offsets: Vec<u64>,
|
||||
pub chunk_bytes: Vec<u64>,
|
||||
pub chroma_subsampling: (u16, u16),
|
||||
/// Will decompression yield host-endian samples?
|
||||
///
|
||||
/// Standard compression algorithms run on pure bytes, taking the sample slice as it would be
|
||||
/// encoded in the file. However some compression algorithms (currently only SGILog/SGILog24)
|
||||
/// run on samples directly and thus skip the outer byte order entirely, except potentially
|
||||
/// they have interior byte order treatment.
|
||||
pub decompression_to_host_endian: bool,
|
||||
}
|
||||
|
||||
/// Describes how to read a tile-aligned portion of the image.
|
||||
@@ -140,11 +146,35 @@ pub(crate) struct ReadoutLayout {
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub(crate) fn no_image() -> Image {
|
||||
Image {
|
||||
width: 0,
|
||||
height: 0,
|
||||
bits_per_sample: 1,
|
||||
samples: 1,
|
||||
extra_samples: vec![],
|
||||
photometric_samples: 1,
|
||||
sample_format: SampleFormat::Uint,
|
||||
photometric_interpretation: PhotometricInterpretation::BlackIsZero,
|
||||
compression_method: CompressionMethod::None,
|
||||
jpeg_tables: None,
|
||||
predictor: Predictor::None,
|
||||
chunk_type: ChunkType::Strip,
|
||||
planar_config: PlanarConfiguration::Chunky,
|
||||
strip_decoder: None,
|
||||
tile_attributes: None,
|
||||
chunk_offsets: Vec::new(),
|
||||
chunk_bytes: Vec::new(),
|
||||
chroma_subsampling: (2, 2),
|
||||
decompression_to_host_endian: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_reader<R: Read + Seek>(
|
||||
decoder: &mut ValueReader<R>,
|
||||
ifd: Directory,
|
||||
ifd: &Directory,
|
||||
) -> TiffResult<Image> {
|
||||
let mut tag_reader = TagReader { decoder, ifd: &ifd };
|
||||
let mut tag_reader = TagReader { decoder, ifd };
|
||||
|
||||
let width = tag_reader.require_tag(Tag::ImageWidth)?.into_u32()?;
|
||||
let height = tag_reader.require_tag(Tag::ImageLength)?.into_u32()?;
|
||||
@@ -330,15 +360,28 @@ impl Image {
|
||||
strip_decoder = Some(StripDecodeState { rows_per_strip });
|
||||
tile_attributes = None;
|
||||
|
||||
if chunk_offsets.len() != chunk_bytes.len()
|
||||
|| rows_per_strip == 0
|
||||
|| u32::try_from(chunk_offsets.len())?
|
||||
!= (height.saturating_sub(1) / rows_per_strip + 1) * planes as u32
|
||||
{
|
||||
if chunk_offsets.len() != chunk_bytes.len() {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
));
|
||||
}
|
||||
|
||||
if rows_per_strip == 0 {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
));
|
||||
}
|
||||
|
||||
if u32::try_from(chunk_offsets.len())?
|
||||
!= height.div_ceil(rows_per_strip) * planes as u32
|
||||
{
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::IncorrectChunkCount {
|
||||
expected: height.div_ceil(rows_per_strip) * planes as u32,
|
||||
found: chunk_offsets.len() as u32,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
(false, false, true, true) => {
|
||||
chunk_type = ChunkType::Tile;
|
||||
@@ -371,14 +414,23 @@ impl Image {
|
||||
.into_u64_vec()?;
|
||||
|
||||
let tile = tile_attributes.as_ref().unwrap();
|
||||
if chunk_offsets.len() != chunk_bytes.len()
|
||||
|| chunk_offsets.len()
|
||||
!= tile.tiles_down() * tile.tiles_across() * planes as usize
|
||||
{
|
||||
|
||||
if chunk_offsets.len() != chunk_bytes.len() {
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::InconsistentSizesEncountered,
|
||||
));
|
||||
}
|
||||
|
||||
if chunk_offsets.len() != tile.tiles_down() * tile.tiles_across() * planes as usize
|
||||
{
|
||||
return Err(TiffError::FormatError(
|
||||
TiffFormatError::IncorrectChunkCount {
|
||||
expected: (tile.tiles_down() * tile.tiles_across() * planes as usize)
|
||||
as u32,
|
||||
found: chunk_offsets.len() as u32,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
(_, _, _, _) => {
|
||||
return Err(TiffError::FormatError(
|
||||
@@ -387,11 +439,28 @@ impl Image {
|
||||
}
|
||||
};
|
||||
|
||||
let mut sample_format = sample_format;
|
||||
let mut bits_per_sample = bits_per_sample[0];
|
||||
|
||||
// These compression methods only work for specific sample types. They pack each pixel into
|
||||
// a pixel format. And when we decompress them we run an inverse transform inside the
|
||||
// decompression step because that makes sense. (Even for 'raw' data you will not be able
|
||||
// to handle the samples well otherwise). All we are doing is setting up the internal
|
||||
// buffer here. There's an additional transform before the color readout as yo will receive
|
||||
// the color as 32-bit floats (the encoding relies on very specific quantization so
|
||||
// everything else would be very lossy).
|
||||
if let CompressionMethod::SgiLog = compression_method {
|
||||
sample_format = SampleFormat::IEEEFP;
|
||||
bits_per_sample = 32;
|
||||
} else if let CompressionMethod::SgiLog24 = compression_method {
|
||||
sample_format = SampleFormat::IEEEFP;
|
||||
bits_per_sample = 32;
|
||||
}
|
||||
|
||||
Ok(Image {
|
||||
ifd: Some(ifd),
|
||||
width,
|
||||
height,
|
||||
bits_per_sample: bits_per_sample[0],
|
||||
bits_per_sample,
|
||||
samples,
|
||||
extra_samples,
|
||||
photometric_samples,
|
||||
@@ -407,6 +476,10 @@ impl Image {
|
||||
chunk_offsets,
|
||||
chunk_bytes,
|
||||
chroma_subsampling,
|
||||
decompression_to_host_endian: matches!(
|
||||
compression_method,
|
||||
CompressionMethod::SgiLog | CompressionMethod::SgiLog24
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -501,6 +574,46 @@ impl Image {
|
||||
vec![self.bits_per_sample; self.samples as usize],
|
||||
),
|
||||
)),
|
||||
PhotometricInterpretation::CIELogL => match self.samples {
|
||||
1 if matches!(self.bits_per_sample, 32)
|
||||
&& matches!(self.sample_format, SampleFormat::IEEEFP) =>
|
||||
{
|
||||
// We do this transform internally while reading.
|
||||
Ok(ColorType::Gray(32))
|
||||
}
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
vec![self.bits_per_sample; self.samples as usize],
|
||||
),
|
||||
)),
|
||||
},
|
||||
PhotometricInterpretation::CIELogLuv => match self.samples {
|
||||
// Only fully supported for the compression format `SGILog`/`SGILog24` which must
|
||||
// be used with 16-bit per sample.
|
||||
//
|
||||
// It's actually an interesting point what we should output here. Note that in
|
||||
// libtiff there is a RAW mode (a single unsigned sample per pixel) as well as a
|
||||
// 32-bit floating point mode for XYZ data. The `Lu'v'` variant is a heavily
|
||||
// customized quantization so we should not return it directly. The format itself
|
||||
// describes being based off `xyY` so that may make sense but there is a pole in
|
||||
// the relation to `XYZ` (at y=0) which is absent in the chosen quantization since
|
||||
// we assume a bias of `.5` so `v >= 1/820` and thus `y >= 0.49/820` in practice.
|
||||
// Also we lose a bit of accuracy with just 16-bit precision.
|
||||
//
|
||||
// In `libtiff` data is always transformed through XYZ. So let's do that.
|
||||
3 if matches!(self.bits_per_sample, 32)
|
||||
&& matches!(self.sample_format, SampleFormat::IEEEFP) =>
|
||||
{
|
||||
Ok(ColorType::RGB(32))
|
||||
}
|
||||
_ => Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
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:
|
||||
//
|
||||
@@ -526,16 +639,41 @@ impl Image {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_reader<'r, R: 'r + Read>(
|
||||
fn create_reader<'r, R: 'r + Read + Seek>(
|
||||
reader: R,
|
||||
compression_method: CompressionMethod,
|
||||
compressed_length: u64,
|
||||
// FIXME: these should be `expect` attributes or we choose another way of passing them.
|
||||
#[cfg_attr(not(feature = "jpeg"), allow(unused_variables))] jpeg_tables: Option<&[u8]>,
|
||||
#[cfg_attr(not(feature = "fax"), allow(unused_variables))] dimensions: (u32, u32),
|
||||
dimensions: (u32, u32),
|
||||
samples: u16,
|
||||
) -> TiffResult<Box<dyn Read + 'r>> {
|
||||
Ok(match compression_method {
|
||||
CompressionMethod::None => Box::new(reader),
|
||||
CompressionMethod::SgiLog => match samples {
|
||||
1 => Box::new(super::logluv::LogLuv16::new(
|
||||
reader,
|
||||
compressed_length,
|
||||
dimensions.0,
|
||||
)?),
|
||||
3 => Box::new(super::logluv::LogLuv32::new(
|
||||
reader,
|
||||
compressed_length,
|
||||
dimensions.0,
|
||||
)?),
|
||||
_ => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedCompressionMethod(
|
||||
CompressionMethod::SgiLog,
|
||||
),
|
||||
))
|
||||
}
|
||||
},
|
||||
CompressionMethod::SgiLog24 => Box::new(super::logluv::LogLuv24::new(
|
||||
reader,
|
||||
compressed_length,
|
||||
dimensions.0,
|
||||
)?),
|
||||
#[cfg(feature = "lzw")]
|
||||
CompressionMethod::LZW => Box::new(super::stream::LZWReader::new(
|
||||
reader,
|
||||
@@ -607,6 +745,13 @@ impl Image {
|
||||
reader,
|
||||
compressed_length,
|
||||
)?),
|
||||
#[cfg(feature = "webp")]
|
||||
CompressionMethod::WebP => Box::new(super::stream::WebPReader::new(
|
||||
reader,
|
||||
compressed_length,
|
||||
samples,
|
||||
)?),
|
||||
|
||||
method => {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::UnsupportedCompressionMethod(method),
|
||||
@@ -805,7 +950,7 @@ impl Image {
|
||||
|
||||
pub(crate) fn expand_chunk(
|
||||
&self,
|
||||
reader: &mut ValueReader<impl Read>,
|
||||
reader: &mut ValueReader<impl Read + Seek>,
|
||||
buf: &mut [u8],
|
||||
layout: &ReadoutLayout,
|
||||
chunk_index: u32,
|
||||
@@ -816,7 +961,12 @@ impl Image {
|
||||
limits,
|
||||
} = reader;
|
||||
|
||||
let byte_order = reader.byte_order;
|
||||
let decompressed_byte_order = if self.decompression_to_host_endian {
|
||||
ByteOrder::native()
|
||||
} else {
|
||||
// Convert from the byte order of the file.
|
||||
reader.byte_order
|
||||
};
|
||||
|
||||
// Validate that the color type is supported.
|
||||
let color_type = layout.color;
|
||||
@@ -919,6 +1069,7 @@ impl Image {
|
||||
*compressed_bytes,
|
||||
self.jpeg_tables.as_deref().map(|a| &**a),
|
||||
chunk_dims,
|
||||
self.samples,
|
||||
)?;
|
||||
|
||||
if is_output_chunk_rows && is_all_bits {
|
||||
@@ -931,7 +1082,7 @@ impl Image {
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
samples,
|
||||
byte_order,
|
||||
decompressed_byte_order,
|
||||
predictor,
|
||||
);
|
||||
}
|
||||
@@ -977,7 +1128,7 @@ impl Image {
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
samples,
|
||||
byte_order,
|
||||
decompressed_byte_order,
|
||||
predictor,
|
||||
);
|
||||
|
||||
@@ -998,7 +1149,7 @@ impl Image {
|
||||
|
||||
debug_assert!(bits_per_pixel >= photometric_bit_end);
|
||||
|
||||
if bits_per_pixel % 8 != 0 || photometric_bit_end % 8 != 0 {
|
||||
if !bits_per_pixel.is_multiple_of(8) || !photometric_bit_end.is_multiple_of(8) {
|
||||
return Err(TiffError::UnsupportedError(
|
||||
TiffUnsupportedError::InterpretationWithBits(
|
||||
self.photometric_interpretation,
|
||||
@@ -1018,7 +1169,7 @@ impl Image {
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
samples,
|
||||
byte_order,
|
||||
decompressed_byte_order,
|
||||
predictor,
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,541 @@
|
||||
use std::io::{Read, Take};
|
||||
|
||||
use crate::TiffError;
|
||||
|
||||
pub struct LogLuv24<R> {
|
||||
reader: Take<R>,
|
||||
/// The required buffer size. We only decompressed all samples at once.
|
||||
chunk_sz: usize,
|
||||
}
|
||||
|
||||
impl<R> LogLuv24<R> {
|
||||
pub fn new(reader: R, n: u64, npixels: u32) -> Result<Self, TiffError>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
let chunk_sz = usize::try_from(
|
||||
u64::from(npixels)
|
||||
.checked_mul(4 * 3)
|
||||
.ok_or(TiffError::LimitsExceeded)?,
|
||||
)?;
|
||||
|
||||
let reader = reader.take(n);
|
||||
Ok(LogLuv24 { reader, chunk_sz })
|
||||
}
|
||||
|
||||
fn expand_buf(buf: &mut [u8]) {
|
||||
let data_len = buf.len() / 4;
|
||||
|
||||
for i in (0..data_len / 3).rev() {
|
||||
let ch = buf.as_chunks_mut::<3>().0[i];
|
||||
let [a, b, c] = ch.map(u32::from);
|
||||
|
||||
let raw = a << 16 | b << 8 | c;
|
||||
let l = (raw >> 14) & 0x3ff;
|
||||
let uv = decode_quantized_uv(raw & 0x3fff);
|
||||
|
||||
let by = dequantize_24_luma(l);
|
||||
let (u, v) = dequantize_24(uv);
|
||||
|
||||
let s = 1. / (6. * u - 16. * v + 12.);
|
||||
let x = 9. * u * s;
|
||||
let y = 4. * v * s;
|
||||
|
||||
let bx = x / y * by;
|
||||
let bz = (1. - x - y) / y * by;
|
||||
|
||||
let [r, g, b] = xyz_to_rgb([bx, by, bz]);
|
||||
|
||||
let into = &mut buf.as_chunks_mut::<12>().0[i];
|
||||
into[0..4].copy_from_slice(&f32::to_ne_bytes(r));
|
||||
into[4..8].copy_from_slice(&f32::to_ne_bytes(g));
|
||||
into[8..12].copy_from_slice(&f32::to_ne_bytes(b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for LogLuv24<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.read_exact(buf)?;
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
// FIXME: this is the interface we have for compressed chunks but it is not optimal for what we
|
||||
// want. Note `fill_buf` is not super helpful since it does not guarantee any size of the
|
||||
// filled buffer but we need 24-bits at a time. Two code paths is even more awkward.
|
||||
fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
|
||||
if !buf.len().is_multiple_of(self.chunk_sz) {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!(
|
||||
"buffer size {0} must match the chunk size for compression method SGILog24 {1}",
|
||||
buf.len(),
|
||||
self.chunk_sz,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
let data_len = buf.len() / 4;
|
||||
self.reader.read_exact(&mut buf[..data_len])?;
|
||||
Self::expand_buf(buf);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn packbits_decode<R: Read, const N: usize>(
|
||||
reader: &mut R,
|
||||
buf: &mut [[u8; N]],
|
||||
) -> std::io::Result<()> {
|
||||
for offset in (0..N).rev() {
|
||||
let mut i = 0;
|
||||
let mut code = [0u8; 1];
|
||||
|
||||
loop {
|
||||
let Some(pixels) = buf.get_mut(i..) else {
|
||||
break;
|
||||
};
|
||||
|
||||
if pixels.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
let pixels = pixels.iter_mut();
|
||||
reader.read_exact(&mut code)?;
|
||||
let [repeat] = code;
|
||||
|
||||
if repeat >= 128 {
|
||||
reader.read_exact(&mut code)?;
|
||||
let [value] = code;
|
||||
let rc = (repeat - 128) + 2;
|
||||
|
||||
for (_, px) in (0..rc).zip(pixels) {
|
||||
px[offset] = value;
|
||||
}
|
||||
|
||||
i += usize::from(rc);
|
||||
} else {
|
||||
for (_, px) in (0..repeat).zip(pixels) {
|
||||
reader.read_exact(&mut code)?;
|
||||
let [value] = code;
|
||||
px[offset] = value;
|
||||
}
|
||||
|
||||
i += usize::from(repeat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct LogLuv32<R> {
|
||||
reader: Take<R>,
|
||||
/// The required buffer size. We only decompressed all samples at once.
|
||||
chunk_sz: usize,
|
||||
}
|
||||
|
||||
impl<R: Read> LogLuv32<R> {
|
||||
pub fn new(reader: R, n: u64, npixels: u32) -> Result<Self, TiffError> {
|
||||
let chunk_sz = usize::try_from(
|
||||
u64::from(npixels)
|
||||
.checked_mul(4 * 3)
|
||||
.ok_or(TiffError::LimitsExceeded)?,
|
||||
)?;
|
||||
|
||||
let reader = reader.take(n);
|
||||
Ok(LogLuv32 { reader, chunk_sz })
|
||||
}
|
||||
|
||||
fn decode_rle_row(&mut self, row: &mut [u8]) -> std::io::Result<()>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
// 32-bit per pixel format.
|
||||
let n_pixels = self.chunk_sz / 12;
|
||||
// buffer for reading (using only 32-bit per pixel)
|
||||
let raw = &mut row.as_chunks_mut::<4>().0[..n_pixels];
|
||||
packbits_decode::<_, 4>(&mut self.reader, raw)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expand_row(row: &mut [u8]) {
|
||||
let uvscale = 1.0 / 410.0;
|
||||
let n_pixels = row.len() / 12;
|
||||
|
||||
// Work backwards, the 32-bit compressed raw data is at the front of the row buffer and
|
||||
// this avoids overwriting data before we read it.
|
||||
for i in (0..n_pixels).rev() {
|
||||
let raw = row.as_chunks_mut::<4>().0[i];
|
||||
|
||||
let l = u16::from(raw[0]) << 8 | u16::from(raw[1]);
|
||||
let by = log16_to_y(l);
|
||||
|
||||
let u = uvscale * (f32::from(raw[2]) + 0.5);
|
||||
let v = uvscale * (f32::from(raw[3]) + 0.5);
|
||||
|
||||
let s = 1. / (6. * u - 16. * v + 12.);
|
||||
let x = 9. * u * s;
|
||||
let y = 4. * v * s;
|
||||
|
||||
let bx = x / y * by;
|
||||
let bz = (1. - x - y) / y * by;
|
||||
|
||||
let [r, g, b] = xyz_to_rgb([bx, by, bz]);
|
||||
let into = &mut row.as_chunks_mut::<12>().0[i];
|
||||
into[0..4].copy_from_slice(&f32::to_ne_bytes(r));
|
||||
into[4..8].copy_from_slice(&f32::to_ne_bytes(g));
|
||||
into[8..12].copy_from_slice(&f32::to_ne_bytes(b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for LogLuv32<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.read_exact(buf)?;
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
// FIXME: See `LogLuv24::read_exact`
|
||||
fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
|
||||
for row in buf.chunks_exact_mut(self.chunk_sz) {
|
||||
self.decode_rle_row(row)?;
|
||||
Self::expand_row(row);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pack-Bits like encoding for a single 16-bit log-L sample.
|
||||
pub struct LogLuv16<R> {
|
||||
reader: Take<R>,
|
||||
/// The required buffer size. We only decompressed all samples at once.
|
||||
chunk_sz: usize,
|
||||
}
|
||||
|
||||
impl<R: Read> LogLuv16<R> {
|
||||
pub fn new(reader: R, n: u64, npixels: u32) -> Result<Self, TiffError> {
|
||||
let chunk_sz = usize::try_from(
|
||||
u64::from(npixels)
|
||||
.checked_mul(4)
|
||||
.ok_or(TiffError::LimitsExceeded)?,
|
||||
)?;
|
||||
|
||||
let reader = reader.take(n);
|
||||
Ok(LogLuv16 { reader, chunk_sz })
|
||||
}
|
||||
|
||||
fn decode_rle_row(&mut self, row: &mut [u8]) -> std::io::Result<()>
|
||||
where
|
||||
R: Read,
|
||||
{
|
||||
// 32-bit per pixel format.
|
||||
let n_pixels = self.chunk_sz / 4;
|
||||
// buffer for reading (16-bit per pixel)
|
||||
let raw = &mut row.as_chunks_mut::<2>().0[..n_pixels];
|
||||
packbits_decode::<_, 2>(&mut self.reader, raw)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expand_row(row: &mut [u8]) {
|
||||
let n_pixels = row.len() / 4;
|
||||
|
||||
// Work backwards, the 32-bit compressed raw data is at the front of the row buffer and
|
||||
// this avoids overwriting data before we read it.
|
||||
for i in (0..n_pixels).rev() {
|
||||
let raw = row.as_chunks_mut::<2>().0[i];
|
||||
|
||||
let l = u16::from(raw[0]) << 8 | u16::from(raw[1]);
|
||||
let by = log16_to_y(l);
|
||||
|
||||
let into = &mut row.as_chunks_mut::<4>().0[i];
|
||||
into[0..4].copy_from_slice(&f32::to_ne_bytes(by));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for LogLuv16<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.read_exact(buf)?;
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
// FIXME: See `LogLuv24::read_exact`
|
||||
fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
|
||||
for row in buf.chunks_exact_mut(self.chunk_sz) {
|
||||
self.decode_rle_row(row)?;
|
||||
Self::expand_row(row);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Table information from: `libtiff`, 1997 by Greg Ward Larson, SGI
|
||||
#[doc(alias = "UV_SQSIZ")]
|
||||
const UV_QUANTIZATION_WIDTH: f32 = 0.003500;
|
||||
|
||||
// The `v` value corresonding to the bottom most row (quantized v' = 0).
|
||||
const UV_VSTART: f32 = 0.016940;
|
||||
|
||||
/// Number of distinct 'u' rows, each at a different v level. Each is quantized individually with
|
||||
/// the first quantization level starting at its `ustart` and `UV_QUANTIZATION_WIDTH` wide.
|
||||
const UV_NVS: usize = 163;
|
||||
|
||||
/// Contrary to the original C code we calculate the dependent values (cumulative sum).
|
||||
struct UvRow {
|
||||
ustart: f32,
|
||||
num_us: u16,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
const UV_ROWS: [UvRow; UV_NVS] = [
|
||||
UvRow { ustart: 0.247663, num_us: 4, },
|
||||
UvRow { ustart: 0.243779, num_us: 6, },
|
||||
UvRow { ustart: 0.241684, num_us: 7, },
|
||||
UvRow { ustart: 0.237874, num_us: 9, },
|
||||
UvRow { ustart: 0.235906, num_us: 10, },
|
||||
UvRow { ustart: 0.232153, num_us: 12, },
|
||||
UvRow { ustart: 0.228352, num_us: 14, },
|
||||
UvRow { ustart: 0.226259, num_us: 15, },
|
||||
UvRow { ustart: 0.222371, num_us: 17, },
|
||||
UvRow { ustart: 0.220410, num_us: 18, },
|
||||
UvRow { ustart: 0.214710, num_us: 21, },
|
||||
UvRow { ustart: 0.212714, num_us: 22, },
|
||||
UvRow { ustart: 0.210721, num_us: 23, },
|
||||
UvRow { ustart: 0.204976, num_us: 26, },
|
||||
UvRow { ustart: 0.202986, num_us: 27, },
|
||||
UvRow { ustart: 0.199245, num_us: 29, },
|
||||
UvRow { ustart: 0.195525, num_us: 31, },
|
||||
UvRow { ustart: 0.193560, num_us: 32, },
|
||||
UvRow { ustart: 0.189878, num_us: 34, },
|
||||
UvRow { ustart: 0.186216, num_us: 36, },
|
||||
UvRow { ustart: 0.186216, num_us: 36, },
|
||||
UvRow { ustart: 0.182592, num_us: 38, },
|
||||
UvRow { ustart: 0.179003, num_us: 40, },
|
||||
UvRow { ustart: 0.175466, num_us: 42, },
|
||||
UvRow { ustart: 0.172001, num_us: 44, },
|
||||
UvRow { ustart: 0.172001, num_us: 44, },
|
||||
UvRow { ustart: 0.168612, num_us: 46, },
|
||||
UvRow { ustart: 0.168612, num_us: 46, },
|
||||
UvRow { ustart: 0.163575, num_us: 49, },
|
||||
UvRow { ustart: 0.158642, num_us: 52, },
|
||||
UvRow { ustart: 0.158642, num_us: 52, },
|
||||
UvRow { ustart: 0.158642, num_us: 52, },
|
||||
UvRow { ustart: 0.153815, num_us: 55, },
|
||||
UvRow { ustart: 0.153815, num_us: 55, },
|
||||
UvRow { ustart: 0.149097, num_us: 58, },
|
||||
UvRow { ustart: 0.149097, num_us: 58, },
|
||||
UvRow { ustart: 0.142746, num_us: 62, },
|
||||
UvRow { ustart: 0.142746, num_us: 62, },
|
||||
UvRow { ustart: 0.142746, num_us: 62, },
|
||||
UvRow { ustart: 0.138270, num_us: 65, },
|
||||
UvRow { ustart: 0.138270, num_us: 65, },
|
||||
UvRow { ustart: 0.138270, num_us: 65, },
|
||||
UvRow { ustart: 0.132166, num_us: 69, },
|
||||
UvRow { ustart: 0.132166, num_us: 69, },
|
||||
UvRow { ustart: 0.126204, num_us: 73, },
|
||||
UvRow { ustart: 0.126204, num_us: 73, },
|
||||
UvRow { ustart: 0.126204, num_us: 73, },
|
||||
UvRow { ustart: 0.120381, num_us: 77, },
|
||||
UvRow { ustart: 0.120381, num_us: 77, },
|
||||
UvRow { ustart: 0.120381, num_us: 77, },
|
||||
UvRow { ustart: 0.120381, num_us: 77, },
|
||||
UvRow { ustart: 0.112962, num_us: 82, },
|
||||
UvRow { ustart: 0.112962, num_us: 82, },
|
||||
UvRow { ustart: 0.112962, num_us: 82, },
|
||||
UvRow { ustart: 0.107450, num_us: 86, },
|
||||
UvRow { ustart: 0.107450, num_us: 86, },
|
||||
UvRow { ustart: 0.107450, num_us: 86, },
|
||||
UvRow { ustart: 0.107450, num_us: 86, },
|
||||
UvRow { ustart: 0.100343, num_us: 91, },
|
||||
UvRow { ustart: 0.100343, num_us: 91, },
|
||||
UvRow { ustart: 0.100343, num_us: 91, },
|
||||
UvRow { ustart: 0.095126, num_us: 95, },
|
||||
UvRow { ustart: 0.095126, num_us: 95, },
|
||||
UvRow { ustart: 0.095126, num_us: 95, },
|
||||
UvRow { ustart: 0.095126, num_us: 95, },
|
||||
UvRow { ustart: 0.088276, num_us: 100, },
|
||||
UvRow { ustart: 0.088276, num_us: 100, },
|
||||
UvRow { ustart: 0.088276, num_us: 100, },
|
||||
UvRow { ustart: 0.088276, num_us: 100, },
|
||||
UvRow { ustart: 0.081523, num_us: 105, },
|
||||
UvRow { ustart: 0.081523, num_us: 105, },
|
||||
UvRow { ustart: 0.081523, num_us: 105, },
|
||||
UvRow { ustart: 0.081523, num_us: 105, },
|
||||
UvRow { ustart: 0.074861, num_us: 110, },
|
||||
UvRow { ustart: 0.074861, num_us: 110, },
|
||||
UvRow { ustart: 0.074861, num_us: 110, },
|
||||
UvRow { ustart: 0.074861, num_us: 110, },
|
||||
UvRow { ustart: 0.068290, num_us: 115, },
|
||||
UvRow { ustart: 0.068290, num_us: 115, },
|
||||
UvRow { ustart: 0.068290, num_us: 115, },
|
||||
UvRow { ustart: 0.068290, num_us: 115, },
|
||||
UvRow { ustart: 0.063573, num_us: 119, },
|
||||
UvRow { ustart: 0.063573, num_us: 119, },
|
||||
UvRow { ustart: 0.063573, num_us: 119, },
|
||||
UvRow { ustart: 0.063573, num_us: 119, },
|
||||
UvRow { ustart: 0.057219, num_us: 124, },
|
||||
UvRow { ustart: 0.057219, num_us: 124, },
|
||||
UvRow { ustart: 0.057219, num_us: 124, },
|
||||
UvRow { ustart: 0.057219, num_us: 124, },
|
||||
UvRow { ustart: 0.050985, num_us: 129, },
|
||||
UvRow { ustart: 0.050985, num_us: 129, },
|
||||
UvRow { ustart: 0.050985, num_us: 129, },
|
||||
UvRow { ustart: 0.050985, num_us: 129, },
|
||||
UvRow { ustart: 0.050985, num_us: 129, },
|
||||
UvRow { ustart: 0.044859, num_us: 134, },
|
||||
UvRow { ustart: 0.044859, num_us: 134, },
|
||||
UvRow { ustart: 0.044859, num_us: 134, },
|
||||
UvRow { ustart: 0.044859, num_us: 134, },
|
||||
UvRow { ustart: 0.040571, num_us: 138, },
|
||||
UvRow { ustart: 0.040571, num_us: 138, },
|
||||
UvRow { ustart: 0.040571, num_us: 138, },
|
||||
UvRow { ustart: 0.040571, num_us: 138, },
|
||||
UvRow { ustart: 0.036339, num_us: 142, },
|
||||
UvRow { ustart: 0.036339, num_us: 142, },
|
||||
UvRow { ustart: 0.036339, num_us: 142, },
|
||||
UvRow { ustart: 0.036339, num_us: 142, },
|
||||
UvRow { ustart: 0.032139, num_us: 146, },
|
||||
UvRow { ustart: 0.032139, num_us: 146, },
|
||||
UvRow { ustart: 0.032139, num_us: 146, },
|
||||
UvRow { ustart: 0.032139, num_us: 146, },
|
||||
UvRow { ustart: 0.027947, num_us: 150, },
|
||||
UvRow { ustart: 0.027947, num_us: 150, },
|
||||
UvRow { ustart: 0.027947, num_us: 150, },
|
||||
UvRow { ustart: 0.023739, num_us: 154, },
|
||||
UvRow { ustart: 0.023739, num_us: 154, },
|
||||
UvRow { ustart: 0.023739, num_us: 154, },
|
||||
UvRow { ustart: 0.023739, num_us: 154, },
|
||||
UvRow { ustart: 0.019504, num_us: 158, },
|
||||
UvRow { ustart: 0.019504, num_us: 158, },
|
||||
UvRow { ustart: 0.019504, num_us: 158, },
|
||||
UvRow { ustart: 0.016976, num_us: 161, },
|
||||
UvRow { ustart: 0.016976, num_us: 161, },
|
||||
UvRow { ustart: 0.016976, num_us: 161, },
|
||||
UvRow { ustart: 0.016976, num_us: 161, },
|
||||
UvRow { ustart: 0.012639, num_us: 165, },
|
||||
UvRow { ustart: 0.012639, num_us: 165, },
|
||||
UvRow { ustart: 0.012639, num_us: 165, },
|
||||
UvRow { ustart: 0.009991, num_us: 168, },
|
||||
UvRow { ustart: 0.009991, num_us: 168, },
|
||||
UvRow { ustart: 0.009991, num_us: 168, },
|
||||
UvRow { ustart: 0.009016, num_us: 170, },
|
||||
UvRow { ustart: 0.009016, num_us: 170, },
|
||||
UvRow { ustart: 0.009016, num_us: 170, },
|
||||
UvRow { ustart: 0.006217, num_us: 173, },
|
||||
UvRow { ustart: 0.006217, num_us: 173, },
|
||||
UvRow { ustart: 0.005097, num_us: 175, },
|
||||
UvRow { ustart: 0.005097, num_us: 175, },
|
||||
UvRow { ustart: 0.005097, num_us: 175, },
|
||||
UvRow { ustart: 0.003909, num_us: 177, },
|
||||
UvRow { ustart: 0.003909, num_us: 177, },
|
||||
UvRow { ustart: 0.002340, num_us: 177, },
|
||||
UvRow { ustart: 0.002389, num_us: 170, },
|
||||
UvRow { ustart: 0.001068, num_us: 164, },
|
||||
UvRow { ustart: 0.001653, num_us: 157, },
|
||||
UvRow { ustart: 0.000717, num_us: 150, },
|
||||
UvRow { ustart: 0.001614, num_us: 143, },
|
||||
UvRow { ustart: 0.000270, num_us: 136, },
|
||||
UvRow { ustart: 0.000484, num_us: 129, },
|
||||
UvRow { ustart: 0.001103, num_us: 123, },
|
||||
UvRow { ustart: 0.001242, num_us: 115, },
|
||||
UvRow { ustart: 0.001188, num_us: 109, },
|
||||
UvRow { ustart: 0.001011, num_us: 103, },
|
||||
UvRow { ustart: 0.000709, num_us: 97, },
|
||||
UvRow { ustart: 0.000301, num_us: 89, },
|
||||
UvRow { ustart: 0.002416, num_us: 82, },
|
||||
UvRow { ustart: 0.003251, num_us: 76, },
|
||||
UvRow { ustart: 0.003246, num_us: 69, },
|
||||
UvRow { ustart: 0.004141, num_us: 62, },
|
||||
UvRow { ustart: 0.005963, num_us: 55, },
|
||||
UvRow { ustart: 0.008839, num_us: 47, },
|
||||
UvRow { ustart: 0.010490, num_us: 40, },
|
||||
UvRow { ustart: 0.016994, num_us: 31, },
|
||||
UvRow { ustart: 0.023659, num_us: 21, },
|
||||
];
|
||||
|
||||
const UV_CUMULATIVE: [u16; UV_NVS] = {
|
||||
let mut arr = [0u16; UV_NVS];
|
||||
let mut sum = 0;
|
||||
let mut i = 0;
|
||||
while i < UV_NVS {
|
||||
arr[i] = sum;
|
||||
sum += UV_ROWS[i].num_us;
|
||||
i += 1;
|
||||
}
|
||||
arr
|
||||
};
|
||||
|
||||
const UV_NDIVS: u16 = UV_CUMULATIVE[UV_NVS - 1] + UV_ROWS[UV_NVS - 1].num_us;
|
||||
|
||||
fn decode_quantized_uv(uv: u32) -> (u8, u8) {
|
||||
if uv >= UV_NDIVS as u32 {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
// Find the start of the row, or the index after that.
|
||||
let v_index = match UV_CUMULATIVE.binary_search(&(uv as u16)) {
|
||||
Ok(n) => n,
|
||||
Err(n) => n - 1,
|
||||
};
|
||||
|
||||
let u_index = uv as u16 - UV_CUMULATIVE[v_index];
|
||||
(u_index as u8, v_index as u8)
|
||||
}
|
||||
|
||||
fn dequantize_24_luma(l: u32) -> f32 {
|
||||
if l == 0 {
|
||||
0.0
|
||||
} else {
|
||||
let le = (l & 0x3ff) as f32;
|
||||
let exponent = ((le / 64.0).floor() as i32) - 24;
|
||||
let mantissa = le - (exponent as f32 + 24.0) * 64.0;
|
||||
(mantissa + 64.0) / 64.0 * 2f32.powi(exponent)
|
||||
}
|
||||
}
|
||||
|
||||
fn dequantize_24((u, v): (u8, u8)) -> (f32, f32) {
|
||||
let v_f = UV_VSTART + (v as f32) * UV_QUANTIZATION_WIDTH;
|
||||
let u_row = &UV_ROWS[v as usize];
|
||||
let u_f = u_row.ustart + (u as f32) * UV_QUANTIZATION_WIDTH;
|
||||
(u_f, v_f)
|
||||
}
|
||||
|
||||
fn log16_to_y(l: u16) -> f32 {
|
||||
if l == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let le = f32::from(l & 0x7fff);
|
||||
// Y = exp(M_LN2 / 256. * (Le + .5) - M_LN2 * 64.);
|
||||
let y = (std::f32::consts::LN_2 / 256.0 * (le + 0.5) - std::f32::consts::LN_2 * 64.0).exp();
|
||||
|
||||
if l & 0x8000 != 0 {
|
||||
-y
|
||||
} else {
|
||||
y
|
||||
}
|
||||
}
|
||||
|
||||
fn xyz_to_rgb([l, u, v]: [f32; 3]) -> [f32; 3] {
|
||||
// XYZ to sRGB primaries at D65
|
||||
let r = 3.2404542 * l + -1.5371385 * u + -0.4985314 * v;
|
||||
let g = -0.969266 * l + 1.8760108 * u + 0.0415560 * v;
|
||||
let b = 0.0556434 * l + -0.2040259 * u + 1.0572252 * v;
|
||||
[r, g, b]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_quantized_uv() {
|
||||
// Test some known values from the original C code.
|
||||
assert_eq!(decode_quantized_uv(0), (0, 0));
|
||||
assert_eq!(decode_quantized_uv(3), (3, 0));
|
||||
assert_eq!(decode_quantized_uv(4), (0, 1));
|
||||
assert_eq!(decode_quantized_uv(9), (5, 1));
|
||||
assert_eq!(decode_quantized_uv(16288), (20, 162));
|
||||
assert_eq!(decode_quantized_uv(16289), (0, 0)); // Out of range
|
||||
}
|
||||
+521
-169
File diff suppressed because it is too large.
Load diff
+60
-3
@@ -1,4 +1,6 @@
|
||||
//! All IO functionality needed for TIFF decoding
|
||||
#[cfg(feature = "webp")]
|
||||
use std::io::Cursor;
|
||||
use std::io::{self, BufRead, BufReader, Read, Seek, Take};
|
||||
|
||||
pub use crate::tags::ByteOrder;
|
||||
@@ -258,9 +260,9 @@ impl<R: Read> Read for PackBitsReader<R> {
|
||||
pub struct Group4Reader<R: Read> {
|
||||
decoder: fax34::decoder::Group4Decoder<io::Bytes<io::BufReader<io::Take<R>>>>,
|
||||
line_buf: io::Cursor<Vec<u8>>,
|
||||
height: u16,
|
||||
height: u32,
|
||||
width: u16,
|
||||
y: u16,
|
||||
y: u32,
|
||||
}
|
||||
|
||||
#[cfg(feature = "fax")]
|
||||
@@ -271,7 +273,7 @@ impl<R: Read> Group4Reader<R> {
|
||||
compressed_length: u64,
|
||||
) -> crate::TiffResult<Self> {
|
||||
let width = u16::try_from(dimensions.0)?;
|
||||
let height = u16::try_from(dimensions.1)?;
|
||||
let height = dimensions.1;
|
||||
|
||||
Ok(Self {
|
||||
decoder: fax34::decoder::Group4Decoder::new(
|
||||
@@ -354,6 +356,61 @@ impl<R: Read> Read for Group4Reader<R> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "webp")]
|
||||
pub struct WebPReader {
|
||||
inner: Cursor<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "webp")]
|
||||
impl WebPReader {
|
||||
pub fn new<R: Read + Seek>(
|
||||
reader: R,
|
||||
compressed_length: u64,
|
||||
samples: u16,
|
||||
) -> crate::TiffResult<Self> {
|
||||
let mut decoder =
|
||||
image_webp::WebPDecoder::new(io::BufReader::new(reader.take(compressed_length)))
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
|
||||
if !(samples == 4 || (samples == 3 && !decoder.has_alpha())) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"bad sample count for WebP compressed data",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let total_bytes =
|
||||
samples as usize * decoder.dimensions().0 as usize * decoder.dimensions().1 as usize;
|
||||
let mut data = vec![0; total_bytes];
|
||||
|
||||
decoder
|
||||
.read_image(&mut data)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
|
||||
// Add a fully opaque alpha channel if needed
|
||||
if samples == 4 && !decoder.has_alpha() {
|
||||
for i in (0..(total_bytes / 4)).rev() {
|
||||
data[i * 4 + 3] = 255;
|
||||
data[i * 4 + 2] = data[i * 3 + 2];
|
||||
data[i * 4 + 1] = data[i * 3 + 1];
|
||||
data[i * 4] = data[i * 3];
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
inner: Cursor::new(data),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "webp")]
|
||||
impl Read for WebPReader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.inner.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
use crate::{tags::Tag, Directory};
|
||||
use crate::{TiffError, TiffFormatError, TiffResult};
|
||||
use crate::{
|
||||
tags::{Tag, ValueBuffer},
|
||||
Directory, TiffError, TiffFormatError, TiffResult,
|
||||
};
|
||||
|
||||
use super::ifd::{Entry, Value};
|
||||
use super::ValueReader;
|
||||
@@ -14,12 +16,25 @@ pub(crate) struct TagReader<'lt, R: EntryDecoder + ?Sized> {
|
||||
pub(crate) trait EntryDecoder {
|
||||
/// Turn an entry into a value by fetching the required bytes in the stream.
|
||||
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> {
|
||||
fn entry_val(&mut self, entry: &Entry) -> TiffResult<Value> {
|
||||
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> {
|
||||
@@ -32,6 +47,39 @@ impl<'a, R: EntryDecoder + ?Sized> TagReader<'a, R> {
|
||||
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> {
|
||||
match self.find_tag(tag)? {
|
||||
Some(val) => Ok(val),
|
||||
|
||||
+144
-8
@@ -1,5 +1,97 @@
|
||||
use crate::tags::{PhotometricInterpretation, SampleFormat};
|
||||
|
||||
/// Apply floating-point predictor encoding to a row of floating-point samples.
|
||||
///
|
||||
/// The floating-point predictor works by:
|
||||
/// 1. Converting each value to big-endian bytes
|
||||
/// 2. Shuffling bytes so all first bytes are together, all second bytes, etc.
|
||||
/// 3. Applying byte-level horizontal difference (wrapping_sub) across the entire buffer
|
||||
///
|
||||
/// The decoder reverses this by:
|
||||
/// 1. Applying cumulative sum (wrapping_add) on the entire byte stream
|
||||
/// 2. De-shuffling bytes back into float values
|
||||
macro_rules! fp_predict {
|
||||
($name:ident, $float_type:ty) => {
|
||||
fn $name(row: &[$float_type], samples: usize, result: &mut Vec<u8>) {
|
||||
const BYTE_SIZE: usize = core::mem::size_of::<$float_type>();
|
||||
let num_values = row.len();
|
||||
let byte_count = num_values * BYTE_SIZE;
|
||||
let start_len = result.len();
|
||||
|
||||
// Allocate zeroed space so we can write to arbitrary positions
|
||||
result.resize(start_len + byte_count, 0);
|
||||
let output = &mut result[start_len..];
|
||||
|
||||
// Step 1 & 2: Convert to big-endian and scatter bytes
|
||||
for (i, &value) in row.iter().enumerate() {
|
||||
let bytes = value.to_be_bytes();
|
||||
for b in 0..BYTE_SIZE {
|
||||
output[b * num_values + i] = bytes[b];
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Apply byte-level horizontal difference across the entire buffer.
|
||||
// The prediction spans across quarter boundaries, just like the decoder's cumulative sum.
|
||||
//
|
||||
// We process elements in reverse in batches of 16 to enable autovectorization.
|
||||
// Reverse order ensures that when we compute output[i] - output[i-samples],
|
||||
// the value at output[i-samples] hasn't been overwritten yet (critical when
|
||||
// samples < 16, which is the common case: 1, 3, 4, 5).
|
||||
//
|
||||
// 8 bits * 16 = 128 bits, the width of a typical SIMD register.
|
||||
// Even if the target has no vector instructions, this still benefits from
|
||||
// instruction-level parallelism, since unlike in decoding, all subtractions
|
||||
// within a chunk are truly independent of each other.
|
||||
let output = &mut result[start_len..byte_count + start_len];
|
||||
let diff_len = byte_count - samples;
|
||||
let remainder_len = diff_len % 16;
|
||||
// Process full 16-byte chunks in reverse.
|
||||
// We iterate from the end so that predecessor values (at lower indices)
|
||||
// are still in their original state when we read them.
|
||||
let mut chunk_end = byte_count;
|
||||
while chunk_end >= samples + remainder_len + 16 {
|
||||
let chunk_start = chunk_end - 16;
|
||||
let prev_start = chunk_start - samples;
|
||||
|
||||
// Read all values in the chunk and their predecessors first.
|
||||
// This makes all subtractions below independent, enabling vectorization.
|
||||
let curr: [u8; 16] = output[chunk_start..chunk_end].try_into().unwrap();
|
||||
let prev: [u8; 16] = output[prev_start..prev_start + 16].try_into().unwrap();
|
||||
|
||||
let chunk = &mut output[chunk_start..chunk_end];
|
||||
chunk[0] = curr[0].wrapping_sub(prev[0]);
|
||||
chunk[1] = curr[1].wrapping_sub(prev[1]);
|
||||
chunk[2] = curr[2].wrapping_sub(prev[2]);
|
||||
chunk[3] = curr[3].wrapping_sub(prev[3]);
|
||||
chunk[4] = curr[4].wrapping_sub(prev[4]);
|
||||
chunk[5] = curr[5].wrapping_sub(prev[5]);
|
||||
chunk[6] = curr[6].wrapping_sub(prev[6]);
|
||||
chunk[7] = curr[7].wrapping_sub(prev[7]);
|
||||
chunk[8] = curr[8].wrapping_sub(prev[8]);
|
||||
chunk[9] = curr[9].wrapping_sub(prev[9]);
|
||||
chunk[10] = curr[10].wrapping_sub(prev[10]);
|
||||
chunk[11] = curr[11].wrapping_sub(prev[11]);
|
||||
chunk[12] = curr[12].wrapping_sub(prev[12]);
|
||||
chunk[13] = curr[13].wrapping_sub(prev[13]);
|
||||
chunk[14] = curr[14].wrapping_sub(prev[14]);
|
||||
chunk[15] = curr[15].wrapping_sub(prev[15]);
|
||||
|
||||
chunk_end = chunk_start;
|
||||
}
|
||||
// Handle the leading remainder that doesn't fill a complete chunk of 16.
|
||||
// This must be done last (after the reverse chunk loop) because these are
|
||||
// the lowest indices, and the chunks above may read predecessor values
|
||||
// from this range.
|
||||
for i in (samples..samples + remainder_len).rev() {
|
||||
output[i] = output[i].wrapping_sub(output[i - samples]);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fp_predict!(fp_predict_f32, f32);
|
||||
fp_predict!(fp_predict_f64, f64);
|
||||
|
||||
macro_rules! integer_horizontal_predict {
|
||||
() => {
|
||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>) {
|
||||
@@ -23,6 +115,10 @@ macro_rules! integer_horizontal_predict {
|
||||
.map(|(prev, current)| current.wrapping_sub(*prev)),
|
||||
);
|
||||
}
|
||||
|
||||
fn floating_point_predict(_: &[Self::Inner], _: &mut Vec<u8>) {
|
||||
unreachable!("floating-point predictor is only valid for floating-point sample types")
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +134,10 @@ pub trait ColorType {
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat];
|
||||
|
||||
fn horizontal_predict(row: &[Self::Inner], result: &mut Vec<Self::Inner>);
|
||||
|
||||
/// Apply floating-point predictor encoding to a row of samples.
|
||||
/// This is only implemented for floating-point types; integer types will panic.
|
||||
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>);
|
||||
}
|
||||
|
||||
pub struct Gray8;
|
||||
@@ -108,7 +208,11 @@ impl ColorType for Gray32Float {
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||
}
|
||||
|
||||
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +244,11 @@ impl ColorType for Gray64Float {
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||
}
|
||||
|
||||
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,8 +288,13 @@ impl ColorType for RGB32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||
}
|
||||
|
||||
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,8 +314,13 @@ impl ColorType for RGB64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 3];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||
}
|
||||
|
||||
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,8 +360,13 @@ impl ColorType for RGBA32Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[32, 32, 32, 32];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||
}
|
||||
|
||||
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,8 +386,13 @@ impl ColorType for RGBA64Float {
|
||||
const TIFF_VALUE: PhotometricInterpretation = PhotometricInterpretation::RGB;
|
||||
const BITS_PER_SAMPLE: &'static [u16] = &[64, 64, 64, 64];
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||
}
|
||||
|
||||
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +434,11 @@ impl ColorType for CMYK32Float {
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||
}
|
||||
|
||||
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||
fp_predict_f32(row, Self::SAMPLE_FORMAT.len(), result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +460,11 @@ impl ColorType for CMYK64Float {
|
||||
const SAMPLE_FORMAT: &'static [SampleFormat] = &[SampleFormat::IEEEFP; 4];
|
||||
|
||||
fn horizontal_predict(_: &[Self::Inner], _: &mut Vec<Self::Inner>) {
|
||||
unreachable!()
|
||||
unreachable!("horizontal predictor is not valid for floating-point sample types")
|
||||
}
|
||||
|
||||
fn floating_point_predict(row: &[Self::Inner], result: &mut Vec<u8>) {
|
||||
fp_predict_f64(row, Self::SAMPLE_FORMAT.len(), result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,24 +7,27 @@ pub struct Deflate {
|
||||
level: FlateCompression,
|
||||
}
|
||||
|
||||
/// The level of compression used by the Deflate algorithm.
|
||||
/// It allows trading compression ratio for compression speed.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
#[derive(Default)]
|
||||
pub enum DeflateLevel {
|
||||
/// The fastest possible compression mode.
|
||||
Fast = 1,
|
||||
/// The conserative choice between speed and ratio.
|
||||
#[default]
|
||||
Balanced = 6,
|
||||
/// The best compression available with Deflate.
|
||||
Best = 9,
|
||||
}
|
||||
|
||||
impl Deflate {
|
||||
/// Create a new deflate compressor with a specific level of compression.
|
||||
pub fn with_level(level: DeflateLevel) -> Self {
|
||||
///
|
||||
/// The valid levels are 1 through 9 inclusive. The default level is 6.
|
||||
///
|
||||
/// - `1` stands for light but fast compression
|
||||
/// - `6` is the recommended balanced default
|
||||
/// - `9` is maximum compression ratio at the cost of slow encoding
|
||||
pub fn with_level(level: u8) -> Self {
|
||||
Self {
|
||||
level: FlateCompression::new(level as u32),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new deflate compressor with an exact level of compression.
|
||||
///
|
||||
/// `0` stands for uncompressed
|
||||
/// `1` stands for light but fast compression
|
||||
/// `6` is the recommended balanced default
|
||||
/// `9` is maximum compression ratio at the cost of slow encoding
|
||||
pub fn exact_level(level: u8) -> Self {
|
||||
Self {
|
||||
level: FlateCompression::new(level as u32),
|
||||
}
|
||||
@@ -33,7 +36,7 @@ impl Deflate {
|
||||
|
||||
impl Default for Deflate {
|
||||
fn default() -> Self {
|
||||
Self::with_level(DeflateLevel::default())
|
||||
Self::with_level(6)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ mod packbits;
|
||||
mod uncompressed;
|
||||
|
||||
#[cfg(feature = "deflate")]
|
||||
pub use self::deflate::{Deflate, DeflateLevel};
|
||||
pub use self::deflate::Deflate;
|
||||
|
||||
#[cfg(feature = "lzw")]
|
||||
pub use self::lzw::Lzw;
|
||||
|
||||
+143
-21
@@ -12,8 +12,8 @@ use crate::{
|
||||
decoder::ifd::Entry,
|
||||
error::{TiffResult, UsageError},
|
||||
tags::{
|
||||
CompressionMethod, ExtraSamples, IfdPointer, PhotometricInterpretation, ResolutionUnit,
|
||||
SampleFormat, Tag, Type,
|
||||
ByteOrder, CompressionMethod, ExtraSamples, IfdPointer, PhotometricInterpretation,
|
||||
ResolutionUnit, SampleFormat, Tag, Type, ValueBuffer,
|
||||
},
|
||||
Directory, TiffError, TiffFormatError,
|
||||
};
|
||||
@@ -33,13 +33,13 @@ use self::writer::*;
|
||||
/// Image data can be very unpredictable, and thus very hard to compress. Predictors are simple
|
||||
/// passes ran over the image data to prepare it for compression. This is mostly used for LZW
|
||||
/// compression, where using [Predictor::Horizontal] we see a 35% improvement in compression
|
||||
/// ratio over the unpredicted compression !
|
||||
/// ratio over the unpredicted compression!
|
||||
///
|
||||
/// [Predictor::FloatingPoint] is currently not supported.
|
||||
/// [Predictor::Horizontal] is for integer sample types.
|
||||
/// [Predictor::FloatingPoint] is for floating-point sample types (f32, f64).
|
||||
pub type Predictor = crate::tags::Predictor;
|
||||
#[cfg(feature = "deflate")]
|
||||
pub type DeflateLevel = compression::DeflateLevel;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, PartialEq, Default)]
|
||||
pub enum Compression {
|
||||
#[default]
|
||||
@@ -47,7 +47,7 @@ pub enum Compression {
|
||||
#[cfg(feature = "lzw")]
|
||||
Lzw,
|
||||
#[cfg(feature = "deflate")]
|
||||
Deflate(DeflateLevel),
|
||||
Deflate(u8),
|
||||
Packbits,
|
||||
}
|
||||
|
||||
@@ -301,12 +301,12 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
value.write(&mut writer)?;
|
||||
}
|
||||
|
||||
let entry = Self::write_value(
|
||||
let entry = Self::write_entry_inner(
|
||||
self.writer,
|
||||
&DirectoryEntry {
|
||||
DirectoryEntry {
|
||||
data_type: <T>::FIELD_TYPE,
|
||||
count: value.count().try_into()?,
|
||||
data: bytes,
|
||||
data: bytes.into(),
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -315,15 +315,114 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
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.
|
||||
///
|
||||
/// This could be used to write tiff strips.
|
||||
/// This could be used to write tiff strips. 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`]
|
||||
/// instead.
|
||||
pub fn write_data<T: TiffValue>(&mut self, value: T) -> TiffResult<u64> {
|
||||
let offset = self.writer.offset();
|
||||
value.write(self.writer)?;
|
||||
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().is_multiple_of(usize::from(ty.byte_len())) {
|
||||
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
|
||||
@@ -377,17 +476,21 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
fn write_value(
|
||||
// Does it really make sense that this would be a (potentially) compressible write? For actual
|
||||
// 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>,
|
||||
value: &DirectoryEntry<K::OffsetType>,
|
||||
value: DirectoryEntry<K::OffsetType>,
|
||||
) -> TiffResult<Entry> {
|
||||
let &DirectoryEntry {
|
||||
let DirectoryEntry {
|
||||
data: ref bytes,
|
||||
ref count,
|
||||
data_type,
|
||||
} = value;
|
||||
|
||||
let in_entry_bytes = mem::size_of::<K::OffsetType>();
|
||||
|
||||
let mut offset_bytes = [0; 8];
|
||||
|
||||
if bytes.len() > in_entry_bytes {
|
||||
@@ -406,6 +509,7 @@ 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.
|
||||
// Instead, write it to a buffer...
|
||||
let mut count_bytes = [0; 8];
|
||||
debug_assert!(in_entry_bytes == 4 || in_entry_bytes == 8);
|
||||
// Nominally Cow but we only expect `Cow::Borrowed`.
|
||||
count_bytes[..count.bytes()].copy_from_slice(&count.data());
|
||||
|
||||
@@ -419,6 +523,15 @@ 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.
|
||||
fn last_written(&self) -> u64 {
|
||||
self.writer.last_written()
|
||||
@@ -522,11 +635,14 @@ pub struct ImageEncoder<'a, W: 'a + Write + Seek, C: ColorType, K: TiffKind> {
|
||||
impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T, K> {
|
||||
fn sanity_check(compression: Compression, predictor: Predictor) -> TiffResult<()> {
|
||||
match (predictor, compression, T::SAMPLE_FORMAT[0]) {
|
||||
// Horizontal predictor is not valid for floating-point types
|
||||
(Predictor::Horizontal, _, SampleFormat::IEEEFP | SampleFormat::Void) => {
|
||||
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
||||
}
|
||||
// Floating-point predictor is only valid for IEEEFP sample format
|
||||
(Predictor::FloatingPoint, _, SampleFormat::IEEEFP) => Ok(()),
|
||||
(Predictor::FloatingPoint, _, _) => {
|
||||
Err(TiffError::UsageError(UsageError::PredictorUnavailable))
|
||||
Err(TiffError::UsageError(UsageError::PredictorIncompatible))
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
@@ -582,7 +698,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::YResolution, Rational { n: 1, d: 1 })?;
|
||||
encoder.write_tag(Tag::ResolutionUnit, ResolutionUnit::None.to_u16())?;
|
||||
encoder.write_tag(Tag::ResolutionUnit, ResolutionUnit::None)?;
|
||||
|
||||
Ok(ImageEncoder {
|
||||
encoder,
|
||||
@@ -660,7 +776,13 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
}
|
||||
self.encoder.write_data(row_result.as_slice())?
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
Predictor::FloatingPoint => {
|
||||
let mut row_result = Vec::with_capacity(std::mem::size_of_val(value));
|
||||
for row in value.chunks_exact(self.row_samples as usize) {
|
||||
T::floating_point_predict(row, &mut row_result);
|
||||
}
|
||||
self.encoder.write_data(row_result.as_slice())?
|
||||
}
|
||||
};
|
||||
|
||||
let byte_count = self.encoder.last_written() as usize;
|
||||
@@ -766,7 +888,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
} else {
|
||||
let mut sample_format: Vec<_> = <T>::BITS_PER_SAMPLE.to_vec();
|
||||
let replicated =
|
||||
core::iter::repeat(<T>::BITS_PER_SAMPLE[0]).take(self.extra_samples.len());
|
||||
core::iter::repeat_n(<T>::BITS_PER_SAMPLE[0], self.extra_samples.len());
|
||||
sample_format.extend(replicated);
|
||||
|
||||
self.encoder
|
||||
@@ -788,7 +910,7 @@ impl<'a, W: 'a + Write + Seek, T: ColorType, K: TiffKind> ImageEncoder<'a, W, T,
|
||||
// extra samples and not provided via a slice used for samples themselves.
|
||||
.unwrap_or(SampleFormat::Void.to_u16());
|
||||
|
||||
sample_format.extend(core::iter::repeat(extra_format).take(self.extra_samples.len()));
|
||||
sample_format.extend(core::iter::repeat_n(extra_format, self.extra_samples.len()));
|
||||
|
||||
self.encoder
|
||||
.write_tag(Tag::SampleFormat, &sample_format[..])?;
|
||||
@@ -824,10 +946,10 @@ impl<'a, W: Write + Seek, C: ColorType, K: TiffKind> Drop for ImageEncoder<'a, W
|
||||
}
|
||||
}
|
||||
|
||||
struct DirectoryEntry<S> {
|
||||
struct DirectoryEntry<'data, S> {
|
||||
data_type: Type,
|
||||
count: S,
|
||||
data: Vec<u8>,
|
||||
data: std::borrow::Cow<'data, [u8]>,
|
||||
}
|
||||
|
||||
/// Trait to abstract over Tiff/BigTiff differences.
|
||||
|
||||
+165
-1
@@ -1,14 +1,27 @@
|
||||
use std::{borrow::Cow, io::Write, slice::from_ref};
|
||||
|
||||
use crate::{bytecast, tags::Type, TiffError, TiffFormatError, TiffResult};
|
||||
use crate::{bytecast, tags, tags::Type, TiffError, TiffFormatError, TiffResult};
|
||||
|
||||
use super::writer::TiffWriter;
|
||||
|
||||
/// 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 {
|
||||
const BYTE_LEN: u8;
|
||||
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 bytes(&self) -> usize {
|
||||
self.count() * usize::from(Self::BYTE_LEN)
|
||||
}
|
||||
@@ -450,6 +463,136 @@ 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 {
|
||||
const BYTE_LEN: u8 = T::BYTE_LEN;
|
||||
const FIELD_TYPE: Type = T::FIELD_TYPE;
|
||||
@@ -467,6 +610,27 @@ impl<T: TiffValue + ?Sized> TiffValue for &'_ T {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
($inner_type:ty; $bytes:expr; $field_type:expr) => {
|
||||
impl $crate::encoder::TiffValue for [$inner_type] {
|
||||
|
||||
@@ -172,7 +172,7 @@ impl<W: Write> TiffWriter<W> {
|
||||
}
|
||||
|
||||
pub fn pad_word_boundary(&mut self) -> Result<(), io::Error> {
|
||||
if self.offset % 4 != 0 {
|
||||
if !self.offset.is_multiple_of(4) {
|
||||
let padding = [0, 0, 0];
|
||||
let padd_len = 4 - (self.offset % 4);
|
||||
self.writer.write_all(&padding[..padd_len as usize])?;
|
||||
@@ -241,7 +241,9 @@ fn encoder_on_short_writes() {
|
||||
}
|
||||
|
||||
write.inner.set_position(0);
|
||||
let mut tiff_reader = crate::decoder::Decoder::new(&mut write.inner).unwrap();
|
||||
|
||||
let mut tiff_reader = crate::decoder::Decoder::open(&mut write.inner).unwrap();
|
||||
tiff_reader.next_image().unwrap();
|
||||
let _layout = tiff_reader.read_image_to_buffer(&mut data).unwrap();
|
||||
|
||||
assert!(
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::string;
|
||||
use quick_error::quick_error;
|
||||
|
||||
use crate::decoder::ChunkType;
|
||||
use crate::tags::Type;
|
||||
use crate::tags::{
|
||||
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, SampleFormat, Tag,
|
||||
};
|
||||
@@ -69,6 +70,9 @@ quick_error! {
|
||||
InconsistentSizesEncountered {
|
||||
display("inconsistent sizes encountered")
|
||||
}
|
||||
IncorrectChunkCount {expected: u32, found: u32} {
|
||||
display("incorrect number of chunk offsets encoded for the tile count of the image, expected {expected} but found {found}")
|
||||
}
|
||||
InvalidDimensions(width: u32, height: u32) {
|
||||
display("invalid dimensions: {width}x{height}")
|
||||
}
|
||||
@@ -208,6 +212,12 @@ quick_error! {
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -51,7 +51,8 @@ pub enum ColorType {
|
||||
Multiband { bit_depth: u8, num_samples: u16 },
|
||||
}
|
||||
impl ColorType {
|
||||
fn bit_depth(&self) -> u8 {
|
||||
/// The number of bits used to store each sample.
|
||||
pub fn bit_depth(&self) -> u8 {
|
||||
match *self {
|
||||
ColorType::Gray(b)
|
||||
| ColorType::RGB(b)
|
||||
@@ -66,7 +67,8 @@ impl ColorType {
|
||||
}
|
||||
}
|
||||
|
||||
fn num_samples(&self) -> u16 {
|
||||
/// 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,
|
||||
|
||||
+190
-2
@@ -1,3 +1,6 @@
|
||||
use crate::encoder::TiffValue;
|
||||
use core::fmt;
|
||||
|
||||
macro_rules! tags {
|
||||
{
|
||||
// Permit arbitrary meta items, which include documentation.
|
||||
@@ -10,8 +13,9 @@ macro_rules! tags {
|
||||
$( #[$enum_attr] )*
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
#[non_exhaustive]
|
||||
#[repr($ty)]
|
||||
pub enum $name {
|
||||
$($(#[$ident_attr])* $tag,)*
|
||||
$($(#[$ident_attr])* $tag = $val,)*
|
||||
$(
|
||||
#[$unknown_meta]
|
||||
Unknown($ty),
|
||||
@@ -166,6 +170,18 @@ pub enum Tag(u16) unknown(
|
||||
// practice it just returns garbage and the validity does not matter greatly to us).
|
||||
pub struct IfdPointer(pub u64);
|
||||
|
||||
impl fmt::LowerHex for IfdPointer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::LowerHex::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::UpperHex for IfdPointer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt::UpperHex::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
tags! {
|
||||
/// The type of an IFD entry (a 2 byte field).
|
||||
pub enum Type(u16) {
|
||||
@@ -264,6 +280,17 @@ pub enum CompressionMethod(u16) unknown(
|
||||
|
||||
// Self-assigned by libtiff
|
||||
ZSTD = 0xC350,
|
||||
|
||||
// Self-assigned by libtiff
|
||||
WebP = 0xC351,
|
||||
|
||||
// Self-assigned by libtiff/SGI
|
||||
// A packbit like scheme on specific 32-bit samples (16 sint luma, 8 sint u,v). Only really
|
||||
// compatible with PhotometricInterpretation CIELogLuv.
|
||||
SgiLog = 34676,
|
||||
// A packbit like scheme on specific 24-bit samples (10 packed bits luma, 14-bit uv index).
|
||||
// Only really compatible with PhotometricInterpretation CIELogLuv.
|
||||
SgiLog24 = 34677,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,9 +303,41 @@ pub enum PhotometricInterpretation(u16) {
|
||||
TransparencyMask = 4,
|
||||
CMYK = 5,
|
||||
YCbCr = 6,
|
||||
/// Biased Lab with all coefficients stored as unsigned integers.
|
||||
CIELab = 8,
|
||||
IccLab = 9,
|
||||
ItuLab = 10,
|
||||
/// Logarithmic L* color space,
|
||||
///
|
||||
/// see: <https://web.archive.org/web/20250121120358/https://www.anyhere.com/gward/pixformat/tiffluv.html>
|
||||
/// References: <https://doi.org/10.2352/CIC.1998.6.1.art00046>
|
||||
///
|
||||
/// The `uv` coordinates are based on:
|
||||
///
|
||||
/// ```text
|
||||
/// (6*u - 16*v + 12) * (-2x + 12y + 3) = 36
|
||||
///
|
||||
/// u' = 4x / ( -2x + 12y + 3 )
|
||||
/// v' = 9y / ( -2x + 12y + 3 )
|
||||
/// x = 9u' / (6u' - 16v' + 12)
|
||||
/// y = 4v' / (6u' - 16v' + 12)
|
||||
/// ```
|
||||
///
|
||||
/// Since `(-2x + 12y + 3) <= 15` it holds `(6u' - 16v' + 12) >= 36/15` for all XYZ colors
|
||||
/// which makes the back transform to `xyY` well-defined. Also `v' <= 9/15 = 0.6` and also
|
||||
/// by practical means `u' <= 2/3`.
|
||||
///
|
||||
/// The compression format that is supposed to be used with this constrains them as well. It
|
||||
/// encodes each component as a 16-bit unsigned integer scaled and clamped to a quantization
|
||||
/// using 256 out of 410 levels or an index into a table based on a quantization to 177 levels
|
||||
/// each `0.0035` (~1/286) wide and 163 levels of `v` (same quantization width).
|
||||
///
|
||||
/// > Larson, G.W., ``Overcoming Gamut and Dynamic Range Limitations in Digital Images,'' Proceedings of the Sixth Color Imaging Conference, November 1998.
|
||||
CIELogL = 32844,
|
||||
/// Logarithmic L*u'v' color space,
|
||||
///
|
||||
/// See references under [`Self::CIELogL`].
|
||||
CIELogLuv = 32845,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +357,9 @@ pub enum Predictor(u16) {
|
||||
/// This means that instead of having in order `[r1, g1. b1, r2, g2 ...]` you will find
|
||||
/// `[r1, g1, b1, r2-r1, g2-g1, b2-b1, r3-r2, g3-g2, ...]`
|
||||
Horizontal = 2,
|
||||
/// Not currently supported
|
||||
/// Floating-point predictor for IEEE floating-point sample types.
|
||||
///
|
||||
/// This predictor byte-shuffles the data and applies byte-level horizontal differencing.
|
||||
FloatingPoint = 3,
|
||||
}
|
||||
}
|
||||
@@ -335,6 +396,124 @@ pub enum ExtraSamples(u16) {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
@@ -410,3 +589,12 @@ pub(crate) enum EndianBytes {
|
||||
Four,
|
||||
Eight,
|
||||
}
|
||||
|
||||
/// Defines how directories are laid out in the file.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TiffVariant {
|
||||
/// TIFF-V6.0 with 32-bit offsets and counts.
|
||||
Standard,
|
||||
/// Extended TIFF with 64-bit offsets and counts and long directories.
|
||||
BigTiff,
|
||||
}
|
||||
@@ -2,3 +2,11 @@ subsubifd.tif:
|
||||
url: https://data.kitware.com/api/v1/file/hashsum/sha512/372ca32735c8a8fdbe2286dabead9e779da63f10ba81eda84625e5273f76d74ca1a47a978f67e9c00c12f7f72009c7b2c07a641e643bb0c463812f4ae7f15d6e/download
|
||||
credit: https://github.com/DigitalSlideArchive/tifftools/commit/4d00c70dbce828262f86c1431f7c66b1748965ac
|
||||
license: CC0
|
||||
usda/*.tif
|
||||
url: https://registry.opendata.aws/naip/index.html
|
||||
credit: NAIP on AWS was accessed on 2026-01-28 from https://registry.opendata.aws/naip.
|
||||
license: Public Domain
|
||||
libtiffpic/
|
||||
url: https://download.osgeo.org/libtiff/
|
||||
credit: libtiff
|
||||
license: CC0
|
||||
@@ -15,7 +15,9 @@ fn test_big_tiff() {
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image");
|
||||
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(64, 64)
|
||||
|
||||
@@ -16,7 +16,9 @@ fn test_white_ieee_fp16() {
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image");
|
||||
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
@@ -25,6 +27,7 @@ fn test_white_ieee_fp16() {
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
for p in img {
|
||||
assert!(p == half::f16::from_f32_const(1.0));
|
||||
@@ -43,7 +46,9 @@ fn test_one_black_pixel_ieee_fp16() {
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image");
|
||||
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
@@ -52,6 +57,7 @@ fn test_one_black_pixel_ieee_fp16() {
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
for (i, p) in img.iter().enumerate() {
|
||||
if i == 0 {
|
||||
@@ -74,7 +80,9 @@ fn test_pattern_horizontal_differencing_ieee_fp16() {
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image");
|
||||
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
@@ -83,6 +91,7 @@ fn test_pattern_horizontal_differencing_ieee_fp16() {
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
// 0, 2, 5, 8, 12, 16, 255 are black
|
||||
let black = [0, 2, 5, 8, 12, 16, 255];
|
||||
@@ -107,7 +116,9 @@ fn test_pattern_predictor_ieee_fp16() {
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image");
|
||||
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(256, 256)
|
||||
@@ -116,6 +127,7 @@ fn test_pattern_predictor_ieee_fp16() {
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
// 0, 2, 5, 8, 12, 16, 255 are black
|
||||
let black = [0, 2, 5, 8, 12, 16, 255];
|
||||
@@ -175,7 +187,9 @@ fn test_predictor_ieee_fp16() {
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image");
|
||||
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
(16, 16)
|
||||
@@ -184,6 +198,7 @@ fn test_predictor_ieee_fp16() {
|
||||
decoder.colortype().expect("Cannot get colortype"),
|
||||
ColorType::Gray(16)
|
||||
);
|
||||
|
||||
if let DecodingResult::F16(img) = decoder.read_image().unwrap() {
|
||||
for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) {
|
||||
assert!((exp - found.to_f32()).abs() < 0.0001);
|
||||
|
||||
@@ -7,7 +7,7 @@ use tiff::ColorType;
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const TEST_IMAGE_DIR: &str = "./tests/images";
|
||||
const TEST_IMAGE_DIR: &str = "./tests/geotiff";
|
||||
|
||||
#[test]
|
||||
fn test_geo_tiff() {
|
||||
@@ -15,8 +15,9 @@ fn test_geo_tiff() {
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder = decoder.with_limits(tiff::decoder::Limits::unlimited());
|
||||
decoder.next_image().expect("Cannot read image");
|
||||
|
||||
assert_eq!(
|
||||
decoder.dimensions().expect("Cannot get dimensions"),
|
||||
@@ -61,3 +62,21 @@ fn test_geo_tiff() {
|
||||
assert_eq!(data.len(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "webp")]
|
||||
#[test]
|
||||
fn test_webp_tiff() {
|
||||
let filenames = ["usda_naip_256_webp_z3.tif"];
|
||||
let mut buffer = DecodingResult::U8(vec![]);
|
||||
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
|
||||
while decoder.more_images() {
|
||||
decoder.next_image().unwrap();
|
||||
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
-19
@@ -14,7 +14,9 @@ macro_rules! test_image_sum {
|
||||
fn $name(file: &str, expected_type: ColorType, expected_sum: $sum_ty) {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
|
||||
decoder.next_image().expect("Cannot read image IFD");
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
let img_res = decoder.read_image().unwrap();
|
||||
|
||||
@@ -44,7 +46,9 @@ test_image_sum!(test_image_sum_f64, F64, f64);
|
||||
fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image IFD");
|
||||
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
assert!(matches!(
|
||||
decoder.read_image(),
|
||||
@@ -166,7 +170,8 @@ fn test_string_tags() {
|
||||
for filename in filenames.iter() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(filename);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image IFD");
|
||||
let software = decoder.get_tag(tiff::tags::Tag::Software).unwrap();
|
||||
match software {
|
||||
ifd::Value::Ascii(s) => assert_eq!(
|
||||
@@ -190,7 +195,8 @@ fn test_decode_data() {
|
||||
}
|
||||
}
|
||||
let file = File::open("./tests/decodedata-rgb-3c-8b.tiff").unwrap();
|
||||
let mut decoder = Decoder::new(file).unwrap();
|
||||
let mut decoder = Decoder::open(file).unwrap();
|
||||
decoder.next_image().expect("Cannot read image IFD");
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||
if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() {
|
||||
@@ -236,7 +242,8 @@ fn test_tiled_rect_rgb_u8() {
|
||||
fn test_inner_access() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join("tiled-rect-rgb-u8.tif");
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image IFD");
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
|
||||
let c = decoder
|
||||
@@ -322,7 +329,8 @@ fn test_tiled_incremental() {
|
||||
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image IFD");
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
|
||||
let tiles = decoder.tile_count().unwrap();
|
||||
@@ -352,7 +360,8 @@ fn test_read_planar_bands() {
|
||||
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().expect("Cannot read image IFD");
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
|
||||
let chunks = decoder.strip_count().unwrap();
|
||||
@@ -396,7 +405,8 @@ fn test_div_zero() {
|
||||
178, 178, 178,
|
||||
];
|
||||
|
||||
let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let err = decoder.next_image().unwrap_err();
|
||||
|
||||
match err {
|
||||
TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {}
|
||||
@@ -414,7 +424,8 @@ fn test_too_many_value_bytes() {
|
||||
0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18,
|
||||
];
|
||||
|
||||
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let error = decoder.next_image().unwrap_err();
|
||||
|
||||
match error {
|
||||
tiff::TiffError::LimitsExceeded => {}
|
||||
@@ -432,7 +443,8 @@ fn fuzzer_testcase5() {
|
||||
178, 178, 178,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let _ = decoder.next_image().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -445,7 +457,8 @@ fn fuzzer_testcase1() {
|
||||
178,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let _ = decoder.next_image().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -458,7 +471,8 @@ fn fuzzer_testcase6() {
|
||||
178, 178,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let _ = decoder.next_image().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -470,7 +484,8 @@ fn oom() {
|
||||
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let _ = decoder.next_image().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -482,7 +497,8 @@ fn fuzzer_testcase4() {
|
||||
0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let _ = decoder.next_image().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -498,7 +514,8 @@ fn fuzzer_testcase2() {
|
||||
73,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let _ = decoder.next_image().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -514,7 +531,8 @@ fn invalid_jpeg_tag_2() {
|
||||
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let _ = decoder.next_image().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -527,7 +545,8 @@ fn fuzzer_testcase3() {
|
||||
255, 255,
|
||||
];
|
||||
|
||||
let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let _ = decoder.next_image().unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -545,7 +564,8 @@ fn timeout() {
|
||||
0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32,
|
||||
];
|
||||
|
||||
let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err();
|
||||
let mut decoder = Decoder::open(std::io::Cursor::new(&image)).unwrap();
|
||||
let error = decoder.next_image().unwrap_err();
|
||||
|
||||
match error {
|
||||
TiffError::FormatError(TiffFormatError::CycleInOffsets) => {}
|
||||
@@ -583,7 +603,8 @@ fn test_image_bytes(
|
||||
) {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().unwrap();
|
||||
|
||||
let mut buffer = vec![0u8; 0];
|
||||
for (idx, &(expected_type, expected_crc)) in expected.iter().enumerate() {
|
||||
@@ -720,3 +741,30 @@ fn extra_bits_gray() {
|
||||
fn extra_bits_rgb() {
|
||||
test_image_sum_u8("extra_bits_rgb_8b.tiff", ColorType::RGB(8), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "fax")]
|
||||
fn test_decode_huge_g4() {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join("big_g4.tif");
|
||||
let img_file = File::open(path).expect("failed to open large G4 test image");
|
||||
let mut decoder = Decoder::open(img_file).expect("failed to create decoder");
|
||||
|
||||
decoder.next_image().expect("failed to read image IFD");
|
||||
let (width, height) = decoder.dimensions().expect("failed to get dimensions");
|
||||
|
||||
assert!(
|
||||
height > u16::MAX as u32,
|
||||
"Test image height {} is not large enough to test u16 overflow",
|
||||
height
|
||||
);
|
||||
|
||||
let result = decoder
|
||||
.read_image()
|
||||
.expect("failed to decode huge G4 image");
|
||||
|
||||
if let tiff::decoder::DecodingResult::U8(data) = result {
|
||||
assert_eq!(data.len(), (width as usize * height as usize));
|
||||
} else {
|
||||
panic!("Unexpected decoding result type");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
use std::collections::HashMap;
|
||||
use std::{fs::File, path};
|
||||
|
||||
use tiff::decoder::{Decoder, DecodingResult};
|
||||
|
||||
use libtest_mimic::{Arguments, Failed, Trial};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
const REFERENCE_DIR: &str = "tests/libtiffpic";
|
||||
|
||||
// `find test/libtiffpic -type f | sort`
|
||||
const FILES: &[(&str, bool)] = &[
|
||||
("tests/libtiffpic/caspian.tif", true),
|
||||
("tests/libtiffpic/cramps.tif", true),
|
||||
// FIXME: See <https://github.com/image-rs/image-tiff/issues/338>, this has tiles as
|
||||
// StripOffsets and has a RowsPerStrip.
|
||||
("tests/libtiffpic/cramps-tile.tif", false),
|
||||
("tests/libtiffpic/CVS/Entries", false),
|
||||
("tests/libtiffpic/CVS/Repository", false),
|
||||
("tests/libtiffpic/CVS/Root", false),
|
||||
("tests/libtiffpic/depth/CVS/Entries", false),
|
||||
("tests/libtiffpic/depth/CVS/Repository", false),
|
||||
("tests/libtiffpic/depth/CVS/Root", false),
|
||||
("tests/libtiffpic/depth/flower-minisblack-02.tif", true),
|
||||
("tests/libtiffpic/depth/flower-minisblack-04.tif", true),
|
||||
("tests/libtiffpic/depth/flower-minisblack-06.tif", true),
|
||||
("tests/libtiffpic/depth/flower-minisblack-08.tif", true),
|
||||
("tests/libtiffpic/depth/flower-minisblack-10.tif", true),
|
||||
("tests/libtiffpic/depth/flower-minisblack-12.tif", true),
|
||||
("tests/libtiffpic/depth/flower-minisblack-14.tif", true),
|
||||
("tests/libtiffpic/depth/flower-minisblack-16.tif", true),
|
||||
("tests/libtiffpic/depth/flower-minisblack-24.tif", true),
|
||||
("tests/libtiffpic/depth/flower-minisblack-32.tif", true),
|
||||
("tests/libtiffpic/depth/flower-palette-02.tif", true),
|
||||
("tests/libtiffpic/depth/flower-palette-04.tif", true),
|
||||
("tests/libtiffpic/depth/flower-palette-08.tif", true),
|
||||
("tests/libtiffpic/depth/flower-palette-16.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-contig-02.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-contig-04.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-contig-08.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-contig-10.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-contig-12.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-contig-14.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-contig-16.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-contig-24.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-contig-32.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-planar-02.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-planar-04.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-planar-08.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-planar-10.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-planar-12.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-planar-14.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-planar-16.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-planar-24.tif", true),
|
||||
("tests/libtiffpic/depth/flower-rgb-planar-32.tif", true),
|
||||
(
|
||||
"tests/libtiffpic/depth/flower-separated-contig-08.tif",
|
||||
true,
|
||||
),
|
||||
(
|
||||
"tests/libtiffpic/depth/flower-separated-contig-16.tif",
|
||||
true,
|
||||
),
|
||||
(
|
||||
"tests/libtiffpic/depth/flower-separated-planar-08.tif",
|
||||
true,
|
||||
),
|
||||
(
|
||||
"tests/libtiffpic/depth/flower-separated-planar-16.tif",
|
||||
true,
|
||||
),
|
||||
("tests/libtiffpic/depth/genimages", false),
|
||||
("tests/libtiffpic/depth/README.txt", false),
|
||||
("tests/libtiffpic/depth/summary.txt", false),
|
||||
("tests/libtiffpic/dscf0013.tif", true),
|
||||
("tests/libtiffpic/fax2d.tif", true),
|
||||
// No support for direct CCITT Group 3 1D format
|
||||
("tests/libtiffpic/g3test.g3", false),
|
||||
("tests/libtiffpic/g3test.tif", true),
|
||||
("tests/libtiffpic/jello.tif", true),
|
||||
("tests/libtiffpic/jim___ah.tif", true),
|
||||
("tests/libtiffpic/jim___cg.tif", true),
|
||||
("tests/libtiffpic/jim___dg.tif", true),
|
||||
("tests/libtiffpic/jim___gg.tif", true),
|
||||
("tests/libtiffpic/ladoga.tif", true),
|
||||
("tests/libtiffpic/off_l16.tif", true),
|
||||
("tests/libtiffpic/off_luv24.tif", true),
|
||||
("tests/libtiffpic/off_luv32.tif", true),
|
||||
("tests/libtiffpic/oxford.tif", true),
|
||||
("tests/libtiffpic/pc260001.tif", true),
|
||||
("tests/libtiffpic/quad-jpeg.tif", true),
|
||||
("tests/libtiffpic/quad-lzw.tif", true),
|
||||
// FIXME: See <https://github.com/image-rs/image-tiff/issues/338>, this has tiles as
|
||||
// StripOffsets and has a RowsPerStrip.
|
||||
("tests/libtiffpic/quad-tile.tif", false),
|
||||
("tests/libtiffpic/README", false),
|
||||
("tests/libtiffpic/smallliz.tif", true),
|
||||
("tests/libtiffpic/strike.tif", true),
|
||||
("tests/libtiffpic/text.tif", true),
|
||||
("tests/libtiffpic/ycbcr-cat.tif", true),
|
||||
("tests/libtiffpic/zackthecat.tif", true),
|
||||
];
|
||||
|
||||
fn main() {
|
||||
let mut candidates: HashMap<_, _> = FILES.iter().copied().collect();
|
||||
let mut trials = Vec::new();
|
||||
|
||||
for entry in WalkDir::new(REFERENCE_DIR) {
|
||||
let entry = entry.unwrap();
|
||||
|
||||
if !entry.file_type().is_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(path) = entry.path().to_str() else {
|
||||
continue;
|
||||
};
|
||||
let path = path.replace(std::path::MAIN_SEPARATOR, "/"); // always use UNIX path separators
|
||||
|
||||
// Mark this path as consumed in either case by removing it.
|
||||
let Some(should_decode) = candidates.remove(path.as_str()) else {
|
||||
panic!("File {} not in reference list", path);
|
||||
};
|
||||
|
||||
// Check if we should turn it into a test case.
|
||||
if !should_decode {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = path.to_string();
|
||||
trials.push(Trial::test(
|
||||
path.to_string(),
|
||||
move || -> Result<(), Failed> {
|
||||
decode_libtiffpic(&path)
|
||||
.map_err(|e| Failed::from(format_args!("Decoding failed for {}: {}", path, e)))
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
if !candidates.is_empty() {
|
||||
for (path, _) in candidates {
|
||||
eprintln!("Reference file {} not found in directory walk", path);
|
||||
}
|
||||
|
||||
panic!("Some reference files were not found");
|
||||
}
|
||||
|
||||
let args = Arguments::from_args();
|
||||
libtest_mimic::run(&args, trials).exit();
|
||||
}
|
||||
|
||||
fn decode_libtiffpic(path: &str) -> Result<(), tiff::TiffError> {
|
||||
let path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path);
|
||||
|
||||
let img_file = File::open(path)?;
|
||||
let mut decoder = Decoder::open(img_file)?;
|
||||
|
||||
let mut buffer = DecodingResult::U8(vec![]);
|
||||
while decoder.more_images() {
|
||||
decoder.next_image()?;
|
||||
let _layout = decoder.read_image_to_buffer(&mut buffer);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+53
-3
@@ -1,4 +1,4 @@
|
||||
use tiff::decoder::{Decoder, TiffCodingUnit};
|
||||
use tiff::decoder::{Decoder, DecodingResult, Limits, TiffCodingUnit};
|
||||
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
@@ -10,8 +10,9 @@ 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::open(io).unwrap();
|
||||
|
||||
let mut tif = Decoder::new(io).unwrap();
|
||||
tif.next_image().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());
|
||||
@@ -36,8 +37,9 @@ 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::open(io).unwrap();
|
||||
|
||||
let mut tif = Decoder::new(io).unwrap();
|
||||
tif.next_image().unwrap();
|
||||
let layout = tif.image_coding_unit_layout(TiffCodingUnit(0)).unwrap();
|
||||
|
||||
assert_eq!(layout.planes, 3);
|
||||
@@ -74,3 +76,51 @@ fn read_first_coding_unit() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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::open(io).unwrap();
|
||||
tif = tif.with_limits({
|
||||
limits.decoding_buffer_size = 374 * 499;
|
||||
limits.clone()
|
||||
});
|
||||
|
||||
tif.next_image().unwrap();
|
||||
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);
|
||||
}
|
||||
+166
-14
@@ -1,7 +1,7 @@
|
||||
extern crate tiff;
|
||||
|
||||
use tiff::decoder::{ifd, Decoder, DecodingResult};
|
||||
use tiff::encoder::{colortype, Ifd, Ifd8, SRational, TiffEncoder};
|
||||
use tiff::encoder::{colortype, Ifd, Ifd8, Predictor, SRational, TiffEncoder};
|
||||
use tiff::tags::Tag;
|
||||
use tiff::ColorType;
|
||||
|
||||
@@ -33,7 +33,9 @@ fn encode_decode() {
|
||||
}
|
||||
{
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
let mut decoder = Decoder::open(&mut file).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||
|
||||
@@ -116,7 +118,9 @@ fn encode_decode_big() {
|
||||
}
|
||||
{
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
let mut decoder = Decoder::open(&mut file).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8));
|
||||
assert_eq!(decoder.dimensions().unwrap(), (100, 100));
|
||||
|
||||
@@ -207,8 +211,10 @@ fn test_encode_ifd() {
|
||||
|
||||
// Rewind the cursor for reading
|
||||
data.set_position(0);
|
||||
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||
let mut decoder = Decoder::open(&mut data).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
|
||||
assert_eq!(decoder.assert_tag_u32(65000), 42);
|
||||
assert_eq!(decoder.assert_tag_u32_vec(65000), [42]);
|
||||
@@ -247,7 +253,9 @@ macro_rules! test_roundtrip {
|
||||
) {
|
||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||
let img_file = File::open(path).expect("Cannot find test image!");
|
||||
let mut decoder = Decoder::new(img_file).expect("Cannot create decoder");
|
||||
let mut decoder = Decoder::open(img_file).expect("Cannot create decoder");
|
||||
decoder.next_image().unwrap();
|
||||
|
||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||
|
||||
let mut buffer = tiff::decoder::DecodingResult::U8(vec![]);
|
||||
@@ -268,7 +276,8 @@ macro_rules! test_roundtrip {
|
||||
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::open(&mut file).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||
if let DecodingResult::$buffer(img_res) = buffer {
|
||||
assert_eq!(*image_data, img_res);
|
||||
@@ -379,6 +388,144 @@ fn test_ycbcr_u8_roundtrip() {
|
||||
test_u8_roundtrip::<colortype::YCbCr8>("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8));
|
||||
}
|
||||
|
||||
/// Test that encoding with the floating-point predictor works correctly
|
||||
/// and can be decoded back to the original data.
|
||||
#[test]
|
||||
fn test_gray_f32_floating_point_predictor() {
|
||||
// Generate test data: a gradient of f32 values
|
||||
let width = 32u32;
|
||||
let height = 32u32;
|
||||
let mut image_data: Vec<f32> = Vec::with_capacity((width * height) as usize);
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
// Create a gradient pattern
|
||||
image_data.push((x as f32 + y as f32 * 0.1) / 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
let mut file = Cursor::new(Vec::new());
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut file)
|
||||
.unwrap()
|
||||
.with_predictor(Predictor::FloatingPoint);
|
||||
tiff.write_image::<colortype::Gray32Float>(width, height, &image_data)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::open(&mut file).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(32));
|
||||
|
||||
let mut buffer = DecodingResult::F32(vec![]);
|
||||
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||
if let DecodingResult::F32(img_res) = buffer {
|
||||
assert_eq!(image_data.len(), img_res.len());
|
||||
for (orig, decoded) in image_data.iter().zip(img_res.iter()) {
|
||||
assert_eq!(
|
||||
*orig, *decoded,
|
||||
"Mismatch at value: orig={}, decoded={}",
|
||||
orig, decoded
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test floating-point predictor with RGB f32 data
|
||||
#[test]
|
||||
fn test_rgb_f32_floating_point_predictor() {
|
||||
let width = 16u32;
|
||||
let height = 16u32;
|
||||
let mut image_data: Vec<f32> = Vec::with_capacity((width * height * 3) as usize);
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
// RGB pattern
|
||||
image_data.push(x as f32 / width as f32); // R
|
||||
image_data.push(y as f32 / height as f32); // G
|
||||
image_data.push((x + y) as f32 / (width + height) as f32); // B
|
||||
}
|
||||
}
|
||||
|
||||
let mut file = Cursor::new(Vec::new());
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut file)
|
||||
.unwrap()
|
||||
.with_predictor(Predictor::FloatingPoint);
|
||||
tiff.write_image::<colortype::RGB32Float>(width, height, &image_data)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::open(&mut file).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(32));
|
||||
|
||||
let mut buffer = DecodingResult::F32(vec![]);
|
||||
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||
if let DecodingResult::F32(img_res) = buffer {
|
||||
assert_eq!(image_data.len(), img_res.len());
|
||||
for (i, (orig, decoded)) in image_data.iter().zip(img_res.iter()).enumerate() {
|
||||
assert_eq!(
|
||||
*orig, *decoded,
|
||||
"Mismatch at index {}: orig={}, decoded={}",
|
||||
i, orig, decoded
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test floating-point predictor with f64 data
|
||||
#[test]
|
||||
fn test_gray_f64_floating_point_predictor() {
|
||||
let width = 16u32;
|
||||
let height = 16u32;
|
||||
let mut image_data: Vec<f64> = Vec::with_capacity((width * height) as usize);
|
||||
for y in 0..height {
|
||||
for x in 0..width {
|
||||
image_data.push((x as f64 + y as f64 * 0.1) / 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
let mut file = Cursor::new(Vec::new());
|
||||
{
|
||||
let mut tiff = TiffEncoder::new(&mut file)
|
||||
.unwrap()
|
||||
.with_predictor(Predictor::FloatingPoint);
|
||||
tiff.write_image::<colortype::Gray64Float>(width, height, &image_data)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::open(&mut file).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(decoder.colortype().unwrap(), ColorType::Gray(64));
|
||||
|
||||
let mut buffer = DecodingResult::F64(vec![]);
|
||||
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||
if let DecodingResult::F64(img_res) = buffer {
|
||||
assert_eq!(image_data.len(), img_res.len());
|
||||
for (orig, decoded) in image_data.iter().zip(img_res.iter()) {
|
||||
assert_eq!(
|
||||
*orig, *decoded,
|
||||
"Mismatch: orig={}, decoded={}",
|
||||
orig, decoded
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!("Wrong data type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait AssertDecode {
|
||||
fn assert_tag_u32(&mut self, tag: u16) -> u32;
|
||||
fn assert_tag_u32_vec(&mut self, tag: u16) -> Vec<u32>;
|
||||
@@ -456,7 +603,8 @@ fn test_multiple_byte() {
|
||||
|
||||
data.set_position(0);
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||
let mut decoder = Decoder::open(&mut data).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
|
||||
assert_eq!(decoder.assert_tag_u32_vec(65000), [1]);
|
||||
assert_eq!(decoder.assert_tag_u32_vec(65001), [1, 2]);
|
||||
@@ -537,7 +685,8 @@ fn test_signed() {
|
||||
//Rewind the cursor for reading
|
||||
data.set_position(0);
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||
let mut decoder = Decoder::open(&mut data).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
|
||||
assert_eq!(decoder.assert_tag_i32(65000), -1);
|
||||
assert_eq!(decoder.assert_tag_i32_vec(65001), [-1]);
|
||||
@@ -589,13 +738,14 @@ fn test_multipage_image() {
|
||||
img_file.seek(SeekFrom::Start(0)).unwrap();
|
||||
|
||||
{
|
||||
let mut img_decoder = Decoder::new(&mut img_file).unwrap();
|
||||
let mut decoder = Decoder::open(&mut img_file).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
|
||||
// check the dimensions of the image in the first page
|
||||
assert_eq!(img_decoder.dimensions().unwrap(), (2, 2));
|
||||
img_decoder.next_image().unwrap();
|
||||
assert_eq!(decoder.dimensions().unwrap(), (2, 2));
|
||||
decoder.next_image().unwrap();
|
||||
// check the dimensions of the image in the second page
|
||||
assert_eq!(img_decoder.dimensions().unwrap(), (3, 3));
|
||||
assert_eq!(decoder.dimensions().unwrap(), (3, 3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,7 +774,8 @@ fn test_rows_per_strip() {
|
||||
|
||||
file.seek(SeekFrom::Start(0)).unwrap();
|
||||
{
|
||||
let mut decoder = Decoder::new(&mut file).unwrap();
|
||||
let mut decoder = Decoder::open(&mut file).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(decoder.get_tag_u64(Tag::RowsPerStrip).unwrap(), 2);
|
||||
assert_eq!(decoder.strip_count().unwrap(), 50);
|
||||
|
||||
@@ -664,9 +815,10 @@ fn test_auxiliary_directory() {
|
||||
}
|
||||
|
||||
data.set_position(0);
|
||||
let mut decoder = Decoder::new(&mut data).unwrap();
|
||||
let mut decoder = Decoder::open(&mut data).unwrap();
|
||||
|
||||
{
|
||||
decoder.next_image().unwrap();
|
||||
assert_eq!(decoder.dimensions().unwrap(), (1, 1));
|
||||
let _ = decoder.read_image().unwrap();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use tiff::{
|
||||
decoder::{Decoder, DecodingResult},
|
||||
encoder::{
|
||||
colortype::{self, ColorType},
|
||||
compression::*,
|
||||
Compression, TiffEncoder, TiffValue,
|
||||
},
|
||||
};
|
||||
@@ -110,7 +109,8 @@ fn encode_decode_with_compression(compression: Compression) {
|
||||
// Decode tiff
|
||||
data.set_position(0);
|
||||
{
|
||||
let mut decoder = Decoder::new(data).unwrap();
|
||||
let mut decoder = Decoder::open(data).unwrap();
|
||||
decoder.next_image().unwrap();
|
||||
|
||||
// Check the RGB image
|
||||
assert_eq!(
|
||||
@@ -147,9 +147,9 @@ fn encode_decode_with_lzw() {
|
||||
#[test]
|
||||
#[cfg(feature = "deflate")]
|
||||
fn encode_decode_with_deflate() {
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Fast));
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Balanced));
|
||||
encode_decode_with_compression(Compression::Deflate(DeflateLevel::Best));
|
||||
encode_decode_with_compression(Compression::Deflate(1));
|
||||
encode_decode_with_compression(Compression::Deflate(6));
|
||||
encode_decode_with_compression(Compression::Deflate(9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+2
-1
@@ -13,7 +13,8 @@ fn test_directory<F: Fn(File) -> bool>(path: &str, f: F) {
|
||||
}
|
||||
|
||||
fn decode_tiff(file: File) -> TiffResult<()> {
|
||||
let mut decoder = Decoder::new(file)?;
|
||||
let mut decoder = Decoder::open(file)?;
|
||||
decoder.next_image()?;
|
||||
decoder.read_image()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
File renamed without changes.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
/cramps-tile.tif/1.1/Sat Nov 23 05:17:31 2002/-kb/
|
||||
/cramps.tif/1.1/Sat Nov 23 05:17:31 2002/-kb/
|
||||
/fax2d.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/g3test.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/jello.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/jim___ah.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/jim___cg.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/jim___dg.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/jim___gg.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/off_l16.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/off_luv24.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/off_luv32.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/oxford.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/quad-jpeg.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/quad-lzw.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/quad-tile.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/smallliz.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/strike.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/text.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
/ycbcr-cat.tif/1.1/Sat Nov 23 05:17:32 2002/-kb/
|
||||
D/depth////
|
||||
/README/1.2/Thu Dec 29 22:06:14 2005//
|
||||
/caspian.tif/1.1/Thu Dec 29 22:06:43 2005/-kb/
|
||||
/dscf0013.tif/1.1/Thu Dec 29 22:07:23 2005/-kb/
|
||||
/g3test.g3/1.1/Thu Dec 29 22:07:27 2005/-kb/
|
||||
/ladoga.tif/1.1/Thu Dec 29 22:07:29 2005/-kb/
|
||||
/pc260001.tif/1.1/Mon Dec 26 14:54:00 2005/-kb/
|
||||
/zackthecat.tif/1.1/Thu Dec 29 22:08:43 2005/-kb/
|
||||
@@ -0,0 +1 @@
|
||||
libtiffpic
|
||||
@@ -0,0 +1 @@
|
||||
:pserver:dron@cvs.maptools.org:/cvs/maptools/cvsroot
|
||||
@@ -0,0 +1,101 @@
|
||||
The following image files are included for testing. Note that a wide
|
||||
variety of images can be generated using the tools provided with the
|
||||
library (e.g. tiffcp, tiff2bw, tiffmedian, etc.); see below for some
|
||||
hints on this.
|
||||
|
||||
PlanarConfiguration = 1 (packed data)
|
||||
-------------------------------------
|
||||
caspian.tif 279x220 64-bit floating point (deflate) Caspian Sea from space
|
||||
cramps.tif 800x607 8-bit b&w (packbits) "cramps poster"
|
||||
cramps-tile.tif 256x256 tiled version of cramps.tif (no compression)
|
||||
dscf0013.tif 640x480 YCbCr digital camera image which lacks Reference
|
||||
Black/White values. Contains EXIF SubIFD. No compression.
|
||||
fax2d.tif 1728x1082 1-bit b&w (G3/2D) facsimile
|
||||
g3test.g3 raw Group 3 encoded fax file
|
||||
g3test.tif TIFF equivalent of g3test.g3 created by fax2tiff
|
||||
jello.tif 256x192 8-bit RGB (lzw palette) Paul Heckbert "jello"
|
||||
ladoga.tif 158x118 16-bit unsigned, single band, deflate
|
||||
off_l16.tif 333x225 8-bit CIE LogL (SGILog) office from Greg Larson
|
||||
off_luv24.tif 333x225 8-bit CIE LogLuv (SGILog24) office from " "
|
||||
off_luv32.tif 333x225 8-bit CIE LogLuv (SGILog) office from " "
|
||||
pc260001.tif 640x480 8-bit RGB digital camera image. Contains EXIF SubIFD.
|
||||
No compression.
|
||||
quad-jpeg.tif 512x384 8-bit YCbCr (jpeg) version of quad-lzw.tif
|
||||
quad-lzw.tif 512x384 8-bit RGB (lzw) "quadric surfaces"
|
||||
quad-tile.tif 512x384 tiled version of quad-lzw.tif (lzw)
|
||||
strike.tif 256x200 8-bit RGBA (lzw) "bowling pins" from Pixar
|
||||
text.tif 1512x359 4-bit b&w (thunderscan) am-express credit card
|
||||
ycbcr-cat.tif 250x325 8-bit YCbCr (lzw) "kitty" created by rgb2ycbcr
|
||||
|
||||
smallliz.tif 160x160 8-bit YCbCr (OLD jpeg) lizard from HP**
|
||||
zackthecat.tif 234x213 8-bit YCbCr (OLD jpeg) tiled "ZackTheCat" from NeXT**
|
||||
|
||||
** These images are in a deprecated format and are included only for testing
|
||||
backwards compatibility. JPEG-encoded TIFF images use a different scheme
|
||||
described in TIFF Technical Note #2 and implemented by libtiff.
|
||||
|
||||
PlanarConfiguration = 2 (separated samples)
|
||||
-------------------------------------------
|
||||
oxford.tif 601x81 8-bit RGB (lzw) screendump off oxford
|
||||
|
||||
The other images are from Hewlett Packard and exemplify the use of the
|
||||
HalftoneHints tag (in their words):
|
||||
|
||||
The images are all the same subject, and should all appear the same
|
||||
after rendering. Each of the images is slightly different as outlined
|
||||
by the following table:
|
||||
|
||||
FileName ToneRange HalftoneHints
|
||||
jim___cg.tif A Y
|
||||
jim___dg.tif B N
|
||||
jim___gg.tif B Y
|
||||
|
||||
The images with tone range A are 0-100% linear reflectance images,
|
||||
in other words, fixed full gamut tone range. The images with tone
|
||||
range B are what we call "normalized", meaning that the image has
|
||||
been tone adjusted such the Highlight and Shadow are at gray levels
|
||||
1 & 2**BitsPerSample-2.
|
||||
|
||||
Each of the three grayscale images should be rendered to look like
|
||||
the halftone in pics/jim___ah.tif when printed on a Canon SX engine.
|
||||
|
||||
Generated Images
|
||||
----------------
|
||||
The file g3test.tif was created from g3test.g3 with the command:
|
||||
|
||||
fax2tiff -o t.tif -1 -p -M -R 98 g3test.g3
|
||||
|
||||
Tiled images can be created with tiffcp; e.g.
|
||||
|
||||
tiffcp -t -c none cramps.tif cramps-tile.tif
|
||||
|
||||
(but the tiled images included here were actually generated by the
|
||||
Silicon Graphics Image Library).
|
||||
|
||||
The file quad-jpeg.tif was created from quad-lzw.tif with the command:
|
||||
|
||||
tiffcp -c jpeg quad-lzw.tif quad-jpeg.tif
|
||||
|
||||
To generate a tiled image with separated samples the following might
|
||||
be used:
|
||||
|
||||
tiffcp -t -p separate quad-lzw.tif t.tif
|
||||
|
||||
[ Beware that tiffcp does not handle every format TIFF image. In particular,
|
||||
when conversion between tiled and strip'd images is reliable only when
|
||||
BitsPerSample is 8. ]
|
||||
|
||||
To generate a G4-encoded image with a single strip the following would work:
|
||||
|
||||
tiffcp -c g4 -r 1082 fax2d.tif t.tif
|
||||
|
||||
Contributing New Images
|
||||
-----------------------
|
||||
If you have images that are significantly different from those included
|
||||
in this package and that do NOT HAVE A COPYRIGHT; feel free to send them
|
||||
to me at sam@engr.sgi.com. I prefer to receive a URL to a location from
|
||||
which I can retrieve the image by public FTP. However if that is not
|
||||
possible you can send it to me in a mail message uuencode'd. Please do
|
||||
not send large images by mail. Also, please do not send me an image
|
||||
unless it is notably unique; e.g. not easily generated using the tools
|
||||
I provide.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,40 @@
|
||||
/README.txt/1.1/Sun Jan 30 23:03:00 2005//
|
||||
/genimages/1.1/Mon Jan 31 02:49:00 2005//
|
||||
/summary.txt/1.1/Mon Jan 31 02:49:00 2005//
|
||||
/flower-minisblack-24.tif/1.2/Sun Nov 13 17:25:25 2005/-kb/
|
||||
/flower-rgb-contig-24.tif/1.2/Sun Nov 13 17:22:46 2005/-kb/
|
||||
/flower-rgb-planar-24.tif/1.2/Sun Nov 13 17:20:24 2005/-kb/
|
||||
/flower-minisblack-02.tif/1.1/Thu Dec 29 22:08:43 2005/-kb/
|
||||
/flower-minisblack-04.tif/1.1/Thu Dec 29 22:08:43 2005/-kb/
|
||||
/flower-minisblack-06.tif/1.1/Thu Dec 29 22:08:43 2005/-kb/
|
||||
/flower-minisblack-08.tif/1.1/Thu Dec 29 22:08:44 2005/-kb/
|
||||
/flower-minisblack-10.tif/1.1/Thu Dec 29 22:08:44 2005/-kb/
|
||||
/flower-minisblack-12.tif/1.1/Thu Dec 29 22:08:45 2005/-kb/
|
||||
/flower-minisblack-14.tif/1.1/Thu Dec 29 22:08:45 2005/-kb/
|
||||
/flower-minisblack-16.tif/1.1/Thu Dec 29 22:08:46 2005/-kb/
|
||||
/flower-minisblack-32.tif/1.1/Thu Dec 29 22:08:47 2005/-kb/
|
||||
/flower-palette-02.tif/1.1/Thu Dec 29 22:08:47 2005/-kb/
|
||||
/flower-palette-04.tif/1.1/Thu Dec 29 22:08:47 2005/-kb/
|
||||
/flower-palette-08.tif/1.1/Thu Dec 29 22:08:47 2005/-kb/
|
||||
/flower-palette-16.tif/1.1/Thu Dec 29 22:08:49 2005/-kb/
|
||||
/flower-rgb-contig-02.tif/1.1/Thu Dec 29 22:08:49 2005/-kb/
|
||||
/flower-rgb-contig-04.tif/1.1/Thu Dec 29 22:08:49 2005/-kb/
|
||||
/flower-rgb-contig-08.tif/1.1/Thu Dec 29 22:08:50 2005/-kb/
|
||||
/flower-rgb-contig-10.tif/1.1/Thu Dec 29 22:08:51 2005/-kb/
|
||||
/flower-rgb-contig-12.tif/1.1/Thu Dec 29 22:08:52 2005/-kb/
|
||||
/flower-rgb-contig-14.tif/1.1/Thu Dec 29 22:08:53 2005/-kb/
|
||||
/flower-rgb-contig-16.tif/1.1/Thu Dec 29 22:08:55 2005/-kb/
|
||||
/flower-rgb-contig-32.tif/1.1/Thu Dec 29 22:08:58 2005/-kb/
|
||||
/flower-rgb-planar-02.tif/1.1/Thu Dec 29 22:08:58 2005/-kb/
|
||||
/flower-rgb-planar-04.tif/1.1/Thu Dec 29 22:08:58 2005/-kb/
|
||||
/flower-rgb-planar-08.tif/1.1/Thu Dec 29 22:08:59 2005/-kb/
|
||||
/flower-rgb-planar-10.tif/1.1/Thu Dec 29 22:09:00 2005/-kb/
|
||||
/flower-rgb-planar-12.tif/1.1/Thu Dec 29 22:09:02 2005/-kb/
|
||||
/flower-rgb-planar-14.tif/1.1/Thu Dec 29 22:09:02 2005/-kb/
|
||||
/flower-rgb-planar-16.tif/1.1/Thu Dec 29 22:09:04 2005/-kb/
|
||||
/flower-rgb-planar-32.tif/1.1/Thu Dec 29 22:09:08 2005/-kb/
|
||||
/flower-separated-contig-08.tif/1.1/Thu Dec 29 22:09:10 2005/-kb/
|
||||
/flower-separated-contig-16.tif/1.1/Thu Dec 29 22:09:12 2005/-kb/
|
||||
/flower-separated-planar-08.tif/1.1/Thu Dec 29 22:09:12 2005/-kb/
|
||||
/flower-separated-planar-16.tif/1.1/Thu Dec 29 22:09:14 2005/-kb/
|
||||
D
|
||||
@@ -0,0 +1 @@
|
||||
libtiffpic/depth
|
||||
@@ -0,0 +1 @@
|
||||
:pserver:dron@cvs.maptools.org:/cvs/maptools/cvsroot
|
||||
@@ -0,0 +1,9 @@
|
||||
These sample TIFF image files are prepared by Bob Friesenhahn
|
||||
<bfriesen@simple.dallas.tx.us> using a development version of
|
||||
GraphicsMagick 1.2.
|
||||
|
||||
See the file summary.txt for a description of the images.
|
||||
|
||||
These files are hereby placed in the public domain.
|
||||
|
||||
|
||||
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.
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.
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.
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.
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.
Binary file not shown.
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/ksh
|
||||
|
||||
GM=/scratch/bfriesen/build/GraphicsMagick-32-static/utilities/gm
|
||||
GM_STD_OPT="-compress none"
|
||||
MASTER_IMAGE_ORIGINAL='/home/bfriesen/images/david/112_1201-flowers.jpg'
|
||||
MASTER_IMAGE_GRAY=gray.miff
|
||||
MASTER_IMAGE_RGB=rgb.miff
|
||||
MASTER_IMAGE_CMYK=cmyk.miff
|
||||
COLUMNS=73
|
||||
ROWS=43
|
||||
|
||||
GRAY_DEPTHS='2 4 6 8 10 12 14 16 24 32'
|
||||
PSEUDO_DEPTHS='2 4 8 16'
|
||||
TRUE_COLORS='2 4 8 10 12 14 16 24 32'
|
||||
CMYK_COLORS='8 16'
|
||||
|
||||
SUMMARY=summary.txt
|
||||
echo > $SUMMARY
|
||||
|
||||
$GM convert $MASTER_IMAGE_ORIGINAL +profile '*' -depth 32 -type Grayscale -density 72x72 -geometry "${COLUMNS}x${ROWS}" $MASTER_IMAGE_GRAY
|
||||
$GM convert $MASTER_IMAGE_ORIGINAL +profile '*' -depth 32 -density 72x72 -geometry "${COLUMNS}x${ROWS}" $MASTER_IMAGE_RGB
|
||||
$GM convert $MASTER_IMAGE_ORIGINAL +profile '*' -colorspace cmyk -depth 32 -density 72x72 -geometry "${COLUMNS}x${ROWS}" $MASTER_IMAGE_CMYK
|
||||
|
||||
# Gray images
|
||||
for depth in $GRAY_DEPTHS
|
||||
do
|
||||
outname=`printf "flower-minisblack-%02d.tif" $depth`
|
||||
echo "$outname\t${COLUMNS}x${ROWS} $depth-bit minisblack gray image" >> $SUMMARY
|
||||
$GM convert -verbose $MASTER_IMAGE_GRAY $GM_STD_OPT -type Grayscale -define tiff:bits-per-sample=$depth $outname
|
||||
done
|
||||
|
||||
# Colormapped images
|
||||
for depth in $PSEUDO_DEPTHS
|
||||
do
|
||||
colors=`echo "2^${depth}" | bc`
|
||||
outname=`printf "flower-palette-%02d.tif" $depth`
|
||||
echo "$outname\t${COLUMNS}x${ROWS} ${colors}-entry colormapped image" >> $SUMMARY
|
||||
$GM convert -verbose $MASTER_IMAGE_RGB $GM_STD_OPT -colors $colors $outname
|
||||
done
|
||||
|
||||
# Truecolor images
|
||||
for depth in $TRUE_COLORS
|
||||
do
|
||||
outname=`printf "flower-rgb-contig-%02d.tif" $depth`
|
||||
echo "$outname\t${COLUMNS}x${ROWS} $depth-bit contiguous RGB image" >> $SUMMARY
|
||||
$GM convert -verbose $MASTER_IMAGE_RGB $GM_STD_OPT -type TrueColor -define tiff:bits-per-sample=$depth $outname
|
||||
done
|
||||
|
||||
# Truecolor planar images
|
||||
for depth in $TRUE_COLORS
|
||||
do
|
||||
outname=`printf "flower-rgb-planar-%02d.tif" $depth`
|
||||
echo "$outname\t${COLUMNS}x${ROWS} $depth-bit seperated RGB image" >> $SUMMARY
|
||||
$GM convert -verbose $MASTER_IMAGE_RGB $GM_STD_OPT -type TrueColor -interlace plane -define tiff:bits-per-sample=$depth $outname
|
||||
done
|
||||
|
||||
# CMYK images
|
||||
for depth in $CMYK_COLORS
|
||||
do
|
||||
outname=`printf "flower-separated-contig-%02d.tif" $depth`
|
||||
echo "$outname\t${COLUMNS}x${ROWS} $depth-bit contiguous CMYK image" >> $SUMMARY
|
||||
$GM convert -verbose $MASTER_IMAGE_RGB $GM_STD_OPT -type ColorSeparation -define tiff:bits-per-sample=$depth $outname
|
||||
done
|
||||
|
||||
# CMYK planar images
|
||||
for depth in $CMYK_COLORS
|
||||
do
|
||||
outname=`printf "flower-separated-planar-%02d.tif" $depth`
|
||||
echo "$outname\t${COLUMNS}x${ROWS} $depth-bit separated CMYK image" >> $SUMMARY
|
||||
$GM convert -verbose $MASTER_IMAGE_RGB $GM_STD_OPT -type ColorSeparation -interlace plane -define tiff:bits-per-sample=$depth $outname
|
||||
done
|
||||
|
||||
rm -f $MASTER_IMAGE_GRAY $MASTER_IMAGE_RGB $MASTER_IMAGE_CMYK
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
flower-minisblack-02.tif 73x43 2-bit minisblack gray image
|
||||
flower-minisblack-04.tif 73x43 4-bit minisblack gray image
|
||||
flower-minisblack-06.tif 73x43 6-bit minisblack gray image
|
||||
flower-minisblack-08.tif 73x43 8-bit minisblack gray image
|
||||
flower-minisblack-10.tif 73x43 10-bit minisblack gray image
|
||||
flower-minisblack-12.tif 73x43 12-bit minisblack gray image
|
||||
flower-minisblack-14.tif 73x43 14-bit minisblack gray image
|
||||
flower-minisblack-16.tif 73x43 16-bit minisblack gray image
|
||||
flower-minisblack-24.tif 73x43 24-bit minisblack gray image
|
||||
flower-minisblack-32.tif 73x43 32-bit minisblack gray image
|
||||
flower-palette-02.tif 73x43 4-entry colormapped image
|
||||
flower-palette-04.tif 73x43 16-entry colormapped image
|
||||
flower-palette-08.tif 73x43 256-entry colormapped image
|
||||
flower-palette-16.tif 73x43 65536-entry colormapped image
|
||||
flower-rgb-contig-02.tif 73x43 2-bit contiguous RGB image
|
||||
flower-rgb-contig-04.tif 73x43 4-bit contiguous RGB image
|
||||
flower-rgb-contig-08.tif 73x43 8-bit contiguous RGB image
|
||||
flower-rgb-contig-10.tif 73x43 10-bit contiguous RGB image
|
||||
flower-rgb-contig-12.tif 73x43 12-bit contiguous RGB image
|
||||
flower-rgb-contig-14.tif 73x43 14-bit contiguous RGB image
|
||||
flower-rgb-contig-16.tif 73x43 16-bit contiguous RGB image
|
||||
flower-rgb-contig-24.tif 73x43 24-bit contiguous RGB image
|
||||
flower-rgb-contig-32.tif 73x43 32-bit contiguous RGB image
|
||||
flower-rgb-planar-02.tif 73x43 2-bit seperated RGB image
|
||||
flower-rgb-planar-04.tif 73x43 4-bit seperated RGB image
|
||||
flower-rgb-planar-08.tif 73x43 8-bit seperated RGB image
|
||||
flower-rgb-planar-10.tif 73x43 10-bit seperated RGB image
|
||||
flower-rgb-planar-12.tif 73x43 12-bit seperated RGB image
|
||||
flower-rgb-planar-14.tif 73x43 14-bit seperated RGB image
|
||||
flower-rgb-planar-16.tif 73x43 16-bit seperated RGB image
|
||||
flower-rgb-planar-24.tif 73x43 24-bit seperated RGB image
|
||||
flower-rgb-planar-32.tif 73x43 32-bit seperated RGB image
|
||||
flower-separated-contig-08.tif 73x43 8-bit contiguous CMYK image
|
||||
flower-separated-contig-16.tif 73x43 16-bit contiguous CMYK image
|
||||
flower-separated-planar-08.tif 73x43 8-bit separated CMYK image
|
||||
flower-separated-planar-16.tif 73x43 16-bit separated CMYK image
|
||||
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.
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.
Binary file not shown.
Binary file not shown.
Loaded 100 of 109 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user