ndarray_image/
lib.rs

1use image::{
2    Bgr, Bgra, ImageBuffer, ImageError, ImageResult, Luma, LumaA, Pixel, Primitive, Rgb, Rgba,
3};
4use ndarray::ShapeBuilder;
5use ndarray::{Array2, Array3, ArrayView, ArrayViewMut, Ix2, Ix3};
6use std::ops::Deref;
7use std::path::Path;
8
9/// This newtype struct can wrap an image from either the `ndarray` or `image` crates to
10/// automatically allow them to be turned `into()` the equivalents in the other crate.
11/// This works without copying.
12pub struct NdImage<T>(pub T);
13
14pub type NdGray<'a, A = u8> = ArrayView<'a, A, Ix2>;
15pub type NdGrayMut<'a, A = u8> = ArrayViewMut<'a, A, Ix2>;
16pub type NdColor<'a, A = u8> = ArrayView<'a, A, Ix3>;
17pub type NdColorMut<'a, A = u8> = ArrayViewMut<'a, A, Ix3>;
18
19pub type ImgLuma<'a, A = u8> = ImageBuffer<Luma<A>, &'a [A]>;
20pub type ImgLumaA<'a, A = u8> = ImageBuffer<LumaA<A>, &'a [A]>;
21pub type ImgRgb<'a, A = u8> = ImageBuffer<Rgb<A>, &'a [A]>;
22pub type ImgRgba<'a, A = u8> = ImageBuffer<Rgba<A>, &'a [A]>;
23pub type ImgBgr<'a, A = u8> = ImageBuffer<Bgr<A>, &'a [A]>;
24pub type ImgBgra<'a, A = u8> = ImageBuffer<Bgra<A>, &'a [A]>;
25
26pub enum Colors {
27    Luma,
28    LumaA,
29    Rgb,
30    Rgba,
31    Bgr,
32    Bgra,
33}
34
35/// Opens a gray image using the `image` crate and loads it into a 2d array.
36/// This performs a copy.
37pub fn open_gray_image(path: impl AsRef<Path>) -> ImageResult<Array2<u8>> {
38    let image = image::open(path)?;
39    let image = image.to_luma();
40    let image: NdGray = NdImage(&image).into();
41    Ok(image.to_owned())
42}
43
44/// Opens a color image using the `image` crate and loads it into a 3d array.
45/// This performs a copy.
46pub fn open_image(path: impl AsRef<Path>, colors: Colors) -> ImageResult<Array3<u8>> {
47    let image = image::open(path)?;
48    let image = match colors {
49        Colors::Luma => {
50            let image = image.to_luma();
51            let image: NdColor = NdImage(&image).into();
52            image.to_owned()
53        }
54        Colors::LumaA => {
55            let image = image.to_luma_alpha();
56            let image: NdColor = NdImage(&image).into();
57            image.to_owned()
58        }
59        Colors::Rgb => {
60            let image = image.to_rgb();
61            let image: NdColor = NdImage(&image).into();
62            image.to_owned()
63        }
64        Colors::Rgba => {
65            let image = image.to_rgba();
66            let image: NdColor = NdImage(&image).into();
67            image.to_owned()
68        }
69        Colors::Bgr => {
70            let image = image.to_bgr();
71            let image: NdColor = NdImage(&image).into();
72            image.to_owned()
73        }
74        Colors::Bgra => {
75            let image = image.to_bgra();
76            let image: NdColor = NdImage(&image).into();
77            image.to_owned()
78        }
79    };
80    Ok(image)
81}
82
83/// Saves a gray image using the `image` crate from a 3d array.
84pub fn save_gray_image(path: impl AsRef<Path>, image: NdGray<'_, u8>) -> ImageResult<()> {
85    let image: Option<ImgLuma> = NdImage(image.view()).into();
86    let image = image.ok_or_else(|| {
87        ImageError::Decoding(image::error::DecodingError::new(
88            image::error::ImageFormatHint::Unknown,
89            "non-contiguous ndarray Array",
90        ))
91    })?;
92    image.save(path)?;
93    Ok(())
94}
95
96/// Saves a color image using the `image` crate from a 3d array.
97pub fn save_image(
98    path: impl AsRef<Path>,
99    image: NdColor<'_, u8>,
100    colors: Colors,
101) -> ImageResult<()> {
102    match colors {
103        Colors::Luma => {
104            let image: Option<ImgLuma> = NdImage(image.view()).into();
105            let image = image.ok_or_else(|| {
106                ImageError::Decoding(image::error::DecodingError::new(
107                    image::error::ImageFormatHint::Unknown,
108                    "non-contiguous ndarray Array",
109                ))
110            })?;
111            image.save(path)?;
112        }
113        Colors::LumaA => {
114            let image: Option<ImgLumaA> = NdImage(image.view()).into();
115            let image = image.ok_or_else(|| {
116                ImageError::Decoding(image::error::DecodingError::new(
117                    image::error::ImageFormatHint::Unknown,
118                    "non-contiguous ndarray Array",
119                ))
120            })?;
121            image.save(path)?;
122        }
123        Colors::Rgb => {
124            let image: Option<ImgRgb> = NdImage(image.view()).into();
125            let image = image.ok_or_else(|| {
126                ImageError::Decoding(image::error::DecodingError::new(
127                    image::error::ImageFormatHint::Unknown,
128                    "non-contiguous ndarray Array",
129                ))
130            })?;
131            image.save(path)?;
132        }
133        Colors::Rgba => {
134            let image: Option<ImgRgba> = NdImage(image.view()).into();
135            let image = image.ok_or_else(|| {
136                ImageError::Decoding(image::error::DecodingError::new(
137                    image::error::ImageFormatHint::Unknown,
138                    "non-contiguous ndarray Array",
139                ))
140            })?;
141            image.save(path)?;
142        }
143        Colors::Bgr => {
144            let image: Option<ImgBgr> = NdImage(image.view()).into();
145            let image = image.ok_or_else(|| {
146                ImageError::Decoding(image::error::DecodingError::new(
147                    image::error::ImageFormatHint::Unknown,
148                    "non-contiguous ndarray Array",
149                ))
150            })?;
151            image.save(path)?;
152        }
153        Colors::Bgra => {
154            let image: Option<ImgBgra> = NdImage(image.view()).into();
155            let image = image.ok_or_else(|| {
156                ImageError::Decoding(image::error::DecodingError::new(
157                    image::error::ImageFormatHint::Unknown,
158                    "non-contiguous ndarray Array",
159                ))
160            })?;
161            image.save(path)?;
162        }
163    }
164    Ok(())
165}
166
167/// Turn grayscale images into 2d array views.
168impl<'a, C, A: 'static> Into<NdGray<'a, A>> for NdImage<&'a ImageBuffer<Luma<A>, C>>
169where
170    A: Primitive,
171    C: Deref<Target = [A]> + AsRef<[A]>,
172{
173    fn into(self) -> NdGray<'a, A> {
174        let NdImage(image) = self;
175        let (width, height) = image.dimensions();
176        let (width, height) = (width as usize, height as usize);
177        let slice: &'a [A] = unsafe { std::mem::transmute(image.as_flat_samples().as_slice()) };
178        ArrayView::from_shape((height, width).strides((width, 1)), slice).unwrap()
179    }
180}
181
182/// Turn grayscale images into mutable 2d array views.
183impl<'a, C, A: 'static> Into<NdGrayMut<'a, A>> for NdImage<&'a mut ImageBuffer<Luma<A>, C>>
184where
185    A: Primitive,
186    C: Deref<Target = [A]> + AsRef<[A]>,
187{
188    fn into(self) -> NdGrayMut<'a, A> {
189        let NdImage(image) = self;
190        let (width, height) = image.dimensions();
191        let (width, height) = (width as usize, height as usize);
192        #[allow(clippy::transmute_ptr_to_ref)]
193        let slice: &'a mut [A] =
194            unsafe { std::mem::transmute(image.as_flat_samples().as_slice() as *const [A]) };
195        ArrayViewMut::from_shape((height, width).strides((width, 1)), slice).unwrap()
196    }
197}
198
199/// Turn arbitrary images into 3d array views with one dimension for the color channel.
200impl<'a, C, P: 'static, A: 'static> Into<NdColor<'a, A>> for NdImage<&'a ImageBuffer<P, C>>
201where
202    A: Primitive,
203    P: Pixel<Subpixel = A>,
204    C: Deref<Target = [P::Subpixel]> + AsRef<[A]>,
205{
206    fn into(self) -> NdColor<'a, A> {
207        let NdImage(image) = self;
208        let (width, height) = image.dimensions();
209        let (width, height) = (width as usize, height as usize);
210        let channels = P::CHANNEL_COUNT as usize;
211        let slice: &'a [A] = unsafe { std::mem::transmute(image.as_flat_samples().as_slice()) };
212        ArrayView::from_shape(
213            (height, width, channels).strides((width * channels, channels, 1)),
214            slice,
215        )
216        .unwrap()
217    }
218}
219
220/// Turn arbitrary images into mutable 3d array views with one dimension for the color channel.
221impl<'a, C, P: 'static, A: 'static> Into<NdColorMut<'a, A>> for NdImage<&'a mut ImageBuffer<P, C>>
222where
223    A: Primitive,
224    P: Pixel<Subpixel = A>,
225    C: Deref<Target = [P::Subpixel]> + AsRef<[A]>,
226{
227    fn into(self) -> NdColorMut<'a, A> {
228        let NdImage(image) = self;
229        let (width, height) = image.dimensions();
230        let (width, height) = (width as usize, height as usize);
231        let channels = P::CHANNEL_COUNT as usize;
232        #[allow(clippy::transmute_ptr_to_ref)]
233        let slice: &'a mut [A] =
234            unsafe { std::mem::transmute(image.as_flat_samples().as_slice() as *const [A]) };
235        ArrayViewMut::from_shape(
236            (height, width, channels).strides((width * channels, channels, 1)),
237            slice,
238        )
239        .unwrap()
240    }
241}
242
243/// Turn 2d `ArrayView` into a `Luma` image.
244///
245/// Can fail if the `ArrayView` is not contiguous.
246impl<'a, A: 'static> Into<Option<ImgLuma<'a, A>>> for NdImage<NdGray<'a, A>>
247where
248    A: Primitive,
249{
250    fn into(self) -> Option<ImgLuma<'a, A>> {
251        let NdImage(image) = self;
252        if let [height, width] = *image.shape() {
253            image.to_slice().map(|slice| {
254                ImageBuffer::from_raw(width as u32, height as u32, slice)
255                    .expect("failed to create image from slice")
256            })
257        } else {
258            unreachable!("the ndarray had more than 2 dimensions");
259        }
260    }
261}
262
263/// Turn 3d `ArrayView` into a `Luma` image.
264///
265/// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels.
266impl<'a, A: 'static> Into<Option<ImgLuma<'a, A>>> for NdImage<NdColor<'a, A>>
267where
268    A: Primitive,
269{
270    fn into(self) -> Option<ImgLuma<'a, A>> {
271        let NdImage(image) = self;
272        if let [height, width, 1] = *image.shape() {
273            image.to_slice().map(|slice| {
274                ImageBuffer::from_raw(width as u32, height as u32, slice)
275                    .expect("failed to create image from raw vec")
276            })
277        } else {
278            None
279        }
280    }
281}
282
283/// Turn 3d `ArrayView` into a `LumaA` image.
284///
285/// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels.
286impl<'a, A: 'static> Into<Option<ImgLumaA<'a, A>>> for NdImage<NdColor<'a, A>>
287where
288    A: Primitive,
289{
290    fn into(self) -> Option<ImgLumaA<'a, A>> {
291        let NdImage(image) = self;
292        if let [height, width, 2] = *image.shape() {
293            image.to_slice().map(|slice| {
294                ImageBuffer::from_raw(width as u32, height as u32, slice)
295                    .expect("failed to create image from raw vec")
296            })
297        } else {
298            None
299        }
300    }
301}
302
303/// Turn 3d `ArrayView` into a `Rgb` image.
304///
305/// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels.
306impl<'a, A: 'static> Into<Option<ImgRgb<'a, A>>> for NdImage<NdColor<'a, A>>
307where
308    A: Primitive,
309{
310    fn into(self) -> Option<ImgRgb<'a, A>> {
311        let NdImage(image) = self;
312        if let [height, width, 3] = *image.shape() {
313            image.to_slice().map(|slice| {
314                ImageBuffer::from_raw(width as u32, height as u32, slice)
315                    .expect("failed to create image from raw vec")
316            })
317        } else {
318            None
319        }
320    }
321}
322
323/// Turn 3d `ArrayView` into a `Rgba` image.
324///
325/// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels.
326impl<'a, A: 'static> Into<Option<ImgRgba<'a, A>>> for NdImage<NdColor<'a, A>>
327where
328    A: Primitive,
329{
330    fn into(self) -> Option<ImgRgba<'a, A>> {
331        let NdImage(image) = self;
332        if let [height, width, 4] = *image.shape() {
333            image.to_slice().map(|slice| {
334                ImageBuffer::from_raw(width as u32, height as u32, slice)
335                    .expect("failed to create image from raw vec")
336            })
337        } else {
338            None
339        }
340    }
341}
342
343/// Turn 3d `ArrayView` into a `Bgr` image.
344///
345/// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels.
346impl<'a, A: 'static> Into<Option<ImgBgr<'a, A>>> for NdImage<NdColor<'a, A>>
347where
348    A: Primitive,
349{
350    fn into(self) -> Option<ImgBgr<'a, A>> {
351        let NdImage(image) = self;
352        if let [height, width, 3] = *image.shape() {
353            image.to_slice().map(|slice| {
354                ImageBuffer::from_raw(width as u32, height as u32, slice)
355                    .expect("failed to create image from raw vec")
356            })
357        } else {
358            None
359        }
360    }
361}
362
363/// Turn 3d `ArrayView` into a `Bgra` image.
364///
365/// Can fail if the `ArrayView` is not contiguous or has the wrong number of channels.
366impl<'a, A: 'static> Into<Option<ImgBgra<'a, A>>> for NdImage<NdColor<'a, A>>
367where
368    A: Primitive,
369{
370    fn into(self) -> Option<ImgBgra<'a, A>> {
371        let NdImage(image) = self;
372        if let [height, width, 4] = *image.shape() {
373            image.to_slice().map(|slice| {
374                ImageBuffer::from_raw(width as u32, height as u32, slice)
375                    .expect("failed to create image from raw vec")
376            })
377        } else {
378            None
379        }
380    }
381}