1#![forbid(unsafe_code)]
30use crate::ar30::{Ar30ByteOrder, Rgb30};
31use crate::convolution::{ConvolutionOptions, HorizontalConvolutionPass, VerticalConvolutionPass};
32use crate::image_size::ImageSize;
33use crate::image_store::{
34 AssociateAlpha, CheckStoreDensity, ImageStore, ImageStoreMut, UnassociateAlpha,
35};
36use crate::math::WeightsGenerator;
37use crate::nearest_sampler::resize_nearest;
38use crate::pic_scale_error::{PicScaleError, try_vec};
39use crate::resize_ar30::resize_ar30_impl;
40use crate::support::check_image_size_overflow;
41use crate::threading_policy::ThreadingPolicy;
42use crate::{
43 CbCr8ImageStore, CbCr16ImageStore, CbCrF32ImageStore, Planar8ImageStore, Planar16ImageStore,
44 PlanarF32ImageStore, ResamplingFunction, Rgb8ImageStore, Rgb16ImageStore, RgbF32ImageStore,
45 Rgba8ImageStore, Rgba16ImageStore, RgbaF32ImageStore,
46};
47use std::fmt::Debug;
48
49#[derive(Debug, Copy, Clone)]
50pub struct Scaler {
52 pub(crate) function: ResamplingFunction,
53 pub(crate) threading_policy: ThreadingPolicy,
54 pub workload_strategy: WorkloadStrategy,
55}
56
57pub trait Scaling {
59 fn set_threading_policy(&mut self, threading_policy: ThreadingPolicy);
72
73 fn resize_plane<'a>(
86 &'a self,
87 store: &ImageStore<'a, u8, 1>,
88 into: &mut ImageStoreMut<'a, u8, 1>,
89 ) -> Result<(), PicScaleError>;
90
91 fn resize_cbcr8<'a>(
106 &'a self,
107 store: &ImageStore<'a, u8, 2>,
108 into: &mut ImageStoreMut<'a, u8, 2>,
109 ) -> Result<(), PicScaleError>;
110
111 fn resize_gray_alpha<'a>(
126 &'a self,
127 store: &ImageStore<'a, u8, 2>,
128 into: &mut ImageStoreMut<'a, u8, 2>,
129 premultiply_alpha: bool,
130 ) -> Result<(), PicScaleError>;
131
132 fn resize_rgb<'a>(
145 &'a self,
146 store: &ImageStore<'a, u8, 3>,
147 into: &mut ImageStoreMut<'a, u8, 3>,
148 ) -> Result<(), PicScaleError>;
149
150 fn resize_rgba<'a>(
166 &'a self,
167 store: &ImageStore<'a, u8, 4>,
168 into: &mut ImageStoreMut<'a, u8, 4>,
169 premultiply_alpha: bool,
170 ) -> Result<(), PicScaleError>;
171}
172
173pub trait ScalingF32 {
175 fn resize_plane_f32<'a>(
188 &'a self,
189 store: &ImageStore<'a, f32, 1>,
190 into: &mut ImageStoreMut<'a, f32, 1>,
191 ) -> Result<(), PicScaleError>;
192
193 fn resize_cbcr_f32<'a>(
208 &'a self,
209 store: &ImageStore<'a, f32, 2>,
210 into: &mut ImageStoreMut<'a, f32, 2>,
211 ) -> Result<(), PicScaleError>;
212
213 fn resize_gray_alpha_f32<'a>(
228 &'a self,
229 store: &ImageStore<'a, f32, 2>,
230 into: &mut ImageStoreMut<'a, f32, 2>,
231 premultiply_alpha: bool,
232 ) -> Result<(), PicScaleError>;
233
234 fn resize_rgb_f32<'a>(
249 &'a self,
250 store: &ImageStore<'a, f32, 3>,
251 into: &mut ImageStoreMut<'a, f32, 3>,
252 ) -> Result<(), PicScaleError>;
253
254 fn resize_rgba_f32<'a>(
270 &'a self,
271 store: &ImageStore<'a, f32, 4>,
272 into: &mut ImageStoreMut<'a, f32, 4>,
273 premultiply_alpha: bool,
274 ) -> Result<(), PicScaleError>;
275}
276
277#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Default)]
279pub enum WorkloadStrategy {
280 PreferQuality,
282 #[default]
284 PreferSpeed,
285}
286
287pub trait ScalingU16 {
289 fn resize_plane_u16<'a>(
313 &'a self,
314 store: &ImageStore<'a, u16, 1>,
315 into: &mut ImageStoreMut<'a, u16, 1>,
316 ) -> Result<(), PicScaleError>;
317
318 fn resize_cbcr_u16<'a>(
344 &'a self,
345 store: &ImageStore<'a, u16, 2>,
346 into: &mut ImageStoreMut<'a, u16, 2>,
347 ) -> Result<(), PicScaleError>;
348
349 fn resize_gray_alpha16<'a>(
364 &'a self,
365 store: &ImageStore<'a, u16, 2>,
366 into: &mut ImageStoreMut<'a, u16, 2>,
367 premultiply_alpha: bool,
368 ) -> Result<(), PicScaleError>;
369
370 fn resize_rgb_u16<'a>(
396 &'a self,
397 store: &ImageStore<'a, u16, 3>,
398 into: &mut ImageStoreMut<'a, u16, 3>,
399 ) -> Result<(), PicScaleError>;
400
401 fn resize_rgba_u16<'a>(
429 &'a self,
430 store: &ImageStore<'a, u16, 4>,
431 into: &mut ImageStoreMut<'a, u16, 4>,
432 premultiply_alpha: bool,
433 ) -> Result<(), PicScaleError>;
434}
435
436impl Scaler {
437 pub fn new(filter: ResamplingFunction) -> Self {
442 Scaler {
443 function: filter,
444 threading_policy: ThreadingPolicy::Single,
445 workload_strategy: WorkloadStrategy::default(),
446 }
447 }
448
449 pub fn set_workload_strategy(&mut self, workload_strategy: WorkloadStrategy) {
453 self.workload_strategy = workload_strategy;
454 }
455}
456
457impl Scaler {
458 pub(crate) fn generic_resize<
459 'a,
460 T: Clone + Copy + Debug + Send + Sync + Default + WeightsGenerator<W> + 'static,
461 W,
462 const N: usize,
463 >(
464 &self,
465 store: &ImageStore<'a, T, N>,
466 into: &mut ImageStoreMut<'a, T, N>,
467 ) -> Result<(), PicScaleError>
468 where
469 ImageStore<'a, T, N>: VerticalConvolutionPass<T, W, N> + HorizontalConvolutionPass<T, W, N>,
470 ImageStoreMut<'a, T, N>: CheckStoreDensity,
471 {
472 let new_size = into.get_size();
473 into.validate()?;
474 store.validate()?;
475 if store.width == 0 || store.height == 0 || new_size.width == 0 || new_size.height == 0 {
476 return Err(PicScaleError::ZeroImageDimensions);
477 }
478
479 if check_image_size_overflow(store.width, store.height, store.channels) {
480 return Err(PicScaleError::SourceImageIsTooLarge);
481 }
482
483 if check_image_size_overflow(new_size.width, new_size.height, store.channels) {
484 return Err(PicScaleError::DestinationImageIsTooLarge);
485 }
486
487 if into.should_have_bit_depth() && !(1..=16).contains(&into.bit_depth) {
488 return Err(PicScaleError::UnsupportedBitDepth(into.bit_depth));
489 }
490
491 if store.width == new_size.width && store.height == new_size.height {
492 store.copied_to_mut(into);
493 return Ok(());
494 }
495
496 let nova_thread_pool = self
497 .threading_policy
498 .get_nova_pool(ImageSize::new(new_size.width, new_size.height));
499
500 if self.function == ResamplingFunction::Nearest {
501 resize_nearest::<T, N>(
502 store.buffer.as_ref(),
503 store.width,
504 store.height,
505 into.buffer.borrow_mut(),
506 new_size.width,
507 new_size.height,
508 &nova_thread_pool,
509 );
510 return Ok(());
511 }
512
513 let should_do_horizontal = store.width != new_size.width;
514 let should_do_vertical = store.height != new_size.height;
515 assert!(should_do_horizontal || should_do_vertical);
516
517 if should_do_vertical && should_do_horizontal {
518 let mut target_vertical = try_vec![T::default(); store.width * new_size.height * N];
519
520 let mut new_image_vertical = ImageStoreMut::<T, N>::from_slice(
521 &mut target_vertical,
522 store.width,
523 new_size.height,
524 )?;
525 new_image_vertical.bit_depth = into.bit_depth;
526 let vertical_filters = T::make_weights(self.function, store.height, new_size.height)?;
527 let options = ConvolutionOptions::new(self.workload_strategy);
528 store.convolve_vertical(
529 vertical_filters,
530 &mut new_image_vertical,
531 &nova_thread_pool,
532 options,
533 );
534
535 let new_immutable_store = ImageStore::<T, N> {
536 buffer: std::borrow::Cow::Owned(target_vertical),
537 channels: N,
538 width: store.width,
539 height: new_size.height,
540 stride: store.width * N,
541 bit_depth: into.bit_depth,
542 };
543 let horizontal_filters = T::make_weights(self.function, store.width, new_size.width)?;
544 let options = ConvolutionOptions::new(self.workload_strategy);
545 new_immutable_store.convolve_horizontal(
546 horizontal_filters,
547 into,
548 &nova_thread_pool,
549 options,
550 );
551 Ok(())
552 } else if should_do_vertical {
553 let vertical_filters = T::make_weights(self.function, store.height, new_size.height)?;
554 let options = ConvolutionOptions::new(self.workload_strategy);
555 store.convolve_vertical(vertical_filters, into, &nova_thread_pool, options);
556 Ok(())
557 } else {
558 assert!(should_do_horizontal);
559 let horizontal_filters = T::make_weights(self.function, store.width, new_size.width)?;
560 let options = ConvolutionOptions::new(self.workload_strategy);
561 store.convolve_horizontal(horizontal_filters, into, &nova_thread_pool, options);
562 Ok(())
563 }
564 }
565
566 fn forward_resize_with_alpha<
567 'a,
568 T: Clone + Copy + Debug + Send + Sync + Default + WeightsGenerator<W> + 'static,
569 W,
570 const N: usize,
571 >(
572 &self,
573 store: &ImageStore<'a, T, N>,
574 into: &mut ImageStoreMut<'a, T, N>,
575 premultiply_alpha_requested: bool,
576 nova_thread_pool: &novtb::ThreadPool,
577 ) -> Result<(), PicScaleError>
578 where
579 ImageStore<'a, T, N>: VerticalConvolutionPass<T, W, N>
580 + HorizontalConvolutionPass<T, W, N>
581 + AssociateAlpha<T, N>,
582 ImageStoreMut<'a, T, N>: CheckStoreDensity + UnassociateAlpha<T, N>,
583 {
584 let new_size = into.get_size();
585 let mut src_store: std::borrow::Cow<'_, ImageStore<'_, T, N>> =
586 std::borrow::Cow::Borrowed(store);
587
588 let mut has_alpha_premultiplied = true;
589
590 if premultiply_alpha_requested {
591 let is_alpha_premultiplication_reasonable =
592 src_store.is_alpha_premultiplication_needed();
593 if is_alpha_premultiplication_reasonable {
594 let mut target_premultiplied =
595 try_vec![T::default(); src_store.width * src_store.height * N];
596 let mut new_store = ImageStoreMut::<T, N>::from_slice(
597 &mut target_premultiplied,
598 src_store.width,
599 src_store.height,
600 )?;
601 new_store.bit_depth = into.bit_depth;
602 src_store.premultiply_alpha(&mut new_store, nova_thread_pool);
603 src_store = std::borrow::Cow::Owned(ImageStore::<T, N> {
604 buffer: std::borrow::Cow::Owned(target_premultiplied),
605 channels: N,
606 width: src_store.width,
607 height: src_store.height,
608 stride: src_store.width * N,
609 bit_depth: into.bit_depth,
610 });
611 has_alpha_premultiplied = true;
612 }
613 }
614
615 let mut target_vertical = try_vec![T::default(); src_store.width * new_size.height * N];
616
617 let mut new_image_vertical = ImageStoreMut::<T, N>::from_slice(
618 &mut target_vertical,
619 src_store.width,
620 new_size.height,
621 )?;
622 new_image_vertical.bit_depth = into.bit_depth;
623 let vertical_filters = T::make_weights(self.function, src_store.height, new_size.height)?;
624 let options = ConvolutionOptions::new(self.workload_strategy);
625 src_store.convolve_vertical(
626 vertical_filters,
627 &mut new_image_vertical,
628 nova_thread_pool,
629 options,
630 );
631
632 let new_immutable_store = ImageStore::<T, N> {
633 buffer: std::borrow::Cow::Owned(target_vertical),
634 channels: N,
635 width: src_store.width,
636 height: new_size.height,
637 stride: src_store.width * N,
638 bit_depth: into.bit_depth,
639 };
640 let horizontal_filters = T::make_weights(self.function, src_store.width, new_size.width)?;
641 let options = ConvolutionOptions::new(self.workload_strategy);
642 new_immutable_store.convolve_horizontal(
643 horizontal_filters,
644 into,
645 nova_thread_pool,
646 options,
647 );
648
649 if premultiply_alpha_requested && has_alpha_premultiplied {
650 into.unpremultiply_alpha(nova_thread_pool, self.workload_strategy);
651 }
652
653 Ok(())
654 }
655
656 fn forward_resize_vertical_with_alpha<
657 'a,
658 T: Clone + Copy + Debug + Send + Sync + Default + WeightsGenerator<W> + 'static,
659 W,
660 const N: usize,
661 >(
662 &self,
663 store: &ImageStore<'a, T, N>,
664 into: &mut ImageStoreMut<'a, T, N>,
665 premultiply_alpha_requested: bool,
666 nova_thread_pool: &novtb::ThreadPool,
667 ) -> Result<(), PicScaleError>
668 where
669 ImageStore<'a, T, N>: VerticalConvolutionPass<T, W, N>
670 + HorizontalConvolutionPass<T, W, N>
671 + AssociateAlpha<T, N>,
672 ImageStoreMut<'a, T, N>: CheckStoreDensity + UnassociateAlpha<T, N>,
673 {
674 let new_size = into.get_size();
675 let mut src_store = std::borrow::Cow::Borrowed(store);
676
677 let mut has_alpha_premultiplied = true;
678
679 if premultiply_alpha_requested {
680 let is_alpha_premultiplication_reasonable =
681 src_store.is_alpha_premultiplication_needed();
682 if is_alpha_premultiplication_reasonable {
683 let mut target_premultiplied =
684 try_vec![T::default(); src_store.width * src_store.height * N];
685 let mut new_store = ImageStoreMut::<T, N>::from_slice(
686 &mut target_premultiplied,
687 src_store.width,
688 src_store.height,
689 )?;
690 new_store.bit_depth = into.bit_depth;
691 src_store.premultiply_alpha(&mut new_store, nova_thread_pool);
692 src_store = std::borrow::Cow::Owned(ImageStore::<T, N> {
693 buffer: std::borrow::Cow::Owned(target_premultiplied),
694 channels: N,
695 width: src_store.width,
696 height: src_store.height,
697 stride: src_store.width * N,
698 bit_depth: into.bit_depth,
699 });
700 has_alpha_premultiplied = true;
701 }
702 }
703
704 let vertical_filters = T::make_weights(self.function, src_store.height, new_size.height)?;
705 let options = ConvolutionOptions::new(self.workload_strategy);
706 src_store.convolve_vertical(vertical_filters, into, nova_thread_pool, options);
707
708 if premultiply_alpha_requested && has_alpha_premultiplied {
709 into.unpremultiply_alpha(nova_thread_pool, self.workload_strategy);
710 }
711
712 Ok(())
713 }
714
715 fn forward_resize_horizontal_with_alpha<
716 'a,
717 T: Clone + Copy + Debug + Send + Sync + Default + WeightsGenerator<W> + 'static,
718 W,
719 const N: usize,
720 >(
721 &self,
722 store: &ImageStore<'a, T, N>,
723 into: &mut ImageStoreMut<'a, T, N>,
724 premultiply_alpha_requested: bool,
725 nova_thread_pool: &novtb::ThreadPool,
726 ) -> Result<(), PicScaleError>
727 where
728 ImageStore<'a, T, N>: VerticalConvolutionPass<T, W, N>
729 + HorizontalConvolutionPass<T, W, N>
730 + AssociateAlpha<T, N>,
731 ImageStoreMut<'a, T, N>: CheckStoreDensity + UnassociateAlpha<T, N>,
732 {
733 let new_size = into.get_size();
734 let mut src_store = std::borrow::Cow::Borrowed(store);
735
736 let mut has_alpha_premultiplied = true;
737
738 if premultiply_alpha_requested {
739 let is_alpha_premultiplication_reasonable =
740 src_store.is_alpha_premultiplication_needed();
741 if is_alpha_premultiplication_reasonable {
742 let mut target_premultiplied =
743 try_vec![T::default(); src_store.width * src_store.height * N];
744 let mut new_store = ImageStoreMut::<T, N>::from_slice(
745 &mut target_premultiplied,
746 src_store.width,
747 src_store.height,
748 )?;
749 new_store.bit_depth = into.bit_depth;
750 src_store.premultiply_alpha(&mut new_store, nova_thread_pool);
751 src_store = std::borrow::Cow::Owned(ImageStore::<T, N> {
752 buffer: std::borrow::Cow::Owned(target_premultiplied),
753 channels: N,
754 width: src_store.width,
755 height: src_store.height,
756 stride: src_store.width * N,
757 bit_depth: into.bit_depth,
758 });
759 has_alpha_premultiplied = true;
760 }
761 }
762
763 let horizontal_filters = T::make_weights(self.function, src_store.width, new_size.width)?;
764 let options = ConvolutionOptions::new(self.workload_strategy);
765 src_store.convolve_horizontal(horizontal_filters, into, nova_thread_pool, options);
766
767 if premultiply_alpha_requested && has_alpha_premultiplied {
768 into.unpremultiply_alpha(nova_thread_pool, self.workload_strategy);
769 }
770
771 Ok(())
772 }
773
774 pub(crate) fn generic_resize_with_alpha<
775 'a,
776 T: Clone + Copy + Debug + Send + Sync + Default + WeightsGenerator<W> + 'static,
777 W,
778 const N: usize,
779 >(
780 &self,
781 store: &ImageStore<'a, T, N>,
782 into: &mut ImageStoreMut<'a, T, N>,
783 premultiply_alpha_requested: bool,
784 ) -> Result<(), PicScaleError>
785 where
786 ImageStore<'a, T, N>: VerticalConvolutionPass<T, W, N>
787 + HorizontalConvolutionPass<T, W, N>
788 + AssociateAlpha<T, N>,
789 ImageStoreMut<'a, T, N>: CheckStoreDensity + UnassociateAlpha<T, N>,
790 {
791 let new_size = into.get_size();
792 into.validate()?;
793 store.validate()?;
794 if store.width == 0 || store.height == 0 || new_size.width == 0 || new_size.height == 0 {
795 return Err(PicScaleError::ZeroImageDimensions);
796 }
797
798 if check_image_size_overflow(store.width, store.height, store.channels) {
799 return Err(PicScaleError::SourceImageIsTooLarge);
800 }
801
802 if check_image_size_overflow(new_size.width, new_size.height, store.channels) {
803 return Err(PicScaleError::DestinationImageIsTooLarge);
804 }
805
806 if into.should_have_bit_depth() && !(1..=16).contains(&into.bit_depth) {
807 return Err(PicScaleError::UnsupportedBitDepth(into.bit_depth));
808 }
809
810 if store.width == new_size.width && store.height == new_size.height {
811 store.copied_to_mut(into);
812 return Ok(());
813 }
814
815 let nova_thread_pool = self
816 .threading_policy
817 .get_nova_pool(ImageSize::new(new_size.width, new_size.height));
818
819 if self.function == ResamplingFunction::Nearest {
820 resize_nearest::<T, N>(
821 store.buffer.as_ref(),
822 store.width,
823 store.height,
824 into.buffer.borrow_mut(),
825 new_size.width,
826 new_size.height,
827 &nova_thread_pool,
828 );
829 return Ok(());
830 }
831
832 let should_do_horizontal = store.width != new_size.width;
833 let should_do_vertical = store.height != new_size.height;
834 assert!(should_do_horizontal || should_do_vertical);
835
836 if should_do_vertical && should_do_horizontal {
837 self.forward_resize_with_alpha(
838 store,
839 into,
840 premultiply_alpha_requested,
841 &nova_thread_pool,
842 )
843 } else if should_do_vertical {
844 self.forward_resize_vertical_with_alpha(
845 store,
846 into,
847 premultiply_alpha_requested,
848 &nova_thread_pool,
849 )
850 } else {
851 assert!(should_do_horizontal);
852 self.forward_resize_horizontal_with_alpha(
853 store,
854 into,
855 premultiply_alpha_requested,
856 &nova_thread_pool,
857 )
858 }
859 }
860}
861
862impl Scaling for Scaler {
863 fn set_threading_policy(&mut self, threading_policy: ThreadingPolicy) {
864 self.threading_policy = threading_policy;
865 }
866
867 fn resize_plane<'a>(
868 &'a self,
869 store: &ImageStore<'a, u8, 1>,
870 into: &mut ImageStoreMut<'a, u8, 1>,
871 ) -> Result<(), PicScaleError> {
872 self.generic_resize(store, into)
873 }
874
875 fn resize_cbcr8<'a>(
876 &'a self,
877 store: &ImageStore<'a, u8, 2>,
878 into: &mut ImageStoreMut<'a, u8, 2>,
879 ) -> Result<(), PicScaleError> {
880 self.generic_resize(store, into)
881 }
882
883 fn resize_gray_alpha<'a>(
884 &'a self,
885 store: &ImageStore<'a, u8, 2>,
886 into: &mut ImageStoreMut<'a, u8, 2>,
887 premultiply_alpha: bool,
888 ) -> Result<(), PicScaleError> {
889 self.generic_resize_with_alpha(store, into, premultiply_alpha)
890 }
891
892 fn resize_rgb<'a>(
893 &'a self,
894 store: &ImageStore<'a, u8, 3>,
895 into: &mut ImageStoreMut<'a, u8, 3>,
896 ) -> Result<(), PicScaleError> {
897 self.generic_resize(store, into)
898 }
899
900 fn resize_rgba<'a>(
901 &'a self,
902 store: &ImageStore<'a, u8, 4>,
903 into: &mut ImageStoreMut<'a, u8, 4>,
904 premultiply_alpha: bool,
905 ) -> Result<(), PicScaleError> {
906 self.generic_resize_with_alpha(store, into, premultiply_alpha)
907 }
908}
909
910impl ScalingF32 for Scaler {
911 fn resize_plane_f32<'a>(
912 &'a self,
913 store: &ImageStore<'a, f32, 1>,
914 into: &mut ImageStoreMut<'a, f32, 1>,
915 ) -> Result<(), PicScaleError> {
916 match self.workload_strategy {
917 WorkloadStrategy::PreferQuality => self.generic_resize::<f32, f64, 1>(store, into),
918 WorkloadStrategy::PreferSpeed => self.generic_resize::<f32, f32, 1>(store, into),
919 }
920 }
921
922 fn resize_cbcr_f32<'a>(
923 &'a self,
924 store: &ImageStore<'a, f32, 2>,
925 into: &mut ImageStoreMut<'a, f32, 2>,
926 ) -> Result<(), PicScaleError> {
927 match self.workload_strategy {
928 WorkloadStrategy::PreferQuality => self.generic_resize::<f32, f64, 2>(store, into),
929 WorkloadStrategy::PreferSpeed => self.generic_resize::<f32, f32, 2>(store, into),
930 }
931 }
932
933 fn resize_gray_alpha_f32<'a>(
934 &'a self,
935 store: &ImageStore<'a, f32, 2>,
936 into: &mut ImageStoreMut<'a, f32, 2>,
937 premultiply_alpha: bool,
938 ) -> Result<(), PicScaleError> {
939 match self.workload_strategy {
940 WorkloadStrategy::PreferQuality => {
941 self.generic_resize_with_alpha::<f32, f64, 2>(store, into, premultiply_alpha)
942 }
943 WorkloadStrategy::PreferSpeed => {
944 self.generic_resize_with_alpha::<f32, f32, 2>(store, into, premultiply_alpha)
945 }
946 }
947 }
948
949 fn resize_rgb_f32<'a>(
950 &'a self,
951 store: &ImageStore<'a, f32, 3>,
952 into: &mut ImageStoreMut<'a, f32, 3>,
953 ) -> Result<(), PicScaleError> {
954 match self.workload_strategy {
955 WorkloadStrategy::PreferQuality => self.generic_resize::<f32, f64, 3>(store, into),
956 WorkloadStrategy::PreferSpeed => self.generic_resize::<f32, f32, 3>(store, into),
957 }
958 }
959
960 fn resize_rgba_f32<'a>(
961 &'a self,
962 store: &ImageStore<'a, f32, 4>,
963 into: &mut ImageStoreMut<'a, f32, 4>,
964 premultiply_alpha: bool,
965 ) -> Result<(), PicScaleError> {
966 match self.workload_strategy {
967 WorkloadStrategy::PreferQuality => {
968 self.generic_resize_with_alpha::<f32, f64, 4>(store, into, premultiply_alpha)
969 }
970 WorkloadStrategy::PreferSpeed => {
971 self.generic_resize_with_alpha::<f32, f32, 4>(store, into, premultiply_alpha)
972 }
973 }
974 }
975}
976
977impl ScalingU16 for Scaler {
978 fn resize_rgb_u16<'a>(
1004 &'a self,
1005 store: &ImageStore<'a, u16, 3>,
1006 into: &mut ImageStoreMut<'a, u16, 3>,
1007 ) -> Result<(), PicScaleError> {
1008 self.generic_resize(store, into)
1009 }
1010
1011 fn resize_cbcr_u16<'a>(
1012 &'a self,
1013 store: &ImageStore<'a, u16, 2>,
1014 into: &mut ImageStoreMut<'a, u16, 2>,
1015 ) -> Result<(), PicScaleError> {
1016 self.generic_resize(store, into)
1017 }
1018
1019 fn resize_gray_alpha16<'a>(
1020 &'a self,
1021 store: &ImageStore<'a, u16, 2>,
1022 into: &mut ImageStoreMut<'a, u16, 2>,
1023 premultiply_alpha: bool,
1024 ) -> Result<(), PicScaleError> {
1025 self.generic_resize_with_alpha(store, into, premultiply_alpha)
1026 }
1027
1028 fn resize_rgba_u16<'a>(
1050 &'a self,
1051 store: &ImageStore<'a, u16, 4>,
1052 into: &mut ImageStoreMut<'a, u16, 4>,
1053 premultiply_alpha: bool,
1054 ) -> Result<(), PicScaleError> {
1055 self.generic_resize_with_alpha(store, into, premultiply_alpha)
1056 }
1057
1058 fn resize_plane_u16<'a>(
1082 &'a self,
1083 store: &ImageStore<'a, u16, 1>,
1084 into: &mut ImageStoreMut<'a, u16, 1>,
1085 ) -> Result<(), PicScaleError> {
1086 self.generic_resize(store, into)
1087 }
1088}
1089
1090impl Scaler {
1091 pub fn resize_ar30(
1101 &self,
1102 src_image: &ImageStore<u8, 4>,
1103 dst_image: &mut ImageStoreMut<u8, 4>,
1104 order: Ar30ByteOrder,
1105 ) -> Result<(), PicScaleError> {
1106 src_image.validate()?;
1107 dst_image.validate()?;
1108 let dst_size = dst_image.get_size();
1109 let dst_stride = dst_image.stride();
1110 match order {
1111 Ar30ByteOrder::Host => {
1112 resize_ar30_impl::<{ Rgb30::Ar30 as usize }, { Ar30ByteOrder::Host as usize }>(
1113 src_image.as_bytes(),
1114 src_image.stride,
1115 src_image.get_size(),
1116 dst_image.buffer.borrow_mut(),
1117 dst_stride,
1118 dst_size,
1119 self,
1120 )
1121 }
1122 Ar30ByteOrder::Network => {
1123 resize_ar30_impl::<{ Rgb30::Ar30 as usize }, { Ar30ByteOrder::Network as usize }>(
1124 src_image.as_bytes(),
1125 src_image.stride,
1126 src_image.get_size(),
1127 dst_image.buffer.borrow_mut(),
1128 dst_stride,
1129 dst_size,
1130 self,
1131 )
1132 }
1133 }
1134 }
1135
1136 pub fn resize_ra30(
1145 &self,
1146 src_image: &ImageStore<u8, 4>,
1147 dst_image: &mut ImageStoreMut<u8, 4>,
1148 order: Ar30ByteOrder,
1149 ) -> Result<(), PicScaleError> {
1150 src_image.validate()?;
1151 dst_image.validate()?;
1152 let dst_size = dst_image.get_size();
1153 let dst_stride = dst_image.stride();
1154 match order {
1155 Ar30ByteOrder::Host => {
1156 resize_ar30_impl::<{ Rgb30::Ra30 as usize }, { Ar30ByteOrder::Host as usize }>(
1157 src_image.as_bytes(),
1158 src_image.stride,
1159 src_image.get_size(),
1160 dst_image.buffer.borrow_mut(),
1161 dst_stride,
1162 dst_size,
1163 self,
1164 )
1165 }
1166 Ar30ByteOrder::Network => {
1167 resize_ar30_impl::<{ Rgb30::Ra30 as usize }, { Ar30ByteOrder::Network as usize }>(
1168 src_image.as_bytes(),
1169 src_image.stride,
1170 src_image.get_size(),
1171 dst_image.buffer.borrow_mut(),
1172 dst_stride,
1173 dst_size,
1174 self,
1175 )
1176 }
1177 }
1178 }
1179}
1180
1181#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)]
1183pub struct ScalingOptions {
1184 pub resampling_function: ResamplingFunction,
1185 pub premultiply_alpha: bool,
1186 pub threading_policy: ThreadingPolicy,
1187}
1188
1189pub trait ImageStoreScaling<'b, T, const N: usize>
1191where
1192 T: Clone + Copy + Debug,
1193{
1194 fn scale(
1195 &self,
1196 store: &mut ImageStoreMut<'b, T, N>,
1197 options: ScalingOptions,
1198 ) -> Result<(), PicScaleError>;
1199}
1200
1201macro_rules! def_image_scaling_alpha {
1202 ($clazz: ident, $fx_type: ident, $cn: expr) => {
1203 impl<'b> ImageStoreScaling<'b, $fx_type, $cn> for $clazz<'b> {
1204 fn scale(
1205 &self,
1206 store: &mut ImageStoreMut<'b, $fx_type, $cn>,
1207 options: ScalingOptions,
1208 ) -> Result<(), PicScaleError> {
1209 let mut scaler = Scaler::new(options.resampling_function);
1210 scaler.set_threading_policy(options.threading_policy);
1211 scaler.generic_resize_with_alpha::<$fx_type, f32, $cn>(
1212 self,
1213 store,
1214 options.premultiply_alpha,
1215 )
1216 }
1217 }
1218 };
1219}
1220
1221macro_rules! def_image_scaling {
1222 ($clazz: ident, $fx_type: ident, $cn: expr) => {
1223 impl<'b> ImageStoreScaling<'b, $fx_type, $cn> for $clazz<'b> {
1224 fn scale(
1225 &self,
1226 store: &mut ImageStoreMut<'b, $fx_type, $cn>,
1227 options: ScalingOptions,
1228 ) -> Result<(), PicScaleError> {
1229 let mut scaler = Scaler::new(options.resampling_function);
1230 scaler.set_threading_policy(options.threading_policy);
1231 scaler.generic_resize::<$fx_type, f32, $cn>(self, store)
1232 }
1233 }
1234 };
1235}
1236
1237def_image_scaling_alpha!(Rgba8ImageStore, u8, 4);
1238def_image_scaling!(Rgb8ImageStore, u8, 3);
1239def_image_scaling!(CbCr8ImageStore, u8, 2);
1240def_image_scaling!(Planar8ImageStore, u8, 1);
1241def_image_scaling!(Planar16ImageStore, u16, 1);
1242def_image_scaling!(CbCr16ImageStore, u16, 2);
1243def_image_scaling!(Rgb16ImageStore, u16, 3);
1244def_image_scaling_alpha!(Rgba16ImageStore, u16, 4);
1245def_image_scaling!(PlanarF32ImageStore, f32, 1);
1246def_image_scaling!(CbCrF32ImageStore, f32, 2);
1247def_image_scaling!(RgbF32ImageStore, f32, 3);
1248def_image_scaling_alpha!(RgbaF32ImageStore, f32, 4);
1249
1250#[cfg(test)]
1251mod tests {
1252 use super::*;
1253
1254 macro_rules! check_rgba8 {
1255 ($dst: expr, $image_width: expr, $max: expr) => {
1256 {
1257 for (y, row) in $dst.chunks_exact($image_width * 4).enumerate() {
1258 for (i, dst) in row.chunks_exact(4).enumerate() {
1259 let diff0 = (dst[0] as i32 - 124).abs();
1260 let diff1 = (dst[1] as i32 - 41).abs();
1261 let diff2 = (dst[2] as i32 - 99).abs();
1262 let diff3 = (dst[3] as i32 - 77).abs();
1263 assert!(
1264 diff0 < $max,
1265 "Diff for channel 0 is expected < {}, but it was {diff0}, at (y: {y}, x: {i})",
1266 $max
1267 );
1268 assert!(
1269 diff1 < $max,
1270 "Diff for channel 1 is expected < {}, but it was {diff1}, at (y: {y}, x: {i})",
1271 $max
1272 );
1273 assert!(
1274 diff2 < $max,
1275 "Diff for channel 2 is expected < {}, but it was {diff2}, at (y: {y}, x: {i})",
1276 $max
1277 );
1278 assert!(
1279 diff3 < $max,
1280 "Diff for channel 3 is expected < {}, but it was {diff3}, at (y: {y}, x: {i})",
1281 $max
1282 );
1283 }
1284 }
1285 }
1286 };
1287 }
1288
1289 macro_rules! check_rgb16 {
1290 ($dst: expr, $image_width: expr, $max: expr) => {
1291 {
1292 for (y, row) in $dst.chunks_exact($image_width * 3).enumerate() {
1293 for (i, dst) in row.chunks_exact(3).enumerate() {
1294 let diff0 = (dst[0] as i32 - 124).abs();
1295 let diff1 = (dst[1] as i32 - 41).abs();
1296 let diff2 = (dst[2] as i32 - 99).abs();
1297 assert!(
1298 diff0 < $max,
1299 "Diff for channel 0 is expected < {}, but it was {diff0}, at (y: {y}, x: {i})",
1300 $max
1301 );
1302 assert!(
1303 diff1 < $max,
1304 "Diff for channel 1 is expected < {}, but it was {diff1}, at (y: {y}, x: {i})",
1305 $max
1306 );
1307 assert!(
1308 diff2 < $max,
1309 "Diff for channel 2 is expected < {}, but it was {diff2}, at (y: {y}, x: {i})",
1310 $max
1311 );
1312 }
1313 }
1314 }
1315 };
1316 }
1317
1318 #[test]
1319 fn check_rgba8_resizing_vertical() {
1320 let image_width = 255;
1321 let image_height = 512;
1322 const CN: usize = 4;
1323 let mut image = vec![0u8; image_height * image_width * CN];
1324 for dst in image.chunks_exact_mut(4) {
1325 dst[0] = 124;
1326 dst[1] = 41;
1327 dst[2] = 99;
1328 dst[3] = 77;
1329 }
1330 let mut scaler = Scaler::new(ResamplingFunction::Bilinear);
1331 scaler.set_threading_policy(ThreadingPolicy::Single);
1332 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1333 let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1334 scaler
1335 .resize_rgba(&src_store, &mut target_store, false)
1336 .unwrap();
1337 let target_data = target_store.buffer.borrow();
1338 check_rgba8!(target_data, image_width, 34);
1339 }
1340
1341 #[test]
1342 fn check_rgba8_resizing_both() {
1343 let image_width = 255;
1344 let image_height = 512;
1345 const CN: usize = 4;
1346 let mut image = vec![0u8; image_height * image_width * CN];
1347 for dst in image.chunks_exact_mut(4) {
1348 dst[0] = 124;
1349 dst[1] = 41;
1350 dst[2] = 99;
1351 dst[3] = 77;
1352 }
1353 image[3] = 78;
1354 let mut scaler = Scaler::new(ResamplingFunction::Bilinear);
1355 scaler.set_threading_policy(ThreadingPolicy::Single);
1356 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1357 let mut target_store = ImageStoreMut::alloc(image_width / 2, image_height / 2);
1358 scaler
1359 .resize_rgba(&src_store, &mut target_store, false)
1360 .unwrap();
1361 let target_data = target_store.buffer.borrow();
1362 check_rgba8!(target_data, image_width, 34);
1363 }
1364
1365 #[test]
1366 fn check_rgba8_resizing_alpha() {
1367 let image_width = 255;
1368 let image_height = 512;
1369 const CN: usize = 4;
1370 let mut image = vec![0u8; image_height * image_width * CN];
1371 for dst in image.chunks_exact_mut(4) {
1372 dst[0] = 124;
1373 dst[1] = 41;
1374 dst[2] = 99;
1375 dst[3] = 77;
1376 }
1377 image[3] = 78;
1378 let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1379 scaler.set_threading_policy(ThreadingPolicy::Single);
1380 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1381 let mut target_store = ImageStoreMut::alloc(image_width / 2, image_height / 2);
1382 scaler
1383 .resize_rgba(&src_store, &mut target_store, true)
1384 .unwrap();
1385 let target_data = target_store.buffer.borrow();
1386 check_rgba8!(target_data, image_width, 160);
1387 }
1388
1389 #[test]
1390 fn check_rgb8_resizing_vertical() {
1391 let image_width = 255;
1392 let image_height = 512;
1393 const CN: usize = 3;
1394 let mut image = vec![0u8; image_height * image_width * CN];
1395 for dst in image.chunks_exact_mut(3) {
1396 dst[0] = 124;
1397 dst[1] = 41;
1398 dst[2] = 99;
1399 }
1400 let mut scaler = Scaler::new(ResamplingFunction::Bilinear);
1401 scaler.set_threading_policy(ThreadingPolicy::Single);
1402 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1403 let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1404 scaler.resize_rgb(&src_store, &mut target_store).unwrap();
1405 let target_data = target_store.buffer.borrow();
1406
1407 check_rgb16!(target_data, image_width, 85);
1408 }
1409
1410 #[test]
1411 fn check_rgb8_resizing_vertical_threading() {
1412 let image_width = 255;
1413 let image_height = 512;
1414 const CN: usize = 3;
1415 let mut image = vec![0u8; image_height * image_width * CN];
1416 for dst in image.chunks_exact_mut(3) {
1417 dst[0] = 124;
1418 dst[1] = 41;
1419 dst[2] = 99;
1420 }
1421 let mut scaler = Scaler::new(ResamplingFunction::Bilinear);
1422 scaler.set_threading_policy(ThreadingPolicy::Adaptive);
1423 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1424 let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1425 scaler.resize_rgb(&src_store, &mut target_store).unwrap();
1426 let target_data = target_store.buffer.borrow();
1427
1428 check_rgb16!(target_data, image_width, 85);
1429 }
1430
1431 #[test]
1432 fn check_rgba10_resizing_vertical() {
1433 let image_width = 8;
1434 let image_height = 8;
1435 const CN: usize = 4;
1436 let mut image = vec![0u16; image_height * image_width * CN];
1437 for dst in image.chunks_exact_mut(4) {
1438 dst[0] = 124;
1439 dst[1] = 41;
1440 dst[2] = 99;
1441 dst[3] = 77;
1442 }
1443 image[3] = 78;
1444 let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1445 scaler.set_threading_policy(ThreadingPolicy::Single);
1446 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1447 src_store.bit_depth = 10;
1448 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 10);
1449 scaler
1450 .resize_rgba_u16(&src_store, &mut target_store, false)
1451 .unwrap();
1452 let target_data = target_store.buffer.borrow();
1453
1454 check_rgba8!(target_data, image_width, 60);
1455 }
1456
1457 #[test]
1458 fn check_rgb10_resizing_vertical() {
1459 let image_width = 8;
1460 let image_height = 4;
1461 const CN: usize = 3;
1462 let mut image = vec![0; image_height * image_width * CN];
1463 for dst in image.chunks_exact_mut(3) {
1464 dst[0] = 124;
1465 dst[1] = 41;
1466 dst[2] = 99;
1467 }
1468 let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1469 scaler.set_threading_policy(ThreadingPolicy::Single);
1470 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1471 src_store.bit_depth = 10;
1472 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 10);
1473 scaler
1474 .resize_rgb_u16(&src_store, &mut target_store)
1475 .unwrap();
1476 let target_data = target_store.buffer.borrow();
1477
1478 check_rgb16!(target_data, image_width, 85);
1479 }
1480
1481 #[test]
1482 fn check_rgb10_resizing_vertical_adaptive() {
1483 let image_width = 8;
1484 let image_height = 4;
1485 const CN: usize = 3;
1486 let mut image = vec![0; image_height * image_width * CN];
1487 for dst in image.chunks_exact_mut(3) {
1488 dst[0] = 124;
1489 dst[1] = 41;
1490 dst[2] = 99;
1491 }
1492 let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1493 scaler.set_threading_policy(ThreadingPolicy::Adaptive);
1494 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1495 src_store.bit_depth = 10;
1496 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 10);
1497 scaler
1498 .resize_rgb_u16(&src_store, &mut target_store)
1499 .unwrap();
1500 let target_data = target_store.buffer.borrow();
1501
1502 check_rgb16!(target_data, image_width, 85);
1503 }
1504
1505 #[test]
1506 fn check_rgb16_resizing_vertical() {
1507 let image_width = 8;
1508 let image_height = 8;
1509 const CN: usize = 3;
1510 let mut image = vec![164; image_height * image_width * CN];
1511 for dst in image.chunks_exact_mut(3) {
1512 dst[0] = 124;
1513 dst[1] = 41;
1514 dst[2] = 99;
1515 }
1516 let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1517 scaler.set_threading_policy(ThreadingPolicy::Single);
1518 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1519 src_store.bit_depth = 10;
1520 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 16);
1521 scaler
1522 .resize_rgb_u16(&src_store, &mut target_store)
1523 .unwrap();
1524 let target_data = target_store.buffer.borrow();
1525
1526 check_rgb16!(target_data, image_width, 100);
1527 }
1528
1529 #[test]
1530 fn check_rgba16_resizing_vertical() {
1531 let image_width = 8;
1532 let image_height = 8;
1533 const CN: usize = 4;
1534 let mut image = vec![0u16; image_height * image_width * CN];
1535 for dst in image.chunks_exact_mut(4) {
1536 dst[0] = 124;
1537 dst[1] = 41;
1538 dst[2] = 99;
1539 dst[3] = 255;
1540 }
1541 let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1542 scaler.set_threading_policy(ThreadingPolicy::Single);
1543 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1544 src_store.bit_depth = 10;
1545 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 16);
1546 scaler
1547 .resize_rgba_u16(&src_store, &mut target_store, false)
1548 .unwrap();
1549 let target_data = target_store.buffer.borrow();
1550
1551 check_rgba8!(target_data, image_width, 180);
1552 }
1553
1554 #[test]
1555 fn check_rgba16_resizing_vertical_threading() {
1556 let image_width = 8;
1557 let image_height = 8;
1558 const CN: usize = 4;
1559 let mut image = vec![0u16; image_height * image_width * CN];
1560 for dst in image.chunks_exact_mut(4) {
1561 dst[0] = 124;
1562 dst[1] = 41;
1563 dst[2] = 99;
1564 dst[3] = 255;
1565 }
1566 let mut scaler = Scaler::new(ResamplingFunction::Lanczos3);
1567 scaler.set_threading_policy(ThreadingPolicy::Adaptive);
1568 let mut src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1569 src_store.bit_depth = 10;
1570 let mut target_store = ImageStoreMut::alloc_with_depth(image_width, image_height / 2, 16);
1571 scaler
1572 .resize_rgba_u16(&src_store, &mut target_store, false)
1573 .unwrap();
1574 let target_data = target_store.buffer.borrow();
1575
1576 check_rgba8!(target_data, image_width, 180);
1577 }
1578
1579 #[test]
1580 fn check_rgba8_nearest_vertical() {
1581 let image_width = 255;
1582 let image_height = 512;
1583 const CN: usize = 4;
1584 let mut image = vec![0u8; image_height * image_width * CN];
1585 for dst in image.chunks_exact_mut(4) {
1586 dst[0] = 124;
1587 dst[1] = 41;
1588 dst[2] = 99;
1589 dst[3] = 77;
1590 }
1591 let mut scaler = Scaler::new(ResamplingFunction::Nearest);
1592 scaler.set_threading_policy(ThreadingPolicy::Single);
1593 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1594 let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1595 scaler
1596 .resize_rgba(&src_store, &mut target_store, false)
1597 .unwrap();
1598 let target_data = target_store.buffer.borrow();
1599
1600 check_rgba8!(target_data, image_width, 80);
1601 }
1602
1603 #[test]
1604 fn check_rgba8_nearest_vertical_threading() {
1605 let image_width = 255;
1606 let image_height = 512;
1607 const CN: usize = 4;
1608 let mut image = vec![0u8; image_height * image_width * CN];
1609 for dst in image.chunks_exact_mut(4) {
1610 dst[0] = 124;
1611 dst[1] = 41;
1612 dst[2] = 99;
1613 dst[3] = 77;
1614 }
1615 let mut scaler = Scaler::new(ResamplingFunction::Nearest);
1616 scaler.set_threading_policy(ThreadingPolicy::Adaptive);
1617 let src_store = ImageStore::from_slice(&image, image_width, image_height).unwrap();
1618 let mut target_store = ImageStoreMut::alloc(image_width, image_height / 2);
1619 scaler
1620 .resize_rgba(&src_store, &mut target_store, false)
1621 .unwrap();
1622 let target_data = target_store.buffer.borrow();
1623
1624 check_rgba8!(target_data, image_width, 80);
1625 }
1626}