Skip to main content

rawshift_image/tiff/
types.rs

1//! TIFF data types and value representations.
2//!
3//! This module defines the fundamental types used in TIFF files:
4//! - Byte ordering (Little/Big Endian)
5//! - The 12 standard TIFF data types
6//! - Rational number types
7//! - Value containers for parsed tag values
8
9use binrw::{BinRead, BinWrite};
10use std::fmt;
11
12/// Byte order marker for TIFF files.
13///
14/// TIFF files begin with either "II" (Intel, little-endian) or "MM" (Motorola, big-endian).
15#[derive(Debug, Clone, Copy, PartialEq, Eq, BinRead, BinWrite)]
16pub enum ByteOrder {
17    /// Little-endian byte order (Intel, "II")
18    #[brw(magic = b"II")]
19    LittleEndian,
20    /// Big-endian byte order (Motorola, "MM")
21    #[brw(magic = b"MM")]
22    BigEndian,
23}
24
25impl ByteOrder {
26    /// Parse byte order from the first two bytes of a TIFF file.
27    pub fn from_bytes(bytes: [u8; 2]) -> Option<Self> {
28        match &bytes {
29            b"II" => Some(ByteOrder::LittleEndian),
30            b"MM" => Some(ByteOrder::BigEndian),
31            _ => None,
32        }
33    }
34
35    /// Returns the two-byte marker for this byte order.
36    pub fn to_bytes(self) -> [u8; 2] {
37        match self {
38            ByteOrder::LittleEndian => *b"II",
39            ByteOrder::BigEndian => *b"MM",
40        }
41    }
42
43    /// Returns the string representation ("LE" or "BE") for annotations.
44    pub fn as_str(&self) -> &'static str {
45        match self {
46            ByteOrder::LittleEndian => "LE",
47            ByteOrder::BigEndian => "BE",
48        }
49    }
50}
51
52impl fmt::Display for ByteOrder {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            ByteOrder::LittleEndian => write!(f, "Little-Endian (II)"),
56            ByteOrder::BigEndian => write!(f, "Big-Endian (MM)"),
57        }
58    }
59}
60
61/// TIFF data type codes.
62///
63/// These are the 12 standard data types defined by the TIFF specification,
64/// plus IFD/IFD8 for pointer types.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, BinRead, BinWrite)]
66#[brw(repr = u16)]
67#[repr(u16)]
68pub enum TiffType {
69    /// 8-bit unsigned integer
70    Byte = 1,
71    /// 8-bit byte containing ASCII character (null-terminated)
72    Ascii = 2,
73    /// 16-bit unsigned integer
74    Short = 3,
75    /// 32-bit unsigned integer
76    Long = 4,
77    /// Two LONGs: numerator and denominator
78    Rational = 5,
79    /// 8-bit signed integer
80    SByte = 6,
81    /// 8-bit byte that may contain anything
82    Undefined = 7,
83    /// 16-bit signed integer
84    SShort = 8,
85    /// 32-bit signed integer
86    SLong = 9,
87    /// Two SLONGs: numerator and denominator
88    SRational = 10,
89    /// Single precision IEEE floating point (4 bytes)
90    Float = 11,
91    /// Double precision IEEE floating point (8 bytes)
92    Double = 12,
93    /// 32-bit unsigned integer offset to IFD
94    Ifd = 13,
95    /// 64-bit unsigned integer (BigTIFF)
96    Long8 = 16,
97    /// 64-bit signed integer (BigTIFF)
98    SLong8 = 17,
99    /// 64-bit unsigned integer offset to IFD (BigTIFF)
100    Ifd8 = 18,
101}
102
103impl TiffType {
104    /// Parse a type code from u16, returning None for unknown types.
105    pub fn from_u16(value: u16) -> Option<Self> {
106        match value {
107            1 => Some(TiffType::Byte),
108            2 => Some(TiffType::Ascii),
109            3 => Some(TiffType::Short),
110            4 => Some(TiffType::Long),
111            5 => Some(TiffType::Rational),
112            6 => Some(TiffType::SByte),
113            7 => Some(TiffType::Undefined),
114            8 => Some(TiffType::SShort),
115            9 => Some(TiffType::SLong),
116            10 => Some(TiffType::SRational),
117            11 => Some(TiffType::Float),
118            12 => Some(TiffType::Double),
119            13 => Some(TiffType::Ifd),
120            16 => Some(TiffType::Long8),
121            17 => Some(TiffType::SLong8),
122            18 => Some(TiffType::Ifd8),
123            _ => None,
124        }
125    }
126
127    /// Returns the size in bytes of a single element of this type.
128    pub fn size(&self) -> usize {
129        match self {
130            TiffType::Byte | TiffType::Ascii | TiffType::SByte | TiffType::Undefined => 1,
131            TiffType::Short | TiffType::SShort => 2,
132            TiffType::Long | TiffType::SLong | TiffType::Float | TiffType::Ifd => 4,
133            TiffType::Rational
134            | TiffType::SRational
135            | TiffType::Double
136            | TiffType::Long8
137            | TiffType::SLong8
138            | TiffType::Ifd8 => 8,
139        }
140    }
141
142    /// Returns whether a value of this type with the given count fits inline in an IFD entry.
143    ///
144    /// Standard TIFF IFD entries have 4 bytes for the value/offset field.
145    /// If the total data size fits in 4 bytes, it's stored inline.
146    pub fn fits_inline(&self, count: u32) -> bool {
147        self.size() * (count as usize) <= 4
148    }
149
150    /// Returns whether a value of this type with the given count fits inline in a BigTIFF entry.
151    ///
152    /// BigTIFF IFD entries have 8 bytes for the value/offset field.
153    pub fn fits_inline_bigtiff(&self, count: u64) -> bool {
154        self.size() * (count as usize) <= 8
155    }
156}
157
158/// Unsigned rational number (two 32-bit unsigned integers).
159#[derive(Debug, Clone, Copy, PartialEq, Eq, BinRead, BinWrite)]
160#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
161pub struct Rational {
162    /// Numerator
163    pub numerator: u32,
164    /// Denominator
165    pub denominator: u32,
166}
167
168impl Rational {
169    /// Create a new Rational.
170    pub fn new(numerator: u32, denominator: u32) -> Self {
171        Self {
172            numerator,
173            denominator,
174        }
175    }
176
177    /// Convert to f64, returning infinity if denominator is zero.
178    pub fn to_f64(&self) -> f64 {
179        if self.denominator == 0 {
180            if self.numerator == 0 {
181                f64::NAN
182            } else {
183                f64::INFINITY
184            }
185        } else {
186            self.numerator as f64 / self.denominator as f64
187        }
188    }
189}
190
191impl From<(u32, u32)> for Rational {
192    fn from((numerator, denominator): (u32, u32)) -> Self {
193        Self {
194            numerator,
195            denominator,
196        }
197    }
198}
199
200impl fmt::Display for Rational {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        write!(f, "{}/{}", self.numerator, self.denominator)
203    }
204}
205
206/// Signed rational number (two 32-bit signed integers).
207#[derive(Debug, Clone, Copy, PartialEq, Eq, BinRead, BinWrite)]
208#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
209pub struct SRational {
210    /// Numerator
211    pub numerator: i32,
212    /// Denominator
213    pub denominator: i32,
214}
215
216impl SRational {
217    /// Create a new SRational.
218    pub fn new(numerator: i32, denominator: i32) -> Self {
219        Self {
220            numerator,
221            denominator,
222        }
223    }
224
225    /// Convert to f64, returning infinity if denominator is zero.
226    pub fn to_f64(&self) -> f64 {
227        if self.denominator == 0 {
228            if self.numerator == 0 {
229                f64::NAN
230            } else if self.numerator > 0 {
231                f64::INFINITY
232            } else {
233                f64::NEG_INFINITY
234            }
235        } else {
236            self.numerator as f64 / self.denominator as f64
237        }
238    }
239}
240
241impl From<(i32, i32)> for SRational {
242    fn from((numerator, denominator): (i32, i32)) -> Self {
243        Self {
244            numerator,
245            denominator,
246        }
247    }
248}
249
250impl fmt::Display for SRational {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        write!(f, "{}/{}", self.numerator, self.denominator)
253    }
254}
255
256// ── Conversions to the format-agnostic metadata rationals ────────────────────
257// The wire-level TIFF rationals carry binrw derives and cannot live in
258// `rawshift-core`; these `From` impls bridge them to the core metadata model.
259
260impl From<Rational> for rawshift_core::metadata::URational {
261    fn from(r: Rational) -> Self {
262        Self::new(r.numerator, r.denominator)
263    }
264}
265
266impl From<SRational> for rawshift_core::metadata::SRational {
267    fn from(r: SRational) -> Self {
268        Self::new(r.numerator, r.denominator)
269    }
270}
271
272/// Container for parsed TIFF tag values.
273///
274/// This enum holds the actual parsed data for a tag value,
275/// with variants for each possible TIFF data type.
276#[derive(Debug, Clone, PartialEq)]
277pub enum TiffValue {
278    /// BYTE values (u8)
279    Bytes(Vec<u8>),
280    /// ASCII string (without null terminator)
281    Ascii(String),
282    /// SHORT values (u16)
283    Shorts(Vec<u16>),
284    /// LONG values (u32)
285    Longs(Vec<u32>),
286    /// RATIONAL values
287    Rationals(Vec<Rational>),
288    /// SBYTE values (i8)
289    SBytes(Vec<i8>),
290    /// UNDEFINED values (raw bytes)
291    Undefined(Vec<u8>),
292    /// SSHORT values (i16)
293    SShorts(Vec<i16>),
294    /// SLONG values (i32)
295    SLongs(Vec<i32>),
296    /// SRATIONAL values
297    SRationals(Vec<SRational>),
298    /// FLOAT values (f32)
299    Floats(Vec<f32>),
300    /// DOUBLE values (f64)
301    Doubles(Vec<f64>),
302    /// LONG8 values (u64, BigTIFF)
303    Long8s(Vec<u64>),
304    /// SLONG8 values (i64, BigTIFF)
305    SLong8s(Vec<i64>),
306}
307
308impl TiffValue {
309    /// Get as a single u32 value, if applicable.
310    pub fn as_u32(&self) -> Option<u32> {
311        match self {
312            TiffValue::Bytes(v) if v.len() == 1 => Some(v[0] as u32),
313            TiffValue::Shorts(v) if v.len() == 1 => Some(v[0] as u32),
314            TiffValue::Longs(v) if v.len() == 1 => Some(v[0]),
315            _ => None,
316        }
317    }
318
319    /// Get the first element as u32, useful for array tags like BitsPerSample.
320    pub fn first_u32(&self) -> Option<u32> {
321        match self {
322            TiffValue::Bytes(v) if !v.is_empty() => Some(v[0] as u32),
323            TiffValue::Shorts(v) if !v.is_empty() => Some(v[0] as u32),
324            TiffValue::Longs(v) if !v.is_empty() => Some(v[0]),
325            _ => None,
326        }
327    }
328
329    /// Get as a Vec<u32>, coercing numeric types if possible.
330    pub fn as_u32_vec(&self) -> Option<Vec<u32>> {
331        match self {
332            TiffValue::Bytes(v) => Some(v.iter().map(|&x| x as u32).collect()),
333            TiffValue::Shorts(v) => Some(v.iter().map(|&x| x as u32).collect()),
334            TiffValue::Longs(v) => Some(v.clone()),
335            _ => None,
336        }
337    }
338
339    /// Get as a single u64 value, if applicable.
340    pub fn as_u64(&self) -> Option<u64> {
341        match self {
342            TiffValue::Bytes(v) if v.len() == 1 => Some(v[0] as u64),
343            TiffValue::Shorts(v) if v.len() == 1 => Some(v[0] as u64),
344            TiffValue::Longs(v) if v.len() == 1 => Some(v[0] as u64),
345            TiffValue::Long8s(v) if v.len() == 1 => Some(v[0]),
346            _ => None,
347        }
348    }
349
350    /// Get as a Vec<u64>, coercing numeric types if possible.
351    pub fn as_u64_vec(&self) -> Option<Vec<u64>> {
352        match self {
353            TiffValue::Bytes(v) => Some(v.iter().map(|&x| x as u64).collect()),
354            TiffValue::Shorts(v) => Some(v.iter().map(|&x| x as u64).collect()),
355            TiffValue::Longs(v) => Some(v.iter().map(|&x| x as u64).collect()),
356            TiffValue::Long8s(v) => Some(v.clone()),
357            _ => None,
358        }
359    }
360
361    /// Get as a Vec<f64>, coercing numeric types if possible.
362    /// Supports Rationals, SRationals, Floats, Doubles, and integer types.
363    pub fn as_f64_vec(&self) -> Option<Vec<f64>> {
364        match self {
365            TiffValue::Bytes(v) => Some(v.iter().map(|&x| x as f64).collect()),
366            TiffValue::Shorts(v) => Some(v.iter().map(|&x| x as f64).collect()),
367            TiffValue::Longs(v) => Some(v.iter().map(|&x| x as f64).collect()),
368            TiffValue::Rationals(v) => Some(v.iter().map(|r| r.to_f64()).collect()),
369            TiffValue::SRationals(v) => Some(v.iter().map(|r| r.to_f64()).collect()),
370            TiffValue::Floats(v) => Some(v.iter().map(|&x| x as f64).collect()),
371            TiffValue::Doubles(v) => Some(v.clone()),
372            _ => None,
373        }
374    }
375
376    /// Get as string, if this is an ASCII value.
377    pub fn as_str(&self) -> Option<&str> {
378        match self {
379            TiffValue::Ascii(s) => Some(s.as_str()),
380            _ => None,
381        }
382    }
383
384    /// Get the number of elements in this value.
385    pub fn len(&self) -> usize {
386        match self {
387            TiffValue::Bytes(v) | TiffValue::Undefined(v) => v.len(),
388            TiffValue::Ascii(s) => s.len() + 1, // Include null terminator
389            TiffValue::Shorts(v) => v.len(),
390            TiffValue::Longs(v) => v.len(),
391            TiffValue::Rationals(v) => v.len(),
392            TiffValue::SBytes(v) => v.len(),
393            TiffValue::SShorts(v) => v.len(),
394            TiffValue::SLongs(v) => v.len(),
395            TiffValue::SRationals(v) => v.len(),
396            TiffValue::Floats(v) => v.len(),
397            TiffValue::Doubles(v) => v.len(),
398            TiffValue::Long8s(v) => v.len(),
399            TiffValue::SLong8s(v) => v.len(),
400        }
401    }
402
403    /// Check if the value is empty.
404    pub fn is_empty(&self) -> bool {
405        self.len() == 0
406    }
407}
408
409#[cfg(test)]
410mod tests {
411    use super::*;
412
413    #[test]
414    fn test_byte_order_from_bytes() {
415        assert_eq!(ByteOrder::from_bytes(*b"II"), Some(ByteOrder::LittleEndian));
416        assert_eq!(ByteOrder::from_bytes(*b"MM"), Some(ByteOrder::BigEndian));
417        assert_eq!(ByteOrder::from_bytes(*b"XX"), None);
418    }
419
420    #[test]
421    fn test_byte_order_to_bytes() {
422        assert_eq!(ByteOrder::LittleEndian.to_bytes(), *b"II");
423        assert_eq!(ByteOrder::BigEndian.to_bytes(), *b"MM");
424    }
425
426    #[test]
427    fn test_byte_order_as_str() {
428        assert_eq!(ByteOrder::LittleEndian.as_str(), "LE");
429        assert_eq!(ByteOrder::BigEndian.as_str(), "BE");
430    }
431
432    #[test]
433    fn test_tiff_type_size() {
434        assert_eq!(TiffType::Byte.size(), 1);
435        assert_eq!(TiffType::Ascii.size(), 1);
436        assert_eq!(TiffType::Short.size(), 2);
437        assert_eq!(TiffType::Long.size(), 4);
438        assert_eq!(TiffType::Rational.size(), 8);
439        assert_eq!(TiffType::Double.size(), 8);
440        assert_eq!(TiffType::Long8.size(), 8);
441    }
442
443    #[test]
444    fn test_tiff_type_fits_inline() {
445        // 4 bytes or less fit inline
446        assert!(TiffType::Byte.fits_inline(4));
447        assert!(TiffType::Byte.fits_inline(1));
448        assert!(!TiffType::Byte.fits_inline(5));
449
450        assert!(TiffType::Short.fits_inline(2));
451        assert!(!TiffType::Short.fits_inline(3));
452
453        assert!(TiffType::Long.fits_inline(1));
454        assert!(!TiffType::Long.fits_inline(2));
455
456        // Rational (8 bytes) never fits in 4
457        assert!(!TiffType::Rational.fits_inline(1));
458    }
459
460    #[test]
461    fn test_rational_to_f64() {
462        let r = Rational::new(1, 2);
463        assert_eq!(r.to_f64(), 0.5);
464
465        let r = Rational::new(0, 0);
466        assert!(r.to_f64().is_nan());
467
468        let r = Rational::new(1, 0);
469        assert!(r.to_f64().is_infinite());
470    }
471
472    #[test]
473    fn test_srational_to_f64() {
474        let r = SRational::new(-1, 2);
475        assert_eq!(r.to_f64(), -0.5);
476
477        let r = SRational::new(-1, 0);
478        assert_eq!(r.to_f64(), f64::NEG_INFINITY);
479    }
480
481    #[test]
482    fn test_tiff_value_as_u32() {
483        let v = TiffValue::Shorts(vec![42]);
484        assert_eq!(v.as_u32(), Some(42));
485
486        let v = TiffValue::Longs(vec![1000]);
487        assert_eq!(v.as_u32(), Some(1000));
488
489        let v = TiffValue::Longs(vec![1, 2]);
490        assert_eq!(v.as_u32(), None); // More than one element
491    }
492
493    #[test]
494    fn test_tiff_value_as_u64() {
495        // Short variant
496        let v = TiffValue::Shorts(vec![255]);
497        assert_eq!(v.as_u64(), Some(255u64));
498
499        // Long variant
500        let v = TiffValue::Longs(vec![100_000]);
501        assert_eq!(v.as_u64(), Some(100_000u64));
502
503        // Long8 variant
504        let v = TiffValue::Long8s(vec![u64::MAX]);
505        assert_eq!(v.as_u64(), Some(u64::MAX));
506
507        // More than one element → None
508        let v = TiffValue::Shorts(vec![1, 2]);
509        assert_eq!(v.as_u64(), None);
510
511        // Unrelated variant → None
512        let v = TiffValue::Ascii("hello".to_string());
513        assert_eq!(v.as_u64(), None);
514    }
515
516    #[test]
517    fn test_tiff_value_as_string() {
518        let v = TiffValue::Ascii("Canon".to_string());
519        assert_eq!(v.as_str(), Some("Canon"));
520
521        // Non-ASCII variants return None
522        let v = TiffValue::Shorts(vec![1]);
523        assert_eq!(v.as_str(), None);
524
525        let v = TiffValue::Bytes(vec![65, 66]);
526        assert_eq!(v.as_str(), None);
527    }
528}