fix: resolve comments

This commit is contained in:
陈启 committed 2026-07-03 17:19:48 +08:00
1 parent c7a1176381
commit 1c6001ea5d
5 files changed
+18 -251

No files matched your search

-1
View File
@@ -3,4 +3,3 @@
**/*.rs.bk
Cargo.lock
tests/benches/corpus/
.DS_Store
+4 -2
View File
@@ -2,13 +2,15 @@ use tiff::decoder::{BufferLayoutPreference, Decoder, DecodingSampleBuffer, Decod
fn main() -> Result<(), Box<dyn std::error::Error>> {
let Some(image) = std::env::args_os().nth(1) else {
eprintln!("Usage: decode FILE");
eprintln!("Usage: decode FILE [--lenient]");
return Ok(());
};
let lenient = std::env::args().nth(2).as_deref() == Some("--lenient");
let file = std::fs::File::open(image)?;
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![]);
-233
View File
@@ -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
View File
@@ -227,7 +227,6 @@ pub struct TiffHeader {
pub byte_order: ByteOrder,
pub variant: TiffVariant,
pub first_ifd: IfdPointer,
lenient: bool,
}
/// The representation of a TIFF decoder
@@ -677,11 +676,7 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) {
}
impl TiffHeader {
pub fn parse<R: Read + Seek>(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> {
pub fn parse<R: Read + Seek>(mut r: R) -> TiffResult<Self> {
let mut endianess = Vec::with_capacity(2);
(&mut r).take(2).read_to_end(&mut endianess)?;
let byte_order = match &*endianess {
@@ -734,7 +729,6 @@ impl TiffHeader {
TiffVariant::Standard
},
first_ifd: next_ifd,
lenient,
})
}
@@ -758,7 +752,7 @@ impl TiffHeader {
seen_ifds: cycles::IfdCycles::new(),
image: None,
scratch: Vec::new(),
lenient: self.lenient,
lenient: false,
}
}
}
@@ -801,11 +795,6 @@ impl<R: Read + Seek> Decoder<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.
///
/// 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
}
/// 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
// requested in some form or another in a number of issues.
#[expect(clippy::unnecessary_unwrap)] // Lifetimes would overlap due to return.
+3 -2
View File
@@ -16,8 +16,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::open_with_options(img_file, true).expect("Cannot create decoder");
let mut decoder = Decoder::open(img_file)
.expect("Cannot create decoder")
.with_lenient(true);
decoder.next_image().expect("Cannot read image IFD");
assert_eq!(decoder.colortype().unwrap(), expected_type);