Skip to main content

async_tiff/
array.rs

1use bytemuck::{cast_slice, cast_vec, try_cast_vec};
2
3use crate::data_type::DataType;
4use crate::error::{AsyncTiffError, AsyncTiffResult};
5
6/// A 3D array that represents decoded TIFF image data.
7#[derive(Debug, Clone)]
8pub struct Array {
9    /// The raw byte data of the array.
10    pub(crate) data: TypedArray,
11
12    /// The 3D shape of the array.
13    ///
14    /// The axis ordering depends on the PlanarConfiguration:
15    ///
16    /// - PlanarConfiguration=1 (chunky): (height, width, bands)
17    /// - PlanarConfiguration=2 (planar): (bands, height, width)
18    pub(crate) shape: [usize; 3],
19
20    /// The data type of the array elements.
21    ///
22    /// If None, the data type is unsupported or unknown.
23    pub(crate) data_type: Option<DataType>,
24}
25
26impl Array {
27    pub(crate) fn try_new(
28        data: Vec<u8>,
29        shape: [usize; 3],
30        data_type: Option<DataType>,
31    ) -> AsyncTiffResult<Self> {
32        let expected_len = shape[0] * shape[1] * shape[2];
33
34        let typed_data = if data_type == Some(DataType::Bool) {
35            let required_bytes = expected_len.div_ceil(8);
36            if data.len() < required_bytes {
37                return Err(AsyncTiffError::General(format!(
38                    "Bool data length {} is less than required {} bytes for {} elements",
39                    data.len(),
40                    required_bytes,
41                    expected_len
42                )));
43            }
44            TypedArray::Bool(expand_bitmask(&data, expected_len))
45        } else {
46            let typed_data = TypedArray::try_new(data, data_type)?;
47            if typed_data.len() != expected_len {
48                return Err(AsyncTiffError::General(format!(
49                    "Internal error: incorrect shape or data length passed to Array::try_new. Got data length {}, expected {}",
50                    typed_data.len(),
51                    expected_len
52                )));
53            }
54            typed_data
55        };
56
57        Ok(Self {
58            data: typed_data,
59            shape,
60            data_type,
61        })
62    }
63
64    /// Access the raw underlying byte data of the array.
65    pub fn data(&self) -> &TypedArray {
66        &self.data
67    }
68
69    /// Consume the Array and return its components.
70    pub fn into_inner(self) -> (TypedArray, [usize; 3], Option<DataType>) {
71        (self.data, self.shape, self.data_type)
72    }
73
74    /// Get the shape of the array.
75    ///
76    /// The shape matches the physical array data exposed, but the _interpretation_ depends on the
77    /// value of `PlanarConfiguration`:
78    ///
79    /// - PlanarConfiguration=1 (chunky): (height, width, bands)
80    /// - PlanarConfiguration=2 (planar): (bands, height, width)
81    pub fn shape(&self) -> [usize; 3] {
82        self.shape
83    }
84
85    /// The logical data type of the array elements.
86    ///
87    /// If None, the data type is unsupported or unknown.
88    pub fn data_type(&self) -> Option<DataType> {
89        self.data_type
90    }
91}
92
93/// An enum representing a typed view of the array data.
94///
95/// ```
96/// use async_tiff::{DataType, TypedArray};
97///
98/// let data = TypedArray::try_new(vec![10, 20, 30], Some(DataType::UInt8)).unwrap();
99/// match &data {
100///     TypedArray::UInt8(v) => assert_eq!(v, &[10, 20, 30]),
101///     _ => panic!("expected UInt8"),
102/// }
103///
104/// let bytes = std::f32::consts::PI.to_ne_bytes().to_vec();
105/// let data = TypedArray::try_new(bytes, Some(DataType::Float32)).unwrap();
106/// match &data {
107///     TypedArray::Float32(v) => assert_eq!(v[0], std::f32::consts::PI),
108///     _ => panic!("expected Float32"),
109/// }
110/// ```
111#[derive(Debug, Clone)]
112pub enum TypedArray {
113    /// Boolean mask array.
114    ///
115    /// Per TIFF spec, `true` = valid pixel, `false` = transparent/masked pixel.
116    Bool(Vec<bool>),
117    /// Unsigned 8-bit integer array.
118    UInt8(Vec<u8>),
119    /// Unsigned 16-bit integer array.
120    UInt16(Vec<u16>),
121    /// Unsigned 32-bit integer array.
122    UInt32(Vec<u32>),
123    /// Unsigned 64-bit integer array.
124    UInt64(Vec<u64>),
125    /// Signed 8-bit integer array.
126    Int8(Vec<i8>),
127    /// Signed 16-bit integer array.
128    Int16(Vec<i16>),
129    /// Signed 32-bit integer array.
130    Int32(Vec<i32>),
131    /// Signed 64-bit integer array.
132    Int64(Vec<i64>),
133    /// 32-bit floating point array.
134    Float32(Vec<f32>),
135    /// 64-bit floating point array.
136    Float64(Vec<f64>),
137}
138
139impl TypedArray {
140    /// Create a new TypedArray from raw byte data and a specified DataType.
141    ///
142    /// Returns an error if the data length is not divisible by the element size.
143    pub fn try_new(data: Vec<u8>, data_type: Option<DataType>) -> AsyncTiffResult<Self> {
144        match data_type {
145            None | Some(DataType::UInt8) => Ok(TypedArray::UInt8(data)),
146            Some(DataType::Bool) => {
147                // Bool requires knowing the element count for expansion.
148                // Construct Bool directly via Array::try_new.
149                Err(AsyncTiffError::General(
150                    "Bool must be constructed via Array::try_new".to_string(),
151                ))
152            }
153            Some(DataType::UInt16) => {
154                if !data.len().is_multiple_of(2) {
155                    return Err(AsyncTiffError::General(format!(
156                        "Data length {} is not divisible by UInt16 size (2 bytes)",
157                        data.len()
158                    )));
159                }
160                Ok(TypedArray::UInt16(try_cast_vec(data).unwrap_or_else(
161                    |(_, data)| {
162                        // Fallback to manual conversion when not aligned
163                        data.chunks_exact(2)
164                            .map(|b| u16::from_ne_bytes([b[0], b[1]]))
165                            .collect()
166                    },
167                )))
168            }
169            Some(DataType::UInt32) => {
170                if !data.len().is_multiple_of(4) {
171                    return Err(AsyncTiffError::General(format!(
172                        "Data length {} is not divisible by UInt32 size (4 bytes)",
173                        data.len()
174                    )));
175                }
176                Ok(TypedArray::UInt32(try_cast_vec(data).unwrap_or_else(
177                    |(_, data)| {
178                        // Fallback to manual conversion when not aligned
179                        data.chunks_exact(4)
180                            .map(|b| u32::from_ne_bytes([b[0], b[1], b[2], b[3]]))
181                            .collect()
182                    },
183                )))
184            }
185            Some(DataType::UInt64) => {
186                if !data.len().is_multiple_of(8) {
187                    return Err(AsyncTiffError::General(format!(
188                        "Data length {} is not divisible by UInt64 size (8 bytes)",
189                        data.len()
190                    )));
191                }
192                Ok(TypedArray::UInt64(try_cast_vec(data).unwrap_or_else(
193                    |(_, data)| {
194                        // Fallback to manual conversion when not aligned
195                        data.chunks_exact(8)
196                            .map(|b| {
197                                u64::from_ne_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
198                            })
199                            .collect()
200                    },
201                )))
202            }
203            // Casting u8 to i8 is safe as they have the same memory representation
204            Some(DataType::Int8) => Ok(TypedArray::Int8(cast_vec(data))),
205            Some(DataType::Int16) => {
206                if !data.len().is_multiple_of(2) {
207                    return Err(AsyncTiffError::General(format!(
208                        "Data length {} is not divisible by Int16 size (2 bytes)",
209                        data.len()
210                    )));
211                }
212                Ok(TypedArray::Int16(try_cast_vec(data).unwrap_or_else(
213                    |(_, data)| {
214                        // Fallback to manual conversion when not aligned
215                        data.chunks_exact(2)
216                            .map(|b| i16::from_ne_bytes([b[0], b[1]]))
217                            .collect()
218                    },
219                )))
220            }
221            Some(DataType::Int32) => {
222                if !data.len().is_multiple_of(4) {
223                    return Err(AsyncTiffError::General(format!(
224                        "Data length {} is not divisible by Int32 size (4 bytes)",
225                        data.len()
226                    )));
227                }
228                Ok(TypedArray::Int32(try_cast_vec(data).unwrap_or_else(
229                    |(_, data)| {
230                        // Fallback to manual conversion when not aligned
231                        data.chunks_exact(4)
232                            .map(|b| i32::from_ne_bytes([b[0], b[1], b[2], b[3]]))
233                            .collect()
234                    },
235                )))
236            }
237            Some(DataType::Int64) => {
238                if !data.len().is_multiple_of(8) {
239                    return Err(AsyncTiffError::General(format!(
240                        "Data length {} is not divisible by Int64 size (8 bytes)",
241                        data.len()
242                    )));
243                }
244                Ok(TypedArray::Int64(try_cast_vec(data).unwrap_or_else(
245                    |(_, data)| {
246                        // Fallback to manual conversion when not aligned
247                        data.chunks_exact(8)
248                            .map(|b| {
249                                i64::from_ne_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
250                            })
251                            .collect()
252                    },
253                )))
254            }
255            Some(DataType::Float32) => {
256                if !data.len().is_multiple_of(4) {
257                    return Err(AsyncTiffError::General(format!(
258                        "Data length {} is not divisible by Float32 size (4 bytes)",
259                        data.len()
260                    )));
261                }
262                Ok(TypedArray::Float32(try_cast_vec(data).unwrap_or_else(
263                    |(_, data)| {
264                        // Fallback to manual conversion when not aligned
265                        data.chunks_exact(4)
266                            .map(|b| f32::from_ne_bytes([b[0], b[1], b[2], b[3]]))
267                            .collect()
268                    },
269                )))
270            }
271            Some(DataType::Float64) => {
272                if !data.len().is_multiple_of(8) {
273                    return Err(AsyncTiffError::General(format!(
274                        "Data length {} is not divisible by Float64 size (8 bytes)",
275                        data.len()
276                    )));
277                }
278                Ok(TypedArray::Float64(try_cast_vec(data).unwrap_or_else(
279                    |(_, data)| {
280                        // Fallback to manual conversion when not aligned
281                        data.chunks_exact(8)
282                            .map(|b| {
283                                f64::from_ne_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
284                            })
285                            .collect()
286                    },
287                )))
288            }
289        }
290    }
291
292    /// Get the length (number of elements) of the typed array.
293    pub fn len(&self) -> usize {
294        match self {
295            TypedArray::Bool(data) => data.len(),
296            TypedArray::UInt8(data) => data.len(),
297            TypedArray::UInt16(data) => data.len(),
298            TypedArray::UInt32(data) => data.len(),
299            TypedArray::UInt64(data) => data.len(),
300            TypedArray::Int8(data) => data.len(),
301            TypedArray::Int16(data) => data.len(),
302            TypedArray::Int32(data) => data.len(),
303            TypedArray::Int64(data) => data.len(),
304            TypedArray::Float32(data) => data.len(),
305            TypedArray::Float64(data) => data.len(),
306        }
307    }
308
309    /// Check if the typed array is empty.
310    pub fn is_empty(&self) -> bool {
311        self.len() == 0
312    }
313}
314
315impl AsRef<[u8]> for TypedArray {
316    fn as_ref(&self) -> &[u8] {
317        match self {
318            TypedArray::Bool(data) => cast_slice(data),
319            TypedArray::UInt8(data) => data.as_slice(),
320            TypedArray::UInt16(data) => cast_slice(data),
321            TypedArray::UInt32(data) => cast_slice(data),
322            TypedArray::UInt64(data) => cast_slice(data),
323            TypedArray::Int8(data) => cast_slice(data),
324            TypedArray::Int16(data) => cast_slice(data),
325            TypedArray::Int32(data) => cast_slice(data),
326            TypedArray::Int64(data) => cast_slice(data),
327            TypedArray::Float32(data) => cast_slice(data),
328            TypedArray::Float64(data) => cast_slice(data),
329        }
330    }
331}
332
333/// Expands a packed bitmask to `Vec<bool>`.
334///
335/// Per TIFF spec, 1 = valid pixel, 0 = transparent/masked pixel.
336fn expand_bitmask(data: &[u8], len: usize) -> Vec<bool> {
337    let mut result = Vec::with_capacity(len);
338    for i in 0..len {
339        let byte_idx = i / 8;
340        let bit_idx = 7 - (i % 8); // MSB first within each byte
341        let bit = (data[byte_idx] >> bit_idx) & 1;
342        result.push(bit == 1);
343    }
344    result
345}