1use std::path::Path;
5
6use fast_image_resize::PixelType;
7use image::{DynamicImage, ImageBuffer, Luma, Rgb, open as open_dynamic};
8use npyz::{self, DType, NpyFile, TypeChar};
9
10use crate::constant;
11use crate::cv::transform;
12use crate::error::PineappleError;
13use crate::im::{MaskingStyle, PineappleBuffer, PineappleMaskView, PineappleView};
14use crate::impl_enum_dispatch;
15use crate::io::write_numpy;
16
17#[derive(Debug, Clone)]
48pub enum PineappleImage {
49 U8(PineappleBuffer<u8, Vec<u8>>),
50 U16(PineappleBuffer<u16, Vec<u16>>),
51 U32(PineappleBuffer<u32, Vec<u32>>),
52 U64(PineappleBuffer<u64, Vec<u64>>),
53 I32(PineappleBuffer<i32, Vec<i32>>),
54 I64(PineappleBuffer<i64, Vec<i64>>),
55 F32(PineappleBuffer<f32, Vec<f32>>),
56 F64(PineappleBuffer<f64, Vec<f64>>),
57}
58
59impl PineappleImage {
62 pub fn open<P: AsRef<Path>>(path: P) -> Result<PineappleImage, PineappleError> {
73 let extension = path
74 .as_ref()
75 .extension()
76 .and_then(|s| s.to_str())
77 .map(|s| s.to_lowercase());
78
79 if let Some(ext) = extension {
80 if ext == "npy" {
81 if let Ok(bytes) = std::fs::read(&path)
82 && let Ok(npy) = NpyFile::new(&bytes[..]) {
83 Self::new_from_numpy(npy.clone()).unwrap();
84 return Self::new_from_numpy(npy);
85 }
86
87 return Err(PineappleError::ImageReadError);
88 }
89
90 if constant::IMAGE_DYNAMIC_FORMATS.iter().any(|e| e == &ext) {
91 if let Ok(image) = open_dynamic(&path) {
92 return Self::new_from_default(image);
93 }
94
95 return Err(PineappleError::ImageReadError);
96 }
97 }
98
99 Err(PineappleError::ImageExtensionError)
100 }
101
102 pub fn new_from_default(image: DynamicImage) -> Result<PineappleImage, PineappleError> {
119 let width = image.width();
120 let height = image.height();
121
122 match image {
123 DynamicImage::ImageLuma8(buffer) => Ok(PineappleImage::U8(PineappleBuffer::new(
124 width,
125 height,
126 1,
127 buffer.into_raw(),
128 )?)),
129 DynamicImage::ImageLumaA8(buffer) => Ok(PineappleImage::U8(PineappleBuffer::new(
130 width,
131 height,
132 1,
133 buffer
134 .into_raw()
135 .chunks_exact(2)
136 .map(|pixel| pixel[0])
137 .collect(),
138 )?)),
139 DynamicImage::ImageLuma16(buffer) => Ok(PineappleImage::U16(PineappleBuffer::new(
140 width,
141 height,
142 1,
143 buffer.into_raw(),
144 )?)),
145 DynamicImage::ImageLumaA16(buffer) => Ok(PineappleImage::U16(PineappleBuffer::new(
146 width,
147 height,
148 1,
149 buffer
150 .into_raw()
151 .chunks_exact(2)
152 .map(|pixel| pixel[0])
153 .collect(),
154 )?)),
155 DynamicImage::ImageRgb8(buffer) => Ok(PineappleImage::U8(PineappleBuffer::new(
156 width,
157 height,
158 3,
159 buffer.into_raw(),
160 )?)),
161 DynamicImage::ImageRgba8(buffer) => Ok(PineappleImage::U8(PineappleBuffer::new(
162 width,
163 height,
164 3,
165 buffer
166 .into_raw()
167 .chunks_exact(4)
168 .flat_map(|pixel| [pixel[0], pixel[1], pixel[2]])
169 .collect(),
170 )?)),
171 DynamicImage::ImageRgb16(buffer) => Ok(PineappleImage::U16(PineappleBuffer::new(
172 width,
173 height,
174 3,
175 buffer.into_raw(),
176 )?)),
177 DynamicImage::ImageRgba16(buffer) => Ok(PineappleImage::U16(PineappleBuffer::new(
178 width,
179 height,
180 3,
181 buffer
182 .into_raw()
183 .chunks_exact(4)
184 .flat_map(|pixel| [pixel[0], pixel[1], pixel[2]])
185 .collect(),
186 )?)),
187 DynamicImage::ImageRgb32F(buffer) => Ok(PineappleImage::F32(PineappleBuffer::new(
188 width,
189 height,
190 3,
191 buffer.into_raw(),
192 )?)),
193 DynamicImage::ImageRgba32F(buffer) => Ok(PineappleImage::F32(PineappleBuffer::new(
194 width,
195 height,
196 3,
197 buffer
198 .into_raw()
199 .chunks_exact(4)
200 .flat_map(|pixel| [pixel[0], pixel[1], pixel[2]])
201 .collect(),
202 )?)),
203 _ => Err(PineappleError::ImageError(
204 "A dynamic image with a valid data type was not detected.",
205 )),
206 }
207 }
208
209 pub fn new_from_numpy(npy: NpyFile<&[u8]>) -> Result<PineappleImage, PineappleError> {
226 let shape = npy.shape().to_vec();
227
228 let (h, w, c) = match shape.len() {
229 2 => (shape[0] as u32, shape[1] as u32, 1u32),
230 3 => (shape[0] as u32, shape[1] as u32, shape[2] as u32),
231 _ => {
232 return Err(PineappleError::ImageError(
233 "Numpy array inputs must have an (H, W) or (H, W, C) shape.",
234 ));
235 }
236 };
237
238 match npy.dtype() {
239 DType::Plain(x) => match (x.type_char(), x.size_field()) {
240 (TypeChar::Uint, 1) => Ok(PineappleImage::U8(PineappleBuffer::new(
241 w,
242 h,
243 c,
244 npy.into_vec().unwrap(),
245 )?)),
246 (TypeChar::Uint, 2) => Ok(PineappleImage::U16(PineappleBuffer::new(
247 w,
248 h,
249 c,
250 npy.into_vec().unwrap(),
251 )?)),
252 (TypeChar::Int, 4) => Ok(PineappleImage::I32(PineappleBuffer::new(
253 w,
254 h,
255 c,
256 npy.into_vec().unwrap(),
257 )?)),
258 (TypeChar::Int, 8) => Ok(PineappleImage::I64(PineappleBuffer::new(
259 w,
260 h,
261 c,
262 npy.into_vec().unwrap(),
263 )?)),
264 (TypeChar::Float, 4) => Ok(PineappleImage::F32(PineappleBuffer::new(
265 w,
266 h,
267 c,
268 npy.into_vec().unwrap(),
269 )?)),
270 (TypeChar::Float, 8) => Ok(PineappleImage::F64(PineappleBuffer::new(
271 w,
272 h,
273 c,
274 npy.into_vec().unwrap(),
275 )?)),
276 _ => Err(PineappleError::ImageError(
277 "A numpy array with a valid data type was not detected.",
278 )),
279 },
280 _ => Err(PineappleError::ImageError(
281 "Only plain numpy arrays are currentled supported.",
282 )),
283 }
284 }
285
286 pub fn save<P: AsRef<Path>>(self, path: P) -> Result<(), PineappleError> {
298 let extension = path
299 .as_ref()
300 .extension()
301 .and_then(|s| s.to_str())
302 .map(|s| s.to_lowercase());
303
304 if let Some(ext) = extension {
305 if ext == "npy" {
306 return self.save_as_numpy(path);
307 }
308
309 if constant::IMAGE_DYNAMIC_FORMATS.iter().any(|e| e == &ext) {
310 return self.save_as_default(path);
311 }
312 }
313
314 Err(PineappleError::ImageExtensionError)
315 }
316
317 pub fn save_as_default<P: AsRef<Path>>(self, path: P) -> Result<(), PineappleError> {
335 let channels = self.channels();
336 match (self, channels) {
337 (PineappleImage::U8(buffer), 1) => {
338 let image_buffer = ImageBuffer::<Luma<u8>, Vec<u8>>::from_raw(
339 buffer.width(),
340 buffer.height(),
341 buffer.into_raw(),
342 )
343 .ok_or(PineappleError::ImageWriteError)?;
344
345 image_buffer
346 .save(path)
347 .map_err(|_| PineappleError::ImageWriteError)
348 }
349 (PineappleImage::U16(buffer), 1) => {
350 let image_buffer = ImageBuffer::<Luma<u16>, Vec<u16>>::from_raw(
351 buffer.width(),
352 buffer.height(),
353 buffer.into_raw(),
354 )
355 .ok_or(PineappleError::ImageWriteError)?;
356
357 image_buffer
358 .save(path)
359 .map_err(|_| PineappleError::ImageWriteError)
360 }
361 (PineappleImage::U8(buffer), 3) => {
362 let image_buffer = ImageBuffer::<Rgb<u8>, Vec<u8>>::from_raw(
363 buffer.width(),
364 buffer.height(),
365 buffer.into_raw(),
366 )
367 .ok_or(PineappleError::ImageWriteError)?;
368
369 image_buffer
370 .save(path)
371 .map_err(|_| PineappleError::ImageWriteError)
372 }
373 (PineappleImage::U16(buffer), 3) => {
374 let image_buffer = ImageBuffer::<Rgb<u16>, Vec<u16>>::from_raw(
375 buffer.width(),
376 buffer.height(),
377 buffer.into_raw(),
378 )
379 .ok_or(PineappleError::ImageWriteError)?;
380
381 image_buffer
382 .save(path)
383 .map_err(|_| PineappleError::ImageWriteError)
384 }
385 (PineappleImage::F32(buffer), 3) => {
386 let image_buffer = ImageBuffer::<Rgb<f32>, Vec<f32>>::from_raw(
387 buffer.width(),
388 buffer.height(),
389 buffer.into_raw(),
390 )
391 .ok_or(PineappleError::ImageWriteError)?;
392
393 image_buffer
394 .save(path)
395 .map_err(|_| PineappleError::ImageWriteError)
396 }
397 _ => Err(PineappleError::ImageError(
398 "Only 1 or 3 channel RGB/grayscale images can be saved as a default image format (e.g. png).",
399 )),
400 }
401 }
402
403 pub fn save_as_numpy<P: AsRef<Path>>(self, path: P) -> Result<(), PineappleError> {
421 let shape = vec![
422 self.height() as u64,
423 self.width() as u64,
424 self.channels() as u64,
425 ];
426
427 match self {
428 PineappleImage::U8(buffer) => write_numpy(path.as_ref(), buffer.into_raw(), shape),
429 PineappleImage::U16(buffer) => write_numpy(path.as_ref(), buffer.into_raw(), shape),
430 PineappleImage::U32(buffer) => write_numpy(path.as_ref(), buffer.into_raw(), shape),
431 PineappleImage::U64(buffer) => write_numpy(path.as_ref(), buffer.into_raw(), shape),
432 PineappleImage::I32(buffer) => write_numpy(path.as_ref(), buffer.into_raw(), shape),
433 PineappleImage::I64(buffer) => write_numpy(path.as_ref(), buffer.into_raw(), shape),
434 PineappleImage::F32(buffer) => write_numpy(path.as_ref(), buffer.into_raw(), shape),
435 PineappleImage::F64(buffer) => write_numpy(path.as_ref(), buffer.into_raw(), shape),
436 }
437 }
438}
439
440impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; width(&self) -> u32);
445impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; height(&self) -> u32);
446impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; channels(&self) -> u32);
447impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; shape(&self) -> (u32, u32, u32));
448impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; len(&self) -> usize);
449impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; is_empty(&self) -> bool);
450
451impl PineappleImage {
452 pub fn dtype_min(&self) -> f64 {
454 match self {
455 PineappleImage::U8(_) => u8::MIN as f64,
456 PineappleImage::U16(_) => u16::MIN as f64,
457 PineappleImage::U32(_) => u32::MIN as f64,
458 PineappleImage::U64(_) => u64::MIN as f64,
459 PineappleImage::I32(_) => i32::MIN as f64,
460 PineappleImage::I64(_) => i64::MIN as f64,
461 PineappleImage::F32(_) => f32::MIN as f64,
462 PineappleImage::F64(_) => f64::MIN,
463 }
464 }
465
466 pub fn dtype_max(&self) -> f64 {
468 match self {
469 PineappleImage::U8(_) => u8::MAX as f64,
470 PineappleImage::U16(_) => u16::MAX as f64,
471 PineappleImage::U32(_) => u32::MAX as f64,
472 PineappleImage::U64(_) => u64::MAX as f64,
473 PineappleImage::I32(_) => i32::MAX as f64,
474 PineappleImage::I64(_) => i64::MAX as f64,
475 PineappleImage::F32(_) => f32::MAX as f64,
476 PineappleImage::F64(_) => f64::MAX,
477 }
478 }
479}
480
481impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; to_u8(&self) -> Vec<u8>);
486impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; to_u16(&self) -> Vec<u16>);
487impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; to_u32(&self) -> Vec<u32>);
488impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; to_f32(&self) -> Vec<f32>);
489impl_enum_dispatch!(PineappleImage, U8, U16, U32, I32, I64, U64, F32, F64; to_f64(&self) -> Vec<f64>);
490
491impl PineappleImage {
496 pub fn crop_view(&self, x: u32, y: u32, w: u32, h: u32) -> PineappleView<'_> {
505 match self {
506 PineappleImage::U8(buffer) => PineappleView::U8(buffer.crop_view(x, y, w, h)),
507 PineappleImage::U16(buffer) => PineappleView::U16(buffer.crop_view(x, y, w, h)),
508 PineappleImage::U32(buffer) => PineappleView::U32(buffer.crop_view(x, y, w, h)),
509 PineappleImage::U64(buffer) => PineappleView::U64(buffer.crop_view(x, y, w, h)),
510 PineappleImage::I32(buffer) => PineappleView::I32(buffer.crop_view(x, y, w, h)),
511 PineappleImage::I64(buffer) => PineappleView::I64(buffer.crop_view(x, y, w, h)),
512 PineappleImage::F32(buffer) => PineappleView::F32(buffer.crop_view(x, y, w, h)),
513 PineappleImage::F64(buffer) => PineappleView::F64(buffer.crop_view(x, y, w, h)),
514 }
515 }
516
517 pub fn crop(&self, x: u32, y: u32, w: u32, h: u32) -> Result<PineappleImage, PineappleError> {
526 match self {
527 PineappleImage::U8(buffer) => Ok(PineappleImage::U8(buffer.crop(x, y, w, h)?)),
528 PineappleImage::U16(buffer) => Ok(PineappleImage::U16(buffer.crop(x, y, w, h)?)),
529 PineappleImage::U32(buffer) => Ok(PineappleImage::U32(buffer.crop(x, y, w, h)?)),
530 PineappleImage::U64(buffer) => Ok(PineappleImage::U64(buffer.crop(x, y, w, h)?)),
531 PineappleImage::I32(buffer) => Ok(PineappleImage::I32(buffer.crop(x, y, w, h)?)),
532 PineappleImage::I64(buffer) => Ok(PineappleImage::I64(buffer.crop(x, y, w, h)?)),
533 PineappleImage::F32(buffer) => Ok(PineappleImage::F32(buffer.crop(x, y, w, h)?)),
534 PineappleImage::F64(buffer) => Ok(PineappleImage::F64(buffer.crop(x, y, w, h)?)),
535 }
536 }
537
538 pub fn crop_masked(
549 &self,
550 x: u32,
551 y: u32,
552 w: u32,
553 h: u32,
554 mask: &PineappleMaskView,
555 mask_style: MaskingStyle,
556 ) -> Result<PineappleImage, PineappleError> {
557 match self {
558 PineappleImage::U8(buffer) => Ok(PineappleImage::U8(
559 buffer.crop_masked(x, y, w, h, mask, mask_style)?,
560 )),
561 PineappleImage::U16(buffer) => Ok(PineappleImage::U16(
562 buffer.crop_masked(x, y, w, h, mask, mask_style)?,
563 )),
564 PineappleImage::U32(buffer) => Ok(PineappleImage::U32(
565 buffer.crop_masked(x, y, w, h, mask, mask_style)?,
566 )),
567 PineappleImage::U64(buffer) => Ok(PineappleImage::U64(
568 buffer.crop_masked(x, y, w, h, mask, mask_style)?,
569 )),
570 PineappleImage::I32(buffer) => Ok(PineappleImage::I32(
571 buffer.crop_masked(x, y, w, h, mask, mask_style)?,
572 )),
573 PineappleImage::I64(buffer) => Ok(PineappleImage::I64(
574 buffer.crop_masked(x, y, w, h, mask, mask_style)?,
575 )),
576 PineappleImage::F32(buffer) => Ok(PineappleImage::F32(
577 buffer.crop_masked(x, y, w, h, mask, mask_style)?,
578 )),
579 PineappleImage::F64(buffer) => Ok(PineappleImage::F64(
580 buffer.crop_masked(x, y, w, h, mask, mask_style)?,
581 )),
582 }
583 }
584
585 pub fn resize(&self, width: u32, height: u32) -> Result<PineappleImage, PineappleError> {
592 let channels = self.channels();
593 match (self, channels) {
594 (PineappleImage::U8(buffer), 1) => Ok(PineappleImage::U8(PineappleBuffer::new(
595 width,
596 height,
597 1,
598 transform::resize_bilinear_fast(
599 &DynamicImage::ImageLuma8(
600 ImageBuffer::<Luma<u8>, Vec<u8>>::from_raw(
601 buffer.width(),
602 buffer.height(),
603 buffer.as_raw().to_vec(),
604 )
605 .ok_or(PineappleError::ImageError("Failed to resize image"))?,
606 ),
607 width,
608 height,
609 PixelType::U8,
610 ),
611 )?)),
612 (PineappleImage::U8(buffer), 3) => Ok(PineappleImage::U8(
613 PineappleBuffer::new(
614 width,
615 height,
616 3,
617 transform::resize_bilinear_fast(
618 &DynamicImage::ImageRgb8(
619 ImageBuffer::<Rgb<u8>, Vec<u8>>::from_raw(
620 buffer.width(),
621 buffer.height(),
622 buffer.as_raw().to_vec(),
623 )
624 .ok_or(PineappleError::ImageError("Failed to resize image"))?,
625 ),
626 width,
627 height,
628 PixelType::U8x3,
629 ),
630 )
631 .map_err(|_| PineappleError::ImageError("Failed to resize image."))?,
632 )),
633 (PineappleImage::U16(buffer), 1) => Ok(PineappleImage::U16(PineappleBuffer::new(
634 width,
635 height,
636 1,
637 transform::resize_bilinear_default(
638 &ImageBuffer::<Luma<u16>, Vec<u16>>::from_raw(
639 buffer.width(),
640 buffer.height(),
641 buffer.as_raw().to_vec(),
642 )
643 .ok_or(PineappleError::ImageError("Failed to resize image"))?,
644 width,
645 height,
646 )
647 .into_raw(),
648 )?)),
649 (PineappleImage::U16(buffer), 3) => Ok(PineappleImage::U16(PineappleBuffer::new(
650 width,
651 height,
652 3,
653 transform::resize_bilinear_default(
654 &ImageBuffer::<Rgb<u16>, Vec<u16>>::from_raw(
655 buffer.width(),
656 buffer.height(),
657 buffer.as_raw().to_vec(),
658 )
659 .ok_or(PineappleError::ImageError("Failed to resize image"))?,
660 width,
661 height,
662 )
663 .into_raw(),
664 )?)),
665 (PineappleImage::F32(buffer), 3) => Ok(PineappleImage::F32(PineappleBuffer::new(
666 width,
667 height,
668 3,
669 transform::resize_bilinear_default(
670 &ImageBuffer::<Rgb<f32>, Vec<f32>>::from_raw(
671 buffer.width(),
672 buffer.height(),
673 buffer.as_raw().to_vec(),
674 )
675 .ok_or(PineappleError::ImageError("Failed to resize image"))?,
676 width,
677 height,
678 )
679 .into_raw(),
680 )?)),
681 (PineappleImage::U8(buffer), _) => Ok(PineappleImage::U8(PineappleBuffer::new(
682 width,
683 height,
684 channels,
685 transform::resize_bilinear_general::<u8>(
686 buffer.as_raw(),
687 buffer.width() as usize,
688 buffer.height() as usize,
689 channels as usize,
690 width as usize,
691 height as usize,
692 true,
693 ),
694 )?)),
695 (PineappleImage::U16(buffer), _) => Ok(PineappleImage::U16(PineappleBuffer::new(
696 width,
697 height,
698 channels,
699 transform::resize_bilinear_general::<u16>(
700 buffer.as_raw(),
701 buffer.width() as usize,
702 buffer.height() as usize,
703 channels as usize,
704 width as usize,
705 height as usize,
706 true,
707 ),
708 )?)),
709 (PineappleImage::U32(buffer), _) => Ok(PineappleImage::U32(PineappleBuffer::new(
710 width,
711 height,
712 channels,
713 transform::resize_bilinear_general::<u32>(
714 buffer.as_raw(),
715 buffer.width() as usize,
716 buffer.height() as usize,
717 channels as usize,
718 width as usize,
719 height as usize,
720 true,
721 ),
722 )?)),
723 (PineappleImage::U64(buffer), _) => Ok(PineappleImage::U64(PineappleBuffer::new(
724 width,
725 height,
726 channels,
727 transform::resize_bilinear_general::<u64>(
728 buffer.as_raw(),
729 buffer.width() as usize,
730 buffer.height() as usize,
731 channels as usize,
732 width as usize,
733 height as usize,
734 true,
735 ),
736 )?)),
737 (PineappleImage::I32(buffer), _) => Ok(PineappleImage::I32(PineappleBuffer::new(
738 width,
739 height,
740 channels,
741 transform::resize_bilinear_general::<i32>(
742 buffer.as_raw(),
743 buffer.width() as usize,
744 buffer.height() as usize,
745 channels as usize,
746 width as usize,
747 height as usize,
748 true,
749 ),
750 )?)),
751 (PineappleImage::I64(buffer), _) => Ok(PineappleImage::I64(PineappleBuffer::new(
752 width,
753 height,
754 channels,
755 transform::resize_bilinear_general::<i64>(
756 buffer.as_raw(),
757 buffer.width() as usize,
758 buffer.height() as usize,
759 channels as usize,
760 width as usize,
761 height as usize,
762 true,
763 ),
764 )?)),
765 (PineappleImage::F32(buffer), _) => Ok(PineappleImage::F32(PineappleBuffer::new(
766 width,
767 height,
768 channels,
769 transform::resize_bilinear_general::<f32>(
770 buffer.as_raw(),
771 buffer.width() as usize,
772 buffer.height() as usize,
773 channels as usize,
774 width as usize,
775 height as usize,
776 true,
777 ),
778 )?)),
779 (PineappleImage::F64(buffer), _) => Ok(PineappleImage::F64(PineappleBuffer::new(
780 width,
781 height,
782 channels,
783 transform::resize_bilinear_general::<f64>(
784 buffer.as_raw(),
785 buffer.width() as usize,
786 buffer.height() as usize,
787 channels as usize,
788 width as usize,
789 height as usize,
790 true,
791 ),
792 )?)),
793 }
794 }
795}
796
797#[cfg(test)]
800mod test {
801
802 use super::*;
803 use image::{
804 DynamicImage, GrayAlphaImage, GrayImage, ImageBuffer, Luma, LumaA, Rgb, Rgb32FImage,
805 RgbImage, Rgba, Rgba32FImage, RgbaImage,
806 };
807
808 const TEST_GRAY: &str = "../data/tests/test_grayscale";
809 const TEST_RGB: &str = "../data/tests/test_rgb";
810
811 #[test]
812 fn test_grayscale_open() {
813 let extensions = [
814 ".jpeg", ".npy", ".pbm", ".png", ".tga", ".tif", "_f32.npy", "_f64.npy", "_i32.npy",
815 "_i64.npy", "_u16.npy",
816 ];
817
818 for ext in extensions.into_iter() {
819 let img = PineappleImage::open(format!("{}{}", TEST_GRAY, ext));
820 assert!(img.is_ok(), "{}", ext);
821
822 let img = img.unwrap();
823 assert_eq!(img.width(), 621);
824 assert_eq!(img.height(), 621);
825 assert_eq!(img.channels(), 1, "{}", ext);
826 }
827
828 let extensions = [".bmp", ".webp"];
831 for ext in extensions.into_iter() {
832 let img = PineappleImage::open(format!("{}{}", TEST_GRAY, ext));
833 assert!(img.is_ok(), "{}", ext);
834
835 let img = img.unwrap();
836 assert_eq!(img.width(), 621);
837 assert_eq!(img.height(), 621);
838 assert_eq!(img.channels(), 3, "{}", ext);
839 }
840 }
841
842 #[test]
843 fn test_rgb_open() {
844 let extensions = [
845 ".bmp", ".jpeg", ".npy", ".pbm", ".png", ".tga", ".tif", ".webp", "_f32.npy",
846 "_f64.npy", "_i32.npy", "_i64.npy",
847 ];
848
849 for ext in extensions.into_iter() {
850 let img = PineappleImage::open(format!("{}{}", TEST_RGB, ext));
851 assert!(img.is_ok(), "{}", ext);
852
853 let img = img.unwrap();
854 assert_eq!(img.width(), 621);
855 assert_eq!(img.height(), 621);
856 assert_eq!(img.channels(), 3, "{}", ext);
857 }
858 }
859
860 #[test]
861 fn test_grayscale_save() {
862 const TEST_DEFAULT: &str = "TEST_SAVE_DEFAULT_GRAY.png";
863 const TEST_NUMPY: &str = "TEST_SAVE_NUMPY_GRAY.npy";
864
865 let img =
866 PineappleImage::U8(PineappleBuffer::<u8, Vec<u8>>::new(2, 2, 1, vec![0, 1, 2, 3]).unwrap());
867
868 img.clone().save(TEST_DEFAULT).unwrap();
869 img.clone().save(TEST_NUMPY).unwrap();
870
871 let img_default = PineappleImage::open(TEST_DEFAULT).unwrap();
872 let img_numpy = PineappleImage::open(TEST_NUMPY).unwrap();
873
874 assert_eq!(img.to_u8(), img_default.to_u8());
875 assert_eq!(img.to_u8(), img_numpy.to_u8());
876
877 std::fs::remove_file(TEST_DEFAULT).unwrap();
878 std::fs::remove_file(TEST_NUMPY).unwrap();
879 }
880
881 #[test]
882 fn test_rgb_save() {
883 const TEST_DEFAULT: &str = "TEST_SAVE_DEFAULT_RGB.png";
884 const TEST_NUMPY: &str = "TEST_SAVE_NUMPY_RGB.npy";
885
886 let img = PineappleImage::U8(
887 PineappleBuffer::<u8, Vec<u8>>::new(2, 2, 3, vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
888 .unwrap(),
889 );
890
891 img.clone().save(TEST_DEFAULT).unwrap();
892 img.clone().save(TEST_NUMPY).unwrap();
893
894 let img_default = PineappleImage::open(TEST_DEFAULT).unwrap();
895 let img_numpy = PineappleImage::open(TEST_NUMPY).unwrap();
896
897 assert_eq!(img.to_u8(), img_default.to_u8());
898 assert_eq!(img.to_u8(), img_numpy.to_u8());
899
900 std::fs::remove_file(TEST_DEFAULT).unwrap();
901 std::fs::remove_file(TEST_NUMPY).unwrap();
902 }
903
904 #[test]
905 fn test_multichannel_save() {
906 const TEST_DEFAULT: &str = "TEST_SAVE_DEFAULT_MULTI.png";
907 const TEST_NUMPY: &str = "TEST_SAVE_NUMPY_MULTI.npy";
908
909 let img = PineappleImage::U8(
910 PineappleBuffer::<u8, Vec<u8>>::new(
911 2,
912 2,
913 4,
914 vec![0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 8, 9, 10, 11, 11],
915 )
916 .unwrap(),
917 );
918
919 let failure = img.clone().save(TEST_DEFAULT);
920 assert!(failure.is_err());
921
922 img.clone().save(TEST_NUMPY).unwrap();
923
924 let img_numpy = PineappleImage::open(TEST_NUMPY).unwrap();
925 assert_eq!(img.to_u8(), img_numpy.to_u8());
926 std::fs::remove_file(TEST_NUMPY).unwrap();
927 }
928
929 #[test]
930 fn test_dynamic_gray_u8() {
931 let dynamic = DynamicImage::ImageLuma8(GrayImage::from_fn(10, 10, |x, _| Luma([x as u8])));
932
933 let image = PineappleImage::new_from_default(dynamic);
934 assert!(image.is_ok());
935
936 let image = image.unwrap();
937 assert_eq!(image.width(), 10);
938 assert_eq!(image.height(), 10);
939 }
940
941 #[test]
942 fn test_dynamic_gray_alpha_u8() {
943 let dynamic =
944 DynamicImage::ImageLumaA8(GrayAlphaImage::from_fn(10, 10, |x, _| LumaA([x as u8, 10])));
945
946 let image = PineappleImage::new_from_default(dynamic);
947 assert!(image.is_ok());
948
949 let image = image.unwrap();
950 assert_eq!(image.width(), 10);
951 assert_eq!(image.height(), 10);
952
953 let data = image.to_u8();
954 assert_eq!(data[0], 0);
955 assert_eq!(data[1], 1);
956 }
957
958 #[test]
959 fn test_dynamic_gray_u16() {
960 let dynamic = DynamicImage::ImageLuma16(ImageBuffer::<Luma<u16>, Vec<u16>>::from_fn(
961 10,
962 10,
963 |x, _| Luma([x as u16]),
964 ));
965
966 let image = PineappleImage::new_from_default(dynamic);
967 assert!(image.is_ok());
968
969 let image = image.unwrap();
970 assert_eq!(image.width(), 10);
971 assert_eq!(image.height(), 10);
972 }
973
974 #[test]
975 fn test_dynamic_gray_alpha_u16() {
976 let dynamic = DynamicImage::ImageLumaA16(ImageBuffer::<LumaA<u16>, Vec<u16>>::from_fn(
977 10,
978 10,
979 |x, _| LumaA([x as u16, 10]),
980 ));
981
982 let image = PineappleImage::new_from_default(dynamic);
983 assert!(image.is_ok());
984
985 let image = image.unwrap();
986 assert_eq!(image.width(), 10);
987 assert_eq!(image.height(), 10);
988
989 let data = image.to_u16();
990 assert_eq!(data[0], 0);
991 assert_eq!(data[1], 1);
992 }
993
994 #[test]
995 fn test_dynamic_rgb_u8() {
996 let dynamic = DynamicImage::ImageRgb8(RgbImage::from_fn(10, 10, |x, _| {
997 Rgb([x as u8, x as u8, x as u8])
998 }));
999
1000 let image = PineappleImage::new_from_default(dynamic);
1001 assert!(image.is_ok());
1002
1003 let image = image.unwrap();
1004 assert_eq!(image.width(), 10);
1005 assert_eq!(image.height(), 10);
1006
1007 let data = image.to_u8();
1008 assert_eq!((data[0], data[1], data[2]), (0, 0, 0));
1009 assert_eq!((data[3], data[4], data[5]), (1, 1, 1));
1010 }
1011
1012 #[test]
1013 fn test_dynamic_rgb_alpha_u8() {
1014 let dynamic = DynamicImage::ImageRgba8(RgbaImage::from_fn(10, 10, |x, _| {
1015 Rgba([x as u8, x as u8, x as u8, 10])
1016 }));
1017
1018 let image = PineappleImage::new_from_default(dynamic);
1019 assert!(image.is_ok());
1020
1021 let image = image.unwrap();
1022 assert_eq!(image.width(), 10);
1023 assert_eq!(image.height(), 10);
1024
1025 let data = image.to_u8();
1026 assert_eq!((data[0], data[1], data[2]), (0, 0, 0));
1027 assert_eq!((data[3], data[4], data[5]), (1, 1, 1));
1028 }
1029
1030 #[test]
1031 fn test_dynamic_rgb_u16() {
1032 let dynamic = DynamicImage::ImageRgb16(ImageBuffer::<Rgb<u16>, Vec<u16>>::from_fn(
1033 10,
1034 10,
1035 |x, _| Rgb([x as u16, x as u16, x as u16]),
1036 ));
1037
1038 let image = PineappleImage::new_from_default(dynamic);
1039 assert!(image.is_ok());
1040
1041 let image = image.unwrap();
1042 assert_eq!(image.width(), 10);
1043 assert_eq!(image.height(), 10);
1044
1045 let data = image.to_u16();
1046 assert_eq!((data[0], data[1], data[2]), (0, 0, 0));
1047 assert_eq!((data[3], data[4], data[5]), (1, 1, 1));
1048 }
1049
1050 #[test]
1051 fn test_dynamic_rgb_alpha_u16() {
1052 let dynamic = DynamicImage::ImageRgba16(ImageBuffer::<Rgba<u16>, Vec<u16>>::from_fn(
1053 10,
1054 10,
1055 |x, _| Rgba([x as u16, x as u16, x as u16, 10]),
1056 ));
1057
1058 let image = PineappleImage::new_from_default(dynamic);
1059 assert!(image.is_ok());
1060
1061 let image = image.unwrap();
1062 assert_eq!(image.width(), 10);
1063 assert_eq!(image.height(), 10);
1064
1065 let data = image.to_u16();
1066 assert_eq!((data[0], data[1], data[2]), (0, 0, 0));
1067 assert_eq!((data[3], data[4], data[5]), (1, 1, 1));
1068 }
1069
1070 #[test]
1071 fn test_dynamic_rgb_f32() {
1072 let dynamic = DynamicImage::ImageRgb32F(Rgb32FImage::from_fn(10, 10, |x, _| {
1073 Rgb([x as f32, x as f32, x as f32])
1074 }));
1075
1076 let image = PineappleImage::new_from_default(dynamic);
1077 assert!(image.is_ok());
1078
1079 let image = image.unwrap();
1080 assert_eq!(image.width(), 10);
1081 assert_eq!(image.height(), 10);
1082 }
1083
1084 #[test]
1085 fn test_dynamic_rgb_alpha_f32() {
1086 let dynamic = DynamicImage::ImageRgba32F(Rgba32FImage::from_fn(10, 10, |x, _| {
1087 Rgba([x as f32, x as f32, x as f32, 10.0])
1088 }));
1089
1090 let image = PineappleImage::new_from_default(dynamic);
1091 assert!(image.is_ok());
1092
1093 let image = image.unwrap();
1094 assert_eq!(image.width(), 10);
1095 assert_eq!(image.height(), 10);
1096 }
1097
1098 #[test]
1099 fn test_resize_u8() {
1100 let dynamic = DynamicImage::ImageLuma8(GrayImage::from_fn(10, 10, |x, _| Luma([x as u8])));
1101
1102 let one_channel = PineappleImage::new_from_default(dynamic).unwrap();
1103
1104 let downsampled = one_channel.resize(3, 4).unwrap();
1105 assert_eq!(downsampled.width(), 3);
1106 assert_eq!(downsampled.height(), 4);
1107
1108 let upsampled = one_channel.resize(23, 24).unwrap();
1109 assert_eq!(upsampled.width(), 23);
1110 assert_eq!(upsampled.height(), 24);
1111
1112 let dynamic = DynamicImage::ImageRgb8(RgbImage::from_fn(10, 10, |x, _| {
1113 Rgb([x as u8, x as u8, x as u8])
1114 }));
1115
1116 let three_channel = PineappleImage::new_from_default(dynamic).unwrap();
1117
1118 let downsampled = three_channel.resize(3, 4).unwrap();
1119 assert_eq!(downsampled.width(), 3);
1120 assert_eq!(downsampled.height(), 4);
1121
1122 let upsampled = three_channel.resize(23, 24).unwrap();
1123 assert_eq!(upsampled.width(), 23);
1124 assert_eq!(upsampled.height(), 24);
1125
1126 let two_channel = PineappleImage::U8(
1127 PineappleBuffer::<u8, Vec<u8>>::new(2, 2, 2, vec![0, 1, 2, 3, 4, 5, 6, 7]).unwrap(),
1128 );
1129
1130 let downsampled = two_channel.resize(3, 4).unwrap();
1131 assert_eq!(downsampled.width(), 3);
1132 assert_eq!(downsampled.height(), 4);
1133
1134 let upsampled = two_channel.resize(23, 24).unwrap();
1135 assert_eq!(upsampled.width(), 23);
1136 assert_eq!(upsampled.height(), 24);
1137 }
1138
1139 #[test]
1140 fn test_resize_u16() {
1141 let dynamic = DynamicImage::ImageLuma16(ImageBuffer::<Luma<u16>, Vec<u16>>::from_fn(
1142 10,
1143 10,
1144 |x, _| Luma([x as u16]),
1145 ));
1146
1147 let one_channel = PineappleImage::new_from_default(dynamic).unwrap();
1148
1149 let downsampled = one_channel.resize(3, 4).unwrap();
1150 assert_eq!(downsampled.width(), 3);
1151 assert_eq!(downsampled.height(), 4);
1152
1153 let upsampled = one_channel.resize(23, 24).unwrap();
1154 assert_eq!(upsampled.width(), 23);
1155 assert_eq!(upsampled.height(), 24);
1156
1157 let dynamic = DynamicImage::ImageRgb16(ImageBuffer::<Rgb<u16>, Vec<u16>>::from_fn(
1158 10,
1159 10,
1160 |x, _| Rgb([x as u16, x as u16, x as u16]),
1161 ));
1162
1163 let three_channel = PineappleImage::new_from_default(dynamic).unwrap();
1164
1165 let downsampled = three_channel.resize(3, 4).unwrap();
1166 assert_eq!(downsampled.width(), 3);
1167 assert_eq!(downsampled.height(), 4);
1168
1169 let upsampled = three_channel.resize(23, 24).unwrap();
1170 assert_eq!(upsampled.width(), 23);
1171 assert_eq!(upsampled.height(), 24);
1172
1173 let two_channel = PineappleImage::U16(
1174 PineappleBuffer::<u16, Vec<u16>>::new(2, 2, 2, vec![0, 1, 2, 3, 4, 5, 6, 7]).unwrap(),
1175 );
1176
1177 let downsampled = two_channel.resize(3, 4).unwrap();
1178 assert_eq!(downsampled.width(), 3);
1179 assert_eq!(downsampled.height(), 4);
1180
1181 let upsampled = two_channel.resize(23, 24).unwrap();
1182 assert_eq!(upsampled.width(), 23);
1183 assert_eq!(upsampled.height(), 24);
1184 }
1185}