Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c81d0a9c94 | ||
|
|
983c5baea0 | ||
|
|
abf3cc617d | ||
|
|
fb4ee9b377 | ||
|
|
45cd3c3b63 | ||
|
|
701fcc46b8 | ||
|
|
e41159ce05 | ||
|
|
03979faef8 | ||
|
|
857973cb21 | ||
|
|
cef21c47fb | ||
|
|
a0191ed1a7 |
No files matched your search
@@ -9,14 +9,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
rust: ["1.85.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.85.0' }}
|
||||
if: ${{ matrix.rust == '1.88.0' }}
|
||||
- name: Generate Cargo.lock with minimal-version dependencies
|
||||
if: ${{ matrix.rust == '1.85.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.85.0' }}
|
||||
if: ${{ matrix.rust != '1.88.0' }}
|
||||
run: cargo test -v && cargo doc -v
|
||||
|
||||
features:
|
||||
|
||||
+11
@@ -1,3 +1,14 @@
|
||||
# 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:
|
||||
|
||||
+11
-2
@@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "tiff"
|
||||
version = "0.11.2"
|
||||
version = "0.11.3"
|
||||
edition = "2021"
|
||||
resolver = "2"
|
||||
|
||||
# note: when changed, also update test runner in `.github/workflows/rust.yml`
|
||||
rust-version = "1.85.0"
|
||||
rust-version = "1.88.0"
|
||||
|
||||
license = "MIT"
|
||||
description = "TIFF decoding and encoding library in pure Rust"
|
||||
@@ -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,14 @@ 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
|
||||
|
||||
[[test]]
|
||||
name = "decode_libtiffpic"
|
||||
path = "tests/decode_libtiffpic.rs"
|
||||
harness = false
|
||||
@@ -36,6 +36,31 @@ 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"),
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
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::new(io)?;
|
||||
|
||||
loop {
|
||||
ls_dir(&mut reader);
|
||||
|
||||
if !reader.more_images() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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.image_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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
+149
-20
@@ -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,
|
||||
@@ -86,6 +86,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.
|
||||
@@ -330,15 +337,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 +391,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 +416,29 @@ 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 +454,10 @@ impl Image {
|
||||
chunk_offsets,
|
||||
chunk_bytes,
|
||||
chroma_subsampling,
|
||||
decompression_to_host_endian: matches!(
|
||||
compression_method,
|
||||
CompressionMethod::SgiLog | CompressionMethod::SgiLog24
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -501,6 +552,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 +617,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 +723,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 +928,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 +939,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 +1047,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 +1060,7 @@ impl Image {
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
samples,
|
||||
byte_order,
|
||||
decompressed_byte_order,
|
||||
predictor,
|
||||
);
|
||||
}
|
||||
@@ -977,7 +1106,7 @@ impl Image {
|
||||
row,
|
||||
color_type.bit_depth(),
|
||||
samples,
|
||||
byte_order,
|
||||
decompressed_byte_order,
|
||||
predictor,
|
||||
);
|
||||
|
||||
@@ -998,7 +1127,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 +1147,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
|
||||
}
|
||||
+8
-1
@@ -19,6 +19,7 @@ use self::stream::{ByteOrder, EndianReader};
|
||||
mod cycles;
|
||||
pub mod ifd;
|
||||
mod image;
|
||||
mod logluv;
|
||||
mod stream;
|
||||
mod tag_reader;
|
||||
|
||||
@@ -861,6 +862,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
chunk_offsets: Vec::new(),
|
||||
chunk_bytes: Vec::new(),
|
||||
chroma_subsampling: (2, 2),
|
||||
decompression_to_host_endian: false,
|
||||
},
|
||||
};
|
||||
decoder.next_image()?;
|
||||
@@ -1538,7 +1540,7 @@ impl<R: Read + Seek> Decoder<R> {
|
||||
}
|
||||
|
||||
/// Get the IFD decoder for our current image IFD.
|
||||
fn image_ifd(&mut self) -> IfdDecoder<'_> {
|
||||
pub fn image_ifd(&mut self) -> IfdDecoder<'_> {
|
||||
IfdDecoder {
|
||||
inner: tag_reader::TagReader {
|
||||
decoder: &mut self.value_reader,
|
||||
@@ -1885,6 +1887,11 @@ impl IfdDecoder<'_> {
|
||||
pub fn get_tag_ascii_string(&mut self, tag: Tag) -> TiffResult<String> {
|
||||
self.get_tag(tag)?.into_string()
|
||||
}
|
||||
|
||||
/// Inspect the raw underlying directory.
|
||||
pub fn directory(&self) -> &Directory {
|
||||
self.inner.ifd
|
||||
}
|
||||
}
|
||||
|
||||
impl<'l> IfdDecoder<'l> {
|
||||
|
||||
@@ -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;
|
||||
@@ -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
-1
@@ -403,7 +403,7 @@ impl<'a, W: 'a + Write + Seek, K: TiffKind> DirectoryEncoder<'a, W, K> {
|
||||
/// Returns an [`Entry`]. If the value is short enough it will *not* be written in the file but
|
||||
/// stored in the entry's offset field.
|
||||
pub fn write_entry_bytes(&mut self, ty: Type, data: &[u8]) -> TiffResult<Entry> {
|
||||
if data.len() % usize::from(ty.byte_len()) != 0 {
|
||||
if !data.len().is_multiple_of(usize::from(ty.byte_len())) {
|
||||
return Err(TiffError::UsageError(UsageError::MismatchedEntryLength {
|
||||
ty,
|
||||
found: data.len(),
|
||||
|
||||
@@ -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])?;
|
||||
|
||||
@@ -70,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}")
|
||||
}
|
||||
|
||||
+56
@@ -1,4 +1,5 @@
|
||||
use crate::encoder::TiffValue;
|
||||
use core::fmt;
|
||||
|
||||
macro_rules! tags {
|
||||
{
|
||||
@@ -169,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) {
|
||||
@@ -267,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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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() {
|
||||
@@ -61,3 +61,26 @@ 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::new(img_file).expect("Cannot create decoder");
|
||||
|
||||
loop {
|
||||
decoder.read_image_to_buffer(&mut buffer).unwrap();
|
||||
|
||||
if !decoder.more_images() {
|
||||
break;
|
||||
}
|
||||
|
||||
decoder.next_image().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
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;
|
||||
};
|
||||
|
||||
// Mark this path as consumed in either case by removing it.
|
||||
let Some(should_decode) = candidates.remove(path) 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::new(img_file)?;
|
||||
let mut buffer = DecodingResult::U8(vec![]);
|
||||
|
||||
loop {
|
||||
let _layout = decoder.read_image_to_buffer(&mut buffer);
|
||||
|
||||
if !decoder.more_images() {
|
||||
break;
|
||||
}
|
||||
|
||||
decoder.next_image()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
File renamed without changes.
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.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user