Skip to main content

async_tiff/
data_type.rs

1use crate::tags::SampleFormat;
2
3/// Supported numeric data types for array elements.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum DataType {
6    /// Unsigned 8-bit integer.
7    UInt8,
8    /// Unsigned 16-bit integer.
9    UInt16,
10    /// Unsigned 32-bit integer.
11    UInt32,
12    /// Unsigned 64-bit integer.
13    UInt64,
14    /// Signed 8-bit integer.
15    Int8,
16    /// Signed 16-bit integer.
17    Int16,
18    /// Signed 32-bit integer.
19    Int32,
20    /// Signed 64-bit integer.
21    Int64,
22    /// 32-bit floating point.
23    Float32,
24    /// 64-bit floating point.
25    Float64,
26}
27
28impl DataType {
29    /// The size in bytes of this data type.
30    ///
31    /// ```
32    /// use async_tiff::DataType;
33    ///
34    /// assert_eq!(DataType::UInt8.size(), 1);
35    /// assert_eq!(DataType::Int16.size(), 2);
36    /// assert_eq!(DataType::Float32.size(), 4);
37    /// assert_eq!(DataType::Float64.size(), 8);
38    /// ```
39    pub fn size(&self) -> usize {
40        match self {
41            DataType::UInt8 | DataType::Int8 => 1,
42            DataType::UInt16 | DataType::Int16 => 2,
43            DataType::UInt32 | DataType::Int32 | DataType::Float32 => 4,
44            DataType::UInt64 | DataType::Int64 | DataType::Float64 => 8,
45        }
46    }
47
48    /// Parse a DataType from TIFF IFD tags.
49    ///
50    /// Returns `None` if the combination of sample format and bits per sample
51    /// is not supported, or if the values are inconsistent across samples.
52    ///
53    /// # Arguments
54    /// * `sample_format` - The SampleFormat values from the TIFF IFD
55    /// * `bits_per_sample` - The BitsPerSample values from the TIFF IFD
56    pub(crate) fn from_tags(
57        sample_format: &[SampleFormat],
58        bits_per_sample: &[u16],
59    ) -> Option<Self> {
60        // All samples must have the same format and bit depth
61        let first_format = sample_format.first()?;
62        let first_bits = bits_per_sample.first()?;
63
64        // Check that all samples have consistent format and bit depth
65        if !sample_format.iter().all(|f| f == first_format) {
66            return None;
67        }
68        if !bits_per_sample.iter().all(|b| b == first_bits) {
69            return None;
70        }
71
72        match (first_format, first_bits) {
73            (SampleFormat::Uint, 8) => Some(DataType::UInt8),
74            (SampleFormat::Uint, 16) => Some(DataType::UInt16),
75            (SampleFormat::Uint, 32) => Some(DataType::UInt32),
76            (SampleFormat::Uint, 64) => Some(DataType::UInt64),
77            (SampleFormat::Int, 8) => Some(DataType::Int8),
78            (SampleFormat::Int, 16) => Some(DataType::Int16),
79            (SampleFormat::Int, 32) => Some(DataType::Int32),
80            (SampleFormat::Int, 64) => Some(DataType::Int64),
81            (SampleFormat::Float, 32) => Some(DataType::Float32),
82            (SampleFormat::Float, 64) => Some(DataType::Float64),
83            // Unsupported combinations (e.g., Void, Unknown, or unusual bit depths)
84            _ => None,
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_from_tags_uint_types() {
95        assert_eq!(
96            DataType::from_tags(&[SampleFormat::Uint], &[8]),
97            Some(DataType::UInt8),
98            "Uint 8-bit should be UInt8"
99        );
100        assert_eq!(
101            DataType::from_tags(&[SampleFormat::Uint], &[16]),
102            Some(DataType::UInt16),
103            "Uint 16-bit should be UInt16"
104        );
105        assert_eq!(
106            DataType::from_tags(&[SampleFormat::Uint], &[32]),
107            Some(DataType::UInt32),
108            "Uint 32-bit should be UInt32"
109        );
110        assert_eq!(
111            DataType::from_tags(&[SampleFormat::Uint], &[64]),
112            Some(DataType::UInt64),
113            "Uint 64-bit should be UInt64"
114        );
115    }
116
117    #[test]
118    fn test_from_tags_int_types() {
119        assert_eq!(
120            DataType::from_tags(&[SampleFormat::Int], &[8]),
121            Some(DataType::Int8),
122            "Int 8-bit should be Int8"
123        );
124        assert_eq!(
125            DataType::from_tags(&[SampleFormat::Int], &[16]),
126            Some(DataType::Int16),
127            "Int 16-bit should be Int16"
128        );
129        assert_eq!(
130            DataType::from_tags(&[SampleFormat::Int], &[32]),
131            Some(DataType::Int32),
132            "Int 32-bit should be Int32"
133        );
134        assert_eq!(
135            DataType::from_tags(&[SampleFormat::Int], &[64]),
136            Some(DataType::Int64),
137            "Int 64-bit should be Int64"
138        );
139    }
140
141    #[test]
142    fn test_from_tags_float_types() {
143        assert_eq!(
144            DataType::from_tags(&[SampleFormat::Float], &[32]),
145            Some(DataType::Float32),
146            "Float 32-bit should be Float32"
147        );
148        assert_eq!(
149            DataType::from_tags(&[SampleFormat::Float], &[64]),
150            Some(DataType::Float64),
151            "Float 64-bit should be Float64"
152        );
153    }
154
155    #[test]
156    fn test_from_tags_rgb_consistent() {
157        // RGB image with 3 samples, all UInt8
158        assert_eq!(
159            DataType::from_tags(
160                &[SampleFormat::Uint, SampleFormat::Uint, SampleFormat::Uint],
161                &[8, 8, 8]
162            ),
163            Some(DataType::UInt8),
164            "RGB with consistent UInt8 should succeed"
165        );
166
167        // RGB image with 3 samples, all UInt16
168        assert_eq!(
169            DataType::from_tags(
170                &[SampleFormat::Uint, SampleFormat::Uint, SampleFormat::Uint],
171                &[16, 16, 16]
172            ),
173            Some(DataType::UInt16),
174            "RGB with consistent UInt16 should succeed"
175        );
176    }
177
178    #[test]
179    fn test_from_tags_inconsistent_format() {
180        // Mixed formats should return None
181        assert_eq!(
182            DataType::from_tags(&[SampleFormat::Uint, SampleFormat::Int], &[8, 8]),
183            None,
184            "Inconsistent sample formats should return None"
185        );
186    }
187
188    #[test]
189    fn test_from_tags_inconsistent_bits() {
190        // Mixed bit depths should return None
191        assert_eq!(
192            DataType::from_tags(&[SampleFormat::Uint, SampleFormat::Uint], &[8, 16]),
193            None,
194            "Inconsistent bit depths should return None"
195        );
196    }
197
198    #[test]
199    fn test_from_tags_empty_arrays() {
200        assert_eq!(
201            DataType::from_tags(&[], &[]),
202            None,
203            "Empty arrays should return None"
204        );
205    }
206
207    #[test]
208    fn test_from_tags_unsupported_bit_depth() {
209        // Unsupported bit depth
210        assert_eq!(
211            DataType::from_tags(&[SampleFormat::Uint], &[12]),
212            None,
213            "Unsupported bit depth (12) should return None"
214        );
215        assert_eq!(
216            DataType::from_tags(&[SampleFormat::Uint], &[24]),
217            None,
218            "Unsupported bit depth (24) should return None"
219        );
220    }
221
222    #[test]
223    fn test_from_tags_unsupported_format() {
224        // Void format is not supported
225        assert_eq!(
226            DataType::from_tags(&[SampleFormat::Void], &[8]),
227            None,
228            "Void format should return None"
229        );
230
231        // Unknown format should also return None
232        assert_eq!(
233            DataType::from_tags(&[SampleFormat::Unknown(99)], &[8]),
234            None,
235            "Unknown format should return None"
236        );
237    }
238}