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        // Validate that the data length matches the expected size
33        let expected_len = shape[0] * shape[1] * shape[2];
34
35        let typed_data = TypedArray::try_new(data, data_type)?;
36        if typed_data.len() != expected_len {
37            return Err(AsyncTiffError::General(format!("Internal error: incorrect shape or data length passed to Array::try_new. Got data length {}, expected {}", typed_data.len(), expected_len)));
38        }
39
40        Ok(Self {
41            data: typed_data,
42            shape,
43            data_type,
44        })
45    }
46
47    /// Access the raw underlying byte data of the array.
48    pub fn data(&self) -> &TypedArray {
49        &self.data
50    }
51
52    /// Consume the Array and return its components.
53    pub fn into_inner(self) -> (TypedArray, [usize; 3], Option<DataType>) {
54        (self.data, self.shape, self.data_type)
55    }
56
57    /// Get the shape of the array.
58    ///
59    /// The shape matches the physical array data exposed, but the _interpretation_ depends on the
60    /// value of `PlanarConfiguration`:
61    ///
62    /// - PlanarConfiguration=1 (chunky): (height, width, bands)
63    /// - PlanarConfiguration=2 (planar): (bands, height, width)
64    pub fn shape(&self) -> [usize; 3] {
65        self.shape
66    }
67
68    /// The logical data type of the array elements.
69    ///
70    /// If None, the data type is unsupported or unknown.
71    pub fn data_type(&self) -> Option<DataType> {
72        self.data_type
73    }
74}
75
76/// An enum representing a typed view of the array data.
77///
78/// ```
79/// use async_tiff::{DataType, TypedArray};
80///
81/// let data = TypedArray::try_new(vec![10, 20, 30], Some(DataType::UInt8)).unwrap();
82/// match &data {
83///     TypedArray::UInt8(v) => assert_eq!(v, &[10, 20, 30]),
84///     _ => panic!("expected UInt8"),
85/// }
86///
87/// let bytes = std::f32::consts::PI.to_ne_bytes().to_vec();
88/// let data = TypedArray::try_new(bytes, Some(DataType::Float32)).unwrap();
89/// match &data {
90///     TypedArray::Float32(v) => assert_eq!(v[0], std::f32::consts::PI),
91///     _ => panic!("expected Float32"),
92/// }
93/// ```
94#[derive(Debug, Clone)]
95pub enum TypedArray {
96    /// Unsigned 8-bit integer array.
97    UInt8(Vec<u8>),
98    /// Unsigned 16-bit integer array.
99    UInt16(Vec<u16>),
100    /// Unsigned 32-bit integer array.
101    UInt32(Vec<u32>),
102    /// Unsigned 64-bit integer array.
103    UInt64(Vec<u64>),
104    /// Signed 8-bit integer array.
105    Int8(Vec<i8>),
106    /// Signed 16-bit integer array.
107    Int16(Vec<i16>),
108    /// Signed 32-bit integer array.
109    Int32(Vec<i32>),
110    /// Signed 64-bit integer array.
111    Int64(Vec<i64>),
112    /// 32-bit floating point array.
113    Float32(Vec<f32>),
114    /// 64-bit floating point array.
115    Float64(Vec<f64>),
116}
117
118impl TypedArray {
119    /// Create a new TypedArray from raw byte data and a specified DataType.
120    ///
121    /// Returns an error if the data length is not divisible by the element size.
122    pub fn try_new(data: Vec<u8>, data_type: Option<DataType>) -> AsyncTiffResult<Self> {
123        match data_type {
124            None | Some(DataType::UInt8) => Ok(TypedArray::UInt8(data)),
125            Some(DataType::UInt16) => {
126                if !data.len().is_multiple_of(2) {
127                    return Err(AsyncTiffError::General(format!(
128                        "Data length {} is not divisible by UInt16 size (2 bytes)",
129                        data.len()
130                    )));
131                }
132                Ok(TypedArray::UInt16(try_cast_vec(data).unwrap_or_else(
133                    |(_, data)| {
134                        // Fallback to manual conversion when not aligned
135                        data.chunks_exact(2)
136                            .map(|b| u16::from_ne_bytes([b[0], b[1]]))
137                            .collect()
138                    },
139                )))
140            }
141            Some(DataType::UInt32) => {
142                if !data.len().is_multiple_of(4) {
143                    return Err(AsyncTiffError::General(format!(
144                        "Data length {} is not divisible by UInt32 size (4 bytes)",
145                        data.len()
146                    )));
147                }
148                Ok(TypedArray::UInt32(try_cast_vec(data).unwrap_or_else(
149                    |(_, data)| {
150                        // Fallback to manual conversion when not aligned
151                        data.chunks_exact(4)
152                            .map(|b| u32::from_ne_bytes([b[0], b[1], b[2], b[3]]))
153                            .collect()
154                    },
155                )))
156            }
157            Some(DataType::UInt64) => {
158                if !data.len().is_multiple_of(8) {
159                    return Err(AsyncTiffError::General(format!(
160                        "Data length {} is not divisible by UInt64 size (8 bytes)",
161                        data.len()
162                    )));
163                }
164                Ok(TypedArray::UInt64(try_cast_vec(data).unwrap_or_else(
165                    |(_, data)| {
166                        // Fallback to manual conversion when not aligned
167                        data.chunks_exact(8)
168                            .map(|b| {
169                                u64::from_ne_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
170                            })
171                            .collect()
172                    },
173                )))
174            }
175            // Casting u8 to i8 is safe as they have the same memory representation
176            Some(DataType::Int8) => Ok(TypedArray::Int8(cast_vec(data))),
177            Some(DataType::Int16) => {
178                if !data.len().is_multiple_of(2) {
179                    return Err(AsyncTiffError::General(format!(
180                        "Data length {} is not divisible by Int16 size (2 bytes)",
181                        data.len()
182                    )));
183                }
184                Ok(TypedArray::Int16(try_cast_vec(data).unwrap_or_else(
185                    |(_, data)| {
186                        // Fallback to manual conversion when not aligned
187                        data.chunks_exact(2)
188                            .map(|b| i16::from_ne_bytes([b[0], b[1]]))
189                            .collect()
190                    },
191                )))
192            }
193            Some(DataType::Int32) => {
194                if !data.len().is_multiple_of(4) {
195                    return Err(AsyncTiffError::General(format!(
196                        "Data length {} is not divisible by Int32 size (4 bytes)",
197                        data.len()
198                    )));
199                }
200                Ok(TypedArray::Int32(try_cast_vec(data).unwrap_or_else(
201                    |(_, data)| {
202                        // Fallback to manual conversion when not aligned
203                        data.chunks_exact(4)
204                            .map(|b| i32::from_ne_bytes([b[0], b[1], b[2], b[3]]))
205                            .collect()
206                    },
207                )))
208            }
209            Some(DataType::Int64) => {
210                if !data.len().is_multiple_of(8) {
211                    return Err(AsyncTiffError::General(format!(
212                        "Data length {} is not divisible by Int64 size (8 bytes)",
213                        data.len()
214                    )));
215                }
216                Ok(TypedArray::Int64(try_cast_vec(data).unwrap_or_else(
217                    |(_, data)| {
218                        // Fallback to manual conversion when not aligned
219                        data.chunks_exact(8)
220                            .map(|b| {
221                                i64::from_ne_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
222                            })
223                            .collect()
224                    },
225                )))
226            }
227            Some(DataType::Float32) => {
228                if !data.len().is_multiple_of(4) {
229                    return Err(AsyncTiffError::General(format!(
230                        "Data length {} is not divisible by Float32 size (4 bytes)",
231                        data.len()
232                    )));
233                }
234                Ok(TypedArray::Float32(try_cast_vec(data).unwrap_or_else(
235                    |(_, data)| {
236                        // Fallback to manual conversion when not aligned
237                        data.chunks_exact(4)
238                            .map(|b| f32::from_ne_bytes([b[0], b[1], b[2], b[3]]))
239                            .collect()
240                    },
241                )))
242            }
243            Some(DataType::Float64) => {
244                if !data.len().is_multiple_of(8) {
245                    return Err(AsyncTiffError::General(format!(
246                        "Data length {} is not divisible by Float64 size (8 bytes)",
247                        data.len()
248                    )));
249                }
250                Ok(TypedArray::Float64(try_cast_vec(data).unwrap_or_else(
251                    |(_, data)| {
252                        // Fallback to manual conversion when not aligned
253                        data.chunks_exact(8)
254                            .map(|b| {
255                                f64::from_ne_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
256                            })
257                            .collect()
258                    },
259                )))
260            }
261        }
262    }
263
264    /// Get the length (number of elements) of the typed array.
265    pub fn len(&self) -> usize {
266        match self {
267            TypedArray::UInt8(data) => data.len(),
268            TypedArray::UInt16(data) => data.len(),
269            TypedArray::UInt32(data) => data.len(),
270            TypedArray::UInt64(data) => data.len(),
271            TypedArray::Int8(data) => data.len(),
272            TypedArray::Int16(data) => data.len(),
273            TypedArray::Int32(data) => data.len(),
274            TypedArray::Int64(data) => data.len(),
275            TypedArray::Float32(data) => data.len(),
276            TypedArray::Float64(data) => data.len(),
277        }
278    }
279
280    /// Check if the typed array is empty.
281    pub fn is_empty(&self) -> bool {
282        self.len() == 0
283    }
284}
285
286impl AsRef<[u8]> for TypedArray {
287    fn as_ref(&self) -> &[u8] {
288        match self {
289            TypedArray::UInt8(data) => data.as_slice(),
290            TypedArray::UInt16(data) => cast_slice(data),
291            TypedArray::UInt32(data) => cast_slice(data),
292            TypedArray::UInt64(data) => cast_slice(data),
293            TypedArray::Int8(data) => cast_slice(data),
294            TypedArray::Int16(data) => cast_slice(data),
295            TypedArray::Int32(data) => cast_slice(data),
296            TypedArray::Int64(data) => cast_slice(data),
297            TypedArray::Float32(data) => cast_slice(data),
298            TypedArray::Float64(data) => cast_slice(data),
299        }
300    }
301}