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.as_chunks::<2>()
164                            .0
165                            .iter()
166                            .copied()
167                            .map(u16::from_ne_bytes)
168                            .collect()
169                    },
170                )))
171            }
172            Some(DataType::UInt32) => {
173                if !data.len().is_multiple_of(4) {
174                    return Err(AsyncTiffError::General(format!(
175                        "Data length {} is not divisible by UInt32 size (4 bytes)",
176                        data.len()
177                    )));
178                }
179                Ok(TypedArray::UInt32(try_cast_vec(data).unwrap_or_else(
180                    |(_, data)| {
181                        // Fallback to manual conversion when not aligned
182                        data.as_chunks::<4>()
183                            .0
184                            .iter()
185                            .copied()
186                            .map(u32::from_ne_bytes)
187                            .collect()
188                    },
189                )))
190            }
191            Some(DataType::UInt64) => {
192                if !data.len().is_multiple_of(8) {
193                    return Err(AsyncTiffError::General(format!(
194                        "Data length {} is not divisible by UInt64 size (8 bytes)",
195                        data.len()
196                    )));
197                }
198                Ok(TypedArray::UInt64(try_cast_vec(data).unwrap_or_else(
199                    |(_, data)| {
200                        // Fallback to manual conversion when not aligned
201                        data.as_chunks::<8>()
202                            .0
203                            .iter()
204                            .copied()
205                            .map(u64::from_ne_bytes)
206                            .collect()
207                    },
208                )))
209            }
210            // Casting u8 to i8 is safe as they have the same memory representation
211            Some(DataType::Int8) => Ok(TypedArray::Int8(cast_vec(data))),
212            Some(DataType::Int16) => {
213                if !data.len().is_multiple_of(2) {
214                    return Err(AsyncTiffError::General(format!(
215                        "Data length {} is not divisible by Int16 size (2 bytes)",
216                        data.len()
217                    )));
218                }
219                Ok(TypedArray::Int16(try_cast_vec(data).unwrap_or_else(
220                    |(_, data)| {
221                        // Fallback to manual conversion when not aligned
222                        data.as_chunks::<2>()
223                            .0
224                            .iter()
225                            .copied()
226                            .map(i16::from_ne_bytes)
227                            .collect()
228                    },
229                )))
230            }
231            Some(DataType::Int32) => {
232                if !data.len().is_multiple_of(4) {
233                    return Err(AsyncTiffError::General(format!(
234                        "Data length {} is not divisible by Int32 size (4 bytes)",
235                        data.len()
236                    )));
237                }
238                Ok(TypedArray::Int32(try_cast_vec(data).unwrap_or_else(
239                    |(_, data)| {
240                        // Fallback to manual conversion when not aligned
241                        data.as_chunks::<4>()
242                            .0
243                            .iter()
244                            .copied()
245                            .map(i32::from_ne_bytes)
246                            .collect()
247                    },
248                )))
249            }
250            Some(DataType::Int64) => {
251                if !data.len().is_multiple_of(8) {
252                    return Err(AsyncTiffError::General(format!(
253                        "Data length {} is not divisible by Int64 size (8 bytes)",
254                        data.len()
255                    )));
256                }
257                Ok(TypedArray::Int64(try_cast_vec(data).unwrap_or_else(
258                    |(_, data)| {
259                        // Fallback to manual conversion when not aligned
260                        data.as_chunks::<8>()
261                            .0
262                            .iter()
263                            .copied()
264                            .map(i64::from_ne_bytes)
265                            .collect()
266                    },
267                )))
268            }
269            Some(DataType::Float32) => {
270                if !data.len().is_multiple_of(4) {
271                    return Err(AsyncTiffError::General(format!(
272                        "Data length {} is not divisible by Float32 size (4 bytes)",
273                        data.len()
274                    )));
275                }
276                Ok(TypedArray::Float32(try_cast_vec(data).unwrap_or_else(
277                    |(_, data)| {
278                        // Fallback to manual conversion when not aligned
279                        data.as_chunks::<4>()
280                            .0
281                            .iter()
282                            .copied()
283                            .map(f32::from_ne_bytes)
284                            .collect()
285                    },
286                )))
287            }
288            Some(DataType::Float64) => {
289                if !data.len().is_multiple_of(8) {
290                    return Err(AsyncTiffError::General(format!(
291                        "Data length {} is not divisible by Float64 size (8 bytes)",
292                        data.len()
293                    )));
294                }
295                Ok(TypedArray::Float64(try_cast_vec(data).unwrap_or_else(
296                    |(_, data)| {
297                        // Fallback to manual conversion when not aligned
298                        data.as_chunks::<8>()
299                            .0
300                            .iter()
301                            .copied()
302                            .map(f64::from_ne_bytes)
303                            .collect()
304                    },
305                )))
306            }
307        }
308    }
309
310    /// Get the length (number of elements) of the typed array.
311    pub fn len(&self) -> usize {
312        match self {
313            TypedArray::Bool(data) => data.len(),
314            TypedArray::UInt8(data) => data.len(),
315            TypedArray::UInt16(data) => data.len(),
316            TypedArray::UInt32(data) => data.len(),
317            TypedArray::UInt64(data) => data.len(),
318            TypedArray::Int8(data) => data.len(),
319            TypedArray::Int16(data) => data.len(),
320            TypedArray::Int32(data) => data.len(),
321            TypedArray::Int64(data) => data.len(),
322            TypedArray::Float32(data) => data.len(),
323            TypedArray::Float64(data) => data.len(),
324        }
325    }
326
327    /// Check if the typed array is empty.
328    pub fn is_empty(&self) -> bool {
329        self.len() == 0
330    }
331}
332
333impl AsRef<[u8]> for TypedArray {
334    fn as_ref(&self) -> &[u8] {
335        match self {
336            TypedArray::Bool(data) => cast_slice(data),
337            TypedArray::UInt8(data) => data.as_slice(),
338            TypedArray::UInt16(data) => cast_slice(data),
339            TypedArray::UInt32(data) => cast_slice(data),
340            TypedArray::UInt64(data) => cast_slice(data),
341            TypedArray::Int8(data) => cast_slice(data),
342            TypedArray::Int16(data) => cast_slice(data),
343            TypedArray::Int32(data) => cast_slice(data),
344            TypedArray::Int64(data) => cast_slice(data),
345            TypedArray::Float32(data) => cast_slice(data),
346            TypedArray::Float64(data) => cast_slice(data),
347        }
348    }
349}
350
351/// Expands a packed bitmask to `Vec<bool>`.
352///
353/// Per TIFF spec, 1 = valid pixel, 0 = transparent/masked pixel.
354fn expand_bitmask(data: &[u8], len: usize) -> Vec<bool> {
355    let mut result = Vec::with_capacity(len);
356    for i in 0..len {
357        let byte_idx = i / 8;
358        let bit_idx = 7 - (i % 8); // MSB first within each byte
359        let bit = (data[byte_idx] >> bit_idx) & 1;
360        result.push(bit == 1);
361    }
362    result
363}