xisf_rs/image/
image_data.rs

1use ndarray::{ArrayD, CowArray, IxDyn};
2use num_complex::Complex;
3use std::{
4    marker::PhantomData,
5    ops::{Deref, DerefMut}
6};
7use super::{PixelStorage, SampleFormat};
8use crate::error::DowncastDynImageError as DowncastError;
9
10/// An `enum` wrapper for images of all possible [`SampleFormat`](super::SampleFormat)s
11///
12/// If you think you know the sample format of a given image, or your program only accepts data of a certain type,
13/// you can attempt to convert a `DynImageData` into that type with `let raw: ImageData<TYPE> = image.read_data(&ctx)?.try_into()?;`
14///
15/// See also [`SampleFormat`]
16#[derive(Clone, Debug)]
17pub enum DynImageData {
18    /// Pixel samples are scalar `u8`s
19    UInt8(ImageData<u8>),
20    /// Pixel samples are scalar `u16`s
21    UInt16(ImageData<u16>),
22    /// Pixel samples are scalar `u32`s
23    UInt32(ImageData<u32>),
24    /// Pixel samples are scalar `u64`s
25    UInt64(ImageData<u64>),
26    /// Pixel samples are scalar `f32`s
27    Float32(ImageData<f32>),
28    /// Pixel samples are scalar `f64`s
29    Float64(ImageData<f64>),
30    /// Pixel samples are complex with `f32` parts
31    Complex32(ImageData<Complex<f32>>),
32    /// Pixel samples are complex with `f64` parts
33    Complex64(ImageData<Complex<f64>>),
34}
35impl DynImageData {
36    /// Returns the sample format of this enum variant
37    pub fn sample_format(&self) -> SampleFormat {
38        match self {
39            DynImageData::UInt8(_) => SampleFormat::UInt8,
40            DynImageData::UInt16(_) => SampleFormat::UInt16,
41            DynImageData::UInt32(_) => SampleFormat::UInt32,
42            DynImageData::UInt64(_) => SampleFormat::UInt64,
43            DynImageData::Float32(_) => SampleFormat::Float32,
44            DynImageData::Float64(_) => SampleFormat::Float64,
45            DynImageData::Complex32(_) => SampleFormat::Complex32,
46            DynImageData::Complex64(_) => SampleFormat::Complex64,
47        }
48    }
49}
50macro_rules! try_into_impl {
51    ($enum:ident, $t:ty) => {
52        #[doc=concat!("Returns `Ok(ImageData<", stringify!($t), ">)` for [`", stringify!($enum), "`](Self::", stringify!($enum), ") variants, and `Err(())` otherwise")]
53        impl TryInto<ImageData<$t>> for DynImageData {
54            type Error = DowncastError;
55            fn try_into(self) -> Result<ImageData<$t>, Self::Error> {
56                match self {
57                    Self::$enum(raw) => Ok(raw),
58                    _ => Err(DowncastError(stringify!(ident))),
59                }
60            }
61        }
62    }
63}
64
65try_into_impl!(UInt8, u8);
66try_into_impl!(UInt16, u16);
67try_into_impl!(UInt32, u32);
68try_into_impl!(UInt64, u64);
69try_into_impl!(Float32, f32);
70try_into_impl!(Float64, f64);
71try_into_impl!(Complex32, Complex<f32>);
72try_into_impl!(Complex64, Complex<f64>);
73
74/// Would ideally just be an [`Into`] implementation, but it needs to know the pixel storage as
75/// supplementary information, which isn't in [`Into::into()`]'s function signature
76pub(crate) trait IntoDynImageData {
77    fn into_dyn_img(self, layout: PixelStorage) -> DynImageData;
78}
79macro_rules! into_impl {
80    ($enum:ident, $t:ty) => {
81        impl IntoDynImageData for ArrayD<$t> {
82            /// Wraps the buffer inside of a [`DynImageData`]
83            ///
84            /// # Panics
85            /// If the image is 0-dimensional (should be impossible when parsed from this library).
86            /// An array can have elements in it and still be considered 0-dimensional,
87            /// as one of the axes is considered a channel instead of a dimension.
88            fn into_dyn_img(self, layout: PixelStorage) -> DynImageData {
89                assert!(self.shape().len() >= 2);
90                DynImageData::$enum(ImageData::<$t>::new(self, layout))
91            }
92        }
93    }
94}
95into_impl!(UInt8, u8);
96into_impl!(UInt16, u16);
97into_impl!(UInt32, u32);
98into_impl!(UInt64, u64);
99into_impl!(Float32, f32);
100into_impl!(Float64, f64);
101into_impl!(Complex32, Complex<f32>);
102into_impl!(Complex64, Complex<f64>);
103
104use memory_layout::*;
105
106/// A wrapper around the raw pixel data with some compile-time checks to avoid unintentionally ignoring its [memory layout](PixelStorage)
107///
108/// For an image with *N* dimensions, the inner [`ArrayD<T>`] has *N + 1* axes,
109/// with either the first or last axis encoding the number of channels, depending on the layout:
110/// - If `L` is [`Planar`]: The **first** axis of the inner [`ArrayD<T>`] is the channel axis, and the remaining *N* axes are image dimensions.
111/// - If `L` is [`Normal`]: The **last** axis of the inner [`ArrayD<T>`] is the channel axis, and the previous *N* axes are image dimensions.
112/// - If `L` is [`Raw`] or [`Accept`]: This data is arranged however it was in the file, according to the [`Image`](super::Image)'s .
113///
114/// Allows transparent access to the inner buffer through [`Deref`] and [`DerefMut`] only if `L` is `Planar`, `Normal`, or `Accept`.
115/// Since all images are returned with `L = Raw` when read from file, this forces the end user to make a conscious decision to either
116/// accept the responsibility of handling multiple image formats with [`Self::accept_current_layout()`] or transform it into a specific layout
117/// their program is equipped to handle with [`Self::into_planar_layout()`] or [`Self::into_normal_layout()`].
118///
119/// For more information about pixel memory layouts, see the documentation for [`PixelStorage`]
120///
121/// # Example
122///
123/// ```rust
124/// # use std::error::Error;
125/// # use xisf_rs::{XISF, image::ImageData};
126/// # use ndarray::s;
127/// # fn main() -> Result<(), Box<dyn Error>> {
128/// let (xisf, ctx) = XISF::open("tests/files/2ch.xisf", &Default::default())?;
129/// let raw: ImageData<u16> = xisf.image(0).read_data(&ctx)?.try_into()?;
130/// let planar = raw.to_planar_layout();
131/// let normal = raw.to_normal_layout();
132/// assert_eq!(planar.shape(), &[2, 10, 4]);
133/// assert_eq!(normal.shape(), &[10, 4, 2]);
134/// for c in 0..2 {
135///     assert_eq!(planar.slice(s![c, .., ..]), normal.slice(s![.., .., c]));
136/// }
137/// # Ok(())
138/// # }
139/// ```
140#[derive(Debug)]
141pub struct ImageData<T, L: Layout = Raw> {
142    inner: ArrayD<T>,
143    /// Uses an associated type to save some memory once the layout is known.
144    /// When the data is first read and `L` is [`Raw`], `layout` will store a [`PixelStorage`] value.
145    /// After the data has been transformed into a specific memory layout with [`Self::into_planar_layout()`],
146    /// `layout` will just be [`PhantomData`]
147    /// since the layout is encoded in the template parameter, and storing it here would be redundant.
148    layout: L::Storage,
149}
150impl<T, L: Layout> Clone for ImageData<T, L> where T: Clone {
151    fn clone(&self) -> Self {
152        Self { inner: self.inner.clone(), layout: self.layout.clone() }
153    }
154}
155impl<T, L: Layout> ImageData<T, L> where T: Clone {
156    /// Reorganizes the pixel samples into [planar layout](Planar), consuming `self`
157    ///
158    /// - If the current memory layout is normal, enough memory is needed to temporarily duplicate the image
159    /// - If the current memory layout is already planar, this is a no-op.
160    pub fn into_planar_layout(self) -> ImageData<T, Planar> {
161        L::into_planar(self)
162    }
163    /// Reorganizes the pixel samples into [normal layout](Normal), consuming `self`
164    ///
165    /// If the current memory layout is already normal, this is a no-op.
166    pub fn into_normal_layout(self) -> ImageData<T, Normal> {
167        L::into_normal(self)
168    }
169    /// Reorganizes the pixel samples into [planar layout](Planar),
170    /// returning a copy-on-write (Cow) view of the underlying memory buffer
171    ///
172    /// - If the memory layout is already planar, the underlying memory buffer of the output
173    /// Cow points to the same memory as `self` for operations which don't require mutable access.
174    /// (That is, `std::ptr::eq(planar.to_slice(), planar.to_planar_layout().to_slice())) == true`.)
175    /// When any method that requires mutable access to the output buffer is called, the buffer is cloned.
176    /// - If the current memory layout is normal, the buffer is cloned immediately upon conversion.
177    /// <div class="warning">
178    ///
179    /// Only recommended for use when owned access is impossible, since this will unnecessarily duplicate an
180    /// arbitrarily large image buffer if mutable access to the buffer is required later.
181    /// For most cases, use [`Self::into_planar_layout()`].
182    ///
183    /// </div>
184    pub fn to_planar_layout(&self) -> CowImageData<'_, T, Planar> {
185        L::to_planar(self)
186    }
187    /// Reorganizes the pixel samples into [normal layout](Normal),
188    /// returning a copy-on-write (Cow) view of the underlying memory buffer
189    ///
190    /// - If the memory layout is already normal, the underlying memory buffer of the output
191    /// Cow points to the same memory as `self` for operations which don't require mutable access.
192    /// (That is, `std::ptr::eq(normal.to_slice(), normal.to_normal_layout().to_slice())) == true`.)
193    /// When any method that requires mutable access to the output buffer is called, the buffer is cloned.
194    /// - If the current memory layout is planar, the buffer is cloned immediately upon conversion.
195    /// <div class="warning">
196    ///
197    /// Only recommended for use when owned access is impossible, since this will unnecessarily duplicate an
198    /// arbitrarily large image buffer if mutable access to the buffer is required later.
199    /// For most cases, use [`Self::into_normal_layout()`].
200    ///
201    /// </div>
202    pub fn to_normal_layout(&self) -> CowImageData<'_, T, Normal> {
203        L::to_normal(self)
204    }
205    /// Returns the current memory layout
206    pub fn layout(&self) -> PixelStorage {
207        L::layout(&self)
208    }
209    /// Returns the number of channels in the image
210    pub fn num_channels(&self) -> usize {
211        match self.layout() {
212            PixelStorage::Planar => *self.inner.shape().first().unwrap(),
213            PixelStorage::Normal => *self.inner.shape().last().unwrap(),
214        }
215    }
216    /// Returns the number of dimensions in the image
217    pub fn num_dimensions(&self) -> usize {
218        self.inner.shape().len() - 1
219    }
220}
221impl<T> ImageData<T, Raw> {
222    fn new(buf: ArrayD<T>, layout: PixelStorage) -> Self {
223        Self {
224            inner: buf,
225            layout,
226        }
227    }
228    /// Allows a method of accessing the underlying buffer without risking a memory clone
229    pub fn accept_current_layout(self) -> ImageData<T, Accept> {
230        ImageData::<T, Accept> {
231            inner: self.inner,
232            layout: self.layout,
233        }
234    }
235}
236impl<T, L: Known> Deref for ImageData<T, L> {
237    type Target = ArrayD<T>;
238
239    fn deref(&self) -> &Self::Target {
240        &self.inner
241    }
242}
243impl<T, L: Known> DerefMut for ImageData<T, L> {
244    fn deref_mut(&mut self) -> &mut Self::Target {
245        &mut self.inner
246    }
247}
248
249/// [`ImageData<T, L>`] but with a copy-on-write internal array
250#[derive(Clone, Debug)]
251pub struct CowImageData<'a, T, L: Layout> {
252    inner: CowArray<'a, T, IxDyn>,
253    layout: L::Storage,
254}
255impl<'a, T, L: Layout> CowImageData<'a, T, L> where T: Clone {
256    /// Convert this to an owned representation ([`ImageData<T, L>`])
257    pub fn to_owned(&self) -> ImageData<T, L> {
258        ImageData::<T, L> {
259            inner: self.inner.to_owned(),
260            layout: self.layout.clone(),
261        }
262    }
263}
264impl<'a, T, L: Layout> From<&'a ImageData<T, L>> for CowImageData<'a, T, L> {
265    fn from(value: &'a ImageData<T, L>) -> Self {
266        Self {
267            inner: (&value.inner).into(),
268            layout: value.layout.clone(),
269        }
270    }
271}
272impl<'a, T, L: Layout> From<ImageData<T, L>> for CowImageData<'a, T, L> {
273    fn from(value: ImageData<T, L>) -> Self {
274        Self {
275            inner: value.inner.into(),
276            layout: value.layout.clone(),
277        }
278    }
279}
280impl<'a, T, L: Known> Deref for CowImageData<'a, T, L> {
281    type Target = CowArray<'a, T, IxDyn>;
282
283    fn deref(&self) -> &Self::Target {
284        &self.inner
285    }
286}
287impl<'a, T, L: Known> DerefMut for CowImageData<'a, T, L> {
288    fn deref_mut(&mut self) -> &mut Self::Target {
289        &mut self.inner
290    }
291}
292
293/// A small library of functions and types to simplify management of different [memory layouts](PixelStorage)
294pub mod memory_layout {
295    use super::*;
296    use std::fmt::Debug;
297    pub(crate) use sealed::Layout;
298
299    mod sealed {
300        use super::*;
301        pub trait Layout: Sized {
302            type Storage: Clone + Debug;
303            fn into_planar<T: Clone>(img: ImageData<T, Self>) -> ImageData<T, Planar>;
304            fn into_normal<T: Clone>(img: ImageData<T, Self>) -> ImageData<T, Normal>;
305            fn to_planar<T: Clone>(img: &ImageData<T, Self>) -> CowImageData<'_, T, Planar>;
306            fn to_normal<T: Clone>(img: &ImageData<T, Self>) -> CowImageData<'_, T, Normal>;
307            fn layout<T>(img: &ImageData<T, Self>) -> PixelStorage;
308        }
309    }
310
311    // TODO: investigate alternatives with lower memory overhead
312    // there's no way to do this without some kind of a buffer, but I don't need to duplicate the whole image
313    // I think iterating axis by axis would work, but it would lose cache coherency
314    // - did it ever have it? probably not. check `as_standard_layout()` implementation
315    fn normal_to_planar<T>(buf: ArrayD<T>) -> ArrayD<T> where T: Clone {
316        let num_axes = buf.shape().len();
317        let mut indices: Vec<_> = (0..num_axes).into_iter().collect();
318        indices.rotate_right(1);
319
320        buf.permuted_axes(indices.as_slice())
321            .as_standard_layout()
322            .to_owned()
323    }
324    fn planar_to_normal<T>(buf: ArrayD<T>) -> ArrayD<T> where T: Clone {
325        let num_axes = buf.shape().len();
326        let mut indices: Vec<_> = (0..num_axes).into_iter().collect();
327        indices.rotate_left(1);
328
329        buf.permuted_axes(indices.as_slice())
330            .as_standard_layout()
331            .to_owned()
332    }
333
334    /// The image is laid out with each channel having all of its values for each pixel stored sequentially
335    ///
336    /// For example, consider a 2D RGB image.
337    /// Since XISF images are stored in row-major order, this means the pixels are laid out in the order
338    /// `(0,0), (0,1), (1,0), (1,1)`. For a normal-layout image, the samples would in the following order:
339    /// `(0,0,R), (0,1,R), (1,0,R), (1,1,R), (0,0,G), (0,1,G), (1,0,G), (1,1,G), (0,0,B), (0,1,B), (1,0,B), (1,1,B)`.
340    ///  See also [`PixelStorage`]; contrast with [`Normal`].
341    /// See [the spec](https://pixinsight.com/doc/docs/XISF-1.0-spec/XISF-1.0-spec.html#__XISF_Data_Objects_:_XISF_Image_:_Pixel_Storage_Models_:_Planar_Pixel_Storage_Model__)
342    /// for more information.
343    ///
344    /// <div style="text-align: center; background-color: lightgray; padding: 1rem;">
345    /// <img src="https://pixinsight.com/doc/docs/XISF-1.0-spec/images/planar-pixel-storage-model.svg" width="50%" />
346    /// </div>
347    #[derive(Clone, Debug)]
348    pub struct Planar;
349    impl Layout for Planar {
350        type Storage = PhantomData<Self>;
351
352        #[inline]
353        fn into_planar<T>(img: ImageData<T, Self>) -> ImageData<T, Planar> {
354            img
355        }
356
357        fn into_normal<T: Clone>(img: ImageData<T, Self>) -> ImageData<T, Normal> {
358            ImageData::<T, Normal> {
359                inner: planar_to_normal(img.inner),
360                layout: PhantomData,
361            }
362        }
363
364        #[inline]
365        fn to_planar<T>(img: &ImageData<T, Self>) -> CowImageData<'_, T, Planar> {
366            img.into()
367        }
368
369        #[inline]
370        fn to_normal<T: Clone>(img: &ImageData<T, Self>) -> CowImageData<'_, T, Normal> {
371            Self::into_normal(img.clone()).into()
372        }
373
374        #[inline]
375        fn layout<T>(_img: &ImageData<T, Self>) -> PixelStorage {
376            PixelStorage::Planar
377        }
378    }
379
380    /// The image is laid out with each pixel having all of its values for each channel stored sequentially
381    ///
382    /// For example, consider a 2D RGB image.
383    /// Since XISF images are stored in row-major order, this means the pixels are laid out in the order
384    /// `(0,0), (0,1), (1,0), (1,1)`. For a normal-layout image, the samples would in the following order:
385    /// `(0,0,R), (0,0,G), (0,0,B), (0,1,R), (0,1,G), (0,1,B), (1,0,R), (1,0,G), (1,0,B), (1,1,R), (1,1,G), (1,1,B)`.
386    ///  See also [`PixelStorage`]; contrast with [`Planar`].
387    /// See [the spec](https://pixinsight.com/doc/docs/XISF-1.0-spec/XISF-1.0-spec.html#__XISF_Data_Objects_:_XISF_Image_:_Pixel_Storage_Models_:_Normal_Pixel_Storage_Model__)
388    /// for more information.
389    ///
390    /// <div style="text-align: center; background-color: lightgray; padding: 1rem;">
391    /// <img src="https://pixinsight.com/doc/docs/XISF-1.0-spec/images/normal-pixel-storage-model.svg" width="50%" />
392    /// </div>
393    #[derive(Clone, Debug)]
394    pub struct Normal;
395    impl Layout for Normal {
396        type Storage = PhantomData<Self>;
397
398        fn into_planar<T: Clone>(img: ImageData<T, Self>) -> ImageData<T, Planar> {
399            ImageData::<T, Planar> {
400                inner: normal_to_planar(img.inner),
401                layout: PhantomData,
402            }
403        }
404
405        #[inline]
406        fn into_normal<T>(img: ImageData<T, Self>) -> ImageData<T, Normal> {
407            img
408        }
409
410        #[inline]
411        fn to_planar<T: Clone>(img: &ImageData<T, Self>) -> CowImageData<'_, T, Planar> {
412            Self::into_planar(img.clone()).into()
413        }
414
415        #[inline]
416        fn to_normal<T>(img: &ImageData<T, Self>) -> CowImageData<'_, T, Normal> {
417            img.into()
418        }
419
420        #[inline]
421        fn layout<T>(_img: &ImageData<T, Self>) -> PixelStorage {
422            PixelStorage::Normal
423        }
424    }
425
426    /// The pixels are laid out however they were in the file
427    ///
428    /// Does not provide direct access to the underlying buffer.
429    /// To read the image, either convert it to planar/normal layout
430    /// or accept it as-is with [`ImageData<T, Raw>::accept_current_layout()`]
431    #[derive(Clone, Debug)]
432    pub struct Raw;
433    impl Layout for Raw {
434        type Storage = PixelStorage;
435        fn into_planar<T: Clone>(img: ImageData<T, Self>) -> ImageData<T, Planar> {
436            ImageData::<T, Planar> {
437                inner: match img.layout {
438                    PixelStorage::Planar => img.inner,
439                    PixelStorage::Normal => normal_to_planar(img.inner),
440                },
441                layout: PhantomData,
442            }
443        }
444
445        fn into_normal<T: Clone>(img: ImageData<T, Self>) -> ImageData<T, Normal> {
446            ImageData::<T, Normal> {
447                inner: match img.layout {
448                    PixelStorage::Planar => planar_to_normal(img.inner),
449                    PixelStorage::Normal => img.inner,
450                },
451                layout: PhantomData,
452            }
453        }
454
455        fn to_planar<T: Clone>(img: &ImageData<T, Self>) -> CowImageData<'_, T, Planar> {
456            CowImageData::<T, Planar> {
457                inner: match img.layout {
458                    PixelStorage::Planar => (&img.inner).into(),
459                    PixelStorage::Normal => normal_to_planar(img.inner.clone()).into(),
460                },
461                layout: PhantomData,
462            }
463        }
464
465        fn to_normal<T: Clone>(img: &ImageData<T, Self>) -> CowImageData<'_, T, Normal> {
466            CowImageData::<T, Normal> {
467                inner: match img.layout {
468                    PixelStorage::Planar => planar_to_normal(img.inner.clone()).into(),
469                    PixelStorage::Normal => (&img.inner).into(),
470                },
471                layout: PhantomData,
472            }
473        }
474
475        #[inline]
476        fn layout<T>(img: &ImageData<T, Self>) -> PixelStorage {
477            img.layout
478        }
479    }
480
481    /// The exact same thing as [`Raw`] except [`ImageData<T, AcceptCurrent>`] implements [`Deref`], meaning its inner data can be accessed
482    #[derive(Clone, Debug)]
483    pub struct Accept;
484    impl Layout for Accept {
485        type Storage = <Raw as Layout>::Storage;
486
487        fn into_planar<T: Clone>(img: ImageData<T, Self>) -> ImageData<T, Planar> {
488            ImageData::<T, Planar> {
489                inner: match img.layout {
490                    PixelStorage::Planar => img.inner,
491                    PixelStorage::Normal => normal_to_planar(img.inner),
492                },
493                layout: PhantomData,
494            }
495        }
496
497        fn into_normal<T: Clone>(img: ImageData<T, Self>) -> ImageData<T, Normal> {
498            ImageData::<T, Normal> {
499                inner: match img.layout {
500                    PixelStorage::Planar => planar_to_normal(img.inner),
501                    PixelStorage::Normal => img.inner,
502                },
503                layout: PhantomData,
504            }
505        }
506
507        fn to_planar<T: Clone>(img: &ImageData<T, Self>) -> CowImageData<'_, T, Planar> {
508            CowImageData::<T, Planar> {
509                inner: match img.layout {
510                    PixelStorage::Planar => (&img.inner).into(),
511                    PixelStorage::Normal => normal_to_planar(img.inner.clone()).into(),
512                },
513                layout: PhantomData,
514            }
515        }
516
517        fn to_normal<T: Clone>(img: &ImageData<T, Self>) -> CowImageData<'_, T, Normal> {
518            CowImageData::<T, Normal> {
519                inner: match img.layout {
520                    PixelStorage::Planar => planar_to_normal(img.inner.clone()).into(),
521                    PixelStorage::Normal => (&img.inner).into(),
522                },
523                layout: PhantomData,
524            }
525        }
526
527        fn layout<T>(img: &ImageData<T, Self>) -> PixelStorage {
528            img.layout
529        }
530    }
531
532    pub(crate) trait Known: Layout {}
533    impl Known for Planar {}
534    impl Known for Normal {}
535    impl Known for Accept {}
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541    use std::ptr;
542    use ndarray::{s, Array3, ArrayD};
543
544    #[test]
545    fn cow_no_premature_copy() {
546        // doesn't actually matter what's in the array since we're only testing reference equality, so just make it a single value
547        let arr = ArrayD::zeros(IxDyn(&[1]));
548
549        let mut cow = CowImageData::<u16, Accept> {
550            inner: (&arr).into(),
551            layout: PixelStorage::Planar,
552        };
553        let inner = cow.inner.as_slice().unwrap().as_ptr();
554        let inner_clone = cow.inner.clone().as_slice().unwrap().as_ptr();
555        let clone = cow.clone().as_slice().unwrap().as_ptr();
556        let to_owned = cow.to_owned().as_slice().unwrap().as_ptr();
557        let deref = cow.deref().as_slice().unwrap().as_ptr();
558        let mut_deref = cow.deref_mut().as_slice().unwrap().as_ptr();
559        let mut_slice = cow.deref_mut().as_slice_mut().unwrap().as_ptr();
560        assert!(ptr::eq(inner, inner_clone));
561        // cloning a borrowed cow just clones the reference, not the data
562        assert!(ptr::eq(inner_clone, clone));
563        assert!(!ptr::eq(clone, to_owned));
564        assert!(ptr::eq(clone, deref));
565        // the data only clones when the inner buffer is accessed mutably, not when deref_mut() is called
566        assert!(ptr::eq(deref, mut_deref));
567        assert!(!ptr::eq(mut_deref, mut_slice));
568
569        // no-copy conversions to planar layout
570
571        let planar = ImageData::<u16, Planar> {
572            inner: arr.clone(),
573            layout: PhantomData,
574        };
575        let mut cow = planar.to_planar_layout();
576        assert!(ptr::eq(planar.as_slice().unwrap(), cow.as_slice().unwrap()));
577        // should clone when we request write access with as_slice_mut()
578        assert!(!ptr::eq(planar.as_slice().unwrap(), cow.as_slice_mut().unwrap()));
579
580        let raw_planar = ImageData::<u16, Raw> {
581            inner: arr.clone(),
582            layout: PixelStorage::Planar,
583        };
584        let mut cow = raw_planar.to_planar_layout();
585        assert!(ptr::eq(raw_planar.inner.as_slice().unwrap(), cow.as_slice().unwrap()));
586        // should clone when we request write access with as_slice_mut()
587        assert!(!ptr::eq(raw_planar.inner.as_slice().unwrap(), cow.as_slice_mut().unwrap()));
588
589        let accept_planar = raw_planar.accept_current_layout();
590        let mut cow = accept_planar.to_planar_layout();
591        assert!(ptr::eq(accept_planar.as_slice().unwrap(), cow.as_slice().unwrap()));
592        // should clone when we request write access with as_slice_mut()
593        assert!(!ptr::eq(accept_planar.as_slice().unwrap(), cow.as_slice_mut().unwrap()));
594
595        // copying conversions to planar layout
596
597        let normal = ImageData::<u16, Normal> {
598            inner: arr.clone(),
599            layout: PhantomData,
600        };
601        let cow = normal.to_planar_layout();
602        assert!(!ptr::eq(normal.as_slice().unwrap(), cow.as_slice().unwrap()));
603
604        let raw_normal = ImageData::<u16, Raw> {
605            inner: arr.clone(),
606            layout: PixelStorage::Normal,
607        };
608        let cow = raw_normal.to_planar_layout();
609        assert!(!ptr::eq(raw_normal.inner.as_slice().unwrap(), cow.as_slice().unwrap()));
610
611        let accept_normal = raw_normal.accept_current_layout();
612        let cow = accept_normal.to_planar_layout();
613        assert!(!ptr::eq(accept_normal.as_slice().unwrap(), cow.as_slice().unwrap()));
614
615        // no-copy conversions to normal layout
616
617        let normal = ImageData::<u16, Normal> {
618            inner: arr.clone(),
619            layout: PhantomData,
620        };
621        let mut cow = normal.to_normal_layout();
622        assert!(ptr::eq(normal.as_slice().unwrap(), cow.as_slice().unwrap()));
623        // should clone when we request write access with as_slice_mut()
624        assert!(!ptr::eq(normal.as_slice().unwrap(), cow.as_slice_mut().unwrap()));
625
626        let raw_normal = ImageData::<u16, Raw> {
627            inner: arr.clone(),
628            layout: PixelStorage::Normal,
629        };
630        let mut cow = raw_normal.to_normal_layout();
631        assert!(ptr::eq(raw_normal.inner.as_slice().unwrap(), cow.as_slice().unwrap()));
632        // should clone when we request write access with as_slice_mut()
633        assert!(!ptr::eq(raw_normal.inner.as_slice().unwrap(), cow.as_slice_mut().unwrap()));
634
635        let accept_normal = raw_normal.accept_current_layout();
636        let mut cow = accept_normal.to_normal_layout();
637        assert!(ptr::eq(accept_normal.as_slice().unwrap(), cow.as_slice().unwrap()));
638        // should clone when we request write access with as_slice_mut()
639        assert!(!ptr::eq(accept_normal.as_slice().unwrap(), cow.as_slice_mut().unwrap()));
640
641        // copying conversions to normal layout
642
643        let arr = ArrayD::zeros(IxDyn(&[256, 256]));
644        let planar = ImageData::<u16, Planar> {
645            inner: arr.clone(),
646            layout: PhantomData,
647        };
648        let cow = planar.to_normal_layout();
649        assert!(!ptr::eq(planar.as_slice().unwrap(), cow.as_slice().unwrap()));
650
651        let raw_planar = ImageData::<u16, Raw> {
652            inner: arr.clone(),
653            layout: PixelStorage::Planar,
654        };
655        let cow = raw_planar.to_normal_layout();
656        assert!(!ptr::eq(raw_planar.inner.as_slice().unwrap(), cow.as_slice().unwrap()));
657
658        let accept_planar = raw_planar.accept_current_layout();
659        let cow = accept_planar.to_normal_layout();
660        assert!(!ptr::eq(accept_planar.as_slice().unwrap(), cow.as_slice().unwrap()));
661    }
662
663    #[test]
664    fn layout_conversions() {
665        // https://stackoverflow.com/a/56762490
666        let mut array: Array3<u8> = Array3::zeros((200, 250, 3)); // 200x250 RGB
667        for ((x, y, z), v) in array.indexed_iter_mut() {
668            *v = match z {
669                0 => y as u8,
670                1 => x as u8,
671                2 => 255 - (x as u8).min(y as u8),
672                _ => unreachable!(),
673            };
674        }
675
676        macro_rules! test_with_origin {
677            ($l:ty, $layout:expr, $sanity:expr) => {
678                let raw = ImageData::<u8, $l> {
679                    inner: array.clone().into_dyn(),
680                    layout: $layout,
681                };
682                assert_eq!(raw.layout(), $sanity);
683
684                let into_planar = raw.clone().into_planar_layout();
685                let into_normal = raw.clone().into_normal_layout();
686                let to_planar = raw.to_planar_layout();
687                let to_normal = raw.to_normal_layout();
688                assert_eq!(into_planar.shape(), &[3, 200, 250]);
689                assert_eq!(into_normal.shape(), &[200, 250, 3]);
690                assert_eq!(to_planar.shape(), &[3, 200, 250]);
691                assert_eq!(to_normal.shape(), &[200, 250, 3]);
692                assert_eq!(into_planar.layout(), PixelStorage::Planar);
693                assert_eq!(into_normal.layout(), PixelStorage::Normal);
694                assert_eq!(into_planar.num_channels(), 3);
695                assert_eq!(into_normal.num_channels(), 3);
696                assert_eq!(into_planar.num_dimensions(), 2);
697                assert_eq!(into_normal.num_dimensions(), 2);
698                // TODO
699                // assert_eq!(to_planar.layout(), PixelStorage::Planar);
700                // assert_eq!(to_normal.layout(), PixelStorage::Normal);
701                for c in 0..3 {
702                    // checks equivalence across to_planar -> to_normal -> into_planar -> into_normal
703                    assert!(to_planar.slice(s![c, .., ..]) == to_normal.slice(s![.., .., c]));
704                    assert_eq!(to_normal.slice(s![.., .., c]), into_planar.slice(s![c, .., ..]));
705                    assert_eq!(into_planar.slice(s![c, .., ..]), into_normal.slice(s![.., .., c]));
706                }
707            }
708        }
709
710        test_with_origin!(Normal, PhantomData, PixelStorage::Normal);
711        test_with_origin!(Raw, PixelStorage::Normal, PixelStorage::Normal);
712        test_with_origin!(Accept, PixelStorage::Normal, PixelStorage::Normal);
713
714        array = array.into_shape((3, 200, 250)).unwrap();
715
716        test_with_origin!(Planar, PhantomData, PixelStorage::Planar);
717        test_with_origin!(Raw, PixelStorage::Planar, PixelStorage::Planar);
718        test_with_origin!(Accept, PixelStorage::Planar, PixelStorage::Planar);
719    }
720
721    #[test]
722    fn dyn_image_conversions() {
723        macro_rules! test_conversion {
724            ($i:ident, $t:ty) => {
725                let arr = ArrayD::<$t>::zeros(IxDyn(&[2, 3, 4, 5]));
726                let there = arr.into_dyn_img(PixelStorage::Normal);
727                assert!(matches!(there, DynImageData::$i(_)));
728                let back_again: Result<ImageData<$t>, _> = there.try_into();
729                assert!(back_again.is_ok());
730            }
731        }
732        test_conversion!(UInt8, u8);
733        test_conversion!(UInt16, u16);
734        test_conversion!(UInt32, u32);
735        test_conversion!(UInt64, u64);
736        test_conversion!(Float32, f32);
737        test_conversion!(Float64, f64);
738        test_conversion!(Complex32, Complex<f32>);
739        test_conversion!(Complex64, Complex<f64>);
740    }
741}