fix: resolve comments
This commit is contained in:
1 parent
c7a1176381
commit
1c6001ea5d
5 files changed
+18
-251
No files matched your search
@@ -3,4 +3,3 @@
|
|||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
tests/benches/corpus/
|
tests/benches/corpus/
|
||||||
.DS_Store
|
|
||||||
+4
-2
@@ -2,13 +2,15 @@ use tiff::decoder::{BufferLayoutPreference, Decoder, DecodingSampleBuffer, Decod
|
|||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let Some(image) = std::env::args_os().nth(1) else {
|
let Some(image) = std::env::args_os().nth(1) else {
|
||||||
eprintln!("Usage: decode FILE");
|
eprintln!("Usage: decode FILE [--lenient]");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let lenient = std::env::args().nth(2).as_deref() == Some("--lenient");
|
||||||
|
|
||||||
let file = std::fs::File::open(image)?;
|
let file = std::fs::File::open(image)?;
|
||||||
let io = std::io::BufReader::new(file);
|
let io = std::io::BufReader::new(file);
|
||||||
let mut reader = Decoder::open(io)?;
|
let mut reader = Decoder::open(io)?.with_lenient(lenient);
|
||||||
|
|
||||||
let mut data = DecodingSampleBuffer::I8(vec![]);
|
let mut data = DecodingSampleBuffer::I8(vec![]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,233 +0,0 @@
|
|||||||
use tiff::decoder::{BufferLayoutPreference, Decoder, DecodingSampleBuffer, DecodingSampleSlice};
|
|
||||||
|
|
||||||
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_with_options(io, true)?;
|
|
||||||
|
|
||||||
let mut data = DecodingSampleBuffer::I8(vec![]);
|
|
||||||
|
|
||||||
for i in 0u32.. {
|
|
||||||
if !reader.more_images() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.next_directory()?;
|
|
||||||
|
|
||||||
let colortype = reader.colortype()?;
|
|
||||||
let dimensions = reader.dimensions()?;
|
|
||||||
let layout = reader.read_image_to_buffer(&mut data)?;
|
|
||||||
|
|
||||||
debug_planes(i, &mut data, &layout, &colortype, dimensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn debug_planes(
|
|
||||||
index: u32,
|
|
||||||
data: &mut DecodingSampleBuffer,
|
|
||||||
layout: &BufferLayoutPreference,
|
|
||||||
colortype: &tiff::ColorType,
|
|
||||||
(width, height): (u32, u32),
|
|
||||||
) {
|
|
||||||
match data {
|
|
||||||
DecodingSampleBuffer::F16(v) => {
|
|
||||||
let samples = v
|
|
||||||
.iter()
|
|
||||||
.map(|&f| (f64::from(f) * f64::from(u16::MAX)) as u16)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
*data = DecodingSampleBuffer::U16(samples);
|
|
||||||
}
|
|
||||||
DecodingSampleBuffer::F32(v) => {
|
|
||||||
let samples = v
|
|
||||||
.iter()
|
|
||||||
.map(|&f| (f64::from(f) * f64::from(u16::MAX)) as u16)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
*data = DecodingSampleBuffer::U16(samples);
|
|
||||||
}
|
|
||||||
DecodingSampleBuffer::F64(v) => {
|
|
||||||
let samples = v
|
|
||||||
.iter()
|
|
||||||
.map(|&f| (f * f64::from(u16::MAX)) as u16)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
*data = DecodingSampleBuffer::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"),
|
|
||||||
tiff::ColorType::Palette(_) => (1, "PALETTE"),
|
|
||||||
tiff::ColorType::CMYK(_) => (4, "CMYK"),
|
|
||||||
tiff::ColorType::Multiband { num_samples: 2, .. } => (2, "GRAYSCALE_ALPHA"),
|
|
||||||
_ => {
|
|
||||||
eprintln!("Unsupported color type for PAM output: {:?}", colortype);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Number of samples in a line and number of sample lines in the image, depending on whether
|
|
||||||
// they are planar or not. This determines the layout of the image buffer when expanding bit
|
|
||||||
// packed samples to a full byte (we support power-of-two sample sizes, positive and negative,
|
|
||||||
// but only those).
|
|
||||||
let (swidth, sheight) = if layout.planes > 1 {
|
|
||||||
(
|
|
||||||
u64::from(width) * u64::from(colortype.num_samples()),
|
|
||||||
u64::from(height),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
u64::from(width),
|
|
||||||
u64::from(height) * u64::from(colortype.num_samples()),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut fallback_buffer;
|
|
||||||
let maxval = match data.as_buffer(0) {
|
|
||||||
// `U8` is also our representation of bit-packed color. PAM uses the smallest number of
|
|
||||||
// *bytes* needed to represent a sample but we must also adhere to maxval. We could explode
|
|
||||||
// them here (e.g. represent 1-bit as `{0, 1}`) but we'd also set `maxval` accordingly and
|
|
||||||
// this requires tupltype to be `BLACKANDWHITE` so introducing a bunch of complexity. If
|
|
||||||
// you want to do that in your real code, go ahead.
|
|
||||||
DecodingSampleSlice::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 = DecodingSampleBuffer::U8(fallback_buffer);
|
|
||||||
u64::from(u8::MAX)
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
fallback_buffer = vec![0; (swidth * sheight) as usize];
|
|
||||||
expand_2bit(buf, swidth, &mut fallback_buffer);
|
|
||||||
*data = DecodingSampleBuffer::U8(fallback_buffer);
|
|
||||||
u64::from(u8::MAX)
|
|
||||||
}
|
|
||||||
1 => {
|
|
||||||
fallback_buffer = vec![0; (swidth * sheight) as usize];
|
|
||||||
expand_1bit(buf, swidth, &mut fallback_buffer);
|
|
||||||
*data = DecodingSampleBuffer::U8(fallback_buffer);
|
|
||||||
u64::from(u8::MAX)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
eprintln!(
|
|
||||||
"Unsupported bit depth for U8 buffer: {}",
|
|
||||||
colortype.bit_depth()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DecodingSampleSlice::U16(_) => u64::from(u16::MAX),
|
|
||||||
DecodingSampleSlice::U32(_) => u64::from(u32::MAX),
|
|
||||||
DecodingSampleSlice::U64(_) => u64::MAX,
|
|
||||||
// Eh, this is a good effort I guess.
|
|
||||||
DecodingSampleSlice::I8(items) => {
|
|
||||||
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
|
||||||
u64::from(i8::MAX as u8)
|
|
||||||
}
|
|
||||||
DecodingSampleSlice::I16(items) => {
|
|
||||||
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
|
||||||
u64::from(i16::MAX as u16)
|
|
||||||
}
|
|
||||||
DecodingSampleSlice::I32(items) => {
|
|
||||||
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
|
||||||
u64::from(i32::MAX as u32)
|
|
||||||
}
|
|
||||||
DecodingSampleSlice::I64(items) => {
|
|
||||||
items.iter_mut().for_each(|v| *v = 0.max(*v));
|
|
||||||
i64::MAX as u64
|
|
||||||
}
|
|
||||||
// Not supported by PAM.
|
|
||||||
DecodingSampleSlice::F16(_) | DecodingSampleSlice::F32(_) | DecodingSampleSlice::F64(_) => {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let byte_len = if layout.planes > 1 {
|
|
||||||
tupltype = "GRAYSCALE";
|
|
||||||
layout.plane_stride.unwrap().get()
|
|
||||||
} else {
|
|
||||||
data.as_buffer(0).byte_len()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write out this image's planes to PAM files.
|
|
||||||
let header = format!("P7\nWIDTH {width}\nHEIGHT {height}\nDEPTH {depth}\nMAXVAL {maxval}\nTUPLTYPE {tupltype}\nENDHDR\n");
|
|
||||||
|
|
||||||
for (plane, plane_data) in data
|
|
||||||
.as_buffer(0)
|
|
||||||
.as_bytes_mut()
|
|
||||||
.chunks_exact(byte_len)
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
let filename = format!("image_{index:03}_plane_{plane:03}.pam");
|
|
||||||
let mut file = std::fs::File::create(filename).expect("Failed to create output file");
|
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
file.write_all(header.as_bytes())
|
|
||||||
.expect("Failed to write PAM header");
|
|
||||||
file.write_all(plane_data)
|
|
||||||
.expect("Failed to write PAM data");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Put exactly the ith `SCALE`-bit chunk of `val` into the least significant bits.
|
|
||||||
#[inline(always)]
|
|
||||||
fn bits_extract<const SCALE: u8>(val: u8, i: u8) -> u8 {
|
|
||||||
// Shift left to discard high bits, then right to bring desired bits down. This way also
|
|
||||||
// conveniently avoids all cases of overflow.
|
|
||||||
(val << (i * SCALE)) >> (8 - SCALE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_1bit(from: &[u8], samples_per_padded: u64, into: &mut [u8]) {
|
|
||||||
let stride = samples_per_padded.div_ceil(8);
|
|
||||||
|
|
||||||
let from_row = from.chunks_exact(stride as usize);
|
|
||||||
let into_row = into.chunks_exact_mut(samples_per_padded as usize);
|
|
||||||
|
|
||||||
for (from_row, into_row) in from_row.zip(into_row) {
|
|
||||||
for (into, &from) in into_row.chunks_mut(8).zip(from_row) {
|
|
||||||
let data =
|
|
||||||
core::array::from_fn::<u8, 8, _>(|i| 0xff * bits_extract::<1>(from, i as u8));
|
|
||||||
into.copy_from_slice(&data[..into.len()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_2bit(from: &[u8], samples_per_padded: u64, into: &mut [u8]) {
|
|
||||||
let stride = samples_per_padded.div_ceil(4);
|
|
||||||
|
|
||||||
let from_row = from.chunks_exact(stride as usize);
|
|
||||||
let into_row = into.chunks_exact_mut(samples_per_padded as usize);
|
|
||||||
|
|
||||||
for (from_row, into_row) in from_row.zip(into_row) {
|
|
||||||
for (into, &from) in into_row.chunks_mut(4).zip(from_row) {
|
|
||||||
let data =
|
|
||||||
core::array::from_fn::<u8, 4, _>(|i| 0xaa * bits_extract::<2>(from, i as u8));
|
|
||||||
into.copy_from_slice(&data[..into.len()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expand_4bit(from: &[u8], samples_per_padded: u64, into: &mut [u8]) {
|
|
||||||
let stride = samples_per_padded.div_ceil(2);
|
|
||||||
|
|
||||||
let from_row = from.chunks_exact(stride as usize);
|
|
||||||
let into_row = into.chunks_exact_mut(samples_per_padded as usize);
|
|
||||||
|
|
||||||
for (from_row, into_row) in from_row.zip(into_row) {
|
|
||||||
for (into, &from) in into_row.chunks_mut(2).zip(from_row) {
|
|
||||||
let data =
|
|
||||||
core::array::from_fn::<u8, 2, _>(|i| 0x88 * bits_extract::<4>(from, i as u8));
|
|
||||||
into.copy_from_slice(&data[..into.len()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-13
@@ -227,7 +227,6 @@ pub struct TiffHeader {
|
|||||||
pub byte_order: ByteOrder,
|
pub byte_order: ByteOrder,
|
||||||
pub variant: TiffVariant,
|
pub variant: TiffVariant,
|
||||||
pub first_ifd: IfdPointer,
|
pub first_ifd: IfdPointer,
|
||||||
lenient: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The representation of a TIFF decoder
|
/// The representation of a TIFF decoder
|
||||||
@@ -677,11 +676,7 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TiffHeader {
|
impl TiffHeader {
|
||||||
pub fn parse<R: Read + Seek>(r: R) -> TiffResult<Self> {
|
pub fn parse<R: Read + Seek>(mut r: R) -> TiffResult<Self> {
|
||||||
Self::parse_with_options(r, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_with_options<R: Read + Seek>(mut r: R, lenient: bool) -> TiffResult<Self> {
|
|
||||||
let mut endianess = Vec::with_capacity(2);
|
let mut endianess = Vec::with_capacity(2);
|
||||||
(&mut r).take(2).read_to_end(&mut endianess)?;
|
(&mut r).take(2).read_to_end(&mut endianess)?;
|
||||||
let byte_order = match &*endianess {
|
let byte_order = match &*endianess {
|
||||||
@@ -734,7 +729,6 @@ impl TiffHeader {
|
|||||||
TiffVariant::Standard
|
TiffVariant::Standard
|
||||||
},
|
},
|
||||||
first_ifd: next_ifd,
|
first_ifd: next_ifd,
|
||||||
lenient,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -758,7 +752,7 @@ impl TiffHeader {
|
|||||||
seen_ifds: cycles::IfdCycles::new(),
|
seen_ifds: cycles::IfdCycles::new(),
|
||||||
image: None,
|
image: None,
|
||||||
scratch: Vec::new(),
|
scratch: Vec::new(),
|
||||||
lenient: self.lenient,
|
lenient: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -801,11 +795,6 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
Ok(header.open(r))
|
Ok(header.open(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_with_options(mut r: R, lenient: bool) -> TiffResult<Decoder<R>> {
|
|
||||||
let header = TiffHeader::parse_with_options(&mut r, lenient)?;
|
|
||||||
Ok(header.open(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a decoder and attempt to parse the first image directory.
|
/// Create a decoder and attempt to parse the first image directory.
|
||||||
///
|
///
|
||||||
/// To open a file without parsing any directory yet (for instance to retain a [`Decoder`] on
|
/// To open a file without parsing any directory yet (for instance to retain a [`Decoder`] on
|
||||||
@@ -846,6 +835,15 @@ impl<R: Read + Seek> Decoder<R> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use a lenient decoder.
|
||||||
|
///
|
||||||
|
/// Configuring this make the parser respect libtiff's behavior to maintain compatibility with
|
||||||
|
/// some older softwares (e.g. some old Adobe suite products).
|
||||||
|
pub fn with_lenient(mut self, lenient: bool) -> Decoder<R> {
|
||||||
|
self.lenient = lenient;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: this tuple would be a good candidate for a proxy, like IfdDecoder. That has been
|
// FIXME: this tuple would be a good candidate for a proxy, like IfdDecoder. That has been
|
||||||
// requested in some form or another in a number of issues.
|
// requested in some form or another in a number of issues.
|
||||||
#[expect(clippy::unnecessary_unwrap)] // Lifetimes would overlap due to return.
|
#[expect(clippy::unnecessary_unwrap)] // Lifetimes would overlap due to return.
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ macro_rules! test_image_sum {
|
|||||||
fn $name(file: &str, expected_type: ColorType, expected_sum: $sum_ty) {
|
fn $name(file: &str, expected_type: ColorType, expected_sum: $sum_ty) {
|
||||||
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
let path = PathBuf::from(TEST_IMAGE_DIR).join(file);
|
||||||
let img_file = File::open(path).expect("Cannot find test image!");
|
let img_file = File::open(path).expect("Cannot find test image!");
|
||||||
let mut decoder =
|
let mut decoder = Decoder::open(img_file)
|
||||||
Decoder::open_with_options(img_file, true).expect("Cannot create decoder");
|
.expect("Cannot create decoder")
|
||||||
|
.with_lenient(true);
|
||||||
|
|
||||||
decoder.next_image().expect("Cannot read image IFD");
|
decoder.next_image().expect("Cannot read image IFD");
|
||||||
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
assert_eq!(decoder.colortype().unwrap(), expected_type);
|
||||||
|
|||||||
Reference in new issue
Block a user