1use crate::{allocator::ImageAllocator, error::ImageError};
2use kornia_tensor::{Tensor, Tensor2, Tensor3};
3use rayon::prelude::*;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct ImageSize {
24 pub width: usize,
26 pub height: usize,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum PixelFormat {
33 U8,
35 U16,
37 F32,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum InterpolationMode {
44 Bilinear,
46 Nearest,
48 Lanczos,
50 Bicubic,
52}
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct ImageLayout {
57 pub image_size: ImageSize,
59 pub channels: u8,
61 pub pixel_format: PixelFormat,
63}
64
65impl ImageLayout {
66 pub fn new(image_size: ImageSize, channels: u8, pixel_format: PixelFormat) -> Self {
68 Self {
69 image_size,
70 channels,
71 pixel_format,
72 }
73 }
74}
75
76impl std::fmt::Display for ImageSize {
77 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
78 write!(
79 f,
80 "ImageSize {{ width: {}, height: {} }}",
81 self.width, self.height
82 )
83 }
84}
85
86impl From<[usize; 2]> for ImageSize {
87 fn from(size: [usize; 2]) -> Self {
88 ImageSize {
89 width: size[0],
90 height: size[1],
91 }
92 }
93}
94
95impl From<ImageSize> for [u32; 2] {
96 fn from(size: ImageSize) -> Self {
97 [size.width as u32, size.height as u32]
98 }
99}
100
101impl ImageSize {
102 #[inline]
104 pub fn index(&self, y: usize, x: usize) -> usize {
105 y * self.width + x
106 }
107
108 #[inline]
110 pub fn coords(&self, idx: usize) -> (usize, usize) {
111 let y = idx / self.width;
112 let x = idx % self.width;
113 (y, x)
114 }
115}
116
117#[derive(Clone)]
118pub struct Image<T, const C: usize, A: ImageAllocator>(pub Tensor3<T, A>);
122
123impl<T, const C: usize, A: ImageAllocator> std::ops::Deref for Image<T, C, A> {
125 type Target = Tensor3<T, A>;
126
127 fn deref(&self) -> &Self::Target {
129 &self.0
130 }
131}
132
133impl<T, const C: usize, A: ImageAllocator> std::ops::DerefMut for Image<T, C, A> {
135 fn deref_mut(&mut self) -> &mut Self::Target {
137 &mut self.0
138 }
139}
140
141impl<T, const C: usize, A: ImageAllocator> Image<T, C, A> {
142 pub fn new(size: ImageSize, data: Vec<T>, alloc: A) -> Result<Self, ImageError> {
178 if data.len() != size.width * size.height * C {
180 return Err(ImageError::InvalidChannelShape(
181 data.len(),
182 size.width * size.height * C,
183 ));
184 }
185
186 Ok(Self(Tensor3::from_shape_vec(
188 [size.height, size.width, C],
189 data,
190 alloc,
191 )?))
192 }
193
194 pub fn from_size_val(size: ImageSize, val: T, alloc: A) -> Result<Self, ImageError>
227 where
228 T: Clone,
229 {
230 let data = vec![val; size.width * size.height * C];
231 let image = Image::new(size, data, alloc)?;
232
233 Ok(image)
234 }
235
236 pub unsafe fn from_raw_parts(
252 size: ImageSize,
253 data: *const T,
254 len: usize,
255 alloc: A,
256 ) -> Result<Self, ImageError>
257 where
258 T: Clone,
259 {
260 Tensor::from_raw_parts([size.height, size.width, C], data, len, alloc)?.try_into()
261 }
262
263 pub fn from_size_slice(size: ImageSize, data: &[T], alloc: A) -> Result<Self, ImageError>
279 where
280 T: Clone,
281 {
282 let tensor: Tensor3<T, A> =
283 Tensor::from_shape_slice([size.height, size.width, C], data, alloc)?;
284 Image::try_from(tensor)
285 }
286
287 pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Result<Image<U, C, A>, ImageError> {
297 let data = self.as_slice().iter().map(f).collect::<Vec<U>>();
298 let alloc = self.storage.alloc();
299 Image::<U, C, A>::new(self.size(), data, alloc.clone())
300 }
301
302 pub fn cast<U>(&self) -> Result<Image<U, C, A>, ImageError>
337 where
338 U: num_traits::NumCast + Copy,
339 T: num_traits::NumCast + Copy,
340 {
341 let data = self
342 .as_slice()
343 .iter()
344 .map(|&x| U::from(x).ok_or(ImageError::CastError))
345 .collect::<Result<Vec<U>, ImageError>>()?;
346
347 let alloc = self.storage.alloc().clone();
348 let tensor = Tensor3::from_shape_vec(self.0.shape, data, alloc)?;
349 Ok(Image(tensor))
350 }
351
352 pub fn channel(&self, channel: usize) -> Result<Image<T, 1, A>, ImageError>
365 where
366 T: Clone,
367 {
368 if channel >= C {
369 return Err(ImageError::ChannelIndexOutOfBounds(channel, C));
370 }
371
372 let channel_data = self
373 .as_slice()
374 .iter()
375 .skip(channel)
376 .step_by(C)
377 .cloned()
378 .collect();
379
380 let alloc = self.storage.alloc();
381
382 Image::new(self.size(), channel_data, alloc.clone())
383 }
384
385 pub fn split_channels(&self) -> Result<Vec<Image<T, 1, A>>, ImageError>
409 where
410 T: Copy,
411 {
412 let mut channels = Vec::with_capacity(C);
413
414 for i in 0..C {
415 channels.push(self.channel(i)?);
416 }
417
418 Ok(channels)
419 }
420
421 pub fn size(&self) -> ImageSize {
423 ImageSize {
424 width: self.shape[1],
425 height: self.shape[0],
426 }
427 }
428
429 pub fn cols(&self) -> usize {
431 self.shape[1]
432 }
433
434 pub fn rows(&self) -> usize {
436 self.shape[0]
437 }
438
439 pub fn width(&self) -> usize {
441 self.cols()
442 }
443
444 pub fn height(&self) -> usize {
446 self.rows()
447 }
448
449 pub fn num_channels(&self) -> usize {
451 C
452 }
453
454 #[allow(clippy::uninit_vec)]
490 pub fn cast_and_scale<U>(self, scale: U) -> Result<Image<U, C, A>, ImageError>
491 where
492 U: num_traits::NumCast + std::ops::Mul<Output = U> + Clone + Copy + Send + Sync,
493 T: num_traits::NumCast + Clone + Copy + Send + Sync,
494 {
495 let slice = self.as_slice();
496 let mut casted_data = Vec::with_capacity(slice.len());
497 unsafe {
499 casted_data.set_len(slice.len());
500 }
501
502 slice
503 .par_iter()
504 .zip(casted_data.par_iter_mut())
505 .try_for_each(|(&x, out)| {
506 let xu = U::from(x).ok_or(ImageError::CastError)?;
507 *out = xu * scale;
508 Ok::<(), ImageError>(())
509 })?;
510
511 let alloc = self.storage.alloc();
512 Image::new(self.size(), casted_data, alloc.clone())
513 }
514
515 #[allow(clippy::uninit_vec)]
525 pub fn scale_and_cast<U>(&self, scale: T) -> Result<Image<U, C, A>, ImageError>
526 where
527 U: num_traits::NumCast + Clone + Copy + Send + Sync,
528 T: num_traits::NumCast + std::ops::Mul<Output = T> + Clone + Copy + Send + Sync,
529 {
530 let slice = self.as_slice();
531 let mut casted_data = Vec::with_capacity(slice.len());
532 unsafe {
534 casted_data.set_len(slice.len());
535 }
536
537 slice
538 .par_iter()
539 .zip(casted_data.par_iter_mut())
540 .try_for_each(|(&x, out)| {
541 *out = U::from(x * scale).ok_or(ImageError::CastError)?;
542 Ok::<(), ImageError>(())
543 })?;
544
545 let alloc = self.storage.alloc();
546 Image::new(self.size(), casted_data, alloc.clone())
547 }
548
549 pub fn get_pixel(&self, x: usize, y: usize, ch: usize) -> Result<&T, ImageError> {
564 if x >= self.width() || y >= self.height() {
565 return Err(ImageError::PixelIndexOutOfBounds(
566 x,
567 y,
568 self.width(),
569 self.height(),
570 ));
571 }
572
573 if ch >= C {
574 return Err(ImageError::ChannelIndexOutOfBounds(ch, C));
575 }
576
577 let val = match self.get([y, x, ch]) {
578 Some(v) => v,
579 None => return Err(ImageError::ImageDataNotContiguous),
580 };
581
582 Ok(val)
583 }
584
585 pub fn set_pixel(&mut self, x: usize, y: usize, ch: usize, val: T) -> Result<(), ImageError> {
601 if x >= self.width() || y >= self.height() {
602 return Err(ImageError::PixelIndexOutOfBounds(
603 x,
604 y,
605 self.width(),
606 self.height(),
607 ));
608 }
609
610 if ch >= C {
611 return Err(ImageError::ChannelIndexOutOfBounds(ch, C));
612 }
613
614 let idx = y * self.width() * C + x * C + ch;
615 self.as_slice_mut()[idx] = val;
616
617 Ok(())
618 }
619
620 pub fn into_vec(self) -> Vec<T> {
622 self.0.into_vec()
623 }
624
625 pub fn to_vec(&self) -> Vec<T>
627 where
628 T: Clone,
629 {
630 self.as_slice().to_vec()
631 }
632}
633
634impl<T, A: ImageAllocator> TryFrom<Tensor2<T, A>> for Image<T, 1, A>
636where
637 T: Clone,
638{
639 type Error = ImageError;
640
641 fn try_from(value: Tensor2<T, A>) -> Result<Self, Self::Error> {
642 let alloc = value.storage.alloc();
643
644 Self::from_size_slice(
645 ImageSize {
646 width: value.shape[1],
647 height: value.shape[0],
648 },
649 value.as_slice(),
650 alloc.clone(),
651 )
652 }
653}
654
655impl<T, const C: usize, A: ImageAllocator> TryFrom<Tensor3<T, A>> for Image<T, C, A> {
657 type Error = ImageError;
658
659 fn try_from(value: Tensor3<T, A>) -> Result<Self, Self::Error> {
660 if value.shape[2] != C {
661 return Err(ImageError::InvalidChannelShape(value.shape[2], C));
662 }
663 Ok(Self(value))
664 }
665}
666
667impl<T, const C: usize, A: ImageAllocator> TryInto<Tensor3<T, A>> for Image<T, C, A> {
668 type Error = ImageError;
669
670 fn try_into(self) -> Result<Tensor3<T, A>, Self::Error> {
671 Ok(self.0)
672 }
673}
674
675#[cfg(test)]
676mod tests {
677 use crate::image::{Image, ImageError, ImageSize};
678 use kornia_tensor::{CpuAllocator, Tensor};
679
680 #[test]
681 fn test_image_size() {
682 let image_size = ImageSize {
683 width: 10,
684 height: 20,
685 };
686 assert_eq!(image_size.width, 10);
687 assert_eq!(image_size.height, 20);
688 }
689
690 #[test]
691 fn test_image_size_index_coords() {
692 let size = ImageSize {
693 width: 8,
694 height: 6,
695 };
696 assert_eq!(size.index(0, 0), 0);
698 assert_eq!(size.index(1, 0), 8);
699 assert_eq!(size.index(2, 3), 2 * 8 + 3);
700 assert_eq!(size.index(5, 7), 5 * 8 + 7);
701
702 assert_eq!(size.coords(0), (0, 0));
704 assert_eq!(size.coords(8), (1, 0));
705 assert_eq!(size.coords(19), (2, 3));
706 assert_eq!(size.coords(47), (5, 7));
707 }
708
709 #[test]
710 fn test_image_smoke() -> Result<(), ImageError> {
711 let image = Image::<u8, 3, CpuAllocator>::new(
712 ImageSize {
713 width: 10,
714 height: 20,
715 },
716 vec![0u8; 10 * 20 * 3],
717 CpuAllocator,
718 )?;
719 assert_eq!(image.size().width, 10);
720 assert_eq!(image.size().height, 20);
721 assert_eq!(image.num_channels(), 3);
722
723 Ok(())
724 }
725
726 #[test]
727 fn test_image_from_vec() -> Result<(), ImageError> {
728 let image: Image<f32, 3, CpuAllocator> = Image::new(
729 ImageSize {
730 height: 3,
731 width: 2,
732 },
733 vec![0.0; 3 * 2 * 3],
734 CpuAllocator,
735 )?;
736 assert_eq!(image.size().width, 2);
737 assert_eq!(image.size().height, 3);
738 assert_eq!(image.num_channels(), 3);
739
740 Ok(())
741 }
742
743 #[test]
744 fn test_image_from_empty_vec() -> Result<(), ImageError> {
745 let image: Result<Image<f32, 1, CpuAllocator>, ImageError> = Image::new(
746 ImageSize {
747 height: 0,
748 width: 0,
749 },
750 vec![0.0; 0],
751 CpuAllocator,
752 );
753 assert!(
754 image.is_ok(),
755 "Image::new should create an empty image and drop it without segfault"
756 );
757
758 Ok(())
759 }
760
761 #[test]
762 fn test_image_cast() -> Result<(), ImageError> {
763 let data = vec![0, 1, 2, 3, 4, 5];
764 let image_u8 = Image::<_, 3, CpuAllocator>::new(
765 ImageSize {
766 height: 2,
767 width: 1,
768 },
769 data,
770 CpuAllocator,
771 )?;
772 assert_eq!(image_u8.get([1, 0, 2]), Some(&5u8));
773
774 let image_i32: Image<i32, 3, CpuAllocator> = image_u8.cast()?;
775 assert_eq!(image_i32.get([1, 0, 2]), Some(&5i32));
776
777 Ok(())
778 }
779
780 #[test]
781 fn test_image_rgbd() -> Result<(), ImageError> {
782 let image = Image::<f32, 4, CpuAllocator>::new(
783 ImageSize {
784 height: 2,
785 width: 3,
786 },
787 vec![0f32; 2 * 3 * 4],
788 CpuAllocator,
789 )?;
790 assert_eq!(image.size().width, 3);
791 assert_eq!(image.size().height, 2);
792 assert_eq!(image.num_channels(), 4);
793
794 Ok(())
795 }
796
797 #[test]
798 fn test_image_channel() -> Result<(), ImageError> {
799 let image = Image::<f32, 3, CpuAllocator>::new(
800 ImageSize {
801 height: 2,
802 width: 1,
803 },
804 vec![0., 1., 2., 3., 4., 5.],
805 CpuAllocator,
806 )?;
807
808 let channel = image.channel(2)?;
809 assert_eq!(channel.get([1, 0, 0]), Some(&5.0f32));
810
811 Ok(())
812 }
813
814 #[test]
815 fn test_image_split_channels() -> Result<(), ImageError> {
816 let image = Image::<f32, 3, CpuAllocator>::new(
817 ImageSize {
818 height: 2,
819 width: 1,
820 },
821 vec![0., 1., 2., 3., 4., 5.],
822 CpuAllocator,
823 )
824 .unwrap();
825 let channels = image.split_channels()?;
826 assert_eq!(channels.len(), 3);
827 assert_eq!(channels[0].get([1, 0, 0]), Some(&3.0f32));
828 assert_eq!(channels[1].get([1, 0, 0]), Some(&4.0f32));
829 assert_eq!(channels[2].get([1, 0, 0]), Some(&5.0f32));
830
831 Ok(())
832 }
833
834 #[test]
835 fn test_scale_and_cast() -> Result<(), ImageError> {
836 let data = vec![0u8, 0, 255, 0, 0, 255];
837 let image_u8 = Image::<u8, 3, CpuAllocator>::new(
838 ImageSize {
839 height: 2,
840 width: 1,
841 },
842 data,
843 CpuAllocator,
844 )?;
845 let image_f32 = image_u8.cast_and_scale::<f32>(1. / 255.0)?;
846 assert_eq!(image_f32.get([1, 0, 2]), Some(&1.0f32));
847
848 Ok(())
849 }
850
851 #[test]
852 fn test_cast_and_scale() -> Result<(), ImageError> {
853 let data = vec![0u8, 0, 255, 0, 0, 255];
854 let image_u8 = Image::<u8, 3, CpuAllocator>::new(
855 ImageSize {
856 height: 2,
857 width: 1,
858 },
859 data,
860 CpuAllocator,
861 )?;
862 let image_f32 = image_u8.cast_and_scale::<f32>(1. / 255.0)?;
863 assert_eq!(image_f32.get([1, 0, 2]), Some(&1.0f32));
864
865 Ok(())
866 }
867
868 #[test]
869 fn test_image_from_tensor() -> Result<(), ImageError> {
870 let data = vec![0u8, 1, 2, 3, 4, 5];
871 let tensor = Tensor::<u8, 2, _>::from_shape_vec([2, 3], data, CpuAllocator)?;
872
873 let image = Image::<u8, 1, CpuAllocator>::try_from(tensor.clone())?;
874 assert_eq!(image.size().width, 3);
875 assert_eq!(image.size().height, 2);
876 assert_eq!(image.num_channels(), 1);
877
878 let image_2: Image<u8, 1, CpuAllocator> = tensor.try_into()?;
879 assert_eq!(image_2.size().width, 3);
880 assert_eq!(image_2.size().height, 2);
881 assert_eq!(image_2.num_channels(), 1);
882
883 Ok(())
884 }
885
886 #[test]
887 fn test_image_from_tensor_3d() -> Result<(), ImageError> {
888 let tensor = Tensor::<u8, 3, CpuAllocator>::from_shape_vec(
889 [2, 3, 4],
890 vec![0u8; 2 * 3 * 4],
891 CpuAllocator,
892 )?;
893
894 let image = Image::<u8, 4, CpuAllocator>::try_from(tensor.clone())?;
895 assert_eq!(image.size().width, 3);
896 assert_eq!(image.size().height, 2);
897 assert_eq!(image.num_channels(), 4);
898
899 let image_2: Image<u8, 4, CpuAllocator> = tensor.try_into()?;
900 assert_eq!(image_2.size().width, 3);
901 assert_eq!(image_2.size().height, 2);
902 assert_eq!(image_2.num_channels(), 4);
903
904 Ok(())
905 }
906
907 #[test]
908 fn test_image_from_raw_parts() -> Result<(), ImageError> {
909 let data = vec![0u8, 1, 2, 3, 4, 5];
910 let image = unsafe {
911 Image::<_, 1, CpuAllocator>::from_raw_parts(
912 [2, 3].into(),
913 data.as_ptr(),
914 data.len(),
915 CpuAllocator,
916 )?
917 };
918 std::mem::forget(data);
919 assert_eq!(image.size().width, 2);
920 assert_eq!(image.size().height, 3);
921 assert_eq!(image.num_channels(), 1);
922 Ok(())
923 }
924
925 #[test]
926 fn test_get_pixel() -> Result<(), ImageError> {
927 let image = Image::<u8, 3, CpuAllocator>::new(
928 ImageSize {
929 height: 2,
930 width: 1,
931 },
932 vec![1, 2, 5, 19, 255, 128],
933 CpuAllocator,
934 )?;
935 assert_eq!(image.get_pixel(0, 0, 0)?, &1);
936 assert_eq!(image.get_pixel(0, 0, 1)?, &2);
937 assert_eq!(image.get_pixel(0, 0, 2)?, &5);
938 assert_eq!(image.get_pixel(0, 1, 0)?, &19);
939 assert_eq!(image.get_pixel(0, 1, 1)?, &255);
940 assert_eq!(image.get_pixel(0, 1, 2)?, &128);
941 Ok(())
942 }
943
944 #[test]
945 fn test_set_pixel() -> Result<(), ImageError> {
946 let mut image = Image::<u8, 3, CpuAllocator>::new(
947 ImageSize {
948 height: 2,
949 width: 1,
950 },
951 vec![1, 2, 5, 19, 255, 128],
952 CpuAllocator,
953 )?;
954
955 image.set_pixel(0, 0, 0, 128)?;
956 image.set_pixel(0, 1, 1, 25)?;
957
958 assert_eq!(image.get_pixel(0, 0, 0)?, &128);
959 assert_eq!(image.get_pixel(0, 1, 1)?, &25);
960
961 Ok(())
962 }
963
964 #[test]
965 fn test_image_map() -> Result<(), ImageError> {
966 let image_u8 = Image::<u8, 1, CpuAllocator>::new(
967 ImageSize {
968 height: 2,
969 width: 1,
970 },
971 vec![0, 128],
972 CpuAllocator,
973 )?;
974
975 let image_f32 = image_u8.map(|x| (x + 2) as f32)?;
976
977 assert_eq!(image_f32.size().width, 1);
978 assert_eq!(image_f32.size().height, 2);
979 assert_eq!(image_f32.num_channels(), 1);
980 assert_eq!(image_f32.get([0, 0, 0]), Some(&2.0f32));
981 assert_eq!(image_f32.get([1, 0, 0]), Some(&130.0f32));
982
983 Ok(())
984 }
985
986 #[test]
987 fn test_cast_round_trip() -> Result<(), ImageError> {
988 let data_f32 = vec![0.0f32, 128.0, 255.0, 10.0, 20.0, 30.0];
990 let image_f32 = Image::<f32, 3, CpuAllocator>::new(
991 ImageSize {
992 height: 2,
993 width: 1,
994 },
995 data_f32.clone(),
996 CpuAllocator,
997 )?;
998
999 let image_u8 = image_f32.cast::<u8>()?;
1000 let image_f32_rt = image_u8.cast::<f32>()?;
1001
1002 assert_eq!(image_f32_rt.size(), image_f32.size());
1003 assert_eq!(image_f32_rt.num_channels(), 3);
1004 for (original, round_tripped) in data_f32.iter().zip(image_f32_rt.as_slice()) {
1005 assert!((*original - round_tripped).abs() < 1.0);
1006 }
1007
1008 Ok(())
1009 }
1010
1011 #[test]
1012 fn test_cast_out_of_range() -> Result<(), ImageError> {
1013 let image_f32 = Image::<f32, 1, CpuAllocator>::new(
1015 ImageSize {
1016 height: 1,
1017 width: 1,
1018 },
1019 vec![f32::MAX],
1020 CpuAllocator,
1021 )?;
1022
1023 let result = image_f32.cast::<u8>();
1024 assert!(
1025 matches!(result, Err(ImageError::CastError)),
1026 "expected CastError for out-of-range f32 value"
1027 );
1028
1029 Ok(())
1030 }
1031
1032 #[test]
1033 fn test_cast_shape_preservation() -> Result<(), ImageError> {
1034 let data = vec![0u8, 64, 128, 192, 200, 255];
1036 let image_u8 = Image::<u8, 3, CpuAllocator>::new(
1037 ImageSize {
1038 height: 2,
1039 width: 1,
1040 },
1041 data.clone(),
1042 CpuAllocator,
1043 )?;
1044
1045 let image_f32 = image_u8.cast::<f32>()?;
1046
1047 assert_eq!(image_f32.size(), image_u8.size());
1048 assert_eq!(image_f32.num_channels(), 3);
1049 for (original, casted) in data.iter().zip(image_f32.as_slice()) {
1050 assert_eq!(*casted, *original as f32);
1051 }
1052
1053 Ok(())
1054 }
1055}