From 2058b7f3ca80814c03859902b3173cd6fedd7737 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Sun, 2 Nov 2025 14:46:58 +0100 Subject: [PATCH 1/5] Stand-alone size computation of Type --- src/decoder/ifd.rs | 81 +++++++++++++++++++--------------------------- src/tags.rs | 25 ++++++++++++++ 2 files changed, 58 insertions(+), 48 deletions(-) diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 530c4da..99f58ed 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -376,7 +376,10 @@ impl Entry { } /// Returns a mem_reader for the offset/value field - fn r(&self, byte_order: ByteOrder) -> EndianReader>> { + pub(crate) fn offset_field_reader( + &self, + byte_order: ByteOrder, + ) -> EndianReader>> { EndianReader::new(io::Cursor::new(self.offset.to_vec()), byte_order) } @@ -392,43 +395,25 @@ impl Entry { } let bo = reader.byte_order; - - let tag_size = match self.type_ { - Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, - Type::SHORT | Type::SSHORT => 2, - Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4, - Type::LONG8 - | Type::SLONG8 - | Type::DOUBLE - | Type::RATIONAL - | Type::SRATIONAL - | Type::IFD8 => 8, - }; - - let value_bytes = match self.count.checked_mul(tag_size) { - Some(n) => n, - None => { - return Err(TiffError::LimitsExceeded); - } - }; + let value_bytes = self.type_.value_bytes(self.count)?; // Case 2: there is one value. if self.count == 1 { // 2a: the value is 5-8 bytes and we're in BigTiff mode. if bigtiff && value_bytes > 4 && value_bytes <= 8 { return Ok(match self.type_ { - Type::LONG8 => UnsignedBig(self.r(bo).read_u64()?), - Type::SLONG8 => SignedBig(self.r(bo).read_i64()?), - Type::DOUBLE => Double(self.r(bo).read_f64()?), + Type::LONG8 => UnsignedBig(self.offset_field_reader(bo).read_u64()?), + Type::SLONG8 => SignedBig(self.offset_field_reader(bo).read_i64()?), + Type::DOUBLE => Double(self.offset_field_reader(bo).read_f64()?), Type::RATIONAL => { - let mut r = self.r(bo); + let mut r = self.offset_field_reader(bo); Rational(r.read_u32()?, r.read_u32()?) } Type::SRATIONAL => { - let mut r = self.r(bo); + let mut r = self.offset_field_reader(bo); SRational(r.read_i32()?, r.read_i32()?) } - Type::IFD8 => IfdBig(self.r(bo).read_u64()?), + Type::IFD8 => IfdBig(self.offset_field_reader(bo).read_u64()?), Type::BYTE | Type::SBYTE | Type::ASCII @@ -447,11 +432,11 @@ impl Entry { Type::BYTE => Byte(self.offset[0]), Type::SBYTE => SignedByte(self.offset[0] as i8), Type::UNDEFINED => Byte(self.offset[0]), - Type::SHORT => Short(self.r(bo).read_u16()?), - Type::SSHORT => SignedShort(self.r(bo).read_i16()?), - Type::LONG => Unsigned(self.r(bo).read_u32()?), - Type::SLONG => Signed(self.r(bo).read_i32()?), - Type::FLOAT => Float(self.r(bo).read_f32()?), + Type::SHORT => Short(self.offset_field_reader(bo).read_u16()?), + Type::SSHORT => SignedShort(self.offset_field_reader(bo).read_i16()?), + Type::LONG => Unsigned(self.offset_field_reader(bo).read_u32()?), + Type::SLONG => Signed(self.offset_field_reader(bo).read_i32()?), + Type::FLOAT => Float(self.offset_field_reader(bo).read_f32()?), Type::ASCII => { if self.offset[0] == 0 { Ascii("".to_string()) @@ -460,28 +445,28 @@ impl Entry { } } Type::LONG8 => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; + reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?; UnsignedBig(reader.read_u64()?) } Type::SLONG8 => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; + reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?; SignedBig(reader.read_i64()?) } Type::DOUBLE => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; + reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?; Double(reader.read_f64()?) } Type::RATIONAL => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; + reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?; Rational(reader.read_u32()?, reader.read_u32()?) } Type::SRATIONAL => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; + reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?; SRational(reader.read_i32()?, reader.read_i32()?) } - Type::IFD => Ifd(self.r(bo).read_u32()?), + Type::IFD => Ifd(self.offset_field_reader(bo).read_u32()?), Type::IFD8 => { - reader.goto_offset(self.r(bo).read_u32()?.into())?; + reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())?; IfdBig(reader.read_u64()?) } }); @@ -512,7 +497,7 @@ impl Entry { )); } Type::SHORT => { - let mut r = self.r(bo); + let mut r = self.offset_field_reader(bo); let mut v = Vec::new(); for _ in 0..self.count { v.push(Short(r.read_u16()?)); @@ -520,7 +505,7 @@ impl Entry { return Ok(List(v)); } Type::SSHORT => { - let mut r = self.r(bo); + let mut r = self.offset_field_reader(bo); let mut v = Vec::new(); for _ in 0..self.count { v.push(SignedShort(r.read_i16()?)); @@ -528,7 +513,7 @@ impl Entry { return Ok(List(v)); } Type::LONG => { - let mut r = self.r(bo); + let mut r = self.offset_field_reader(bo); let mut v = Vec::new(); for _ in 0..self.count { v.push(Unsigned(r.read_u32()?)); @@ -536,7 +521,7 @@ impl Entry { return Ok(List(v)); } Type::SLONG => { - let mut r = self.r(bo); + let mut r = self.offset_field_reader(bo); let mut v = Vec::new(); for _ in 0..self.count { v.push(Signed(r.read_i32()?)); @@ -544,7 +529,7 @@ impl Entry { return Ok(List(v)); } Type::FLOAT => { - let mut r = self.r(bo); + let mut r = self.offset_field_reader(bo); let mut v = Vec::new(); for _ in 0..self.count { v.push(Float(r.read_f32()?)); @@ -552,7 +537,7 @@ impl Entry { return Ok(List(v)); } Type::IFD => { - let mut r = self.r(bo); + let mut r = self.offset_field_reader(bo); let mut v = Vec::new(); for _ in 0..self.count { v.push(Ifd(r.read_u32()?)); @@ -636,9 +621,9 @@ impl Entry { } if bigtiff { - reader.goto_offset(self.r(bo).read_u64()?)? + reader.goto_offset(self.offset_field_reader(bo).read_u64()?)? } else { - reader.goto_offset(self.r(bo).read_u32()?.into())? + reader.goto_offset(self.offset_field_reader(bo).read_u32()?.into())? } let mut out = vec![0; n]; @@ -674,9 +659,9 @@ impl Entry { let mut v = Vec::with_capacity(value_count); let offset = if bigtiff { - self.r(bo).read_u64()? + self.offset_field_reader(bo).read_u64()? } else { - self.r(bo).read_u32()?.into() + self.offset_field_reader(bo).read_u32()?.into() }; reader.goto_offset(offset)?; diff --git a/src/tags.rs b/src/tags.rs index bbb39fb..f9634b2 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -193,6 +193,31 @@ pub enum Type(u16) { } } +impl Type { + pub(crate) fn byte_len(&self) -> u8 { + match *self { + Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, + Type::SHORT | Type::SSHORT => 2, + Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4, + Type::LONG8 + | Type::SLONG8 + | Type::DOUBLE + | Type::RATIONAL + | Type::SRATIONAL + | Type::IFD8 => 8, + } + } + + pub(crate) fn value_bytes(&self, count: u64) -> Result { + let tag_size = u64::from(self.byte_len()); + + match count.checked_mul(tag_size) { + Some(n) => Ok(n), + None => Err(crate::error::TiffError::LimitsExceeded), + } + } +} + tags! { /// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html) /// for reference. From 19ac30ac3fb62e8f5a3e1c4cfa78a4debcb16164 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Sun, 2 Nov 2025 14:51:28 +0100 Subject: [PATCH 2/5] Share ByteOrder in tags, publicly --- src/decoder/stream.rs | 9 +-------- src/tags.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index 6330969..c7781c2 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -1,14 +1,7 @@ //! All IO functionality needed for TIFF decoding use std::io::{self, BufRead, BufReader, Read, Seek, Take}; -/// Byte order of the TIFF file. -#[derive(Clone, Copy, Debug)] -pub enum ByteOrder { - /// little endian byte order - LittleEndian, - /// big endian byte order - BigEndian, -} +pub use crate::tags::ByteOrder; /// Reader that is aware of the byte order. #[derive(Debug)] diff --git a/src/tags.rs b/src/tags.rs index f9634b2..8592f0b 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -296,3 +296,30 @@ pub enum SampleFormat(u16) unknown( Void = 4, } } + +/// Byte order of the TIFF file. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ByteOrder { + /// little endian byte order + LittleEndian, + /// big endian byte order + BigEndian, +} + +impl ByteOrder { + /// Get the byte order representing the running target. + /// + /// The infallibility of this method represents the fact that only little and big endian + /// systems are supported by the library. No mixed endian and no other weird stuff. (Note: as + /// of Rust 1.90 this is a tautology as Rust itself only has those two kinds). + pub const fn native() -> Self { + match () { + #[cfg(target_endian = "little")] + () => ByteOrder::LittleEndian, + #[cfg(target_endian = "big")] + () => ByteOrder::BigEndian, + #[cfg(not(any(target_endian = "big", target_endian = "little")))] + () => compile_error!("Unsupported target"), + } + } +} 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 3/5] 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, } From 0a2b3f91ab571a47ba0fc92bb1ef0982e3295eb7 Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:55:29 +0100 Subject: [PATCH 4/5] Remove unused method in EndianReader --- src/decoder/stream.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/decoder/stream.rs b/src/decoder/stream.rs index c7781c2..70387df 100644 --- a/src/decoder/stream.rs +++ b/src/decoder/stream.rs @@ -38,17 +38,6 @@ impl EndianReader { }) } - /// Reads an i8 - #[inline(always)] - pub fn read_i8(&mut self) -> Result { - let mut n = [0u8; 1]; - self.reader.read_exact(&mut n)?; - Ok(match self.byte_order { - ByteOrder::LittleEndian => i8::from_le_bytes(n), - ByteOrder::BigEndian => i8::from_be_bytes(n), - }) - } - /// Reads an i16 #[inline(always)] pub fn read_i16(&mut self) -> Result { From 6cdf3d8d220e791ac60b64e1bfc67033ef156c1d Mon Sep 17 00:00:00 2001 From: "A. Molzer" <5550310+197g@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:17:05 +0100 Subject: [PATCH 5/5] Naming pass for byte order conversion --- src/decoder/ifd.rs | 4 ++-- src/decoder/mod.rs | 10 +++++----- src/tags.rs | 28 ++++++++++++++++------------ 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/decoder/ifd.rs b/src/decoder/ifd.rs index 6e336a2..46547b9 100644 --- a/src/decoder/ifd.rs +++ b/src/decoder/ifd.rs @@ -766,7 +766,7 @@ impl Entry { 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 cls = type_.endian_bytes(); let native = ByteOrder::native(); while total_bytes > 0 { @@ -777,7 +777,7 @@ impl Entry { let buffer = &mut buffer[..now as usize]; reader.inner().read_exact(buffer)?; - reader.byte_order.convert_class(cls, buffer, native); + reader.byte_order.convert_endian_bytes(cls, buffer, native); collect(buffer); } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index bad9a39..70df36c 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -538,13 +538,13 @@ fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u8) { 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, + 0..=8 => crate::tags::EndianBytes::One, + 9..=16 => crate::tags::EndianBytes::Two, + 17..=32 => crate::tags::EndianBytes::Four, + _ => crate::tags::EndianBytes::Eight, }; - host.convert_class(class, buf, byte_order); + host.convert_endian_bytes(class, buf, byte_order); } impl Decoder { diff --git a/src/tags.rs b/src/tags.rs index 0c62b85..2cc1344 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -217,17 +217,17 @@ impl Type { } } - pub(crate) fn byte_order_class(self) -> ByteOrderClass { + pub(crate) fn endian_bytes(self) -> EndianBytes { match self { - Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => ByteOrderClass::One, - Type::SHORT | Type::SSHORT => ByteOrderClass::Two, + Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => EndianBytes::One, + Type::SHORT | Type::SSHORT => EndianBytes::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, + | Type::SRATIONAL => EndianBytes::Four, + Type::LONG8 | Type::SLONG8 | Type::DOUBLE | Type::IFD8 => EndianBytes::Eight, } } } @@ -338,33 +338,37 @@ impl ByteOrder { } /// Given a typed buffer, convert its contents to the specified byte order in-place. + /// + /// The buffer is assumed to represent an array of the given type. If the length of the buffer + /// is not divisible into an integer number of values, the behavior for the remaining bytes it + /// not specified. pub fn convert(self, ty: Type, buffer: &mut [u8], to: ByteOrder) { - self.convert_class(ty.byte_order_class(), buffer, to) + self.convert_endian_bytes(ty.endian_bytes(), buffer, to) } - pub(crate) fn convert_class(self, cls: ByteOrderClass, buffer: &mut [u8], to: ByteOrder) { + pub(crate) fn convert_endian_bytes(self, cls: EndianBytes, 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 => { + EndianBytes::One => { // No change needed } - ByteOrderClass::Two => { + EndianBytes::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 => { + EndianBytes::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 => { + EndianBytes::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(); @@ -376,7 +380,7 @@ impl ByteOrder { /// The size of individual byte-order corrected elements. #[derive(Clone, Copy)] -pub(crate) enum ByteOrderClass { +pub(crate) enum EndianBytes { One, Two, Four,