From 99b3c03ce3a2cc4507851248b88469e28a67bad5 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:09:32 +0100 Subject: [PATCH] Use bulk byte order conversion for tag reads --- src/decoder/ifd.rs | 261 ++++++++++++++++++++++++++++++++------------- src/decoder/mod.rs | 39 +++---- src/tags.rs | 59 ++++++++++ 3 files changed, 260 insertions(+), 99 deletions(-) diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 99f58ed..6e336a2 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -556,74 +556,149 @@ impl Entry { } // Case 4: there is more than one value, and it doesn't fit in the offset field. + let mut v; + self.set_reader_offset(bo, bigtiff, reader)?; + match self.type_ { - // TODO check if this could give wrong results - // at a different endianess of file/computer. - Type::BYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - let mut buf = [0; 1]; - reader.inner().read_exact(&mut buf)?; - Ok(Byte(buf[0])) - }), - Type::SBYTE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(SignedByte(reader.read_i8()?)) - }), - Type::SHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(Short(reader.read_u16()?)) - }), - Type::SSHORT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(SignedShort(reader.read_i16()?)) - }), - Type::LONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(Unsigned(reader.read_u32()?)) - }), - Type::SLONG => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(Signed(reader.read_i32()?)) - }), - Type::FLOAT => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(Float(reader.read_f32()?)) - }), - Type::DOUBLE => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(Double(reader.read_f64()?)) - }), + Type::BYTE | Type::UNDEFINED => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend(bytes.iter().copied().map(Byte)) + }) + } + Type::SBYTE => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend(bytes.iter().copied().map(|v| SignedByte(v as i8))) + }) + } + Type::SHORT => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend( + bytes + .chunks_exact(2) + .map(|ch| Short(u16::from_ne_bytes(ch.try_into().unwrap()))), + ) + }) + } + Type::SSHORT => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend( + bytes + .chunks_exact(2) + .map(|ch| SignedShort(i16::from_ne_bytes(ch.try_into().unwrap()))), + ) + }) + } + Type::LONG => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend( + bytes + .chunks_exact(4) + .map(|ch| Unsigned(u32::from_ne_bytes(ch.try_into().unwrap()))), + ) + }) + } + Type::SLONG => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend( + bytes + .chunks_exact(4) + .map(|ch| Signed(i32::from_ne_bytes(ch.try_into().unwrap()))), + ) + }) + } + Type::FLOAT => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend( + bytes + .chunks_exact(4) + .map(|ch| Float(f32::from_ne_bytes(ch.try_into().unwrap()))), + ) + }) + } + Type::DOUBLE => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend( + bytes + .chunks_exact(8) + .map(|ch| Double(f64::from_ne_bytes(ch.try_into().unwrap()))), + ) + }) + } Type::RATIONAL => { - self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(Rational(reader.read_u32()?, reader.read_u32()?)) + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend(bytes.chunks_exact(8).map(|ch| { + Rational( + u32::from_ne_bytes(ch[..4].try_into().unwrap()), + u32::from_ne_bytes(ch[4..].try_into().unwrap()), + ) + })) }) } Type::SRATIONAL => { - self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(SRational(reader.read_i32()?, reader.read_i32()?)) + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend(bytes.chunks_exact(8).map(|ch| { + SRational( + i32::from_ne_bytes(ch[..4].try_into().unwrap()), + i32::from_ne_bytes(ch[4..].try_into().unwrap()), + ) + })) }) } - Type::LONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(UnsignedBig(reader.read_u64()?)) - }), - Type::SLONG8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(SignedBig(reader.read_i64()?)) - }), - Type::IFD => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(Ifd(reader.read_u32()?)) - }), - Type::IFD8 => self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - Ok(IfdBig(reader.read_u64()?)) - }), - Type::UNDEFINED => { - self.decode_offset(self.count, bo, bigtiff, limits, reader, |reader| { - let mut buf = [0; 1]; - reader.inner().read_exact(&mut buf)?; - Ok(Byte(buf[0])) + Type::LONG8 => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend( + bytes + .chunks_exact(8) + .map(|ch| UnsignedBig(u64::from_ne_bytes(ch.try_into().unwrap()))), + ) + }) + } + Type::SLONG8 => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend( + bytes + .chunks_exact(8) + .map(|ch| SignedBig(i64::from_ne_bytes(ch.try_into().unwrap()))), + ) + }) + } + Type::IFD => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend( + bytes + .chunks_exact(4) + .map(|ch| Ifd(u32::from_ne_bytes(ch.try_into().unwrap()))), + ) + }) + } + Type::IFD8 => { + v = Self::vec_with_capacity(self.count, limits)?; + self.decode_values(self.count, self.type_, reader, |bytes| { + v.extend( + bytes + .chunks_exact(8) + .map(|ch| IfdBig(u64::from_ne_bytes(ch.try_into().unwrap()))), + ) }) } Type::ASCII => { let n = usize::try_from(self.count)?; - if n > limits.decoding_buffer_size { - return Err(TiffError::LimitsExceeded); - } - if bigtiff { - reader.goto_offset(self.offset_field_reader(bo).read_u64()?)? - } else { - reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())? + if n > limits.decoding_buffer_size { + return Err(dbg!(TiffError::LimitsExceeded)); } let mut out = vec![0; n]; @@ -632,43 +707,81 @@ impl Entry { if let Some(first) = out.iter().position(|&b| b == 0) { out.truncate(first); } - Ok(Ascii(String::from_utf8(out)?)) + + return Ok(Ascii(String::from_utf8(out)?)); } - } + }?; + + Ok(List(v)) } - #[inline] - fn decode_offset( - &self, + fn vec_with_capacity( value_count: u64, - bo: ByteOrder, - bigtiff: bool, limits: &super::Limits, - reader: &mut EndianReader, - decode_fn: F, - ) -> TiffResult - where - R: Read + Seek, - F: Fn(&mut EndianReader) -> TiffResult, - { + ) -> Result, TiffError> { let value_count = usize::try_from(value_count)?; + if value_count > limits.decoding_buffer_size / mem::size_of::() { return Err(TiffError::LimitsExceeded); } - let mut v = Vec::with_capacity(value_count); + Ok(Vec::with_capacity(value_count)) + } + fn set_reader_offset( + &self, + bo: ByteOrder, + bigtiff: bool, + reader: &mut EndianReader, + ) -> TiffResult<()> + where + R: Read + Seek, + { let offset = if bigtiff { self.offset_field_reader(bo).read_u64()? } else { self.offset_field_reader(bo).read_u32()?.into() }; + reader.goto_offset(offset)?; - for _ in 0..value_count { - v.push(decode_fn(reader)?) + Ok(()) + } + + #[inline] + fn decode_values( + &self, + value_count: u64, + type_: Type, + reader: &mut EndianReader, + mut collect: F, + ) -> TiffResult<()> + where + R: Read + Seek, + F: FnMut(&[u8]), + { + let mut total_bytes = type_.value_bytes(value_count)?; + let mut buffer = [0u8; 512]; + + let buf_unit = usize::from(type_.byte_len()); + let mul_of_ty = buffer.len() / buf_unit * buf_unit; + + let cls = type_.byte_order_class(); + let native = ByteOrder::native(); + + while total_bytes > 0 { + // `now <= mul_of_ty < 512` so casting is mathematical + let now = total_bytes.min(mul_of_ty as u64); + total_bytes -= now; + + let buffer = &mut buffer[..now as usize]; + reader.inner().read_exact(buffer)?; + + reader.byte_order.convert_class(cls, buffer, native); + collect(buffer); } - Ok(List(v)) + + Ok(()) } } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index d4db1c2..bad9a39 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -535,32 +535,16 @@ fn invert_colors( /// Fix endianness. If `byte_order` matches the host, then conversion is a no-op. fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) { - match byte_order { - ByteOrder::LittleEndian => match bit_depth { - 0..=8 => {} - 9..=16 => buf.chunks_exact_mut(2).for_each(|v| { - v.copy_from_slice(&u16::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes()) - }), - 17..=32 => buf.chunks_exact_mut(4).for_each(|v| { - v.copy_from_slice(&u32::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes()) - }), - _ => buf.chunks_exact_mut(8).for_each(|v| { - v.copy_from_slice(&u64::from_le_bytes((*v).try_into().unwrap()).to_ne_bytes()) - }), - }, - ByteOrder::BigEndian => match bit_depth { - 0..=8 => {} - 9..=16 => buf.chunks_exact_mut(2).for_each(|v| { - v.copy_from_slice(&u16::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes()) - }), - 17..=32 => buf.chunks_exact_mut(4).for_each(|v| { - v.copy_from_slice(&u32::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes()) - }), - _ => buf.chunks_exact_mut(8).for_each(|v| { - v.copy_from_slice(&u64::from_be_bytes((*v).try_into().unwrap()).to_ne_bytes()) - }), - }, + let host = ByteOrder::native(); + + let class = match bit_depth { + 0..=8 => crate::tags::ByteOrderClass::One, + 9..=16 => crate::tags::ByteOrderClass::Two, + 17..=32 => crate::tags::ByteOrderClass::Four, + _ => crate::tags::ByteOrderClass::Eight, }; + + host.convert_class(class, buf, byte_order); } impl Decoder { @@ -747,6 +731,11 @@ impl Decoder { } /// Returns the byte_order of the file. + /// + /// # Usage + /// + /// This is only relevant to interpreting raw bytes read from tags. The image decoding methods + /// will correct to the host byte order automatically. pub fn byte_order(&self) -> ByteOrder { self.value_reader.reader.byte_order } diff --git a/src/tags.rs b/src/tags.rs index 8592f0b..0c62b85 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -216,6 +216,20 @@ impl Type { None => Err(crate::error::TiffError::LimitsExceeded), } } + + pub(crate) fn byte_order_class(self) -> ByteOrderClass { + match self { + Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => ByteOrderClass::One, + Type::SHORT | Type::SSHORT => ByteOrderClass::Two, + Type::LONG + | Type::SLONG + | Type::FLOAT + | Type::IFD + | Type::RATIONAL + | Type::SRATIONAL => ByteOrderClass::Four, + Type::LONG8 | Type::SLONG8 | Type::DOUBLE | Type::IFD8 => ByteOrderClass::Eight, + } + } } tags! { @@ -322,4 +336,49 @@ impl ByteOrder { () => compile_error!("Unsupported target"), } } + + /// Given a typed buffer, convert its contents to the specified byte order in-place. + pub fn convert(self, ty: Type, buffer: &mut [u8], to: ByteOrder) { + self.convert_class(ty.byte_order_class(), buffer, to) + } + + pub(crate) fn convert_class(self, cls: ByteOrderClass, buffer: &mut [u8], to: ByteOrder) { + if self == to { + return; + } + + // FIXME: at MSRV 1.89 or higher use `slice::as_chunks_mut`. + match cls { + ByteOrderClass::One => { + // No change needed + } + ByteOrderClass::Two => { + for chunk in buffer.chunks_exact_mut(2) { + let chunk: &mut [u8; 2] = chunk.try_into().unwrap(); + *chunk = u16::from_be_bytes(*chunk).to_le_bytes(); + } + } + ByteOrderClass::Four => { + for chunk in buffer.chunks_exact_mut(4) { + let chunk: &mut [u8; 4] = chunk.try_into().unwrap(); + *chunk = u32::from_be_bytes(*chunk).to_le_bytes(); + } + } + ByteOrderClass::Eight => { + for chunk in buffer.chunks_exact_mut(8) { + let chunk: &mut [u8; 8] = chunk.try_into().unwrap(); + *chunk = u64::from_be_bytes(*chunk).to_le_bytes(); + } + } + } + } +} + +/// The size of individual byte-order corrected elements. +#[derive(Clone, Copy)] +pub(crate) enum ByteOrderClass { + One, + Two, + Four, + Eight, }