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}